procconf.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /*
  2. * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
  3. *
  4. * Permission to use, copy, modify, and/or distribute this software for any
  5. * purpose with or without fee is hereby granted, provided that the above
  6. * copyright notice and this permission notice appear in all copies.
  7. *
  8. * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  9. * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  10. * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  12. * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  13. * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  14. * PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include <string.h>
  17. #include <stdlib.h>
  18. #include <stdio.h>
  19. #include <ctype.h>
  20. #include <sys/types.h>
  21. #include <stdarg.h>
  22. #include <unistd.h>
  23. #include <errno.h>
  24. #include "procconf.h"
  25. static char errmsg[256]; /* for returning error descriptions */
  26. static const char* pc_name;
  27. static const char* pc_usage;
  28. #define INTERNAL_ERROR -1
  29. #define USAGE_ERROR -2
  30. /*
  31. * error: printf-style interface to print an error message or write it into
  32. * global errmsg. If a usage message has been given (the indicator that errors
  33. * should be handled internally), the message is printed to stderr and the
  34. * program is exited. If not, it is written into errmsg.
  35. * Input variables:
  36. * errtype is the error type. If USAGE_ERROR, the program's usage is included
  37. * in error messages, and the exit status is 2; otherwise the exit status
  38. * is 1.
  39. * format and the remaining arguments are as for printf().
  40. */
  41. static void
  42. error(const int errtype, const char* format, ...) {
  43. va_list ap;
  44. va_start(ap,format);
  45. if (pc_usage != NULL) { /* error messages should be printed directly */
  46. fprintf(stderr, "%s: ", pc_name);
  47. vfprintf(stderr, format, ap);
  48. putc('\n', stderr);
  49. if (errtype == USAGE_ERROR) {
  50. fputs(pc_usage, stderr);
  51. }
  52. exit(errtype == USAGE_ERROR ? 2 : 1);
  53. } else {
  54. vsnprintf(errmsg, sizeof(errmsg), format, ap);
  55. }
  56. va_end(ap);
  57. }
  58. /*
  59. * Allocate memory, with error checking.
  60. * On allocation failure, error() is called; see its description.
  61. * The memory is zeroed before being returned.
  62. */
  63. static void*
  64. pc_malloc(size_t size) {
  65. void* ret = calloc(1, size);
  66. if (ret == NULL) {
  67. error(INTERNAL_ERROR, "Out of memory");
  68. }
  69. return(ret);
  70. }
  71. /*
  72. * Generate an error message describing an error in the value passed with an
  73. * option. This is a front end for error(); see its usage for details.
  74. * Input variables:
  75. * expected: A description of what was expected for the value.
  76. * varDesc: The descriptor for the option.
  77. * value: The value that was given with the option.
  78. * detail: If non-null, additional detail to append regardign the problem.
  79. */
  80. static void
  81. opterror(const char* expected, const char* value, const confvar_t* varDesc,
  82. const char* detail) {
  83. if (detail == NULL) {
  84. detail = "";
  85. }
  86. error(USAGE_ERROR,
  87. "Invalid value given for option -%c: expected %s, got: %s%s",
  88. varDesc->outind, expected, value, detail);
  89. }
  90. /*
  91. * Add an option flag or value assignment to the options database.
  92. * This does all option-type-specific processing, and generates linked lists
  93. * of option structures.
  94. *
  95. * Input variables:
  96. * value is the string value assigned to the option, for options that take
  97. * values.
  98. * varDesc is the description structure for this option.
  99. *
  100. * Output variables:
  101. * The option data is stored in a malloced confval structure, which is appended
  102. * to the option list. The first element of the list is pointed to by first.
  103. * first should be NULL if the list contains no elements; in this case it is
  104. * set to point to the new structure. Otherwise the structure is appended to
  105. * the element pointed to by last (it is updated to point to that structure).
  106. * The 'next' element of the new structure is set to NULL; this is the list
  107. * termination indicator when traversing it.
  108. *
  109. * Return value:
  110. * 0 if option was ignored.
  111. * 1 if option was processed & added to option chain.
  112. * On error, a string describing the error is stored in the global errmsg and
  113. * -1 is returned.
  114. */
  115. static int
  116. addOptVal(const char* value, const confvar_t* varDesc,
  117. confval** first, confval** last) {
  118. const void* addr; /* address, if any at which to store value */
  119. confval data; /* data for this option/value */
  120. confval *ret_data; /* allocated structure to add to list */
  121. int seen = *first != NULL; /* has this option been seen before? */
  122. char* ptr; /* character that terminated processing in strtox() */
  123. int err; /* bad numeric value found? */
  124. /* if first instance of this option, store result to given addr */
  125. addr = seen ? NULL : varDesc->addr;
  126. switch (varDesc->type) {
  127. case CF_CHAR:
  128. if (strlen(value) > 1) { /* length 0 is OK; gives null char */
  129. opterror("a single character", value, varDesc, NULL);
  130. return(-1);
  131. }
  132. data.value.charval = *value;
  133. if (addr != NULL) {
  134. *(char*)addr = *value;
  135. }
  136. break;
  137. case CF_STRING:
  138. case CF_NE_STRING:
  139. if (varDesc->type == CF_NE_STRING && *value == '\0') {
  140. opterror("a non-empty string", value, varDesc, NULL);
  141. return(-1);
  142. }
  143. data.value.string = value;
  144. if (addr != NULL) {
  145. *(const char**)addr = value;
  146. }
  147. break;
  148. case CF_INT:
  149. case CF_NON_NEG_INT:
  150. case CF_POS_INT:
  151. /* todo: check for out-of-range result */
  152. errno = 0;
  153. data.value.intval = strtol(value, &ptr, 0);
  154. if (errno == ERANGE) {
  155. opterror("an integer", value, varDesc,
  156. " (out of range)");
  157. return(-1);
  158. }
  159. err = *value == '\0' || *ptr != '\0';
  160. switch (varDesc->type) {
  161. case CF_INT:
  162. if (err) {
  163. opterror("an integer", value, varDesc, NULL);
  164. return(-1);
  165. }
  166. break;
  167. case CF_NON_NEG_INT:
  168. if (err || data.value.intval < 0) {
  169. opterror("a non-negative integer", value, varDesc,
  170. NULL);
  171. return(-1);
  172. }
  173. data.value.nnint = data.value.intval;
  174. break;
  175. case CF_POS_INT:
  176. if (err || data.value.intval <= 0) {
  177. opterror("a positive integer", value, varDesc, NULL);
  178. return(-1);
  179. }
  180. data.value.nnint = data.value.intval;
  181. break;
  182. default:
  183. /* To avoid complaints from -Wall */
  184. ;
  185. }
  186. if (addr != NULL) {
  187. *(int*)addr = data.value.intval;
  188. }
  189. break;
  190. case CF_FLOAT:
  191. case CF_NON_NEG_FLOAT:
  192. case CF_POS_FLOAT:
  193. /* todo: check for out-of-range result */
  194. errno = 0;
  195. data.value.floatval = strtod(value, &ptr);
  196. if (errno == ERANGE) {
  197. opterror("a number", value, varDesc, " (out of range)");
  198. return(-1);
  199. }
  200. err = *value == '\0' || *ptr != '\0';
  201. switch (varDesc->type) {
  202. case CF_FLOAT:
  203. if (err) {
  204. opterror("a number", value, varDesc, NULL);
  205. return(-1);
  206. }
  207. break;
  208. case CF_NON_NEG_FLOAT:
  209. if (err || data.value.floatval < 0) {
  210. opterror("a non-negative number", value, varDesc,
  211. NULL);
  212. return(-1);
  213. }
  214. break;
  215. case CF_POS_FLOAT:
  216. if (err || data.value.floatval <= 0) {
  217. opterror("a positive number", value, varDesc, NULL);
  218. return(-1);
  219. }
  220. break;
  221. default:
  222. /* To avoid complaints from -Wall */
  223. ;
  224. }
  225. if (addr != NULL) {
  226. *(double*)addr = data.value.floatval;
  227. }
  228. break;
  229. case CF_SWITCH:
  230. data.value.switchval = varDesc->value;
  231. value = "1"; /* for debugging */
  232. if (addr != NULL) {
  233. *(int*)addr = varDesc->value;
  234. }
  235. break;
  236. case CF_ENDLIST:
  237. /* To avoid complaints from -Wall */
  238. ;
  239. }
  240. data.strval = value;
  241. data.next = NULL;
  242. if ((ret_data = (confval*)pc_malloc(sizeof(confval))) == NULL) {
  243. return(-1);
  244. }
  245. *ret_data = data;
  246. if (seen) {
  247. (*last)->next = ret_data;
  248. } else {
  249. *first = ret_data;
  250. }
  251. *last = ret_data;
  252. return(1);
  253. }
  254. /*
  255. * Input variables:
  256. * argc, argv: Command line data.
  257. * optConf[]: Option description structures.
  258. *
  259. * Output variables:
  260. * See addOptVal().
  261. * After processing, argc will be the number of non-option arguments and argv
  262. * will start with the first non-option argument.
  263. *
  264. * Return value:
  265. * On success, the number of options processed.
  266. * On error, a string describing the error is stored in the global errmsg and
  267. * -1 is returned.
  268. */
  269. static int
  270. procCmdLineArgs(int* argc, const char** argv[], const confvar_t optConf[],
  271. confval** perOptRecordsFirst, confval** perOptRecordsLast) {
  272. char* p;
  273. extern char* optarg; /* For getopt */
  274. extern int optind; /* For getopt */
  275. extern int optopt; /* For getopt */
  276. char optstr[514]; /* List of option chars, for getopt */
  277. unsigned optCharToConf[256]; /* Map option char/num to confvar */
  278. int optchar; /* value returned by getopt() */
  279. int confNum; /* to iterate over confvars */
  280. int count = 0; /* number of options processed */
  281. p = optstr;
  282. *(p++) = ':';
  283. for (confNum = 0; optConf[confNum].type != CF_ENDLIST; confNum++) {
  284. unsigned outind = optConf[confNum].outind;
  285. if (outind < 256 && isprint(outind)) {
  286. *(p++) = (char)outind;
  287. switch (optConf[confNum].type) {
  288. case CF_SWITCH:
  289. break;
  290. default:
  291. *(p++) = ':';
  292. break;
  293. }
  294. optCharToConf[outind] = confNum;
  295. }
  296. }
  297. *p = '\0';
  298. optind = 1;
  299. while ((optchar = getopt(*argc, *argv, optstr)) != -1)
  300. {
  301. int ind;
  302. int ret;
  303. if (optchar == '?') {
  304. error(USAGE_ERROR, "Unknown option character '%c'", optopt);
  305. return(-1);
  306. } else if (optchar == ':') {
  307. error(USAGE_ERROR, "No value given for option -%c", optopt);
  308. return(-1);
  309. }
  310. ind = optCharToConf[optchar];
  311. switch (ret = addOptVal(optarg, &optConf[ind],
  312. &perOptRecordsFirst[ind],
  313. &perOptRecordsLast[ind])) {
  314. case 1:
  315. count++;
  316. break;
  317. case 0:
  318. break;
  319. default:
  320. return(ret);
  321. }
  322. }
  323. *argc -= optind;
  324. *argv += optind;
  325. return(count);
  326. }
  327. /*
  328. * Input variables:
  329. * argc, argv: Command line data.
  330. * optConf[]: Option description structures.
  331. * name: Name of program, for messages.
  332. * usage: Usage message. If non-null, on error a message is printed to stderr
  333. * and the program exits.
  334. *
  335. * Output variables:
  336. * Option values are stored at the value given by any confvar that has a
  337. * non-null address.
  338. * If confdatda is not null, the processed option values are stored in
  339. * confdata.
  340. * A pointer to the start of the values for each option is stored in
  341. * confdata->optVals[].values at the same offset as the option appears in
  342. * confdata[].
  343. * For any option for option characters/indexes that have been used,
  344. * confdata->map[index] is set to the same data.
  345. * After processing, argc will have been adjusted to be the number of
  346. * non-option arguments and argv will have been adjusted to start with the
  347. * first non-option argument.
  348. * The malloced data structures returned in confdata are:
  349. * optVals
  350. * optVals[0].values
  351. * If any option characters/indexes are used, map. If not used, this will be
  352. * a null pointer.
  353. *
  354. * Return value:
  355. * On success, NULL.
  356. * On error, a message describing the problem.
  357. */
  358. const char*
  359. procOpts(int* argc, const char** argv[], const confvar_t optConf[],
  360. confdata_t* confdata, const char name[],
  361. const char usage[]) {
  362. /*
  363. * First & last records in the linked lists maintained for each option.
  364. * These will point to arrays indexed by option number, giving one pointer
  365. * (each) per option, used to maintain/return the list of values seen for
  366. * that option (see the description of first & last in addOptVal()
  367. */
  368. confval** perOptRecordsFirst;
  369. confval** perOptRecordsLast;
  370. /* Number of configuration options given in optConf */
  371. unsigned numConf;
  372. unsigned maxOptIndex = 0; /* The highest option index number seen */
  373. /* number of option instances + assignments given */
  374. int numOptsFound;
  375. int optNum; /* to iterate through the possible options */
  376. int i; /* index into the global list of option value structures */
  377. confval** valuePointers; /* global list of value structures */
  378. pc_name = name;
  379. pc_usage = usage;
  380. for (numConf = 0; optConf[numConf].type != CF_ENDLIST; numConf++) {
  381. unsigned outind = optConf[numConf].outind;
  382. if ((outind & ~CF_NOTFLAG) > maxOptIndex) {
  383. maxOptIndex = outind & ~CF_NOTFLAG;
  384. }
  385. }
  386. if (numConf == 0) {
  387. error(INTERNAL_ERROR, "Empty confvar list");
  388. return(errmsg);
  389. }
  390. if ((perOptRecordsFirst = (confval**)pc_malloc(sizeof(confval*) * numConf))
  391. == NULL || (perOptRecordsLast =
  392. (confval**)pc_malloc(sizeof(confval*) * numConf)) == NULL) {
  393. return(errmsg);
  394. }
  395. numOptsFound = procCmdLineArgs(argc, argv, optConf, perOptRecordsFirst,
  396. perOptRecordsLast);
  397. free(perOptRecordsLast);
  398. perOptRecordsLast = NULL;
  399. if (numOptsFound < 0)
  400. {
  401. free(perOptRecordsFirst);
  402. return(errmsg);
  403. }
  404. if (confdata == NULL) {
  405. free(perOptRecordsFirst);
  406. return NULL;
  407. }
  408. /*
  409. * All options have been read & initial processing done.
  410. * An array of pointers is now generated for the options.
  411. */
  412. if ((valuePointers =
  413. (confval**)pc_malloc(sizeof(confval*) * numOptsFound)) == NULL ||
  414. (confdata->optVals =
  415. (cf_option*)pc_malloc(sizeof(cf_option) * numConf)) == NULL) {
  416. return(errmsg);
  417. }
  418. /* If option characters / indexes are used, allocate a map for them */
  419. if (maxOptIndex == 0) {
  420. confdata->map = NULL;
  421. } else {
  422. if ((confdata->map = (cf_option**)pc_malloc(sizeof(cf_option) *
  423. (maxOptIndex+1))) == NULL) {
  424. return(errmsg);
  425. }
  426. }
  427. /*
  428. * Store the linked lists of option values into arrays.
  429. * Pointers to all option instances are stored in valuePointers,
  430. * with the values for each particular option being contiguous.
  431. */
  432. i = 0;
  433. for (optNum = 0; optNum < numConf; optNum++) {
  434. unsigned outind = optConf[optNum].outind;
  435. confval* optval;
  436. confdata->optVals[optNum].num = 0;
  437. confdata->optVals[optNum].values = &valuePointers[i];
  438. if (outind != 0) {
  439. confdata->map[outind & ~CF_NOTFLAG] = &confdata->optVals[optNum];
  440. }
  441. for (optval = perOptRecordsFirst[optNum]; optval != NULL;
  442. optval = optval->next) {
  443. confdata->optVals[optNum].num++;
  444. valuePointers[i++] = optval;
  445. }
  446. }
  447. free(perOptRecordsFirst);
  448. return(NULL);
  449. }
  450. /*
  451. * Free the malloced data stored in confdata elements by ProcOpts()
  452. */
  453. void
  454. confdataFree(confdata_t *confdata) {
  455. if (confdata->map != NULL) {
  456. free(confdata->map);
  457. confdata->map = NULL;
  458. }
  459. free(confdata->optVals[0].values);
  460. free(confdata->optVals);
  461. confdata->optVals = NULL;
  462. }