1 /* Command-line and preferences-file options processing.
2    Copyright 2001, 2003, 2004, 2009 Brian R. Gaeke.
3 
4 This file is part of VMIPS.
5 
6 VMIPS is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10 
11 VMIPS is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15 
16 You should have received a copy of the GNU General Public License along
17 with VMIPS; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19 
20 #include "error.h"
21 #include "fileutils.h"
22 #include "options.h"
23 #include "optiontbl.h"
24 #include <cassert>
25 #include <cctype>
26 #include <cerrno>
27 #include <climits>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <cstring>
31 #include <pwd.h>
32 #include <string>
33 #include <unistd.h>
34 #include <vector>
35 
36 #define OPTBUFSIZ 1024
37 
38 int
tilde_expand(char * filename)39 Options::tilde_expand(char *filename)
40 {
41 	struct passwd *pw;
42 	char buf[PATH_MAX], *trailer = NULL;
43 
44 	if (filename[0] != '~') {
45 		return 0;
46 	}
47 	filename[0] = '\0';
48 	trailer = strchr(&filename[1],'/');
49 	if (trailer) {
50 		*trailer++ = '\0';
51 	}
52 	/* &filename[1] now consists of mumble\0 where mumble = zero or more chars
53 	 * representing a username
54 	 */
55 	if (strlen(&filename[1]) == 0) {
56 		pw = getpwuid(getuid());
57 	} else {
58 		pw = getpwnam(&filename[1]);
59 	}
60 	if (!pw) {
61 		return -1;
62 	}
63 	if (trailer) {
64 		sprintf(buf,"%s/%s",pw->pw_dir,trailer);
65 	} else {
66 		strcpy(buf,pw->pw_dir);
67 	}
68 	strcpy(filename, buf);
69 	return 0;
70 }
71 
72 void
process_defaults(void)73 Options::process_defaults(void)
74 {
75 	const char **opt;
76 
77 	for (opt = defaults_table; *opt; opt++) {
78 		process_one_option(*opt);
79 	}
80 	/* ttydev gets a special default, if the OS understands ttyname(0). */
81 	char *ttydev_default = ttyname(0);
82 	if (ttydev_default != NULL) {
83 		set_str_option("ttydev", ttydev_default);
84 	}
85 	for (Option *o = nametable; o->type; o++) {
86 		if (option(o->name) == NULL) {
87 			fprintf(stderr, "Bug: Option `%s' has no compiled-in default.\n",
88 				o->name);
89 		}
90 	}
91 }
92 
93 void
set_str_option(const char * key,const char * value)94 Options::set_str_option(const char *key, const char *value)
95 {
96     int type = STR;
97 
98 	assert (find_option_type(key) == type &&
99 		    "unknown string variable in set_str_option");
100 	Option *o = optstruct(key, true);
101 	assert (o);
102 	o->name = strdup(key);
103 	o->type = type;
104 	o->value.str = strdup(value);
105 }
106 
107 void
set_num_option(const char * key,uint32 value)108 Options::set_num_option(const char *key, uint32 value)
109 {
110     int type = NUM;
111 
112 	assert (find_option_type(key) == type
113             && "unknown numeric variable in set_num_option");
114 	Option *o = optstruct(key, true);
115 	assert (o);
116 	o->name = strdup(key);
117 	o->type = type;
118 	o->value.num = value;
119 }
120 
121 void
set_flag_option(const char * key,bool value)122 Options::set_flag_option(const char *key, bool value)
123 {
124     int type = FLAG;
125 
126 	assert (find_option_type(key) == type
127             && "unknown Boolean variable in set_flag_option");
128 	Option *o = optstruct(key, true);
129 	assert (o);
130 	o->name = strdup(key);
131 	o->type = type;
132 	o->value.flag = value;
133 }
134 
135 
136 /* Returns NULL if the string pointed to by CRACK_SMOKER
137  * does not start with CRACK, or a pointer into CRACK_SMOKER
138  * after the prefix if it does.
139  */
140 const char *
strprefix(const char * crack_smoker,const char * crack)141 Options::strprefix(const char *crack_smoker, const char *crack)
142 {
143 	while (*crack_smoker++ == *crack++);
144 	return (*--crack ? NULL : --crack_smoker);
145 }
146 
147 int
find_option_type(const char * option)148 Options::find_option_type(const char *option)
149 {
150 	Option *o;
151 
152 	for (o = nametable; o->type; o++)
153 		if (strcmp(option, o->name) == 0)
154 			return o->type;
155 	return 0;
156 }
157 
158 void
process_one_option(const char * const option)159 Options::process_one_option(const char *const option)
160 {
161 	char *copy = strdup(option), *equals = NULL;
162 	uint32 num;
163 
164 	if ((equals = strchr(copy, '=')) == NULL) {
165 		/* FLAG option */
166         const char *trailer = NULL;
167         const char *name;
168 		bool value;
169 		if ((trailer = strprefix(copy, "no")) == NULL) {
170 			/* FLAG set to TRUE */
171 			name = copy;
172 			value = true;
173 		} else {
174 			/* FLAG set to FALSE */
175 			name = trailer;
176 			value = false;
177 		}
178         if (find_option_type(name) == FLAG)
179 			set_flag_option(name, value);
180 		else
181 			error("Unknown option: %s\n", name);
182 	} else {
183 		/* STR or NUM option */
184 		*equals++ = '\0';
185                 if (*equals == '\0') {
186                     error ("Option value missing for %s", copy);
187                     free(copy);
188                     return;
189                 }
190 		switch(find_option_type(copy)) {
191 			case STR:
192 				set_str_option(copy, equals);
193 				break;
194 			case NUM:
195 				{
196 					char *endptr;
197 					num = strtoul(equals, &endptr, 0);
198 					switch (*endptr) {
199 						case 'k':
200 						case 'K':
201 							num *= 1024;
202 							break;
203 						case 'm':
204 						case 'M':
205 							num *= 1024 * 1024;
206 							break;
207 						case 'g':
208 						case 'G':
209 							num *= 1024 * 1024 * 1024;
210 							break;
211 						case '\0':
212 							break;
213 						default:
214 							error("bogus suffix on numeric option\n");
215 							break;
216 					}
217 					set_num_option(copy, num);
218 				}
219 				break;
220 			default:
221 				*--equals = '=';
222 				error("Unknown option: %s\n", copy);
223 				break;
224 		}
225 	}
226 	free(copy);
227 }
228 
229 /* This could probably be improved upon. Perhaps it should
230  * be replaced with a Lex rule-set or something...
231  */
232 int
process_first_option(char ** bufptr,int lineno,const char * filename)233 Options::process_first_option(char **bufptr, int lineno, const char *filename)
234 {
235 	char *out, *in, copybuf[OPTBUFSIZ], char_seen;
236 	bool quoting, quotenext, done, string_done;
237 
238 	out = *bufptr;
239 	in = copybuf;
240 	done = string_done = quoting = quotenext = false;
241 
242 	while (isspace(*out)) {
243 		out++;
244 	}
245 	while (!done) {
246 		char_seen = *out++;
247 		if (char_seen == '\0') {
248 			done = true;
249 			string_done = true;
250 		} else {
251 			if (quoting) {
252 				if (char_seen == '\'') {
253 					quoting = false;
254 				} else {
255 					*in++ = char_seen;
256 				}
257 			} else if (quotenext) {
258 				*in++ = char_seen;
259 				quotenext = false;
260 			} else {
261 				if (char_seen == '\'') {
262 					quoting = true;
263 				} else if (char_seen == '\\') {
264 					quotenext = true;
265 				} else if (char_seen == '#') {
266 					done = true;
267 					string_done = true;
268 				} else if (!isspace(char_seen)) {
269 					*in++ = char_seen;
270 				} else {
271 					done = true;
272 					string_done = (out[0] == '\0');
273 				}
274 			}
275 		}
276 	}
277 	*in++ = '\0';
278 	*bufptr = out;
279 	if (quoting) {
280 		fprintf(stderr,
281 			"warning: unterminated quote in config file %s, line %d\n",
282 			filename, lineno);
283 	}
284 	if (strlen(copybuf) > 0) {
285 		process_one_option(copybuf);
286 	}
287 	return string_done ? 0 : 1;
288 }
289 
290 int
process_options_from_file(const char * filename)291 Options::process_options_from_file(const char *filename)
292 {
293 	char *buf = new char[OPTBUFSIZ];
294 	if (!buf) {
295 		fatal_error("Can't allocate %u bytes for I/O buffer; aborting!\n",
296 		            OPTBUFSIZ);
297 	}
298 	FILE *f = fopen(filename, "r");
299 	if (!f) {
300 		if (errno != ENOENT) {
301 		    error ("Can't open config file '%s': %s\n", filename,
302 		           strerror(errno));
303 		}
304 		return -1;
305 	}
306 	char *p = buf;
307 	int rc, lineno = 1;
308     for (char *ptr = fgets(buf, OPTBUFSIZ, f); ptr;
309          ++lineno, p = buf, ptr = fgets(buf, OPTBUFSIZ, f))
310 		do
311 			rc = process_first_option(&p, lineno, filename);
312 		while (rc == 1);
313 	fclose(f);
314 	delete [] buf;
315 	return 0;
316 }
317 
318 void
usage(char * argv0)319 Options::usage(char *argv0)
320 {
321 	printf(
322 "Usage: %s [OPTION]... [ROM-FILE]\n"
323 "Start the %s virtual machine, using the ROM-FILE as the boot ROM.\n"
324 "\n"
325 "  -o OPTION                  behave as if OPTION were specified in .vmipsrc\n"
326 "                               (see manual for details)\n"
327 "  -F FILE                    read options from FILE instead of .vmipsrc\n"
328 "  -n                         do not read the system-wide configuration file\n"
329 "  --version                  display version information and exit\n"
330 "  --help                     display this help message and exit\n"
331 "  --print-config             display compile-time variables and exit\n"
332 "\n"
333 "By default, `romfile.rom' is used if no ROM-FILE is specified.\n"
334 "\n"
335 "Report bugs to <vmips@dgate.org>.\n",
336 	PACKAGE, PACKAGE);
337 }
338 
339 void
process_options(int argc,char ** argv)340 Options::process_options(int argc, char **argv)
341 {
342     /* Get options from defaults. */
343     process_defaults();
344 
345 	/* Get default name of user's config file */
346 	char user_config_filename[PATH_MAX] = "~/.vmipsrc";
347 
348 	/* Process command line */
349 	bool read_system_config_file = true;
350 	std::vector<std::string> command_line_options;
351 	for (int i = 1; i < argc; ++i) {
352 		if (strcmp (argv[i], "--version") == 0) {
353 			print_package_version (PACKAGE, VERSION);
354 			exit (0);
355 		} else if (strcmp (argv[i], "--help") == 0) {
356 			usage (argv[0]);
357 			exit (0);
358 		} else if (strcmp (argv[i], "--print-config") == 0) {
359 			print_config_info ();
360 			exit (0);
361 		} else if (strcmp (argv[i], "-o") == 0) {
362 			if (argc <= i + 1)
363 				error_exit ("The -o flag requires an argument. Try %s --help",
364                             argv[0]);
365 			command_line_options.push_back (argv[i + 1]);
366 			++i;
367 		} else if (strcmp (argv[i], "-F") == 0) {
368 			if (argc <= i + 1)
369 				error_exit ("The -F flag requires an argument. Try %s --help",
370                             argv[0]);
371 			strcpy (user_config_filename, argv[i + 1]);
372 			++i;
373 		} else if (strcmp (argv[i], "-n") == 0) {
374 			read_system_config_file = false;
375 		} else if (i == argc - 1) {
376             if (!can_read_file (argv[i]) && argv[i][0] == '-') {
377 			    error_exit ("Unrecognized option %s. Try %s --help", argv[i],
378                             argv[0]);
379             }
380 			set_str_option("romfile", argv[i]);
381 		} else {
382 			error_exit ("Unrecognized option %s. Try %s --help", argv[i],
383                         argv[0]);
384 		}
385 	}
386 
387 	/* Get options from system configuration file */
388 	if (read_system_config_file)
389 		process_options_from_file (SYSTEM_CONFIG_FILE);
390 
391 	/* Get options from user configuration file */
392 	tilde_expand (user_config_filename);
393 	process_options_from_file (user_config_filename);
394 
395 	/* Process -o options saved from command line, above. */
396 	for (std::vector<std::string>::iterator i = command_line_options.begin (),
397 		 e = command_line_options.end (); i != e; ++i)
398 		process_one_option (i->c_str ());
399 }
400 
401 Option *
optstruct(const char * name,bool install)402 Options::optstruct(const char *name, bool install)
403 {
404     OptionMap::iterator i = table.find (name);
405     if (i == table.end ()) {
406         if (install) {
407             table[name] = Option ();
408             return &table[name];
409         } else {
410             return 0;
411         }
412     }
413 	return &table[name];
414 }
415 
416 union OptionValue *
option(const char * name)417 Options::option(const char *name)
418 {
419 	Option *o = optstruct(name);
420 
421 	if (o)
422 		return &o->value;
423     fatal_error ("Attempt to get the value of unknown option '%s'", name);
424 	return NULL;
425 }
426 
427 void
print_config_info(void)428 Options::print_config_info(void)
429 {
430 #ifdef INTENTIONAL_CONFUSION
431     puts("Registers initialized to random values instead of zero");
432 #else
433     puts("Registers initialized to zero");
434 #endif
435 
436 #ifdef HAVE_LONG_LONG
437     puts("Host compiler has native support for 8-byte integers");
438 #else
439     puts("Host compiler does not natively support 8-byte integers");
440 #endif
441 }
442 
443 void
print_package_version(const char * toolname,const char * version)444 Options::print_package_version(const char *toolname, const char *version)
445 {
446 	printf(
447 "%s %s\n"
448 "Copyright (C) 2001, 2002, 2003, 2004, 2009, 2012, 2013 by Brian R. Gaeke\n"
449 "and others. (See the files `AUTHORS' and `THANKS' in the %s source\n"
450 "distribution for a complete list.)\n"
451 "\n"
452 "%s is free software; you can redistribute it and/or modify it\n"
453 "under the terms of the GNU General Public License as published by the\n"
454 "Free Software Foundation; either version 2 of the License, or (at your\n"
455 "option) any later version.\n"
456 "\n"
457 "%s is distributed in the hope that it will be useful, but\n"
458 "WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n"
459 "or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n"
460 "for more details.\n"
461 "\n"
462 "You should have received a copy of the GNU General Public License along\n"
463 "with %s; if not, write to the Free Software Foundation, Inc.,\n"
464 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n",
465 	toolname, version, toolname, toolname, toolname, toolname);
466 }
467 
468