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