configfile.c 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391
  1. /**
  2. * \file configfile.c
  3. *
  4. * configuration/rc/ini file handling.
  5. *
  6. * @addtogroup autoopts
  7. * @{
  8. */
  9. /*
  10. * This file is part of AutoOpts, a companion to AutoGen.
  11. * AutoOpts is free software.
  12. * AutoOpts is Copyright (C) 1992-2016 by Bruce Korb - all rights reserved
  13. *
  14. * AutoOpts is available under any one of two licenses. The license
  15. * in use must be one of these two and the choice is under the control
  16. * of the user of the license.
  17. *
  18. * The GNU Lesser General Public License, version 3 or later
  19. * See the files "COPYING.lgplv3" and "COPYING.gplv3"
  20. *
  21. * The Modified Berkeley Software Distribution License
  22. * See the file "COPYING.mbsd"
  23. *
  24. * These files have the following sha256 sums:
  25. *
  26. * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3
  27. * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3
  28. * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd
  29. */
  30. /* = = = START-STATIC-FORWARD = = = */
  31. static void
  32. file_preset(tOptions * opts, char const * fname, int dir);
  33. static char *
  34. handle_comment(char * txt);
  35. static char *
  36. handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir);
  37. static char *
  38. handle_directive(tOptions * opts, char * txt);
  39. static char *
  40. aoflags_directive(tOptions * opts, char * txt);
  41. static char *
  42. program_directive(tOptions * opts, char * txt);
  43. static char *
  44. handle_section(tOptions * opts, char * txt);
  45. static int
  46. parse_xml_encoding(char ** ppz);
  47. static char *
  48. trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode);
  49. static void
  50. cook_xml_text(char * pzData);
  51. static char *
  52. handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir);
  53. static char const *
  54. parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ);
  55. static char const *
  56. parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ);
  57. static char const *
  58. parse_value(char const * txt, tOptionValue * typ);
  59. /* = = = END-STATIC-FORWARD = = = */
  60. /**
  61. * Skip over some unknown attribute
  62. * @param[in] txt start of skpped text
  63. * @returns character after skipped text
  64. */
  65. inline static char const *
  66. skip_unkn(char const * txt)
  67. {
  68. txt = BRK_END_XML_TOKEN_CHARS(txt);
  69. return (*txt == NUL) ? NULL : txt;
  70. }
  71. /*=export_func configFileLoad
  72. *
  73. * what: parse a configuration file
  74. * arg: + char const * + fname + the file to load +
  75. *
  76. * ret_type: const tOptionValue *
  77. * ret_desc: An allocated, compound value structure
  78. *
  79. * doc:
  80. * This routine will load a named configuration file and parse the
  81. * text as a hierarchically valued option. The option descriptor
  82. * created from an option definition file is not used via this interface.
  83. * The returned value is "named" with the input file name and is of
  84. * type "@code{OPARG_TYPE_HIERARCHY}". It may be used in calls to
  85. * @code{optionGetValue()}, @code{optionNextValue()} and
  86. * @code{optionUnloadNested()}.
  87. *
  88. * err:
  89. * If the file cannot be loaded or processed, @code{NULL} is returned and
  90. * @var{errno} is set. It may be set by a call to either @code{open(2)}
  91. * @code{mmap(2)} or other file system calls, or it may be:
  92. * @itemize @bullet
  93. * @item
  94. * @code{ENOENT} - the file was not found.
  95. * @item
  96. * @code{ENOMSG} - the file was empty.
  97. * @item
  98. * @code{EINVAL} - the file contents are invalid -- not properly formed.
  99. * @item
  100. * @code{ENOMEM} - not enough memory to allocate the needed structures.
  101. * @end itemize
  102. =*/
  103. const tOptionValue *
  104. configFileLoad(char const * fname)
  105. {
  106. tmap_info_t cfgfile;
  107. tOptionValue * res = NULL;
  108. tOptionLoadMode save_mode = option_load_mode;
  109. char * txt = text_mmap(fname, PROT_READ, MAP_PRIVATE, &cfgfile);
  110. if (TEXT_MMAP_FAILED_ADDR(txt))
  111. return NULL; /* errno is set */
  112. option_load_mode = OPTION_LOAD_COOKED;
  113. res = optionLoadNested(txt, fname, strlen(fname));
  114. if (res == NULL) {
  115. int err = errno;
  116. text_munmap(&cfgfile);
  117. errno = err;
  118. } else
  119. text_munmap(&cfgfile);
  120. option_load_mode = save_mode;
  121. return res;
  122. }
  123. /*=export_func optionFindValue
  124. *
  125. * what: find a hierarcicaly valued option instance
  126. * arg: + const tOptDesc * + odesc + an option with a nested arg type +
  127. * arg: + char const * + name + name of value to find +
  128. * arg: + char const * + val + the matching value +
  129. *
  130. * ret_type: const tOptionValue *
  131. * ret_desc: a compound value structure
  132. *
  133. * doc:
  134. * This routine will find an entry in a nested value option or configurable.
  135. * It will search through the list and return a matching entry.
  136. *
  137. * err:
  138. * The returned result is NULL and errno is set:
  139. * @itemize @bullet
  140. * @item
  141. * @code{EINVAL} - the @code{pOptValue} does not point to a valid
  142. * hierarchical option value.
  143. * @item
  144. * @code{ENOENT} - no entry matched the given name.
  145. * @end itemize
  146. =*/
  147. const tOptionValue *
  148. optionFindValue(const tOptDesc * odesc, char const * name, char const * val)
  149. {
  150. const tOptionValue * res = NULL;
  151. if ( (odesc == NULL)
  152. || (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY)) {
  153. errno = EINVAL;
  154. }
  155. else if (odesc->optCookie == NULL) {
  156. errno = ENOENT;
  157. }
  158. else do {
  159. tArgList * argl = odesc->optCookie;
  160. int argct = argl->useCt;
  161. void ** poptv = (void **)(argl->apzArgs);
  162. if (argct == 0) {
  163. errno = ENOENT;
  164. break;
  165. }
  166. if (name == NULL) {
  167. res = (tOptionValue *)*poptv;
  168. break;
  169. }
  170. while (--argct >= 0) {
  171. const tOptionValue * ov = *(poptv++);
  172. const tOptionValue * rv = optionGetValue(ov, name);
  173. if (rv == NULL)
  174. continue;
  175. if (val == NULL) {
  176. res = ov;
  177. break;
  178. }
  179. }
  180. if (res == NULL)
  181. errno = ENOENT;
  182. } while (false);
  183. return res;
  184. }
  185. /*=export_func optionFindNextValue
  186. *
  187. * FIXME: the handling of 'pzName' and 'pzVal' is just wrong.
  188. *
  189. * what: find a hierarcicaly valued option instance
  190. * arg: + const tOptDesc * + odesc + an option with a nested arg type +
  191. * arg: + const tOptionValue * + pPrevVal + the last entry +
  192. * arg: + char const * + name + name of value to find +
  193. * arg: + char const * + value + the matching value +
  194. *
  195. * ret_type: const tOptionValue *
  196. * ret_desc: a compound value structure
  197. *
  198. * doc:
  199. * This routine will find the next entry in a nested value option or
  200. * configurable. It will search through the list and return the next entry
  201. * that matches the criteria.
  202. *
  203. * err:
  204. * The returned result is NULL and errno is set:
  205. * @itemize @bullet
  206. * @item
  207. * @code{EINVAL} - the @code{pOptValue} does not point to a valid
  208. * hierarchical option value.
  209. * @item
  210. * @code{ENOENT} - no entry matched the given name.
  211. * @end itemize
  212. =*/
  213. tOptionValue const *
  214. optionFindNextValue(const tOptDesc * odesc, const tOptionValue * pPrevVal,
  215. char const * pzName, char const * pzVal)
  216. {
  217. bool old_found = false;
  218. tOptionValue * res = NULL;
  219. (void)pzName;
  220. (void)pzVal;
  221. if ( (odesc == NULL)
  222. || (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY)) {
  223. errno = EINVAL;
  224. }
  225. else if (odesc->optCookie == NULL) {
  226. errno = ENOENT;
  227. }
  228. else do {
  229. tArgList * argl = odesc->optCookie;
  230. int ct = argl->useCt;
  231. void ** poptv = (void **)argl->apzArgs;
  232. while (--ct >= 0) {
  233. tOptionValue * pOV = *(poptv++);
  234. if (old_found) {
  235. res = pOV;
  236. break;
  237. }
  238. if (pOV == pPrevVal)
  239. old_found = true;
  240. }
  241. if (res == NULL)
  242. errno = ENOENT;
  243. } while (false);
  244. return res;
  245. }
  246. /*=export_func optionGetValue
  247. *
  248. * what: get a specific value from a hierarcical list
  249. * arg: + const tOptionValue * + pOptValue + a hierarchcal value +
  250. * arg: + char const * + valueName + name of value to get +
  251. *
  252. * ret_type: const tOptionValue *
  253. * ret_desc: a compound value structure
  254. *
  255. * doc:
  256. * This routine will find an entry in a nested value option or configurable.
  257. * If "valueName" is NULL, then the first entry is returned. Otherwise,
  258. * the first entry with a name that exactly matches the argument will be
  259. * returned. If there is no matching value, NULL is returned and errno is
  260. * set to ENOENT. If the provided option value is not a hierarchical value,
  261. * NULL is also returned and errno is set to EINVAL.
  262. *
  263. * err:
  264. * The returned result is NULL and errno is set:
  265. * @itemize @bullet
  266. * @item
  267. * @code{EINVAL} - the @code{pOptValue} does not point to a valid
  268. * hierarchical option value.
  269. * @item
  270. * @code{ENOENT} - no entry matched the given name.
  271. * @end itemize
  272. =*/
  273. tOptionValue const *
  274. optionGetValue(tOptionValue const * oov, char const * vname)
  275. {
  276. tArgList * arg_list;
  277. tOptionValue * res = NULL;
  278. if ((oov == NULL) || (oov->valType != OPARG_TYPE_HIERARCHY)) {
  279. errno = EINVAL;
  280. return res;
  281. }
  282. arg_list = oov->v.nestVal;
  283. if (arg_list->useCt > 0) {
  284. int ct = arg_list->useCt;
  285. void ** ovlist = (void **)(arg_list->apzArgs);
  286. if (vname == NULL) {
  287. res = (tOptionValue *)*ovlist;
  288. } else do {
  289. tOptionValue * opt_val = *(ovlist++);
  290. if (strcmp(opt_val->pzName, vname) == 0) {
  291. res = opt_val;
  292. break;
  293. }
  294. } while (--ct > 0);
  295. }
  296. if (res == NULL)
  297. errno = ENOENT;
  298. return res;
  299. }
  300. /*=export_func optionNextValue
  301. *
  302. * what: get the next value from a hierarchical list
  303. * arg: + const tOptionValue * + pOptValue + a hierarchcal list value +
  304. * arg: + const tOptionValue * + pOldValue + a value from this list +
  305. *
  306. * ret_type: const tOptionValue *
  307. * ret_desc: a compound value structure
  308. *
  309. * doc:
  310. * This routine will return the next entry after the entry passed in. At the
  311. * end of the list, NULL will be returned. If the entry is not found on the
  312. * list, NULL will be returned and "@var{errno}" will be set to EINVAL.
  313. * The "@var{pOldValue}" must have been gotten from a prior call to this
  314. * routine or to "@code{opitonGetValue()}".
  315. *
  316. * err:
  317. * The returned result is NULL and errno is set:
  318. * @itemize @bullet
  319. * @item
  320. * @code{EINVAL} - the @code{pOptValue} does not point to a valid
  321. * hierarchical option value or @code{pOldValue} does not point to a
  322. * member of that option value.
  323. * @item
  324. * @code{ENOENT} - the supplied @code{pOldValue} pointed to the last entry.
  325. * @end itemize
  326. =*/
  327. tOptionValue const *
  328. optionNextValue(tOptionValue const * ov_list,tOptionValue const * oov )
  329. {
  330. tArgList * arg_list;
  331. tOptionValue * res = NULL;
  332. int err = EINVAL;
  333. if ((ov_list == NULL) || (ov_list->valType != OPARG_TYPE_HIERARCHY)) {
  334. errno = EINVAL;
  335. return NULL;
  336. }
  337. arg_list = ov_list->v.nestVal;
  338. {
  339. int ct = arg_list->useCt;
  340. void ** o_list = (void **)(arg_list->apzArgs);
  341. while (ct-- > 0) {
  342. tOptionValue * nov = *(o_list++);
  343. if (nov == oov) {
  344. if (ct == 0) {
  345. err = ENOENT;
  346. } else {
  347. err = 0;
  348. res = (tOptionValue *)*o_list;
  349. }
  350. break;
  351. }
  352. }
  353. }
  354. if (err != 0)
  355. errno = err;
  356. return res;
  357. }
  358. /**
  359. * Load a file containing presetting information (a configuration file).
  360. */
  361. static void
  362. file_preset(tOptions * opts, char const * fname, int dir)
  363. {
  364. tmap_info_t cfgfile;
  365. tOptState optst = OPTSTATE_INITIALIZER(PRESET);
  366. opt_state_mask_t st_flags = optst.flags;
  367. opt_state_mask_t fl_save = opts->fOptSet;
  368. char * ftext =
  369. text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &cfgfile);
  370. if (TEXT_MMAP_FAILED_ADDR(ftext))
  371. return;
  372. /*
  373. * While processing config files, we ignore errors.
  374. */
  375. opts->fOptSet &= ~OPTPROC_ERRSTOP;
  376. if (dir == DIRECTION_CALLED) {
  377. st_flags = OPTST_DEFINED;
  378. dir = DIRECTION_PROCESS;
  379. }
  380. /*
  381. * IF this is called via "optionProcess", then we are presetting.
  382. * This is the default and the PRESETTING bit will be set.
  383. * If this is called via "optionFileLoad", then the bit is not set
  384. * and we consider stuff set herein to be "set" by the client program.
  385. */
  386. if ((opts->fOptSet & OPTPROC_PRESETTING) == 0)
  387. st_flags = OPTST_SET;
  388. do {
  389. optst.flags = st_flags;
  390. ftext = SPN_WHITESPACE_CHARS(ftext);
  391. if (IS_VAR_FIRST_CHAR(*ftext)) {
  392. ftext = handle_cfg(opts, &optst, ftext, dir);
  393. } else switch (*ftext) {
  394. case '<':
  395. if (IS_VAR_FIRST_CHAR(ftext[1]))
  396. ftext = handle_struct(opts, &optst, ftext, dir);
  397. else switch (ftext[1]) {
  398. case '?':
  399. ftext = handle_directive(opts, ftext);
  400. break;
  401. case '!':
  402. ftext = handle_comment(ftext);
  403. break;
  404. case '/':
  405. ftext = strchr(ftext + 2, '>');
  406. if (ftext++ != NULL)
  407. break;
  408. /* fallthrough */
  409. default:
  410. ftext = NULL;
  411. }
  412. if (ftext == NULL)
  413. goto all_done;
  414. break;
  415. case '[':
  416. ftext = handle_section(opts, ftext);
  417. break;
  418. case '#':
  419. ftext = strchr(ftext + 1, NL);
  420. break;
  421. default:
  422. goto all_done; /* invalid format */
  423. }
  424. } while (ftext != NULL);
  425. all_done:
  426. text_munmap(&cfgfile);
  427. opts->fOptSet = fl_save;
  428. }
  429. /**
  430. * "txt" points to a "<!" sequence.
  431. * Theoretically, we should ensure that it begins with "<!--",
  432. * but actually I don't care that much. It ends with "-->".
  433. */
  434. static char *
  435. handle_comment(char * txt)
  436. {
  437. char * pz = strstr(txt, "-->");
  438. if (pz != NULL)
  439. pz += 3;
  440. return pz;
  441. }
  442. /**
  443. * "txt" points to the start of some value name.
  444. * The end of the entry is the end of the line that is not preceded by
  445. * a backslash escape character. The string value is always processed
  446. * in "cooked" mode.
  447. */
  448. static char *
  449. handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir)
  450. {
  451. char * pzName = txt++;
  452. char * pzEnd = strchr(txt, NL);
  453. if (pzEnd == NULL)
  454. return txt + strlen(txt);
  455. txt = SPN_VALUE_NAME_CHARS(txt);
  456. txt = SPN_WHITESPACE_CHARS(txt);
  457. if (txt > pzEnd) {
  458. name_only:
  459. *pzEnd++ = NUL;
  460. load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
  461. return pzEnd;
  462. }
  463. /*
  464. * Either the first character after the name is a ':' or '=',
  465. * or else we must have skipped over white space. Anything else
  466. * is an invalid format and we give up parsing the text.
  467. */
  468. if ((*txt == '=') || (*txt == ':')) {
  469. txt = SPN_WHITESPACE_CHARS(txt+1);
  470. if (txt > pzEnd)
  471. goto name_only;
  472. } else if (! IS_WHITESPACE_CHAR(txt[-1]))
  473. return NULL;
  474. /*
  475. * IF the value is continued, remove the backslash escape and push "pzEnd"
  476. * on to a newline *not* preceded by a backslash.
  477. */
  478. if (pzEnd[-1] == '\\') {
  479. char * pcD = pzEnd-1;
  480. char * pcS = pzEnd;
  481. for (;;) {
  482. char ch = *(pcS++);
  483. switch (ch) {
  484. case NUL:
  485. pcS = NULL;
  486. /* FALLTHROUGH */
  487. case NL:
  488. *pcD = NUL;
  489. pzEnd = pcS;
  490. goto copy_done;
  491. case '\\':
  492. if (*pcS == NL)
  493. ch = *(pcS++);
  494. /* FALLTHROUGH */
  495. default:
  496. *(pcD++) = ch;
  497. }
  498. } copy_done:;
  499. } else {
  500. /*
  501. * The newline was not preceded by a backslash. NUL it out
  502. */
  503. *(pzEnd++) = NUL;
  504. }
  505. /*
  506. * "pzName" points to what looks like text for one option/configurable.
  507. * It is NUL terminated. Process it.
  508. */
  509. load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
  510. return pzEnd;
  511. }
  512. /**
  513. * "txt" points to a "<?" sequence.
  514. * We handle "<?program" and "<?auto-options" directives.
  515. * All others are treated as comments.
  516. *
  517. * @param[in,out] opts program option descriptor
  518. * @param[in] txt scanning pointer
  519. * @returns the next character to look at
  520. */
  521. static char *
  522. handle_directive(tOptions * opts, char * txt)
  523. {
  524. # define DIRECTIVE_TABLE \
  525. _dt_(zCfgProg, program_directive) \
  526. _dt_(zCfgAO_Flags, aoflags_directive)
  527. typedef char * (directive_func_t)(tOptions *, char *);
  528. # define _dt_(_s, _fn) _fn,
  529. static directive_func_t * dir_disp[] = {
  530. DIRECTIVE_TABLE
  531. };
  532. # undef _dt_
  533. # define _dt_(_s, _fn) 1 +
  534. static int const dir_ct = DIRECTIVE_TABLE 0;
  535. static char const * dir_names[DIRECTIVE_TABLE 0];
  536. # undef _dt_
  537. int ix;
  538. if (dir_names[0] == NULL) {
  539. ix = 0;
  540. # define _dt_(_s, _fn) dir_names[ix++] = _s;
  541. DIRECTIVE_TABLE;
  542. # undef _dt_
  543. }
  544. for (ix = 0; ix < dir_ct; ix++) {
  545. size_t len = strlen(dir_names[ix]);
  546. if ( (strncmp(txt + 2, dir_names[ix], len) == 0)
  547. && (! IS_VALUE_NAME_CHAR(txt[len+2])) )
  548. return dir_disp[ix](opts, txt + len + 2);
  549. }
  550. /*
  551. * We don't know what this is. Skip it.
  552. */
  553. txt = strchr(txt+2, '>');
  554. if (txt != NULL)
  555. txt++;
  556. return txt;
  557. # undef DIRECTIVE_TABLE
  558. }
  559. /**
  560. * handle AutoOpts mode flags.
  561. *
  562. * @param[in,out] opts program option descriptor
  563. * @param[in] txt scanning pointer
  564. * @returns the next character to look at
  565. */
  566. static char *
  567. aoflags_directive(tOptions * opts, char * txt)
  568. {
  569. char * pz;
  570. pz = SPN_WHITESPACE_CHARS(txt+1);
  571. txt = strchr(pz, '>');
  572. if (txt != NULL) {
  573. size_t len = (unsigned)(txt - pz);
  574. char * ftxt = AGALOC(len + 1, "aoflags");
  575. memcpy(ftxt, pz, len);
  576. ftxt[len] = NUL;
  577. set_usage_flags(opts, ftxt);
  578. AGFREE(ftxt);
  579. txt++;
  580. }
  581. return txt;
  582. }
  583. /**
  584. * handle program segmentation of config file.
  585. *
  586. * @param[in,out] opts program option descriptor
  587. * @param[in] txt scanning pointer
  588. * @returns the next character to look at
  589. */
  590. static char *
  591. program_directive(tOptions * opts, char * txt)
  592. {
  593. static char const ttlfmt[] = "<?";
  594. size_t ttl_len = sizeof(ttlfmt) + strlen(zCfgProg);
  595. char * ttl = AGALOC(ttl_len, "prog title");
  596. size_t name_len = strlen(opts->pzProgName);
  597. memcpy(ttl, ttlfmt, sizeof(ttlfmt) - 1);
  598. memcpy(ttl + sizeof(ttlfmt) - 1, zCfgProg, ttl_len - (sizeof(ttlfmt) - 1));
  599. do {
  600. txt = SPN_WHITESPACE_CHARS(txt+1);
  601. if ( (strneqvcmp(txt, opts->pzProgName, (int)name_len) == 0)
  602. && (IS_END_XML_TOKEN_CHAR(txt[name_len])) ) {
  603. txt += name_len;
  604. break;
  605. }
  606. txt = strstr(txt, ttl);
  607. } while (txt != NULL);
  608. AGFREE(ttl);
  609. if (txt != NULL)
  610. for (;;) {
  611. if (*txt == NUL) {
  612. txt = NULL;
  613. break;
  614. }
  615. if (*(txt++) == '>')
  616. break;
  617. }
  618. return txt;
  619. }
  620. /**
  621. * "txt" points to a '[' character.
  622. * The "traditional" [PROG_NAME] segmentation of the config file.
  623. * Do not ever mix with the "<?program prog-name>" variation.
  624. * The templates reject program names over 16 characters.
  625. *
  626. * @param[in,out] opts program option descriptor
  627. * @param[in] txt scanning pointer
  628. * @returns the next character to look at
  629. */
  630. static char *
  631. handle_section(tOptions * opts, char * txt)
  632. {
  633. size_t len = strlen(opts->pzPROGNAME);
  634. if ( (strncmp(txt+1, opts->pzPROGNAME, len) == 0)
  635. && (txt[len+1] == ']'))
  636. return strchr(txt + len + 2, NL);
  637. if (len > 16)
  638. return NULL;
  639. {
  640. char z[24] = "[";
  641. memcpy(z+1, opts->pzPROGNAME, len);
  642. z[++len] = ']';
  643. z[++len] = NUL;
  644. txt = strstr(txt, z);
  645. }
  646. if (txt != NULL)
  647. txt = strchr(txt, NL);
  648. return txt;
  649. }
  650. /**
  651. * parse XML encodings
  652. */
  653. static int
  654. parse_xml_encoding(char ** ppz)
  655. {
  656. # define XMLTABLE \
  657. _xmlNm_(amp, '&') \
  658. _xmlNm_(lt, '<') \
  659. _xmlNm_(gt, '>') \
  660. _xmlNm_(ff, '\f') \
  661. _xmlNm_(ht, '\t') \
  662. _xmlNm_(cr, '\r') \
  663. _xmlNm_(vt, '\v') \
  664. _xmlNm_(bel, '\a') \
  665. _xmlNm_(nl, NL) \
  666. _xmlNm_(space, ' ') \
  667. _xmlNm_(quot, '"') \
  668. _xmlNm_(apos, '\'')
  669. static struct {
  670. char const * const nm_str;
  671. unsigned short nm_len;
  672. short nm_val;
  673. } const xml_names[] = {
  674. # define _xmlNm_(_n, _v) { #_n ";", sizeof(#_n), _v },
  675. XMLTABLE
  676. # undef _xmlNm_
  677. # undef XMLTABLE
  678. };
  679. static int const nm_ct = sizeof(xml_names) / sizeof(xml_names[0]);
  680. int base = 10;
  681. char * pz = *ppz;
  682. if (*pz == '#') {
  683. pz++;
  684. goto parse_number;
  685. }
  686. if (IS_DEC_DIGIT_CHAR(*pz)) {
  687. unsigned long v;
  688. parse_number:
  689. switch (*pz) {
  690. case 'x': case 'X':
  691. /*
  692. * Some forms specify hex with: &#xNN;
  693. */
  694. base = 16;
  695. pz++;
  696. break;
  697. case '0':
  698. /*
  699. * &#0022; is hex and &#22; is decimal. Cool.
  700. * Ya gotta love it.
  701. */
  702. if (pz[1] == '0')
  703. base = 16;
  704. break;
  705. }
  706. v = strtoul(pz, &pz, base);
  707. if ((*pz != ';') || (v > 0x7F))
  708. return NUL;
  709. *ppz = pz + 1;
  710. return (int)v;
  711. }
  712. {
  713. int ix = 0;
  714. do {
  715. if (strncmp(pz, xml_names[ix].nm_str, xml_names[ix].nm_len)
  716. == 0) {
  717. *ppz = pz + xml_names[ix].nm_len;
  718. return xml_names[ix].nm_val;
  719. }
  720. } while (++ix < nm_ct);
  721. }
  722. return NUL;
  723. }
  724. /**
  725. * Find the end marker for the named section of XML.
  726. * Trim that text there, trimming trailing white space for all modes
  727. * except for OPTION_LOAD_UNCOOKED.
  728. */
  729. static char *
  730. trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode)
  731. {
  732. size_t nm_len = strlen(pznm);
  733. char * etext;
  734. {
  735. char z[64], *pz = z;
  736. if (nm_len + 4 >= sizeof(z))
  737. pz = AGALOC(nm_len + 4, "scan name");
  738. pz[0] = '<';
  739. pz[1] = '/';
  740. memcpy(pz+2, pznm, nm_len);
  741. nm_len += 2;
  742. pz[nm_len++] = '>';
  743. pz[nm_len] = NUL;
  744. *intxt = ' ';
  745. etext = strstr(intxt, pz);
  746. if (pz != z) AGFREE(pz);
  747. }
  748. if (etext == NULL)
  749. return etext;
  750. {
  751. char * result = etext + nm_len;
  752. if (mode != OPTION_LOAD_UNCOOKED)
  753. etext = SPN_WHITESPACE_BACK(intxt, etext);
  754. *etext = NUL;
  755. return result;
  756. }
  757. }
  758. /**
  759. */
  760. static void
  761. cook_xml_text(char * pzData)
  762. {
  763. char * pzs = pzData;
  764. char * pzd = pzData;
  765. char bf[4];
  766. bf[2] = NUL;
  767. for (;;) {
  768. int ch = ((int)*(pzs++)) & 0xFF;
  769. switch (ch) {
  770. case NUL:
  771. *pzd = NUL;
  772. return;
  773. case '&':
  774. ch = parse_xml_encoding(&pzs);
  775. *(pzd++) = (char)ch;
  776. if (ch == NUL)
  777. return;
  778. break;
  779. case '%':
  780. bf[0] = *(pzs++);
  781. bf[1] = *(pzs++);
  782. if ((bf[0] == NUL) || (bf[1] == NUL)) {
  783. *pzd = NUL;
  784. return;
  785. }
  786. ch = (int)strtoul(bf, NULL, 16);
  787. /* FALLTHROUGH */
  788. default:
  789. *(pzd++) = (char)ch;
  790. }
  791. }
  792. }
  793. /**
  794. * "txt" points to a '<' character, followed by an alpha.
  795. * The end of the entry is either the "/>" following the name, or else a
  796. * "</name>" string.
  797. */
  798. static char *
  799. handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir)
  800. {
  801. tOptionLoadMode mode = option_load_mode;
  802. tOptionValue valu;
  803. char * pzName = ++txt;
  804. char * pzData;
  805. char * pcNulPoint;
  806. txt = SPN_VALUE_NAME_CHARS(txt);
  807. pcNulPoint = txt;
  808. valu.valType = OPARG_TYPE_STRING;
  809. switch (*txt) {
  810. case ' ':
  811. case '\t':
  812. txt = VOIDP(parse_attrs(
  813. opts, SPN_WHITESPACE_CHARS(txt), &mode, &valu));
  814. if (txt == NULL)
  815. return txt;
  816. if (*txt == '>')
  817. break;
  818. if (*txt != '/')
  819. return NULL;
  820. /* FALLTHROUGH */
  821. case '/':
  822. if (txt[1] != '>')
  823. return NULL;
  824. *txt = NUL;
  825. txt += 2;
  826. load_opt_line(opts, ost, pzName, dir, mode);
  827. return txt;
  828. case '>':
  829. break;
  830. default:
  831. txt = strchr(txt, '>');
  832. if (txt != NULL)
  833. txt++;
  834. return txt;
  835. }
  836. /*
  837. * If we are here, we have a value. "txt" points to a closing angle
  838. * bracket. Separate the name from the value for a moment.
  839. */
  840. *pcNulPoint = NUL;
  841. pzData = ++txt;
  842. txt = trim_xml_text(txt, pzName, mode);
  843. if (txt == NULL)
  844. return txt;
  845. /*
  846. * Rejoin the name and value for parsing by "load_opt_line()".
  847. * Erase any attributes parsed by "parse_attrs()".
  848. */
  849. memset(pcNulPoint, ' ', (size_t)(pzData - pcNulPoint));
  850. /*
  851. * If we are getting a "string" value that is to be cooked,
  852. * then process the XML-ish &xx; XML-ish and %XX hex characters.
  853. */
  854. if ( (valu.valType == OPARG_TYPE_STRING)
  855. && (mode == OPTION_LOAD_COOKED))
  856. cook_xml_text(pzData);
  857. /*
  858. * "pzName" points to what looks like text for one option/configurable.
  859. * It is NUL terminated. Process it.
  860. */
  861. load_opt_line(opts, ost, pzName, dir, mode);
  862. return txt;
  863. }
  864. /**
  865. * Load a configuration file. This may be invoked either from
  866. * scanning the "homerc" list, or from a specific file request.
  867. * (see "optionFileLoad()", the implementation for --load-opts)
  868. */
  869. LOCAL void
  870. intern_file_load(tOptions * opts)
  871. {
  872. uint32_t svfl;
  873. int idx;
  874. int inc;
  875. char f_name[ AG_PATH_MAX+1 ];
  876. if (opts->papzHomeList == NULL)
  877. return;
  878. svfl = opts->fOptSet;
  879. inc = DIRECTION_PRESET;
  880. /*
  881. * Never stop on errors in config files.
  882. */
  883. opts->fOptSet &= ~OPTPROC_ERRSTOP;
  884. /*
  885. * Find the last RC entry (highest priority entry)
  886. */
  887. for (idx = 0; opts->papzHomeList[ idx+1 ] != NULL; ++idx) ;
  888. /*
  889. * For every path in the home list, ... *TWICE* We start at the last
  890. * (highest priority) entry, work our way down to the lowest priority,
  891. * handling the immediate options.
  892. * Then we go back up, doing the normal options.
  893. */
  894. for (;;) {
  895. struct stat sb;
  896. cch_t * path;
  897. /*
  898. * IF we've reached the bottom end, change direction
  899. */
  900. if (idx < 0) {
  901. inc = DIRECTION_PROCESS;
  902. idx = 0;
  903. }
  904. path = opts->papzHomeList[ idx ];
  905. /*
  906. * IF we've reached the top end, bail out
  907. */
  908. if (path == NULL)
  909. break;
  910. idx += inc;
  911. if (! optionMakePath(f_name, (int)sizeof(f_name),
  912. path, opts->pzProgPath))
  913. continue;
  914. /*
  915. * IF the file name we constructed is a directory,
  916. * THEN append the Resource Configuration file name
  917. * ELSE we must have the complete file name
  918. */
  919. if (stat(f_name, &sb) != 0)
  920. continue; /* bogus name - skip the home list entry */
  921. if (S_ISDIR(sb.st_mode)) {
  922. size_t len = strlen(f_name);
  923. size_t nln = strlen(opts->pzRcName) + 1;
  924. char * pz = f_name + len;
  925. if (len + 1 + nln >= sizeof(f_name))
  926. continue;
  927. if (pz[-1] != DIRCH)
  928. *(pz++) = DIRCH;
  929. memcpy(pz, opts->pzRcName, nln);
  930. }
  931. file_preset(opts, f_name, inc);
  932. /*
  933. * IF we are now to skip config files AND we are presetting,
  934. * THEN change direction. We must go the other way.
  935. */
  936. {
  937. tOptDesc * od = opts->pOptDesc + opts->specOptIdx.save_opts + 1;
  938. if (DISABLED_OPT(od) && PRESETTING(inc)) {
  939. idx -= inc; /* go back and reprocess current file */
  940. inc = DIRECTION_PROCESS;
  941. }
  942. }
  943. } /* twice for every path in the home list, ... */
  944. opts->fOptSet = svfl;
  945. }
  946. /*=export_func optionFileLoad
  947. *
  948. * what: Load the locatable config files, in order
  949. *
  950. * arg: + tOptions * + opts + program options descriptor +
  951. * arg: + char const * + prog + program name +
  952. *
  953. * ret_type: int
  954. * ret_desc: 0 -> SUCCESS, -1 -> FAILURE
  955. *
  956. * doc:
  957. *
  958. * This function looks in all the specified directories for a configuration
  959. * file ("rc" file or "ini" file) and processes any found twice. The first
  960. * time through, they are processed in reverse order (last file first). At
  961. * that time, only "immediate action" configurables are processed. For
  962. * example, if the last named file specifies not processing any more
  963. * configuration files, then no more configuration files will be processed.
  964. * Such an option in the @strong{first} named directory will have no effect.
  965. *
  966. * Once the immediate action configurables have been handled, then the
  967. * directories are handled in normal, forward order. In that way, later
  968. * config files can override the settings of earlier config files.
  969. *
  970. * See the AutoOpts documentation for a thorough discussion of the
  971. * config file format.
  972. *
  973. * Configuration files not found or not decipherable are simply ignored.
  974. *
  975. * err: Returns the value, "-1" if the program options descriptor
  976. * is out of date or indecipherable. Otherwise, the value "0" will
  977. * always be returned.
  978. =*/
  979. int
  980. optionFileLoad(tOptions * opts, char const * prog)
  981. {
  982. if (! SUCCESSFUL(validate_struct(opts, prog)))
  983. return -1;
  984. /*
  985. * The pointer to the program name is "const". However, the
  986. * structure is in writable memory, so we coerce the address
  987. * of this pointer to point to writable memory.
  988. */
  989. {
  990. char const ** pp = VOIDP(&(opts->pzProgName));
  991. *pp = prog;
  992. }
  993. intern_file_load(opts);
  994. return 0;
  995. }
  996. /*=export_func optionLoadOpt
  997. * private:
  998. *
  999. * what: Load an option rc/ini file
  1000. * arg: + tOptions * + opts + program options descriptor +
  1001. * arg: + tOptDesc * + odesc + the descriptor for this arg +
  1002. *
  1003. * doc:
  1004. * Processes the options found in the file named with
  1005. * odesc->optArg.argString.
  1006. =*/
  1007. void
  1008. optionLoadOpt(tOptions * opts, tOptDesc * odesc)
  1009. {
  1010. struct stat sb;
  1011. if (opts <= OPTPROC_EMIT_LIMIT)
  1012. return;
  1013. /*
  1014. * IF the option is not being disabled, THEN load the file. There must
  1015. * be a file. (If it is being disabled, then the disablement processing
  1016. * already took place. It must be done to suppress preloading of ini/rc
  1017. * files.)
  1018. */
  1019. if ( DISABLED_OPT(odesc)
  1020. || ((odesc->fOptState & OPTST_RESET) != 0))
  1021. return;
  1022. if (stat(odesc->optArg.argString, &sb) != 0) {
  1023. if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
  1024. return;
  1025. fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
  1026. /* NOT REACHED */
  1027. }
  1028. if (! S_ISREG(sb.st_mode)) {
  1029. if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
  1030. return;
  1031. errno = EINVAL;
  1032. fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
  1033. /* NOT REACHED */
  1034. }
  1035. file_preset(opts, odesc->optArg.argString, DIRECTION_CALLED);
  1036. }
  1037. /**
  1038. * Parse the various attributes of an XML-styled config file entry
  1039. *
  1040. * @returns NULL on failure, otherwise the scan point
  1041. */
  1042. LOCAL char const *
  1043. parse_attrs(tOptions * opts, char const * txt, tOptionLoadMode * pMode,
  1044. tOptionValue * pType)
  1045. {
  1046. size_t len = 0;
  1047. for (;;) {
  1048. len = (size_t)(SPN_LOWER_CASE_CHARS(txt) - txt);
  1049. /*
  1050. * The enumeration used in this switch is derived from this switch
  1051. * statement itself. The "find_option_xat_attribute_cmd" function
  1052. * will return XAT_CMD_MEMBERS for the "txt" string value
  1053. * "members", etc.
  1054. */
  1055. switch (find_option_xat_attribute_cmd(txt, len)) {
  1056. case XAT_CMD_TYPE:
  1057. txt = parse_value(txt+len, pType);
  1058. break;
  1059. case XAT_CMD_WORDS:
  1060. txt = parse_keyword(opts, txt+len, pType);
  1061. break;
  1062. case XAT_CMD_MEMBERS:
  1063. txt = parse_set_mem(opts, txt+len, pType);
  1064. break;
  1065. case XAT_CMD_COOKED:
  1066. txt += len;
  1067. if (! IS_END_XML_TOKEN_CHAR(*txt))
  1068. goto invalid_kwd;
  1069. *pMode = OPTION_LOAD_COOKED;
  1070. break;
  1071. case XAT_CMD_UNCOOKED:
  1072. txt += len;
  1073. if (! IS_END_XML_TOKEN_CHAR(*txt))
  1074. goto invalid_kwd;
  1075. *pMode = OPTION_LOAD_UNCOOKED;
  1076. break;
  1077. case XAT_CMD_KEEP:
  1078. txt += len;
  1079. if (! IS_END_XML_TOKEN_CHAR(*txt))
  1080. goto invalid_kwd;
  1081. *pMode = OPTION_LOAD_KEEP;
  1082. break;
  1083. default:
  1084. case XAT_INVALID_CMD:
  1085. invalid_kwd:
  1086. pType->valType = OPARG_TYPE_NONE;
  1087. return skip_unkn(txt);
  1088. }
  1089. if (txt == NULL)
  1090. return NULL;
  1091. txt = SPN_WHITESPACE_CHARS(txt);
  1092. switch (*txt) {
  1093. case '/': pType->valType = OPARG_TYPE_NONE;
  1094. /* FALLTHROUGH */
  1095. case '>': return txt;
  1096. }
  1097. if (! IS_LOWER_CASE_CHAR(*txt))
  1098. return NULL;
  1099. }
  1100. }
  1101. /**
  1102. * "txt" points to the character after "words=".
  1103. * What should follow is a name of a keyword (enumeration) list.
  1104. *
  1105. * @param opts unused
  1106. * @param[in] txt keyword to skip over
  1107. * @param type unused value type
  1108. * @returns pointer after skipped text
  1109. */
  1110. static char const *
  1111. parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ)
  1112. {
  1113. (void)opts;
  1114. (void)typ;
  1115. return skip_unkn(txt);
  1116. }
  1117. /**
  1118. * "txt" points to the character after "members="
  1119. * What should follow is a name of a "set membership".
  1120. * A collection of bit flags.
  1121. *
  1122. * @param opts unused
  1123. * @param[in] txt keyword to skip over
  1124. * @param type unused value type
  1125. * @returns pointer after skipped text
  1126. */
  1127. static char const *
  1128. parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ)
  1129. {
  1130. (void)opts;
  1131. (void)typ;
  1132. return skip_unkn(txt);
  1133. }
  1134. /**
  1135. * parse the type. The keyword "type" was found, now figure out
  1136. * the type that follows the type.
  1137. *
  1138. * @param[in] txt points to the '=' character after the "type" keyword.
  1139. * @param[out] typ where to store the type found
  1140. * @returns the next byte after the type name
  1141. */
  1142. static char const *
  1143. parse_value(char const * txt, tOptionValue * typ)
  1144. {
  1145. size_t len = 0;
  1146. if (*(txt++) != '=')
  1147. goto woops;
  1148. len = (size_t)(SPN_OPTION_NAME_CHARS(txt) - txt);
  1149. if ((len == 0) || (! IS_END_XML_TOKEN_CHAR(txt[len]))) {
  1150. woops:
  1151. typ->valType = OPARG_TYPE_NONE;
  1152. return skip_unkn(txt + len);
  1153. }
  1154. /*
  1155. * The enumeration used in this switch is derived from this switch
  1156. * statement itself. The "find_option_value_type_cmd" function
  1157. * will return VTP_CMD_INTEGER for the "txt" string value
  1158. * "integer", etc.
  1159. */
  1160. switch (find_option_value_type_cmd(txt, len)) {
  1161. default:
  1162. case VTP_INVALID_CMD: goto woops;
  1163. case VTP_CMD_STRING:
  1164. typ->valType = OPARG_TYPE_STRING;
  1165. break;
  1166. case VTP_CMD_INTEGER:
  1167. typ->valType = OPARG_TYPE_NUMERIC;
  1168. break;
  1169. case VTP_CMD_BOOL:
  1170. case VTP_CMD_BOOLEAN:
  1171. typ->valType = OPARG_TYPE_BOOLEAN;
  1172. break;
  1173. case VTP_CMD_KEYWORD:
  1174. typ->valType = OPARG_TYPE_ENUMERATION;
  1175. break;
  1176. case VTP_CMD_SET:
  1177. case VTP_CMD_SET_MEMBERSHIP:
  1178. typ->valType = OPARG_TYPE_MEMBERSHIP;
  1179. break;
  1180. case VTP_CMD_NESTED:
  1181. case VTP_CMD_HIERARCHY:
  1182. typ->valType = OPARG_TYPE_HIERARCHY;
  1183. }
  1184. return txt + len;
  1185. }
  1186. /** @}
  1187. *
  1188. * Local Variables:
  1189. * mode: C
  1190. * c-file-style: "stroustrup"
  1191. * indent-tabs-mode: nil
  1192. * End:
  1193. * end of autoopts/configfile.c */