procconf.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*
  2. * procopts: process command line options and config file variables.
  3. * This is still more or less a first cut (despite the rewrite!)
  4. * 2000-07-22 John H. DuBois III (john@armory.com)
  5. * 2007-04-28 2.0 Added command line processing. Largely rewritten.
  6. * 2011-10-30 Cleaned up. Added double types and out-of-range checking.
  7. */
  8. #include <string.h>
  9. #include <malloc.h>
  10. #include <stdlib.h>
  11. #include <stdio.h>
  12. #include <ctype.h>
  13. #include <sys/types.h>
  14. #include <stdarg.h>
  15. #include <unistd.h>
  16. #include <errno.h>
  17. #include "procconf.h"
  18. static char errmsg[256]; /* for returning error descriptions */
  19. static const char *pc_name;
  20. static const char *pc_usage;
  21. static unsigned debugLevel = 0; /* set by debug option */
  22. static int readConf = 1; /* should config file be read? */
  23. #define VERBOSE_DEBUG 7
  24. #define INTERNAL_ERROR -1
  25. #define USAGE_ERROR -2
  26. /*
  27. * error: printf-style interface to print an error message or write it into
  28. * global errmsg.
  29. */
  30. static void
  31. error(const int errtype, const char *format, ...)
  32. {
  33. va_list ap;
  34. va_start(ap,format);
  35. if (pc_usage != NULL) { /* error messages should be printed directly */
  36. fprintf(stderr, "%s: ", pc_name);
  37. vfprintf(stderr, format, ap);
  38. putc('\n', stderr);
  39. if (errtype == USAGE_ERROR)
  40. fputs(pc_usage, stderr);
  41. exit(errtype == USAGE_ERROR ? 2 : 1);
  42. }
  43. else
  44. vsnprintf(errmsg, sizeof(errmsg), format, ap);
  45. va_end(ap);
  46. }
  47. static void *
  48. pc_malloc(size_t size)
  49. {
  50. void *ret = malloc(size);
  51. if (ret == NULL)
  52. error(INTERNAL_ERROR, "Out of memory");
  53. return ret;
  54. }
  55. static void
  56. opterror(const char *expected, const char *value, const confvar_t *varDesc,
  57. const char filename[], const char *detail)
  58. {
  59. if (detail == NULL)
  60. detail = "";
  61. if (filename == NULL)
  62. error(USAGE_ERROR,
  63. "Invalid value given for option -%c: expected %s, got: %s%s",
  64. varDesc->outind, expected, value, detail);
  65. else
  66. error(USAGE_ERROR, "Invalid value given in configuration file \"%s\""
  67. " for option %s: expected %s, got: %s%s",
  68. filename, varDesc->varname, expected, value, detail);
  69. }
  70. /*
  71. * Add an option flag or value assignment to the options database.
  72. * This does all option-type-specific processing, and generates linked lists
  73. * of option structures.
  74. *
  75. * Input variables:
  76. * source is the source the option came from (command line, config file, etc.)
  77. * value is the string value assigned to the option, for options that take
  78. * values.
  79. * varDesc is the description structure for this option.
  80. * filename is the configuration file name, if this option came from a config
  81. * file, else null.
  82. *
  83. * Output variables:
  84. * The option value is stored in the option list pointed to by first.
  85. * last is used to track the last record in each option list, so option values
  86. * can be appended easily.
  87. *
  88. * Return value:
  89. * 0 if option was ignored.
  90. * 1 if option was processed & added to option chain.
  91. * On error, a string describing the error is stored in the global errmsg and
  92. * -1 is returned.
  93. */
  94. static int
  95. addOptVal(const cf_source source, const char *value, const confvar_t *varDesc,
  96. confval **first, confval **last, const char filename[])
  97. {
  98. const void *addr;
  99. confval data, *ret_data;
  100. int seen = *first != NULL;
  101. char *ptr;
  102. int err;
  103. /* if first instance of this option, store result to given addr */
  104. addr = seen ? NULL : varDesc->addr;
  105. switch (varDesc->type) {
  106. case CF_CHAR:
  107. if (strlen(value) > 1) { /* length 0 is OK; gives null char */
  108. opterror("a single character", value, varDesc, filename, NULL);
  109. return -1;
  110. }
  111. data.value.charval = *value;
  112. if (addr != NULL)
  113. *(char *) addr = *value;
  114. break;
  115. case CF_STRING:
  116. case CF_NE_STRING:
  117. if (varDesc->type == CF_NE_STRING && *value == '\0') {
  118. opterror("a non-empty string", value, varDesc, filename, NULL);
  119. return -1;
  120. }
  121. data.value.string = value;
  122. if (addr != NULL)
  123. *(const char **) addr = value;
  124. break;
  125. case CF_INT:
  126. case CF_NON_NEG_INT:
  127. case CF_POS_INT:
  128. case CF_PDEBUG:
  129. /* todo: check for out-of-range result */
  130. errno = 0;
  131. data.value.intval = strtol(value, &ptr, 0);
  132. if (errno == ERANGE) {
  133. opterror("an integer", value, varDesc, filename,
  134. " (out of range)");
  135. return -1;
  136. }
  137. err = *value == '\0' || *ptr != '\0';
  138. switch (varDesc->type) {
  139. case CF_INT:
  140. if (err) {
  141. opterror("an integer", value, varDesc, filename, NULL);
  142. return -1;
  143. }
  144. break;
  145. case CF_NON_NEG_INT:
  146. if (err || data.value.intval < 0) {
  147. opterror("a non-negative integer", value, varDesc, filename,
  148. NULL);
  149. return -1;
  150. }
  151. data.value.nnint = data.value.intval;
  152. break;
  153. case CF_POS_INT:
  154. case CF_PDEBUG:
  155. if (err || data.value.intval <= 0) {
  156. opterror("a positive integer", value, varDesc, filename, NULL);
  157. return -1;
  158. }
  159. data.value.nnint = data.value.intval;
  160. break;
  161. default:
  162. /* To avoid complaints from -Wall */
  163. ;
  164. }
  165. if (addr != NULL)
  166. *(int *) addr = data.value.intval;
  167. if (!seen && varDesc->type == CF_PDEBUG)
  168. debugLevel = data.value.intval;
  169. break;
  170. case CF_FLOAT:
  171. case CF_NON_NEG_FLOAT:
  172. case CF_POS_FLOAT:
  173. /* todo: check for out-of-range result */
  174. errno = 0;
  175. data.value.floatval = strtod(value, &ptr);
  176. if (errno == ERANGE) {
  177. opterror("a number", value, varDesc, filename, " (out of range)");
  178. return -1;
  179. }
  180. err = *value == '\0' || *ptr != '\0';
  181. switch (varDesc->type) {
  182. case CF_FLOAT:
  183. if (err) {
  184. opterror("a number", value, varDesc, filename, NULL);
  185. return -1;
  186. }
  187. break;
  188. case CF_NON_NEG_FLOAT:
  189. if (err || data.value.floatval < 0) {
  190. opterror("a non-negative number", value, varDesc, filename,
  191. NULL);
  192. return -1;
  193. }
  194. break;
  195. case CF_POS_FLOAT:
  196. if (err || data.value.floatval <= 0) {
  197. opterror("a positive number", value, varDesc, filename, NULL);
  198. return -1;
  199. }
  200. break;
  201. default:
  202. /* To avoid complaints from -Wall */
  203. ;
  204. }
  205. if (addr != NULL)
  206. *(double *) addr = data.value.floatval;
  207. break;
  208. case CF_SWITCH:
  209. case CF_NOCONF:
  210. case CF_SDEBUG:
  211. /* If option not turned on, ignore */
  212. if (source == CF_FILE && *value != '1')
  213. return 0;
  214. data.value.switchval = varDesc->value;
  215. value = "1"; /* for debugging */
  216. if (addr != NULL)
  217. *(int *) addr = varDesc->value;
  218. if (!seen)
  219. switch (varDesc->type) {
  220. case CF_NOCONF:
  221. readConf = 0;
  222. break;
  223. case CF_SDEBUG:
  224. debugLevel = 9;
  225. break;
  226. default:
  227. /* To avoid complaints from -Wall */
  228. ;
  229. }
  230. break;
  231. case CF_ENDLIST:
  232. /* To avoid complaints from -Wall */
  233. ;
  234. }
  235. data.strval = value;
  236. data.source = source;
  237. data.next = NULL;
  238. if ((ret_data = (confval *)pc_malloc(sizeof(confval))) == NULL)
  239. return -1;
  240. *ret_data = data;
  241. if (seen)
  242. (*last)->next = ret_data;
  243. else
  244. *first = ret_data;
  245. *last = ret_data;
  246. if (debugLevel >= VERBOSE_DEBUG)
  247. fprintf(stderr, "Option %c (%s) gets value \"%s\" from %s%s\n",
  248. 0 < varDesc->outind && varDesc->outind < 256 ?
  249. varDesc->outind : '-',
  250. varDesc->varname == NULL ? "-" : varDesc->varname, value,
  251. source == CF_ARGS ? "command line" : "config file", seen ?
  252. " (already seen)" : "");
  253. return 1;
  254. }
  255. /*
  256. * This currently depends on defread().
  257. * A CDDL version of the def* library exists:
  258. *http://www.opensource.apple.com/source/autofs/autofs-207/automountlib/deflt.c
  259. * But, this should really be rewritten to not use defread().
  260. */
  261. #ifdef DEFREAD
  262. /*
  263. * Input variables:
  264. * filename: Name of configuration file. If it begins with ~/, the ~ is
  265. * replaced with the invoking user's home directory.
  266. * optConf[]: Option description structures.
  267. *
  268. * Output variables:
  269. * See addOptVal().
  270. *
  271. * Return value:
  272. * If the config file does not exist, 0.
  273. * Otherwise, the number of variable assignments read is returned (which may
  274. * also result in a 0 return value).
  275. * On error, a string describing the error is stored in the global errmsg and
  276. * -1 is returned.
  277. */
  278. static int
  279. procConfFile(const char filename[], const confvar_t optConf[],
  280. confval *first[], confval *last[]) {
  281. int count;
  282. char *home;
  283. int ind;
  284. if (!strncmp(filename, "~/", 2)) {
  285. char *path;
  286. if (!(home = getenv("HOME"))) {
  287. if (debugLevel > 2)
  288. fprintf(stderr, "HOME environment variable not set.\n");
  289. return 0;
  290. }
  291. if ((path = pc_malloc(strlen(home) + strlen(filename))) == NULL)
  292. return -1;
  293. strcpy(path, home);
  294. strcat(path, filename+1);
  295. if (defopen(path)) {
  296. free(path);
  297. return 0;
  298. }
  299. free(path);
  300. }
  301. else if (defopen((char *)filename)) {
  302. if (debugLevel > 2)
  303. fprintf(stderr, "Config file '%s' not found.\n", filename);
  304. return 0;
  305. }
  306. count = 0;
  307. for (ind = 0; optConf[ind].type != CF_ENDLIST; ind++) {
  308. char buf[128];
  309. char *s;
  310. if (optConf[ind].varname == NULL)
  311. continue;
  312. strncpy(buf, optConf[ind].varname, 126);
  313. strcat(buf, "=");
  314. if ((s = defread(buf)) != NULL) {
  315. int ret;
  316. if ((s = strdup(s)) == NULL) {
  317. error(INTERNAL_ERROR, "Out of memory");
  318. return -1;
  319. }
  320. switch ((ret = addOptVal(CF_FILE, s, &optConf[ind], &first[ind],
  321. &last[ind], filename))) {
  322. case 1:
  323. count++;
  324. break;
  325. case 0:
  326. break;
  327. default:
  328. return ret;
  329. }
  330. }
  331. }
  332. return count;
  333. }
  334. #endif
  335. /*
  336. * Input variables:
  337. * argc, argv: Command line data.
  338. * optConf[]: Option description structures.
  339. *
  340. * Output variables:
  341. * See addOptVal().
  342. * After processing, argc will be the number of non-option arguments and argv
  343. * will start with the first non-option argument.
  344. *
  345. * Return value:
  346. * On success, the number of options processed.
  347. * On error, a string describing the error is stored in the global errmsg and
  348. * -1 is returned.
  349. */
  350. static int
  351. procCmdLineArgs(int *argc, const char **argv[], const confvar_t optConf[],
  352. confval **first, confval **last)
  353. {
  354. char *p;
  355. extern char *optarg; /* For getopt */
  356. extern int optind; /* For getopt */
  357. extern int optopt; /* For getopt */
  358. char optstr[514];
  359. unsigned optCharToConf[256];
  360. int optchar;
  361. unsigned confNum;
  362. int count = 0;
  363. optind = 1;
  364. p = optstr;
  365. *(p++) = ':';
  366. for (confNum = 0; optConf[confNum].type != CF_ENDLIST; confNum++) {
  367. unsigned outind = optConf[confNum].outind;
  368. if (outind < 256 && isprint(outind)) {
  369. *(p++) = (char) outind;
  370. switch (optConf[confNum].type) {
  371. case CF_SWITCH:
  372. case CF_NOCONF:
  373. case CF_SDEBUG:
  374. break;
  375. default:
  376. *(p++) = ':';
  377. break;
  378. }
  379. optCharToConf[outind] = confNum;
  380. }
  381. }
  382. *p = '\0';
  383. while ((optchar = getopt(*argc, const_cast<char **>(*argv), optstr)) != -1) {
  384. int ind;
  385. int ret;
  386. if (optchar == '?') {
  387. error(USAGE_ERROR, "Unknown option character '%c'", optopt);
  388. return -1;
  389. }
  390. else if (optchar == ':') {
  391. error(USAGE_ERROR, "No value given for option -%c", optopt);
  392. return -1;
  393. }
  394. ind = optCharToConf[optchar];
  395. switch (ret = addOptVal(CF_ARGS, optarg, &optConf[ind], &first[ind],
  396. &last[ind], NULL)) {
  397. case 1:
  398. count++;
  399. break;
  400. case 0:
  401. break;
  402. default:
  403. return ret;
  404. }
  405. }
  406. *argc -= optind;
  407. *argv += optind;
  408. return count;
  409. }
  410. /*
  411. * Input variables:
  412. * argc, argv: Command line data.
  413. * optConf[]: Option description structures.
  414. * confFile[]: Configuration file, or NULL if none provided.
  415. * name: Name of program, for messages.
  416. * usage: Usage message. If non-null, on error a message is printed to stderr
  417. * and the program exits.
  418. *
  419. * Output variables:
  420. * The processed option values are stored in confdata.
  421. * A pointer to the start of the values for each option is stored in
  422. * confdata->optVals[].values at the same offset as the option appears in
  423. * confdata[].
  424. * For any option for option characters/indexes have been used,
  425. * confdata->map[index] is set to the same data.
  426. * After processing, argc will have been adjusted to be the number of
  427. * non-option arguments and argv will have been adjusted to start with the
  428. * first non-option argument.
  429. *
  430. * Return value:
  431. * On success, NULL.
  432. * On error, a message describing the problem.
  433. */
  434. const char *
  435. procOpts(int *argc, const char **argv[], const confvar_t optConf[],
  436. confdata_t *confdata, const char confFile[], const char name[],
  437. const char usage[])
  438. {
  439. /* Number of configuration options given in optConf */
  440. unsigned numConf;
  441. /* First & last records in the linked list maintained for each option */
  442. confval **first, **last;
  443. unsigned maxOptIndex = 0; /* The highest option index number seen */
  444. /* number of option instances + assignments given */
  445. int numOptsFound;
  446. unsigned optNum;
  447. unsigned i;
  448. confval **valuePointers;
  449. pc_name = name;
  450. pc_usage = usage;
  451. for (numConf = 0; optConf[numConf].type != CF_ENDLIST; numConf++) {
  452. unsigned outind = optConf[numConf].outind;
  453. if ((outind & ~CF_NOTFLAG) > maxOptIndex)
  454. maxOptIndex = outind & ~CF_NOTFLAG;
  455. }
  456. if ((first = (confval **)pc_malloc(sizeof(confval *) * numConf)) == NULL ||
  457. (last =
  458. (confval **)pc_malloc(sizeof(confval *) * numConf)) == NULL)
  459. return errmsg;
  460. memset(first, '\0', sizeof(confval *) * numConf);
  461. memset(last, '\0', sizeof(confval *) * numConf);
  462. if ((numOptsFound =
  463. procCmdLineArgs(argc, argv, optConf, first, last)) < 0)
  464. return errmsg;
  465. if (readConf && confFile != NULL) {
  466. #ifdef DEFREAD
  467. int ret;
  468. if ((ret = procConfFile(confFile, optConf, first, last)) < 0)
  469. return errmsg;
  470. else
  471. nummOptsFound += ret;
  472. #else
  473. error(INTERNAL_ERROR, "Built without defread!");
  474. return errmsg;
  475. #endif
  476. }
  477. free(last);
  478. /*
  479. * All options have been read & initial processing done.
  480. * An array of pointers is now generated for the options.
  481. */
  482. if ((valuePointers =
  483. (confval **)pc_malloc(sizeof(confval *) * numOptsFound)) == NULL ||
  484. (confdata->optVals =
  485. (cf_option *)pc_malloc(sizeof(cf_option) * numConf)) == NULL)
  486. return errmsg;
  487. /* If option index numbers are used, allocate a map for them */
  488. if (maxOptIndex != 0) {
  489. if ((confdata->map =
  490. (cf_option **)pc_malloc(sizeof(cf_option) * (maxOptIndex+1)))
  491. == NULL)
  492. return errmsg;
  493. memset(confdata->map, '\0', sizeof(confval *) * (maxOptIndex+1));
  494. }
  495. /*
  496. * Store the linked lists of option values into arrays.
  497. * Pointers to all option instances are stored in valuePointers,
  498. * with the values for each particular option being contiguous.
  499. */
  500. i = 0;
  501. for (optNum = 0; optNum < numConf; optNum++) {
  502. unsigned outind = optConf[optNum].outind;
  503. confval *optval;
  504. confdata->optVals[optNum].num = 0;
  505. confdata->optVals[optNum].values = &valuePointers[i];
  506. if (outind != 0)
  507. confdata->map[outind & ~CF_NOTFLAG] = &confdata->optVals[optNum];
  508. for (optval = first[optNum]; optval != NULL; optval = optval->next) {
  509. confdata->optVals[optNum].num++;
  510. valuePointers[i++] = optval;
  511. }
  512. if (debugLevel > 5)
  513. fprintf(stderr, "Option %c (%s) got %d values\n",
  514. outind == 0 ? '-' : outind,
  515. optConf[optNum].varname == NULL ? "-" :
  516. optConf[optNum].varname,
  517. confdata->optVals[optNum].num);
  518. }
  519. free(first);
  520. return NULL;
  521. }