xref: /freebsd/contrib/bc/src/args.c (revision e9ac41698b2f322d55ccf9da50a3596edb2c1800)
1 /*
2  * *****************************************************************************
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 2018-2024 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 #include <vm.h>
52 
53 /**
54  * Adds @a str to the list of expressions to execute later.
55  * @param str  The string to add to the list of expressions.
56  */
57 static void
58 bc_args_exprs(const char* str)
59 {
60 	BC_SIG_ASSERT_LOCKED;
61 
62 	if (vm->exprs.v == NULL)
63 	{
64 		bc_vec_init(&vm->exprs, sizeof(uchar), BC_DTOR_NONE);
65 	}
66 
67 	bc_vec_concat(&vm->exprs, str);
68 	bc_vec_concat(&vm->exprs, "\n");
69 }
70 
71 /**
72  * Adds the contents of @a file to the list of expressions to execute later.
73  * @param file  The name of the file whose contents should be added to the list
74  *              of expressions to execute.
75  */
76 static void
77 bc_args_file(const char* file)
78 {
79 	char* buf;
80 
81 	BC_SIG_ASSERT_LOCKED;
82 
83 	vm->file = file;
84 
85 	buf = bc_read_file(file);
86 
87 	assert(buf != NULL);
88 
89 	bc_args_exprs(buf);
90 	free(buf);
91 }
92 
93 static BcBigDig
94 bc_args_builtin(const char* arg)
95 {
96 	bool strvalid;
97 	BcNum n;
98 	BcBigDig res;
99 
100 	strvalid = bc_num_strValid(arg);
101 
102 	if (BC_ERR(!strvalid))
103 	{
104 		bc_verr(BC_ERR_FATAL_ARG, arg);
105 	}
106 
107 	bc_num_init(&n, 0);
108 
109 	bc_num_parse(&n, arg, 10);
110 
111 	res = bc_num_bigdig(&n);
112 
113 	bc_num_free(&n);
114 
115 	return res;
116 }
117 
118 #if BC_ENABLED
119 
120 /**
121  * Redefines a keyword, if it exists and is not a POSIX keyword. Otherwise, it
122  * throws a fatal error.
123  * @param keyword  The keyword to redefine.
124  */
125 static void
126 bc_args_redefine(const char* keyword)
127 {
128 	size_t i;
129 
130 	BC_SIG_ASSERT_LOCKED;
131 
132 	for (i = 0; i < bc_lex_kws_len; ++i)
133 	{
134 		const BcLexKeyword* kw = bc_lex_kws + i;
135 
136 		if (!strcmp(keyword, kw->name))
137 		{
138 			if (BC_LEX_KW_POSIX(kw)) break;
139 
140 			vm->redefined_kws[i] = true;
141 
142 			return;
143 		}
144 	}
145 
146 	bc_error(BC_ERR_FATAL_ARG, 0, keyword);
147 }
148 
149 #endif // BC_ENABLED
150 
151 void
152 bc_args(int argc, char* argv[], bool exit_exprs, BcBigDig* scale,
153         BcBigDig* ibase, BcBigDig* obase)
154 {
155 	int c;
156 	size_t i;
157 	bool do_exit = false, version = false;
158 	BcOpt opts;
159 #if BC_ENABLE_EXTRA_MATH
160 	char* seed = NULL;
161 #endif // BC_ENABLE_EXTRA_MATH
162 
163 	BC_SIG_ASSERT_LOCKED;
164 
165 	bc_opt_init(&opts, argv);
166 
167 	// This loop should look familiar to anyone who has used getopt() or
168 	// getopt_long() in C.
169 	while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1)
170 	{
171 		switch (c)
172 		{
173 			case 'c':
174 			{
175 				vm->flags |= BC_FLAG_DIGIT_CLAMP;
176 				break;
177 			}
178 
179 			case 'C':
180 			{
181 				vm->flags &= ~BC_FLAG_DIGIT_CLAMP;
182 				break;
183 			}
184 
185 			case 'e':
186 			{
187 				// Barf if not allowed.
188 				if (vm->no_exprs)
189 				{
190 					bc_verr(BC_ERR_FATAL_OPTION, "-e (--expression)");
191 				}
192 
193 				// Add the expressions and set exit.
194 				bc_args_exprs(opts.optarg);
195 				vm->exit_exprs = (exit_exprs || vm->exit_exprs);
196 
197 				break;
198 			}
199 
200 			case 'f':
201 			{
202 				// Figure out if exiting on expressions is disabled.
203 				if (!strcmp(opts.optarg, "-")) vm->no_exprs = true;
204 				else
205 				{
206 					// Barf if not allowed.
207 					if (vm->no_exprs)
208 					{
209 						bc_verr(BC_ERR_FATAL_OPTION, "-f (--file)");
210 					}
211 
212 					// Add the expressions and set exit.
213 					bc_args_file(opts.optarg);
214 					vm->exit_exprs = (exit_exprs || vm->exit_exprs);
215 				}
216 
217 				break;
218 			}
219 
220 			case 'h':
221 			{
222 				bc_vm_info(vm->help);
223 				do_exit = true;
224 				break;
225 			}
226 
227 			case 'i':
228 			{
229 				vm->flags |= BC_FLAG_I;
230 				break;
231 			}
232 
233 			case 'I':
234 			{
235 				*ibase = bc_args_builtin(opts.optarg);
236 				break;
237 			}
238 
239 			case 'z':
240 			{
241 				vm->flags |= BC_FLAG_Z;
242 				break;
243 			}
244 
245 			case 'L':
246 			{
247 				vm->line_len = 0;
248 				break;
249 			}
250 
251 			case 'O':
252 			{
253 				*obase = bc_args_builtin(opts.optarg);
254 				break;
255 			}
256 
257 			case 'P':
258 			{
259 				vm->flags &= ~(BC_FLAG_P);
260 				break;
261 			}
262 
263 			case 'R':
264 			{
265 				vm->flags &= ~(BC_FLAG_R);
266 				break;
267 			}
268 
269 			case 'S':
270 			{
271 				*scale = bc_args_builtin(opts.optarg);
272 				break;
273 			}
274 
275 #if BC_ENABLE_EXTRA_MATH
276 			case 'E':
277 			{
278 				if (BC_ERR(!bc_num_strValid(opts.optarg)))
279 				{
280 					bc_verr(BC_ERR_FATAL_ARG, opts.optarg);
281 				}
282 
283 				seed = opts.optarg;
284 
285 				break;
286 			}
287 #endif // BC_ENABLE_EXTRA_MATH
288 
289 #if BC_ENABLED
290 			case 'g':
291 			{
292 				assert(BC_IS_BC);
293 				vm->flags |= BC_FLAG_G;
294 				break;
295 			}
296 
297 			case 'l':
298 			{
299 				assert(BC_IS_BC);
300 				vm->flags |= BC_FLAG_L;
301 				break;
302 			}
303 
304 			case 'q':
305 			{
306 				assert(BC_IS_BC);
307 				vm->flags &= ~(BC_FLAG_Q);
308 				break;
309 			}
310 
311 			case 'r':
312 			{
313 				bc_args_redefine(opts.optarg);
314 				break;
315 			}
316 
317 			case 's':
318 			{
319 				assert(BC_IS_BC);
320 				vm->flags |= BC_FLAG_S;
321 				break;
322 			}
323 
324 			case 'w':
325 			{
326 				assert(BC_IS_BC);
327 				vm->flags |= BC_FLAG_W;
328 				break;
329 			}
330 #endif // BC_ENABLED
331 
332 			case 'V':
333 			case 'v':
334 			{
335 				do_exit = version = true;
336 				break;
337 			}
338 
339 #if DC_ENABLED
340 			case 'x':
341 			{
342 				assert(BC_IS_DC);
343 				vm->flags |= DC_FLAG_X;
344 				break;
345 			}
346 #endif // DC_ENABLED
347 
348 #if BC_DEBUG
349 			// We shouldn't get here because bc_opt_error()/bc_error() should
350 			// longjmp() out.
351 			case '?':
352 			case ':':
353 			default:
354 			{
355 				BC_UNREACHABLE
356 #if !BC_CLANG
357 				abort();
358 #endif // !BC_CLANG
359 			}
360 #endif // BC_DEBUG
361 		}
362 	}
363 
364 	if (version) bc_vm_info(NULL);
365 	if (do_exit)
366 	{
367 		vm->status = (sig_atomic_t) BC_STATUS_QUIT;
368 		BC_JMP;
369 	}
370 
371 	// We do not print the banner if expressions are used or dc is used.
372 	if (BC_ARGS_SHOULD_BE_QUIET) vm->flags &= ~(BC_FLAG_Q);
373 
374 	// We need to make sure the files list is initialized. We don't want to
375 	// initialize it if there are no files because it's just a waste of memory.
376 	if (opts.optind < (size_t) argc && vm->files.v == NULL)
377 	{
378 		bc_vec_init(&vm->files, sizeof(char*), BC_DTOR_NONE);
379 	}
380 
381 	// Add all the files to the vector.
382 	for (i = opts.optind; i < (size_t) argc; ++i)
383 	{
384 		bc_vec_push(&vm->files, argv + i);
385 	}
386 
387 #if BC_ENABLE_EXTRA_MATH
388 	if (seed != NULL)
389 	{
390 		BcNum n;
391 
392 		bc_num_init(&n, strlen(seed));
393 
394 		BC_SIG_UNLOCK;
395 
396 		bc_num_parse(&n, seed, BC_BASE);
397 
398 		bc_program_assignSeed(&vm->prog, &n);
399 
400 		BC_SIG_LOCK;
401 
402 		bc_num_free(&n);
403 	}
404 #endif // BC_ENABLE_EXTRA_MATH
405 }
406