xref: /freebsd/contrib/bc/src/args.c (revision 963f5dc7a30624e95d72fb7f87b8892651164e46)
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 
51 /**
52  * Adds @a str to the list of expressions to execute later.
53  * @param str  The string to add to the list of expressions.
54  */
55 static void bc_args_exprs(const char *str) {
56 	BC_SIG_ASSERT_LOCKED;
57 	if (vm.exprs.v == NULL) bc_vec_init(&vm.exprs, sizeof(uchar), BC_DTOR_NONE);
58 	bc_vec_concat(&vm.exprs, str);
59 	bc_vec_concat(&vm.exprs, "\n");
60 }
61 
62 /**
63  * Adds the contents of @a file to the list of expressions to execute later.
64  * @param file  The name of the file whose contents should be added to the list
65  *              of expressions to execute.
66  */
67 static void bc_args_file(const char *file) {
68 
69 	char *buf;
70 
71 	BC_SIG_ASSERT_LOCKED;
72 
73 	vm.file = file;
74 
75 	buf = bc_read_file(file);
76 
77 	assert(buf != NULL);
78 
79 	bc_args_exprs(buf);
80 	free(buf);
81 }
82 
83 #if BC_ENABLED
84 
85 /**
86  * Redefines a keyword, if it exists and is not a POSIX keyword. Otherwise, it
87  * throws a fatal error.
88  * @param keyword  The keyword to redefine.
89  */
90 static void bc_args_redefine(const char *keyword) {
91 
92 	size_t i;
93 
94 	BC_SIG_ASSERT_LOCKED;
95 
96 	for (i = 0; i < bc_lex_kws_len; ++i) {
97 
98 		const BcLexKeyword *kw = bc_lex_kws + i;
99 
100 		if (!strcmp(keyword, kw->name)) {
101 
102 			if (BC_LEX_KW_POSIX(kw)) break;
103 
104 			vm.redefined_kws[i] = true;
105 
106 			return;
107 		}
108 	}
109 
110 	bc_error(BC_ERR_FATAL_ARG, 0, keyword);
111 }
112 
113 #endif // BC_ENABLED
114 
115 void bc_args(int argc, char *argv[], bool exit_exprs) {
116 
117 	int c;
118 	size_t i;
119 	bool do_exit = false, version = false;
120 	BcOpt opts;
121 
122 	BC_SIG_ASSERT_LOCKED;
123 
124 	bc_opt_init(&opts, argv);
125 
126 	// This loop should look familiar to anyone who has used getopt() or
127 	// getopt_long() in C.
128 	while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1) {
129 
130 		switch (c) {
131 
132 			case 'e':
133 			{
134 				// Barf if not allowed.
135 				if (vm.no_exprs)
136 					bc_verr(BC_ERR_FATAL_OPTION, "-e (--expression)");
137 
138 				// Add the expressions and set exit.
139 				bc_args_exprs(opts.optarg);
140 				vm.exit_exprs = (exit_exprs || vm.exit_exprs);
141 
142 				break;
143 			}
144 
145 			case 'f':
146 			{
147 				// Figure out if exiting on expressions is disabled.
148 				if (!strcmp(opts.optarg, "-")) vm.no_exprs = true;
149 				else {
150 
151 					// Barf if not allowed.
152 					if (vm.no_exprs)
153 						bc_verr(BC_ERR_FATAL_OPTION, "-f (--file)");
154 
155 				// Add the expressions and set exit.
156 					bc_args_file(opts.optarg);
157 					vm.exit_exprs = (exit_exprs || vm.exit_exprs);
158 				}
159 
160 				break;
161 			}
162 
163 			case 'h':
164 			{
165 				bc_vm_info(vm.help);
166 				do_exit = true;
167 				break;
168 			}
169 
170 			case 'i':
171 			{
172 				vm.flags |= BC_FLAG_I;
173 				break;
174 			}
175 
176 			case 'z':
177 			{
178 				vm.flags |= BC_FLAG_Z;
179 				break;
180 			}
181 
182 			case 'L':
183 			{
184 				vm.line_len = 0;
185 				break;
186 			}
187 
188 			case 'P':
189 			{
190 				vm.flags &= ~(BC_FLAG_P);
191 				break;
192 			}
193 
194 			case 'R':
195 			{
196 				vm.flags &= ~(BC_FLAG_R);
197 				break;
198 			}
199 
200 #if BC_ENABLED
201 			case 'g':
202 			{
203 				assert(BC_IS_BC);
204 				vm.flags |= BC_FLAG_G;
205 				break;
206 			}
207 
208 			case 'l':
209 			{
210 				assert(BC_IS_BC);
211 				vm.flags |= BC_FLAG_L;
212 				break;
213 			}
214 
215 			case 'q':
216 			{
217 				assert(BC_IS_BC);
218 				vm.flags &= ~(BC_FLAG_Q);
219 				break;
220 			}
221 
222 			case 'r':
223 			{
224 				bc_args_redefine(opts.optarg);
225 				break;
226 			}
227 
228 			case 's':
229 			{
230 				assert(BC_IS_BC);
231 				vm.flags |= BC_FLAG_S;
232 				break;
233 			}
234 
235 			case 'w':
236 			{
237 				assert(BC_IS_BC);
238 				vm.flags |= BC_FLAG_W;
239 				break;
240 			}
241 #endif // BC_ENABLED
242 
243 			case 'V':
244 			case 'v':
245 			{
246 				do_exit = version = true;
247 				break;
248 			}
249 
250 #if DC_ENABLED
251 			case 'x':
252 			{
253 				assert(BC_IS_DC);
254 				vm.flags |= DC_FLAG_X;
255 				break;
256 			}
257 #endif // DC_ENABLED
258 
259 #ifndef NDEBUG
260 			// We shouldn't get here because bc_opt_error()/bc_error() should
261 			// longjmp() out.
262 			case '?':
263 			case ':':
264 			default:
265 			{
266 				BC_UNREACHABLE
267 				abort();
268 			}
269 #endif // NDEBUG
270 		}
271 	}
272 
273 	if (version) bc_vm_info(NULL);
274 	if (do_exit) {
275 		vm.status = (sig_atomic_t) BC_STATUS_QUIT;
276 		BC_JMP;
277 	}
278 
279 	// We do not print the banner if expressions are used or dc is used.
280 	if (!BC_IS_BC || vm.exprs.len > 1) vm.flags &= ~(BC_FLAG_Q);
281 
282 	// We need to make sure the files list is initialized. We don't want to
283 	// initialize it if there are no files because it's just a waste of memory.
284 	if (opts.optind < (size_t) argc && vm.files.v == NULL)
285 		bc_vec_init(&vm.files, sizeof(char*), BC_DTOR_NONE);
286 
287 	// Add all the files to the vector.
288 	for (i = opts.optind; i < (size_t) argc; ++i)
289 		bc_vec_push(&vm.files, argv + i);
290 }
291