xref: /freebsd/contrib/bc/src/args.c (revision 1f1e2261)
1 /*
2  * *****************************************************************************
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 2018-2021 Gavin D. Howard and contributors.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * * Redistributions of source code must retain the above copyright notice, this
12  *   list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright notice,
15  *   this list of conditions and the following disclaimer in the documentation
16  *   and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * *****************************************************************************
31  *
32  * Code for processing command-line arguments.
33  *
34  */
35 
36 #include <assert.h>
37 #include <ctype.h>
38 #include <stdbool.h>
39 #include <stdlib.h>
40 #include <string.h>
41 
42 #ifndef _WIN32
43 #include <unistd.h>
44 #endif // _WIN32
45 
46 #include <vector.h>
47 #include <read.h>
48 #include <args.h>
49 #include <opt.h>
50 #include <num.h>
51 
52 /**
53  * Adds @a str to the list of expressions to execute later.
54  * @param str  The string to add to the list of expressions.
55  */
56 static void
57 bc_args_exprs(const char* str)
58 {
59 	BC_SIG_ASSERT_LOCKED;
60 	if (vm.exprs.v == NULL) bc_vec_init(&vm.exprs, sizeof(uchar), BC_DTOR_NONE);
61 	bc_vec_concat(&vm.exprs, str);
62 	bc_vec_concat(&vm.exprs, "\n");
63 }
64 
65 /**
66  * Adds the contents of @a file to the list of expressions to execute later.
67  * @param file  The name of the file whose contents should be added to the list
68  *              of expressions to execute.
69  */
70 static void
71 bc_args_file(const char* file)
72 {
73 	char* buf;
74 
75 	BC_SIG_ASSERT_LOCKED;
76 
77 	vm.file = file;
78 
79 	buf = bc_read_file(file);
80 
81 	assert(buf != NULL);
82 
83 	bc_args_exprs(buf);
84 	free(buf);
85 }
86 
87 static BcBigDig
88 bc_args_builtin(const char* arg)
89 {
90 	bool strvalid;
91 	BcNum n;
92 	BcBigDig res;
93 
94 	strvalid = bc_num_strValid(arg);
95 
96 	if (BC_ERR(!strvalid))
97 	{
98 		bc_verr(BC_ERR_FATAL_ARG, arg);
99 	}
100 
101 	bc_num_init(&n, 0);
102 
103 	bc_num_parse(&n, arg, 10);
104 
105 	res = bc_num_bigdig(&n);
106 
107 	bc_num_free(&n);
108 
109 	return res;
110 }
111 
112 #if BC_ENABLED
113 
114 /**
115  * Redefines a keyword, if it exists and is not a POSIX keyword. Otherwise, it
116  * throws a fatal error.
117  * @param keyword  The keyword to redefine.
118  */
119 static void
120 bc_args_redefine(const char* keyword)
121 {
122 	size_t i;
123 
124 	BC_SIG_ASSERT_LOCKED;
125 
126 	for (i = 0; i < bc_lex_kws_len; ++i)
127 	{
128 		const BcLexKeyword* kw = bc_lex_kws + i;
129 
130 		if (!strcmp(keyword, kw->name))
131 		{
132 			if (BC_LEX_KW_POSIX(kw)) break;
133 
134 			vm.redefined_kws[i] = true;
135 
136 			return;
137 		}
138 	}
139 
140 	bc_error(BC_ERR_FATAL_ARG, 0, keyword);
141 }
142 
143 #endif // BC_ENABLED
144 
145 void
146 bc_args(int argc, char* argv[], bool exit_exprs, BcBigDig scale)
147 {
148 	int c;
149 	size_t i;
150 	bool do_exit = false, version = false;
151 	BcOpt opts;
152 	BcBigDig newscale = scale, ibase = BC_BASE, obase = BC_BASE;
153 #if BC_ENABLE_EXTRA_MATH
154 	char* seed = NULL;
155 #endif // BC_ENABLE_EXTRA_MATH
156 
157 	BC_SIG_ASSERT_LOCKED;
158 
159 	bc_opt_init(&opts, argv);
160 
161 	// This loop should look familiar to anyone who has used getopt() or
162 	// getopt_long() in C.
163 	while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1)
164 	{
165 		switch (c)
166 		{
167 			case 'e':
168 			{
169 				// Barf if not allowed.
170 				if (vm.no_exprs)
171 				{
172 					bc_verr(BC_ERR_FATAL_OPTION, "-e (--expression)");
173 				}
174 
175 				// Add the expressions and set exit.
176 				bc_args_exprs(opts.optarg);
177 				vm.exit_exprs = (exit_exprs || vm.exit_exprs);
178 
179 				break;
180 			}
181 
182 			case 'f':
183 			{
184 				// Figure out if exiting on expressions is disabled.
185 				if (!strcmp(opts.optarg, "-")) vm.no_exprs = true;
186 				else
187 				{
188 					// Barf if not allowed.
189 					if (vm.no_exprs)
190 					{
191 						bc_verr(BC_ERR_FATAL_OPTION, "-f (--file)");
192 					}
193 
194 					// Add the expressions and set exit.
195 					bc_args_file(opts.optarg);
196 					vm.exit_exprs = (exit_exprs || vm.exit_exprs);
197 				}
198 
199 				break;
200 			}
201 
202 			case 'h':
203 			{
204 				bc_vm_info(vm.help);
205 				do_exit = true;
206 				break;
207 			}
208 
209 			case 'i':
210 			{
211 				vm.flags |= BC_FLAG_I;
212 				break;
213 			}
214 
215 			case 'I':
216 			{
217 				ibase = bc_args_builtin(opts.optarg);
218 				break;
219 			}
220 
221 			case 'z':
222 			{
223 				vm.flags |= BC_FLAG_Z;
224 				break;
225 			}
226 
227 			case 'L':
228 			{
229 				vm.line_len = 0;
230 				break;
231 			}
232 
233 			case 'O':
234 			{
235 				obase = bc_args_builtin(opts.optarg);
236 				break;
237 			}
238 
239 			case 'P':
240 			{
241 				vm.flags &= ~(BC_FLAG_P);
242 				break;
243 			}
244 
245 			case 'R':
246 			{
247 				vm.flags &= ~(BC_FLAG_R);
248 				break;
249 			}
250 
251 			case 'S':
252 			{
253 				newscale = bc_args_builtin(opts.optarg);
254 				break;
255 			}
256 
257 #if BC_ENABLE_EXTRA_MATH
258 			case 'E':
259 			{
260 				if (BC_ERR(!bc_num_strValid(opts.optarg)))
261 				{
262 					bc_verr(BC_ERR_FATAL_ARG, opts.optarg);
263 				}
264 
265 				seed = opts.optarg;
266 
267 				break;
268 			}
269 #endif // BC_ENABLE_EXTRA_MATH
270 
271 #if BC_ENABLED
272 			case 'g':
273 			{
274 				assert(BC_IS_BC);
275 				vm.flags |= BC_FLAG_G;
276 				break;
277 			}
278 
279 			case 'l':
280 			{
281 				assert(BC_IS_BC);
282 				vm.flags |= BC_FLAG_L;
283 				break;
284 			}
285 
286 			case 'q':
287 			{
288 				assert(BC_IS_BC);
289 				vm.flags &= ~(BC_FLAG_Q);
290 				break;
291 			}
292 
293 			case 'r':
294 			{
295 				bc_args_redefine(opts.optarg);
296 				break;
297 			}
298 
299 			case 's':
300 			{
301 				assert(BC_IS_BC);
302 				vm.flags |= BC_FLAG_S;
303 				break;
304 			}
305 
306 			case 'w':
307 			{
308 				assert(BC_IS_BC);
309 				vm.flags |= BC_FLAG_W;
310 				break;
311 			}
312 #endif // BC_ENABLED
313 
314 			case 'V':
315 			case 'v':
316 			{
317 				do_exit = version = true;
318 				break;
319 			}
320 
321 #if DC_ENABLED
322 			case 'x':
323 			{
324 				assert(BC_IS_DC);
325 				vm.flags |= DC_FLAG_X;
326 				break;
327 			}
328 #endif // DC_ENABLED
329 
330 #ifndef NDEBUG
331 			// We shouldn't get here because bc_opt_error()/bc_error() should
332 			// longjmp() out.
333 			case '?':
334 			case ':':
335 			default:
336 			{
337 				BC_UNREACHABLE
338 				abort();
339 			}
340 #endif // NDEBUG
341 		}
342 	}
343 
344 	if (version) bc_vm_info(NULL);
345 	if (do_exit)
346 	{
347 		vm.status = (sig_atomic_t) BC_STATUS_QUIT;
348 		BC_JMP;
349 	}
350 
351 	// We do not print the banner if expressions are used or dc is used.
352 	if (!BC_IS_BC || vm.exprs.len > 1) vm.flags &= ~(BC_FLAG_Q);
353 
354 	// We need to make sure the files list is initialized. We don't want to
355 	// initialize it if there are no files because it's just a waste of memory.
356 	if (opts.optind < (size_t) argc && vm.files.v == NULL)
357 	{
358 		bc_vec_init(&vm.files, sizeof(char*), BC_DTOR_NONE);
359 	}
360 
361 	// Add all the files to the vector.
362 	for (i = opts.optind; i < (size_t) argc; ++i)
363 	{
364 		bc_vec_push(&vm.files, argv + i);
365 	}
366 
367 #if BC_ENABLE_EXTRA_MATH
368 	if (seed != NULL)
369 	{
370 		BcNum n;
371 
372 		bc_num_init(&n, strlen(seed));
373 
374 		BC_SIG_UNLOCK;
375 
376 		bc_num_parse(&n, seed, BC_BASE);
377 
378 		bc_program_assignSeed(&vm.prog, &n);
379 
380 		BC_SIG_LOCK;
381 
382 		bc_num_free(&n);
383 	}
384 #endif // BC_ENABLE_EXTRA_MATH
385 
386 	BC_SIG_UNLOCK;
387 
388 	if (newscale != scale)
389 	{
390 		bc_program_assignBuiltin(&vm.prog, true, false, newscale);
391 	}
392 
393 	if (obase != BC_BASE)
394 	{
395 		bc_program_assignBuiltin(&vm.prog, false, true, obase);
396 	}
397 
398 	// This is last to avoid it affecting the value of the others.
399 	if (ibase != BC_BASE)
400 	{
401 		bc_program_assignBuiltin(&vm.prog, false, false, ibase);
402 	}
403 
404 	BC_SIG_LOCK;
405 }
406