configfile.c 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382
  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-2014 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. default:
  409. ftext = NULL;
  410. }
  411. if (ftext == NULL)
  412. goto all_done;
  413. break;
  414. case '[':
  415. ftext = handle_section(opts, ftext);
  416. break;
  417. case '#':
  418. ftext = strchr(ftext + 1, NL);
  419. break;
  420. default:
  421. goto all_done; /* invalid format */
  422. }
  423. } while (ftext != NULL);
  424. all_done:
  425. text_munmap(&cfgfile);
  426. opts->fOptSet = fl_save;
  427. }
  428. /**
  429. * "txt" points to a "<!" sequence.
  430. * Theoretically, we should ensure that it begins with "<!--",
  431. * but actually I don't care that much. It ends with "-->".
  432. */
  433. static char *
  434. handle_comment(char* txt)
  435. {
  436. char* pz = strstr(txt, "-->");
  437. if (pz != NULL)
  438. pz += 3;
  439. return pz;
  440. }
  441. /**
  442. * "txt" points to the start of some value name.
  443. * The end of the entry is the end of the line that is not preceded by
  444. * a backslash escape character. The string value is always processed
  445. * in "cooked" mode.
  446. */
  447. static char *
  448. handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir)
  449. {
  450. char* pzName = txt++;
  451. char* pzEnd = strchr(txt, NL);
  452. if (pzEnd == NULL)
  453. return txt + strlen(txt);
  454. txt = SPN_VALUE_NAME_CHARS(txt);
  455. txt = SPN_WHITESPACE_CHARS(txt);
  456. if (txt > pzEnd) {
  457. name_only:
  458. *pzEnd++ = NUL;
  459. load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
  460. return pzEnd;
  461. }
  462. /*
  463. * Either the first character after the name is a ':' or '=',
  464. * or else we must have skipped over white space. Anything else
  465. * is an invalid format and we give up parsing the text.
  466. */
  467. if ((*txt == '=') || (*txt == ':')) {
  468. txt = SPN_WHITESPACE_CHARS(txt+1);
  469. if (txt > pzEnd)
  470. goto name_only;
  471. } else if (! IS_WHITESPACE_CHAR(txt[-1]))
  472. return NULL;
  473. /*
  474. * IF the value is continued, remove the backslash escape and push "pzEnd"
  475. * on to a newline *not* preceded by a backslash.
  476. */
  477. if (pzEnd[-1] == '\\') {
  478. char* pcD = pzEnd-1;
  479. char* pcS = pzEnd;
  480. for (;;) {
  481. char ch = *(pcS++);
  482. switch (ch) {
  483. case NUL:
  484. pcS = NULL;
  485. /* FALLTHROUGH */
  486. case NL:
  487. *pcD = NUL;
  488. pzEnd = pcS;
  489. goto copy_done;
  490. case '\\':
  491. if (*pcS == NL)
  492. ch = *(pcS++);
  493. /* FALLTHROUGH */
  494. default:
  495. *(pcD++) = ch;
  496. }
  497. } copy_done:;
  498. } else {
  499. /*
  500. * The newline was not preceded by a backslash. NUL it out
  501. */
  502. *(pzEnd++) = NUL;
  503. }
  504. /*
  505. * "pzName" points to what looks like text for one option/configurable.
  506. * It is NUL terminated. Process it.
  507. */
  508. load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
  509. return pzEnd;
  510. }
  511. /**
  512. * "txt" points to a "<?" sequence.
  513. * We handle "<?program" and "<?auto-options" directives.
  514. * All others are treated as comments.
  515. *
  516. * @param[in,out] opts program option descriptor
  517. * @param[in] txt scanning pointer
  518. * @returns the next character to look at
  519. */
  520. static char *
  521. handle_directive(tOptions * opts, char * txt)
  522. {
  523. # define DIRECTIVE_TABLE \
  524. _dt_(zCfgProg, program_directive) \
  525. _dt_(zCfgAO_Flags, aoflags_directive)
  526. typedef char * (directive_func_t)(tOptions *, char *);
  527. # define _dt_(_s, _fn) _fn,
  528. static directive_func_t * dir_disp[] = {
  529. DIRECTIVE_TABLE
  530. };
  531. # undef _dt_
  532. # define _dt_(_s, _fn) 1 +
  533. static int const dir_ct = DIRECTIVE_TABLE 0;
  534. static char const * dir_names[DIRECTIVE_TABLE 0];
  535. # undef _dt_
  536. int ix;
  537. if (dir_names[0] == NULL) {
  538. ix = 0;
  539. # define _dt_(_s, _fn) dir_names[ix++] = _s;
  540. DIRECTIVE_TABLE;
  541. # undef _dt_
  542. }
  543. for (ix = 0; ix < dir_ct; ix++) {
  544. size_t len = strlen(dir_names[ix]);
  545. if ( (strncmp(txt + 2, dir_names[ix], len) == 0)
  546. && (! IS_VALUE_NAME_CHAR(txt[len+2])) )
  547. return dir_disp[ix](opts, txt + len + 2);
  548. }
  549. /*
  550. * We don't know what this is. Skip it.
  551. */
  552. txt = strchr(txt+2, '>');
  553. if (txt != NULL)
  554. txt++;
  555. return txt;
  556. # undef DIRECTIVE_TABLE
  557. }
  558. /**
  559. * handle AutoOpts mode flags.
  560. *
  561. * @param[in,out] opts program option descriptor
  562. * @param[in] txt scanning pointer
  563. * @returns the next character to look at
  564. */
  565. static char *
  566. aoflags_directive(tOptions * opts, char * txt)
  567. {
  568. char * pz;
  569. pz = SPN_WHITESPACE_CHARS(txt+1);
  570. txt = strchr(pz, '>');
  571. if (txt != NULL) {
  572. size_t len = (unsigned)(txt - pz);
  573. char * ftxt = AGALOC(len + 1, "aoflags");
  574. memcpy(ftxt, pz, len);
  575. ftxt[len] = NUL;
  576. set_usage_flags(opts, ftxt);
  577. AGFREE(ftxt);
  578. txt++;
  579. }
  580. return txt;
  581. }
  582. /**
  583. * handle program segmentation of config file.
  584. *
  585. * @param[in,out] opts program option descriptor
  586. * @param[in] txt scanning pointer
  587. * @returns the next character to look at
  588. */
  589. static char *
  590. program_directive(tOptions * opts, char * txt)
  591. {
  592. static char const ttlfmt[] = "<?";
  593. size_t ttl_len = sizeof(ttlfmt) + strlen(zCfgProg);
  594. char * ttl = AGALOC(ttl_len, "prog title");
  595. size_t name_len = strlen(opts->pzProgName);
  596. memcpy(ttl, ttlfmt, sizeof(ttlfmt) - 1);
  597. memcpy(ttl + sizeof(ttlfmt) - 1, zCfgProg, ttl_len - (sizeof(ttlfmt) - 1));
  598. do {
  599. txt = SPN_WHITESPACE_CHARS(txt+1);
  600. if ( (strneqvcmp(txt, opts->pzProgName, (int)name_len) == 0)
  601. && (IS_END_XML_TOKEN_CHAR(txt[name_len])) ) {
  602. txt += name_len;
  603. break;
  604. }
  605. txt = strstr(txt, ttl);
  606. } while (txt != NULL);
  607. AGFREE(ttl);
  608. if (txt != NULL)
  609. for (;;) {
  610. if (*txt == NUL) {
  611. txt = NULL;
  612. break;
  613. }
  614. if (*(txt++) == '>')
  615. break;
  616. }
  617. return txt;
  618. }
  619. /**
  620. * "txt" points to a '[' character.
  621. * The "traditional" [PROG_NAME] segmentation of the config file.
  622. * Do not ever mix with the "<?program prog-name>" variation.
  623. *
  624. * @param[in,out] opts program option descriptor
  625. * @param[in] txt scanning pointer
  626. * @returns the next character to look at
  627. */
  628. static char *
  629. handle_section(tOptions * opts, char * txt)
  630. {
  631. size_t len = strlen(opts->pzPROGNAME);
  632. if ( (strncmp(txt+1, opts->pzPROGNAME, len) == 0)
  633. && (txt[len+1] == ']'))
  634. return strchr(txt + len + 2, NL);
  635. if (len > 16)
  636. return NULL;
  637. {
  638. char z[24];
  639. sprintf(z, "[%s]", opts->pzPROGNAME);
  640. txt = strstr(txt, z);
  641. }
  642. if (txt != NULL)
  643. txt = strchr(txt, NL);
  644. return txt;
  645. }
  646. /**
  647. * parse XML encodings
  648. */
  649. static int
  650. parse_xml_encoding(char ** ppz)
  651. {
  652. # define XMLTABLE \
  653. _xmlNm_(amp, '&') \
  654. _xmlNm_(lt, '<') \
  655. _xmlNm_(gt, '>') \
  656. _xmlNm_(ff, '\f') \
  657. _xmlNm_(ht, '\t') \
  658. _xmlNm_(cr, '\r') \
  659. _xmlNm_(vt, '\v') \
  660. _xmlNm_(bel, '\a') \
  661. _xmlNm_(nl, NL) \
  662. _xmlNm_(space, ' ') \
  663. _xmlNm_(quot, '"') \
  664. _xmlNm_(apos, '\'')
  665. static struct {
  666. char const * const nm_str;
  667. unsigned short nm_len;
  668. short nm_val;
  669. } const xml_names[] = {
  670. # define _xmlNm_(_n, _v) { #_n ";", sizeof(#_n), _v },
  671. XMLTABLE
  672. # undef _xmlNm_
  673. # undef XMLTABLE
  674. };
  675. static int const nm_ct = sizeof(xml_names) / sizeof(xml_names[0]);
  676. int base = 10;
  677. char * pz = *ppz;
  678. if (*pz == '#') {
  679. pz++;
  680. goto parse_number;
  681. }
  682. if (IS_DEC_DIGIT_CHAR(*pz)) {
  683. unsigned long v;
  684. parse_number:
  685. switch (*pz) {
  686. case 'x': case 'X':
  687. /*
  688. * Some forms specify hex with: &#xNN;
  689. */
  690. base = 16;
  691. pz++;
  692. break;
  693. case '0':
  694. /*
  695. * &#0022; is hex and &#22; is decimal. Cool.
  696. * Ya gotta love it.
  697. */
  698. if (pz[1] == '0')
  699. base = 16;
  700. break;
  701. }
  702. v = strtoul(pz, &pz, base);
  703. if ((*pz != ';') || (v > 0x7F))
  704. return NUL;
  705. *ppz = pz + 1;
  706. return (int)v;
  707. }
  708. {
  709. int ix = 0;
  710. do {
  711. if (strncmp(pz, xml_names[ix].nm_str, xml_names[ix].nm_len)
  712. == 0) {
  713. *ppz = pz + xml_names[ix].nm_len;
  714. return xml_names[ix].nm_val;
  715. }
  716. } while (++ix < nm_ct);
  717. }
  718. return NUL;
  719. }
  720. /**
  721. * Find the end marker for the named section of XML.
  722. * Trim that text there, trimming trailing white space for all modes
  723. * except for OPTION_LOAD_UNCOOKED.
  724. */
  725. static char *
  726. trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode)
  727. {
  728. static char const fmt[] = "</%s>";
  729. size_t len = strlen(pznm) + sizeof(fmt) - 2 /* for %s */;
  730. char * etext;
  731. {
  732. char z[64], *pz = z;
  733. if (len >= sizeof(z))
  734. pz = AGALOC(len, "scan name");
  735. len = (size_t)sprintf(pz, fmt, pznm);
  736. *intxt = ' ';
  737. etext = strstr(intxt, pz);
  738. if (pz != z) AGFREE(pz);
  739. }
  740. if (etext == NULL)
  741. return etext;
  742. {
  743. char * result = etext + len;
  744. if (mode != OPTION_LOAD_UNCOOKED)
  745. etext = SPN_WHITESPACE_BACK(intxt, etext);
  746. *etext = NUL;
  747. return result;
  748. }
  749. }
  750. /**
  751. */
  752. static void
  753. cook_xml_text(char * pzData)
  754. {
  755. char * pzs = pzData;
  756. char * pzd = pzData;
  757. char bf[4];
  758. bf[2] = NUL;
  759. for (;;) {
  760. int ch = ((int)*(pzs++)) & 0xFF;
  761. switch (ch) {
  762. case NUL:
  763. *pzd = NUL;
  764. return;
  765. case '&':
  766. ch = parse_xml_encoding(&pzs);
  767. *(pzd++) = (char)ch;
  768. if (ch == NUL)
  769. return;
  770. break;
  771. case '%':
  772. bf[0] = *(pzs++);
  773. bf[1] = *(pzs++);
  774. if ((bf[0] == NUL) || (bf[1] == NUL)) {
  775. *pzd = NUL;
  776. return;
  777. }
  778. ch = (int)strtoul(bf, NULL, 16);
  779. /* FALLTHROUGH */
  780. default:
  781. *(pzd++) = (char)ch;
  782. }
  783. }
  784. }
  785. /**
  786. * "txt" points to a '<' character, followed by an alpha.
  787. * The end of the entry is either the "/>" following the name, or else a
  788. * "</name>" string.
  789. */
  790. static char *
  791. handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir)
  792. {
  793. tOptionLoadMode mode = option_load_mode;
  794. tOptionValue valu;
  795. char* pzName = ++txt;
  796. char* pzData;
  797. char* pcNulPoint;
  798. txt = SPN_VALUE_NAME_CHARS(txt);
  799. pcNulPoint = txt;
  800. valu.valType = OPARG_TYPE_STRING;
  801. switch (*txt) {
  802. case ' ':
  803. case '\t':
  804. txt = (void *)parse_attrs(
  805. opts, SPN_WHITESPACE_CHARS(txt), &mode, &valu);
  806. if (txt == NULL)
  807. return txt;
  808. if (*txt == '>')
  809. break;
  810. if (*txt != '/')
  811. return NULL;
  812. /* FALLTHROUGH */
  813. case '/':
  814. if (txt[1] != '>')
  815. return NULL;
  816. *txt = NUL;
  817. txt += 2;
  818. load_opt_line(opts, ost, pzName, dir, mode);
  819. return txt;
  820. case '>':
  821. break;
  822. default:
  823. txt = strchr(txt, '>');
  824. if (txt != NULL)
  825. txt++;
  826. return txt;
  827. }
  828. /*
  829. * If we are here, we have a value. "txt" points to a closing angle
  830. * bracket. Separate the name from the value for a moment.
  831. */
  832. *pcNulPoint = NUL;
  833. pzData = ++txt;
  834. txt = trim_xml_text(txt, pzName, mode);
  835. if (txt == NULL)
  836. return txt;
  837. /*
  838. * Rejoin the name and value for parsing by "load_opt_line()".
  839. * Erase any attributes parsed by "parse_attrs()".
  840. */
  841. memset(pcNulPoint, ' ', (size_t)(pzData - pcNulPoint));
  842. /*
  843. * If we are getting a "string" value that is to be cooked,
  844. * then process the XML-ish &xx; XML-ish and %XX hex characters.
  845. */
  846. if ( (valu.valType == OPARG_TYPE_STRING)
  847. && (mode == OPTION_LOAD_COOKED))
  848. cook_xml_text(pzData);
  849. /*
  850. * "pzName" points to what looks like text for one option/configurable.
  851. * It is NUL terminated. Process it.
  852. */
  853. load_opt_line(opts, ost, pzName, dir, mode);
  854. return txt;
  855. }
  856. /**
  857. * Load a configuration file. This may be invoked either from
  858. * scanning the "homerc" list, or from a specific file request.
  859. * (see "optionFileLoad()", the implementation for --load-opts)
  860. */
  861. LOCAL void
  862. intern_file_load(tOptions * opts)
  863. {
  864. uint32_t svfl;
  865. int idx;
  866. int inc;
  867. char f_name[ AG_PATH_MAX+1 ];
  868. if (opts->papzHomeList == NULL)
  869. return;
  870. svfl = opts->fOptSet;
  871. inc = DIRECTION_PRESET;
  872. /*
  873. * Never stop on errors in config files.
  874. */
  875. opts->fOptSet &= ~OPTPROC_ERRSTOP;
  876. /*
  877. * Find the last RC entry (highest priority entry)
  878. */
  879. for (idx = 0; opts->papzHomeList[ idx+1 ] != NULL; ++idx) ;
  880. /*
  881. * For every path in the home list, ... *TWICE* We start at the last
  882. * (highest priority) entry, work our way down to the lowest priority,
  883. * handling the immediate options.
  884. * Then we go back up, doing the normal options.
  885. */
  886. for (;;) {
  887. struct stat sb;
  888. cch_t * path;
  889. /*
  890. * IF we've reached the bottom end, change direction
  891. */
  892. if (idx < 0) {
  893. inc = DIRECTION_PROCESS;
  894. idx = 0;
  895. }
  896. path = opts->papzHomeList[ idx ];
  897. /*
  898. * IF we've reached the top end, bail out
  899. */
  900. if (path == NULL)
  901. break;
  902. idx += inc;
  903. if (! optionMakePath(f_name, (int)sizeof(f_name),
  904. path, opts->pzProgPath))
  905. continue;
  906. /*
  907. * IF the file name we constructed is a directory,
  908. * THEN append the Resource Configuration file name
  909. * ELSE we must have the complete file name
  910. */
  911. if (stat(f_name, &sb) != 0)
  912. continue; /* bogus name - skip the home list entry */
  913. if (S_ISDIR(sb.st_mode)) {
  914. size_t len = strlen(f_name);
  915. size_t nln = strlen(opts->pzRcName) + 1;
  916. char * pz = f_name + len;
  917. if (len + 1 + nln >= sizeof(f_name))
  918. continue;
  919. if (pz[-1] != DIRCH)
  920. *(pz++) = DIRCH;
  921. memcpy(pz, opts->pzRcName, nln);
  922. }
  923. file_preset(opts, f_name, inc);
  924. /*
  925. * IF we are now to skip config files AND we are presetting,
  926. * THEN change direction. We must go the other way.
  927. */
  928. {
  929. tOptDesc * od = opts->pOptDesc + opts->specOptIdx.save_opts + 1;
  930. if (DISABLED_OPT(od) && PRESETTING(inc)) {
  931. idx -= inc; /* go back and reprocess current file */
  932. inc = DIRECTION_PROCESS;
  933. }
  934. }
  935. } /* twice for every path in the home list, ... */
  936. opts->fOptSet = svfl;
  937. }
  938. /*=export_func optionFileLoad
  939. *
  940. * what: Load the locatable config files, in order
  941. *
  942. * arg: + tOptions* + opts + program options descriptor +
  943. * arg: + char const* + prog + program name +
  944. *
  945. * ret_type: int
  946. * ret_desc: 0 -> SUCCESS, -1 -> FAILURE
  947. *
  948. * doc:
  949. *
  950. * This function looks in all the specified directories for a configuration
  951. * file ("rc" file or "ini" file) and processes any found twice. The first
  952. * time through, they are processed in reverse order (last file first). At
  953. * that time, only "immediate action" configurables are processed. For
  954. * example, if the last named file specifies not processing any more
  955. * configuration files, then no more configuration files will be processed.
  956. * Such an option in the @strong{first} named directory will have no effect.
  957. *
  958. * Once the immediate action configurables have been handled, then the
  959. * directories are handled in normal, forward order. In that way, later
  960. * config files can override the settings of earlier config files.
  961. *
  962. * See the AutoOpts documentation for a thorough discussion of the
  963. * config file format.
  964. *
  965. * Configuration files not found or not decipherable are simply ignored.
  966. *
  967. * err: Returns the value, "-1" if the program options descriptor
  968. * is out of date or indecipherable. Otherwise, the value "0" will
  969. * always be returned.
  970. =*/
  971. int
  972. optionFileLoad(tOptions * opts, char const * prog)
  973. {
  974. if (! SUCCESSFUL(validate_struct(opts, prog)))
  975. return -1;
  976. /*
  977. * The pointer to the program name is "const". However, the
  978. * structure is in writable memory, so we coerce the address
  979. * of this pointer to point to writable memory.
  980. */
  981. {
  982. char const ** pp =
  983. (char const **)(void *)&(opts->pzProgName);
  984. *pp = prog;
  985. }
  986. intern_file_load(opts);
  987. return 0;
  988. }
  989. /*=export_func optionLoadOpt
  990. * private:
  991. *
  992. * what: Load an option rc/ini file
  993. * arg: + tOptions* + opts + program options descriptor +
  994. * arg: + tOptDesc* + odesc + the descriptor for this arg +
  995. *
  996. * doc:
  997. * Processes the options found in the file named with
  998. * odesc->optArg.argString.
  999. =*/
  1000. void
  1001. optionLoadOpt(tOptions * opts, tOptDesc * odesc)
  1002. {
  1003. struct stat sb;
  1004. if (opts <= OPTPROC_EMIT_LIMIT)
  1005. return;
  1006. /*
  1007. * IF the option is not being disabled, THEN load the file. There must
  1008. * be a file. (If it is being disabled, then the disablement processing
  1009. * already took place. It must be done to suppress preloading of ini/rc
  1010. * files.)
  1011. */
  1012. if ( DISABLED_OPT(odesc)
  1013. || ((odesc->fOptState & OPTST_RESET) != 0))
  1014. return;
  1015. if (stat(odesc->optArg.argString, &sb) != 0) {
  1016. if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
  1017. return;
  1018. fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
  1019. /* NOT REACHED */
  1020. }
  1021. if (! S_ISREG(sb.st_mode)) {
  1022. if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
  1023. return;
  1024. errno = EINVAL;
  1025. fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
  1026. /* NOT REACHED */
  1027. }
  1028. file_preset(opts, odesc->optArg.argString, DIRECTION_CALLED);
  1029. }
  1030. /**
  1031. * Parse the various attributes of an XML-styled config file entry
  1032. *
  1033. * @returns NULL on failure, otherwise the scan point
  1034. */
  1035. LOCAL char const *
  1036. parse_attrs(tOptions * opts, char const * txt, tOptionLoadMode * pMode,
  1037. tOptionValue * pType)
  1038. {
  1039. size_t len = 0;
  1040. for (;;) {
  1041. len = (size_t)(SPN_LOWER_CASE_CHARS(txt) - txt);
  1042. /*
  1043. * The enumeration used in this switch is derived from this switch
  1044. * statement itself. The "find_option_xat_attribute_cmd" function
  1045. * will return XAT_CMD_MEMBERS for the "txt" string value
  1046. * "members", etc.
  1047. */
  1048. switch (find_option_xat_attribute_cmd(txt, len)) {
  1049. case XAT_CMD_TYPE:
  1050. txt = parse_value(txt+len, pType);
  1051. break;
  1052. case XAT_CMD_WORDS:
  1053. txt = parse_keyword(opts, txt+len, pType);
  1054. break;
  1055. case XAT_CMD_MEMBERS:
  1056. txt = parse_set_mem(opts, txt+len, pType);
  1057. break;
  1058. case XAT_CMD_COOKED:
  1059. txt += len;
  1060. if (! IS_END_XML_TOKEN_CHAR(*txt))
  1061. goto invalid_kwd;
  1062. *pMode = OPTION_LOAD_COOKED;
  1063. break;
  1064. case XAT_CMD_UNCOOKED:
  1065. txt += len;
  1066. if (! IS_END_XML_TOKEN_CHAR(*txt))
  1067. goto invalid_kwd;
  1068. *pMode = OPTION_LOAD_UNCOOKED;
  1069. break;
  1070. case XAT_CMD_KEEP:
  1071. txt += len;
  1072. if (! IS_END_XML_TOKEN_CHAR(*txt))
  1073. goto invalid_kwd;
  1074. *pMode = OPTION_LOAD_KEEP;
  1075. break;
  1076. default:
  1077. case XAT_INVALID_CMD:
  1078. invalid_kwd:
  1079. pType->valType = OPARG_TYPE_NONE;
  1080. return skip_unkn(txt);
  1081. }
  1082. if (txt == NULL)
  1083. return NULL;
  1084. txt = SPN_WHITESPACE_CHARS(txt);
  1085. switch (*txt) {
  1086. case '/': pType->valType = OPARG_TYPE_NONE;
  1087. /* FALLTHROUGH */
  1088. case '>': return txt;
  1089. }
  1090. if (! IS_LOWER_CASE_CHAR(*txt))
  1091. return NULL;
  1092. }
  1093. }
  1094. /**
  1095. * "txt" points to the character after "words=".
  1096. * What should follow is a name of a keyword (enumeration) list.
  1097. *
  1098. * @param opts unused
  1099. * @param[in] txt keyword to skip over
  1100. * @param type unused value type
  1101. * @returns pointer after skipped text
  1102. */
  1103. static char const *
  1104. parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ)
  1105. {
  1106. (void)opts;
  1107. (void)typ;
  1108. return skip_unkn(txt);
  1109. }
  1110. /**
  1111. * "txt" points to the character after "members="
  1112. * What should follow is a name of a "set membership".
  1113. * A collection of bit flags.
  1114. *
  1115. * @param opts unused
  1116. * @param[in] txt keyword to skip over
  1117. * @param type unused value type
  1118. * @returns pointer after skipped text
  1119. */
  1120. static char const *
  1121. parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ)
  1122. {
  1123. (void)opts;
  1124. (void)typ;
  1125. return skip_unkn(txt);
  1126. }
  1127. /**
  1128. * parse the type. The keyword "type" was found, now figure out
  1129. * the type that follows the type.
  1130. *
  1131. * @param[in] txt points to the '=' character after the "type" keyword.
  1132. * @param[out] typ where to store the type found
  1133. * @returns the next byte after the type name
  1134. */
  1135. static char const *
  1136. parse_value(char const * txt, tOptionValue * typ)
  1137. {
  1138. size_t len = 0;
  1139. if (*(txt++) != '=')
  1140. goto woops;
  1141. len = (size_t)(SPN_OPTION_NAME_CHARS(txt) - txt);
  1142. if ((len == 0) || (! IS_END_XML_TOKEN_CHAR(txt[len]))) {
  1143. woops:
  1144. typ->valType = OPARG_TYPE_NONE;
  1145. return skip_unkn(txt + len);
  1146. }
  1147. /*
  1148. * The enumeration used in this switch is derived from this switch
  1149. * statement itself. The "find_option_value_type_cmd" function
  1150. * will return VTP_CMD_INTEGER for the "txt" string value
  1151. * "integer", etc.
  1152. */
  1153. switch (find_option_value_type_cmd(txt, len)) {
  1154. default:
  1155. case VTP_INVALID_CMD: goto woops;
  1156. case VTP_CMD_STRING:
  1157. typ->valType = OPARG_TYPE_STRING;
  1158. break;
  1159. case VTP_CMD_INTEGER:
  1160. typ->valType = OPARG_TYPE_NUMERIC;
  1161. break;
  1162. case VTP_CMD_BOOL:
  1163. case VTP_CMD_BOOLEAN:
  1164. typ->valType = OPARG_TYPE_BOOLEAN;
  1165. break;
  1166. case VTP_CMD_KEYWORD:
  1167. typ->valType = OPARG_TYPE_ENUMERATION;
  1168. break;
  1169. case VTP_CMD_SET:
  1170. case VTP_CMD_SET_MEMBERSHIP:
  1171. typ->valType = OPARG_TYPE_MEMBERSHIP;
  1172. break;
  1173. case VTP_CMD_NESTED:
  1174. case VTP_CMD_HIERARCHY:
  1175. typ->valType = OPARG_TYPE_HIERARCHY;
  1176. }
  1177. return txt + len;
  1178. }
  1179. /** @}
  1180. *
  1181. * Local Variables:
  1182. * mode: C
  1183. * c-file-style: "stroustrup"
  1184. * indent-tabs-mode: nil
  1185. * End:
  1186. * end of autoopts/configfile.c */