1
0

load.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. /**
  2. * \file load.c
  3. *
  4. * This file contains the routines that deal with processing text strings
  5. * for options, either from a NUL-terminated string passed in or from an
  6. * rc/ini file.
  7. *
  8. * @addtogroup autoopts
  9. * @{
  10. */
  11. /*
  12. * This file is part of AutoOpts, a companion to AutoGen.
  13. * AutoOpts is free software.
  14. * AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
  15. *
  16. * AutoOpts is available under any one of two licenses. The license
  17. * in use must be one of these two and the choice is under the control
  18. * of the user of the license.
  19. *
  20. * The GNU Lesser General Public License, version 3 or later
  21. * See the files "COPYING.lgplv3" and "COPYING.gplv3"
  22. *
  23. * The Modified Berkeley Software Distribution License
  24. * See the file "COPYING.mbsd"
  25. *
  26. * These files have the following sha256 sums:
  27. *
  28. * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3
  29. * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3
  30. * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd
  31. */
  32. static bool
  33. get_realpath(char * buf, size_t b_sz)
  34. {
  35. #if defined(HAVE_CANONICALIZE_FILE_NAME)
  36. {
  37. size_t name_len;
  38. char * pz = canonicalize_file_name(buf);
  39. if (pz == NULL)
  40. return false;
  41. name_len = strlen(pz);
  42. if (name_len >= (size_t)b_sz) {
  43. free(pz);
  44. return false;
  45. }
  46. memcpy(buf, pz, name_len + 1);
  47. free(pz);
  48. }
  49. #elif defined(HAVE_REALPATH)
  50. {
  51. size_t name_len;
  52. char z[PATH_MAX+1];
  53. if (realpath(buf, z) == NULL)
  54. return false;
  55. name_len = strlen(z);
  56. if (name_len >= b_sz)
  57. return false;
  58. memcpy(buf, z, name_len + 1);
  59. }
  60. #endif
  61. return true;
  62. }
  63. /*=export_func optionMakePath
  64. * private:
  65. *
  66. * what: translate and construct a path
  67. * arg: + char * + p_buf + The result buffer +
  68. * arg: + int + b_sz + The size of this buffer +
  69. * arg: + char const * + fname + The input name +
  70. * arg: + char const * + prg_path + The full path of the current program +
  71. *
  72. * ret-type: bool
  73. * ret-desc: true if the name was handled, otherwise false.
  74. * If the name does not start with ``$'', then it is handled
  75. * simply by copying the input name to the output buffer and
  76. * resolving the name with either
  77. * @code{canonicalize_file_name(3GLIBC)} or @code{realpath(3C)}.
  78. *
  79. * doc:
  80. *
  81. * This routine will copy the @code{pzName} input name into the
  82. * @code{pzBuf} output buffer, not exceeding @code{bufSize} bytes. If the
  83. * first character of the input name is a @code{'$'} character, then there
  84. * is special handling:
  85. * @*
  86. * @code{$$} is replaced with the directory name of the @code{pzProgPath},
  87. * searching @code{$PATH} if necessary.
  88. * @*
  89. * @code{$@} is replaced with the AutoGen package data installation directory
  90. * (aka @code{pkgdatadir}).
  91. * @*
  92. * @code{$NAME} is replaced by the contents of the @code{NAME} environment
  93. * variable. If not found, the search fails.
  94. *
  95. * Please note: both @code{$$} and @code{$NAME} must be at the start of the
  96. * @code{pzName} string and must either be the entire string or be followed
  97. * by the @code{'/'} (backslash on windows) character.
  98. *
  99. * err: @code{false} is returned if:
  100. * @*
  101. * @bullet{} The input name exceeds @code{bufSize} bytes.
  102. * @*
  103. * @bullet{} @code{$$}, @code{$@@} or @code{$NAME} is not the full string
  104. * and the next character is not '/'.
  105. * @*
  106. * @bullet{} libopts was built without PKGDATADIR defined and @code{$@@}
  107. * was specified.
  108. * @*
  109. * @bullet{} @code{NAME} is not a known environment variable
  110. * @*
  111. * @bullet{} @code{canonicalize_file_name} or @code{realpath} return
  112. * errors (cannot resolve the resulting path).
  113. =*/
  114. bool
  115. optionMakePath(char * p_buf, int b_sz, char const * fname, char const * prg_path)
  116. {
  117. {
  118. size_t len = strlen(fname);
  119. if (((size_t)b_sz <= len) || (len == 0))
  120. return false;
  121. }
  122. /*
  123. * IF not an environment variable, just copy the data
  124. */
  125. if (*fname != '$') {
  126. char const * src = fname;
  127. char * dst = p_buf;
  128. int ct = b_sz;
  129. for (;;) {
  130. if ( (*(dst++) = *(src++)) == NUL)
  131. break;
  132. if (--ct <= 0)
  133. return false;
  134. }
  135. }
  136. /*
  137. * IF the name starts with "$$", then it must be "$$" or
  138. * it must start with "$$/". In either event, replace the "$$"
  139. * with the path to the executable and append a "/" character.
  140. */
  141. else switch (fname[1]) {
  142. case NUL:
  143. return false;
  144. case '$':
  145. if (! add_prog_path(p_buf, b_sz, fname, prg_path))
  146. return false;
  147. break;
  148. case '@':
  149. if (program_pkgdatadir[0] == NUL)
  150. return false;
  151. if (snprintf(p_buf, (size_t)b_sz, "%s%s",
  152. program_pkgdatadir, fname + 2) >= b_sz)
  153. return false;
  154. break;
  155. default:
  156. if (! add_env_val(p_buf, b_sz, fname))
  157. return false;
  158. }
  159. return get_realpath(p_buf, b_sz);
  160. }
  161. /**
  162. * convert a leading "$$" into a path to the executable.
  163. */
  164. static bool
  165. add_prog_path(char * buf, int b_sz, char const * fname, char const * prg_path)
  166. {
  167. char const * path;
  168. char const * pz;
  169. int skip = 2;
  170. size_t fname_len;
  171. size_t dir_len; //!< length of the directory portion of the path to the exe
  172. switch (fname[2]) {
  173. case DIRCH:
  174. skip = 3;
  175. case NUL:
  176. break;
  177. default:
  178. return false;
  179. }
  180. /*
  181. * See if the path is included in the program name.
  182. * If it is, we're done. Otherwise, we have to hunt
  183. * for the program using "pathfind".
  184. */
  185. if (strchr(prg_path, DIRCH) != NULL)
  186. path = prg_path;
  187. else {
  188. path = pathfind(getenv("PATH"), (char *)prg_path, "rx");
  189. if (path == NULL)
  190. return false;
  191. }
  192. pz = strrchr(path, DIRCH);
  193. /*
  194. * IF we cannot find a directory name separator,
  195. * THEN we do not have a path name to our executable file.
  196. */
  197. if (pz == NULL)
  198. return false;
  199. fname += skip;
  200. fname_len = strlen(fname) + 1; // + NUL byte
  201. dir_len = (pz - path) + 1; // + dir sep character
  202. /*
  203. * Concatenate the file name to the end of the executable path.
  204. * The result may be either a file or a directory.
  205. */
  206. if (dir_len + fname_len > (unsigned)b_sz)
  207. return false;
  208. memcpy(buf, path, dir_len);
  209. memcpy(buf + dir_len, fname, fname_len);
  210. /*
  211. * If the "path" path was gotten from "pathfind()", then it was
  212. * allocated and we need to deallocate it.
  213. */
  214. if (path != prg_path)
  215. AGFREE(path);
  216. return true;
  217. }
  218. /**
  219. * Add an environment variable value.
  220. */
  221. static bool
  222. add_env_val(char * buf, int buf_sz, char const * name)
  223. {
  224. char * dir_part = buf;
  225. for (;;) {
  226. int ch = (int)*++name;
  227. if (! IS_VALUE_NAME_CHAR(ch))
  228. break;
  229. *(dir_part++) = (char)ch;
  230. }
  231. if (dir_part == buf)
  232. return false;
  233. *dir_part = NUL;
  234. dir_part = getenv(buf);
  235. /*
  236. * Environment value not found -- skip the home list entry
  237. */
  238. if (dir_part == NULL)
  239. return false;
  240. {
  241. size_t dir_len = strlen(dir_part);
  242. size_t nm_len = strlen(name) + 1;
  243. if (dir_len + nm_len >= (unsigned)buf_sz)
  244. return false;
  245. memcpy(buf, dir_part, dir_len);
  246. memcpy(buf + dir_len, name, nm_len);
  247. }
  248. return true;
  249. }
  250. /**
  251. * Trim leading and trailing white space.
  252. * If we are cooking the text and the text is quoted, then "cook"
  253. * the string. To cook, the string must be quoted.
  254. *
  255. * @param[in,out] txt the input and output string
  256. * @param[in] mode the handling mode (cooking method)
  257. */
  258. static void
  259. munge_str(char * txt, tOptionLoadMode mode)
  260. {
  261. char * end;
  262. if (mode == OPTION_LOAD_KEEP)
  263. return;
  264. if (IS_WHITESPACE_CHAR(*txt)) {
  265. char * src = SPN_WHITESPACE_CHARS(txt+1);
  266. size_t l = strlen(src) + 1;
  267. memmove(txt, src, l);
  268. end = txt + l - 1;
  269. } else
  270. end = txt + strlen(txt);
  271. end = SPN_WHITESPACE_BACK(txt, end);
  272. *end = NUL;
  273. if (mode == OPTION_LOAD_UNCOOKED)
  274. return;
  275. switch (*txt) {
  276. default: return;
  277. case '"':
  278. case '\'': break;
  279. }
  280. switch (end[-1]) {
  281. default: return;
  282. case '"':
  283. case '\'': break;
  284. }
  285. (void)ao_string_cook(txt, NULL);
  286. }
  287. static char *
  288. assemble_arg_val(char * txt, tOptionLoadMode mode)
  289. {
  290. char * end = strpbrk(txt, ARG_BREAK_STR);
  291. int space_break;
  292. /*
  293. * Not having an argument to a configurable name is okay.
  294. */
  295. if (end == NULL)
  296. return txt + strlen(txt);
  297. /*
  298. * If we are keeping all whitespace, then the modevalue starts with the
  299. * character that follows the end of the configurable name, regardless
  300. * of which character caused it.
  301. */
  302. if (mode == OPTION_LOAD_KEEP) {
  303. *(end++) = NUL;
  304. return end;
  305. }
  306. /*
  307. * If the name ended on a white space character, remember that
  308. * because we'll have to skip over an immediately following ':' or '='
  309. * (and the white space following *that*).
  310. */
  311. space_break = IS_WHITESPACE_CHAR(*end);
  312. *(end++) = NUL;
  313. end = SPN_WHITESPACE_CHARS(end);
  314. if (space_break && ((*end == ':') || (*end == '=')))
  315. end = SPN_WHITESPACE_CHARS(end+1);
  316. return end;
  317. }
  318. static char *
  319. trim_quotes(char * arg)
  320. {
  321. switch (*arg) {
  322. case '"':
  323. case '\'':
  324. ao_string_cook(arg, NULL);
  325. }
  326. return arg;
  327. }
  328. /**
  329. * See if the option is to be processed in the current scan direction
  330. * (-1 or +1).
  331. */
  332. static bool
  333. direction_ok(opt_state_mask_t f, int dir)
  334. {
  335. if (dir == 0)
  336. return true;
  337. switch (f & (OPTST_IMM|OPTST_DISABLE_IMM)) {
  338. case 0:
  339. /*
  340. * The selected option has no immediate action.
  341. * THEREFORE, if the direction is PRESETTING
  342. * THEN we skip this option.
  343. */
  344. if (PRESETTING(dir))
  345. return false;
  346. break;
  347. case OPTST_IMM:
  348. if (PRESETTING(dir)) {
  349. /*
  350. * We are in the presetting direction with an option we handle
  351. * immediately for enablement, but normally for disablement.
  352. * Therefore, skip if disabled.
  353. */
  354. if ((f & OPTST_DISABLED) == 0)
  355. return false;
  356. } else {
  357. /*
  358. * We are in the processing direction with an option we handle
  359. * immediately for enablement, but normally for disablement.
  360. * Therefore, skip if NOT disabled.
  361. */
  362. if ((f & OPTST_DISABLED) != 0)
  363. return false;
  364. }
  365. break;
  366. case OPTST_DISABLE_IMM:
  367. if (PRESETTING(dir)) {
  368. /*
  369. * We are in the presetting direction with an option we handle
  370. * immediately for disablement, but normally for handling.
  371. * Therefore, skip if NOT disabled.
  372. */
  373. if ((f & OPTST_DISABLED) != 0)
  374. return false;
  375. } else {
  376. /*
  377. * We are in the processing direction with an option we handle
  378. * immediately for disablement, but normally for handling.
  379. * Therefore, skip if disabled.
  380. */
  381. if ((f & OPTST_DISABLED) == 0)
  382. return false;
  383. }
  384. break;
  385. case OPTST_IMM|OPTST_DISABLE_IMM:
  386. /*
  387. * The selected option is always for immediate action.
  388. * THEREFORE, if the direction is PROCESSING
  389. * THEN we skip this option.
  390. */
  391. if (PROCESSING(dir))
  392. return false;
  393. break;
  394. }
  395. return true;
  396. }
  397. /**
  398. * Load an option from a block of text. The text must start with the
  399. * configurable/option name and be followed by its associated value.
  400. * That value may be processed in any of several ways. See "tOptionLoadMode"
  401. * in autoopts.h.
  402. *
  403. * @param[in,out] opts program options descriptor
  404. * @param[in,out] opt_state option processing state
  405. * @param[in,out] line source line with long option name in it
  406. * @param[in] direction current processing direction (preset or not)
  407. * @param[in] load_mode option loading mode (OPTION_LOAD_*)
  408. */
  409. static void
  410. load_opt_line(tOptions * opts, tOptState * opt_state, char * line,
  411. tDirection direction, tOptionLoadMode load_mode )
  412. {
  413. /*
  414. * When parsing a stored line, we only look at the characters after
  415. * a hyphen. Long names must always be at least two characters and
  416. * short options are always exactly one character long.
  417. */
  418. line = SPN_LOAD_LINE_SKIP_CHARS(line);
  419. {
  420. char * arg = assemble_arg_val(line, load_mode);
  421. if (IS_OPTION_NAME_CHAR(line[1])) {
  422. if (! SUCCESSFUL(opt_find_long(opts, line, opt_state)))
  423. return;
  424. } else if (! SUCCESSFUL(opt_find_short(opts, *line, opt_state)))
  425. return;
  426. if ((! CALLED(direction)) && (opt_state->flags & OPTST_NO_INIT))
  427. return;
  428. opt_state->pzOptArg = trim_quotes(arg);
  429. }
  430. if (! direction_ok(opt_state->flags, direction))
  431. return;
  432. /*
  433. * Fix up the args.
  434. */
  435. if (OPTST_GET_ARGTYPE(opt_state->pOD->fOptState) == OPARG_TYPE_NONE) {
  436. if (*opt_state->pzOptArg != NUL)
  437. return;
  438. opt_state->pzOptArg = NULL;
  439. } else if (opt_state->pOD->fOptState & OPTST_ARG_OPTIONAL) {
  440. if (*opt_state->pzOptArg == NUL)
  441. opt_state->pzOptArg = NULL;
  442. else {
  443. AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg");
  444. opt_state->flags |= OPTST_ALLOC_ARG;
  445. }
  446. } else {
  447. if (*opt_state->pzOptArg == NUL)
  448. opt_state->pzOptArg = zNil;
  449. else {
  450. AGDUPSTR(opt_state->pzOptArg, opt_state->pzOptArg, "opt arg");
  451. opt_state->flags |= OPTST_ALLOC_ARG;
  452. }
  453. }
  454. {
  455. tOptionLoadMode sv = option_load_mode;
  456. option_load_mode = load_mode;
  457. handle_opt(opts, opt_state);
  458. option_load_mode = sv;
  459. }
  460. }
  461. /*=export_func optionLoadLine
  462. *
  463. * what: process a string for an option name and value
  464. *
  465. * arg: tOptions *, opts, program options descriptor
  466. * arg: char const *, line, NUL-terminated text
  467. *
  468. * doc:
  469. *
  470. * This is a client program callable routine for setting options from, for
  471. * example, the contents of a file that they read in. Only one option may
  472. * appear in the text. It will be treated as a normal (non-preset) option.
  473. *
  474. * When passed a pointer to the option struct and a string, it will find
  475. * the option named by the first token on the string and set the option
  476. * argument to the remainder of the string. The caller must NUL terminate
  477. * the string. The caller need not skip over any introductory hyphens.
  478. * Any embedded new lines will be included in the option
  479. * argument. If the input looks like one or more quoted strings, then the
  480. * input will be "cooked". The "cooking" is identical to the string
  481. * formation used in AutoGen definition files (@pxref{basic expression}),
  482. * except that you may not use backquotes.
  483. *
  484. * err: Invalid options are silently ignored. Invalid option arguments
  485. * will cause a warning to print, but the function should return.
  486. =*/
  487. void
  488. optionLoadLine(tOptions * opts, char const * line)
  489. {
  490. tOptState st = OPTSTATE_INITIALIZER(SET);
  491. char * pz;
  492. proc_state_mask_t sv_flags = opts->fOptSet;
  493. opts->fOptSet &= ~OPTPROC_ERRSTOP;
  494. AGDUPSTR(pz, line, "opt line");
  495. load_opt_line(opts, &st, pz, DIRECTION_CALLED, OPTION_LOAD_COOKED);
  496. AGFREE(pz);
  497. opts->fOptSet = sv_flags;
  498. }
  499. /** @}
  500. *
  501. * Local Variables:
  502. * mode: C
  503. * c-file-style: "stroustrup"
  504. * indent-tabs-mode: nil
  505. * End:
  506. * end of autoopts/load.c */