1*4efa1683Schristos /* $NetBSD: configfile.c,v 1.10 2020/05/25 20:54:07 christos Exp $ */
2abb0f93cSkardel
34305584aSkardel /**
44305584aSkardel * \file configfile.c
54305584aSkardel *
6abb0f93cSkardel * configuration/rc/ini file handling.
7abb0f93cSkardel *
8b3d6264cSchristos * @addtogroup autoopts
9b3d6264cSchristos * @{
10b3d6264cSchristos */
11b3d6264cSchristos /*
12abb0f93cSkardel * This file is part of AutoOpts, a companion to AutoGen.
13abb0f93cSkardel * AutoOpts is free software.
144e3b3909Schristos * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
15abb0f93cSkardel *
16abb0f93cSkardel * AutoOpts is available under any one of two licenses. The license
17abb0f93cSkardel * in use must be one of these two and the choice is under the control
18abb0f93cSkardel * of the user of the license.
19abb0f93cSkardel *
20abb0f93cSkardel * The GNU Lesser General Public License, version 3 or later
21abb0f93cSkardel * See the files "COPYING.lgplv3" and "COPYING.gplv3"
22abb0f93cSkardel *
23abb0f93cSkardel * The Modified Berkeley Software Distribution License
24abb0f93cSkardel * See the file "COPYING.mbsd"
25abb0f93cSkardel *
26b3d6264cSchristos * These files have the following sha256 sums:
27abb0f93cSkardel *
28b3d6264cSchristos * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3
29b3d6264cSchristos * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3
30b3d6264cSchristos * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd
31abb0f93cSkardel */
32abb0f93cSkardel
334305584aSkardel /* = = = START-STATIC-FORWARD = = = */
344305584aSkardel static void
354305584aSkardel file_preset(tOptions * opts, char const * fname, int dir);
36abb0f93cSkardel
37abb0f93cSkardel static char *
38b3d6264cSchristos handle_comment(char * txt);
39abb0f93cSkardel
40abb0f93cSkardel static char *
41b3d6264cSchristos handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir);
42abb0f93cSkardel
43abb0f93cSkardel static char *
44b3d6264cSchristos handle_directive(tOptions * opts, char * txt);
45abb0f93cSkardel
46abb0f93cSkardel static char *
47b3d6264cSchristos aoflags_directive(tOptions * opts, char * txt);
48abb0f93cSkardel
49abb0f93cSkardel static char *
50b3d6264cSchristos program_directive(tOptions * opts, char * txt);
51abb0f93cSkardel
52abb0f93cSkardel static char *
53b3d6264cSchristos handle_section(tOptions * opts, char * txt);
544305584aSkardel
554305584aSkardel static int
564305584aSkardel parse_xml_encoding(char ** ppz);
57abb0f93cSkardel
58abb0f93cSkardel static char *
59b3d6264cSchristos trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode);
604305584aSkardel
614305584aSkardel static void
624305584aSkardel cook_xml_text(char * pzData);
63abb0f93cSkardel
64abb0f93cSkardel static char *
65b3d6264cSchristos handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir);
66abb0f93cSkardel
67b3d6264cSchristos static char const *
68b3d6264cSchristos parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ);
694305584aSkardel
70b3d6264cSchristos static char const *
71b3d6264cSchristos parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ);
724305584aSkardel
73b3d6264cSchristos static char const *
74b3d6264cSchristos parse_value(char const * txt, tOptionValue * typ);
75abb0f93cSkardel /* = = = END-STATIC-FORWARD = = = */
76abb0f93cSkardel
77b3d6264cSchristos /**
78b3d6264cSchristos * Skip over some unknown attribute
79b3d6264cSchristos * @param[in] txt start of skpped text
80b3d6264cSchristos * @returns character after skipped text
81b3d6264cSchristos */
82b3d6264cSchristos inline static char const *
skip_unkn(char const * txt)83b3d6264cSchristos skip_unkn(char const * txt)
84b3d6264cSchristos {
85b3d6264cSchristos txt = BRK_END_XML_TOKEN_CHARS(txt);
86b3d6264cSchristos return (*txt == NUL) ? NULL : txt;
87b3d6264cSchristos }
88abb0f93cSkardel
89abb0f93cSkardel /*=export_func configFileLoad
90abb0f93cSkardel *
91abb0f93cSkardel * what: parse a configuration file
92b3d6264cSchristos * arg: + char const * + fname + the file to load +
93abb0f93cSkardel *
94abb0f93cSkardel * ret_type: const tOptionValue *
95abb0f93cSkardel * ret_desc: An allocated, compound value structure
96abb0f93cSkardel *
97abb0f93cSkardel * doc:
98abb0f93cSkardel * This routine will load a named configuration file and parse the
99abb0f93cSkardel * text as a hierarchically valued option. The option descriptor
100abb0f93cSkardel * created from an option definition file is not used via this interface.
101abb0f93cSkardel * The returned value is "named" with the input file name and is of
102abb0f93cSkardel * type "@code{OPARG_TYPE_HIERARCHY}". It may be used in calls to
103abb0f93cSkardel * @code{optionGetValue()}, @code{optionNextValue()} and
104abb0f93cSkardel * @code{optionUnloadNested()}.
105abb0f93cSkardel *
106abb0f93cSkardel * err:
107abb0f93cSkardel * If the file cannot be loaded or processed, @code{NULL} is returned and
108abb0f93cSkardel * @var{errno} is set. It may be set by a call to either @code{open(2)}
109abb0f93cSkardel * @code{mmap(2)} or other file system calls, or it may be:
110abb0f93cSkardel * @itemize @bullet
111abb0f93cSkardel * @item
112b3d6264cSchristos * @code{ENOENT} - the file was not found.
113b3d6264cSchristos * @item
114b3d6264cSchristos * @code{ENOMSG} - the file was empty.
115abb0f93cSkardel * @item
116abb0f93cSkardel * @code{EINVAL} - the file contents are invalid -- not properly formed.
117abb0f93cSkardel * @item
118abb0f93cSkardel * @code{ENOMEM} - not enough memory to allocate the needed structures.
119abb0f93cSkardel * @end itemize
120abb0f93cSkardel =*/
121abb0f93cSkardel const tOptionValue *
configFileLoad(char const * fname)122b3d6264cSchristos configFileLoad(char const * fname)
123abb0f93cSkardel {
124abb0f93cSkardel tmap_info_t cfgfile;
125b3d6264cSchristos tOptionValue * res = NULL;
126abb0f93cSkardel tOptionLoadMode save_mode = option_load_mode;
127abb0f93cSkardel
128b3d6264cSchristos char * txt = text_mmap(fname, PROT_READ, MAP_PRIVATE, &cfgfile);
129abb0f93cSkardel
130b3d6264cSchristos if (TEXT_MMAP_FAILED_ADDR(txt))
131abb0f93cSkardel return NULL; /* errno is set */
132abb0f93cSkardel
133abb0f93cSkardel option_load_mode = OPTION_LOAD_COOKED;
134b3d6264cSchristos res = optionLoadNested(txt, fname, strlen(fname));
135abb0f93cSkardel
136b3d6264cSchristos if (res == NULL) {
137abb0f93cSkardel int err = errno;
138abb0f93cSkardel text_munmap(&cfgfile);
139abb0f93cSkardel errno = err;
140abb0f93cSkardel } else
141abb0f93cSkardel text_munmap(&cfgfile);
142abb0f93cSkardel
143abb0f93cSkardel option_load_mode = save_mode;
144b3d6264cSchristos return res;
145abb0f93cSkardel }
146abb0f93cSkardel
147abb0f93cSkardel
148abb0f93cSkardel /*=export_func optionFindValue
149abb0f93cSkardel *
150abb0f93cSkardel * what: find a hierarcicaly valued option instance
151b3d6264cSchristos * arg: + const tOptDesc * + odesc + an option with a nested arg type +
152abb0f93cSkardel * arg: + char const * + name + name of value to find +
153b3d6264cSchristos * arg: + char const * + val + the matching value +
154abb0f93cSkardel *
155abb0f93cSkardel * ret_type: const tOptionValue *
156abb0f93cSkardel * ret_desc: a compound value structure
157abb0f93cSkardel *
158abb0f93cSkardel * doc:
159abb0f93cSkardel * This routine will find an entry in a nested value option or configurable.
160abb0f93cSkardel * It will search through the list and return a matching entry.
161abb0f93cSkardel *
162abb0f93cSkardel * err:
163abb0f93cSkardel * The returned result is NULL and errno is set:
164abb0f93cSkardel * @itemize @bullet
165abb0f93cSkardel * @item
166abb0f93cSkardel * @code{EINVAL} - the @code{pOptValue} does not point to a valid
167abb0f93cSkardel * hierarchical option value.
168abb0f93cSkardel * @item
169abb0f93cSkardel * @code{ENOENT} - no entry matched the given name.
170abb0f93cSkardel * @end itemize
171abb0f93cSkardel =*/
172abb0f93cSkardel const tOptionValue *
optionFindValue(const tOptDesc * odesc,char const * name,char const * val)173b3d6264cSchristos optionFindValue(const tOptDesc * odesc, char const * name, char const * val)
174abb0f93cSkardel {
175b3d6264cSchristos const tOptionValue * res = NULL;
176abb0f93cSkardel
177b3d6264cSchristos if ( (odesc == NULL)
178b3d6264cSchristos || (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY)) {
179abb0f93cSkardel errno = EINVAL;
180abb0f93cSkardel }
181abb0f93cSkardel
182b3d6264cSchristos else if (odesc->optCookie == NULL) {
183abb0f93cSkardel errno = ENOENT;
184abb0f93cSkardel }
185abb0f93cSkardel
186abb0f93cSkardel else do {
187b3d6264cSchristos tArgList * argl = odesc->optCookie;
188b3d6264cSchristos int argct = argl->useCt;
189335f7552Schristos const void ** poptv = VOIDP(argl->apzArgs);
190abb0f93cSkardel
191b3d6264cSchristos if (argct == 0) {
192abb0f93cSkardel errno = ENOENT;
193abb0f93cSkardel break;
194abb0f93cSkardel }
195abb0f93cSkardel
196b3d6264cSchristos if (name == NULL) {
1974e3b3909Schristos res = (const tOptionValue *)*poptv;
198abb0f93cSkardel break;
199abb0f93cSkardel }
200abb0f93cSkardel
201b3d6264cSchristos while (--argct >= 0) {
202b3d6264cSchristos const tOptionValue * ov = *(poptv++);
203b3d6264cSchristos const tOptionValue * rv = optionGetValue(ov, name);
204abb0f93cSkardel
205b3d6264cSchristos if (rv == NULL)
206abb0f93cSkardel continue;
207abb0f93cSkardel
208b3d6264cSchristos if (val == NULL) {
209b3d6264cSchristos res = ov;
210abb0f93cSkardel break;
211abb0f93cSkardel }
212abb0f93cSkardel }
213b3d6264cSchristos if (res == NULL)
214abb0f93cSkardel errno = ENOENT;
215b3d6264cSchristos } while (false);
216abb0f93cSkardel
217b3d6264cSchristos return res;
218abb0f93cSkardel }
219abb0f93cSkardel
220abb0f93cSkardel
221abb0f93cSkardel /*=export_func optionFindNextValue
222abb0f93cSkardel *
223b3d6264cSchristos * FIXME: the handling of 'pzName' and 'pzVal' is just wrong.
224b3d6264cSchristos *
225abb0f93cSkardel * what: find a hierarcicaly valued option instance
226b3d6264cSchristos * arg: + const tOptDesc * + odesc + an option with a nested arg type +
227abb0f93cSkardel * arg: + const tOptionValue * + pPrevVal + the last entry +
228abb0f93cSkardel * arg: + char const * + name + name of value to find +
229abb0f93cSkardel * arg: + char const * + value + the matching value +
230abb0f93cSkardel *
231abb0f93cSkardel * ret_type: const tOptionValue *
232abb0f93cSkardel * ret_desc: a compound value structure
233abb0f93cSkardel *
234abb0f93cSkardel * doc:
235abb0f93cSkardel * This routine will find the next entry in a nested value option or
236abb0f93cSkardel * configurable. It will search through the list and return the next entry
237abb0f93cSkardel * that matches the criteria.
238abb0f93cSkardel *
239abb0f93cSkardel * err:
240abb0f93cSkardel * The returned result is NULL and errno is set:
241abb0f93cSkardel * @itemize @bullet
242abb0f93cSkardel * @item
243abb0f93cSkardel * @code{EINVAL} - the @code{pOptValue} does not point to a valid
244abb0f93cSkardel * hierarchical option value.
245abb0f93cSkardel * @item
246abb0f93cSkardel * @code{ENOENT} - no entry matched the given name.
247abb0f93cSkardel * @end itemize
248abb0f93cSkardel =*/
2494305584aSkardel tOptionValue const *
optionFindNextValue(const tOptDesc * odesc,const tOptionValue * pPrevVal,char const * pzName,char const * pzVal)250b3d6264cSchristos optionFindNextValue(const tOptDesc * odesc, const tOptionValue * pPrevVal,
251abb0f93cSkardel char const * pzName, char const * pzVal)
252abb0f93cSkardel {
253b3d6264cSchristos bool old_found = false;
2544e3b3909Schristos const tOptionValue * res = NULL;
255abb0f93cSkardel
256b3d6264cSchristos (void)pzName;
257b3d6264cSchristos (void)pzVal;
258b3d6264cSchristos
259b3d6264cSchristos if ( (odesc == NULL)
260b3d6264cSchristos || (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY)) {
261abb0f93cSkardel errno = EINVAL;
262abb0f93cSkardel }
263abb0f93cSkardel
264b3d6264cSchristos else if (odesc->optCookie == NULL) {
265abb0f93cSkardel errno = ENOENT;
266abb0f93cSkardel }
267abb0f93cSkardel
268abb0f93cSkardel else do {
269b3d6264cSchristos tArgList * argl = odesc->optCookie;
270b3d6264cSchristos int ct = argl->useCt;
271335f7552Schristos const void ** poptv = VOIDP(argl->apzArgs);
272abb0f93cSkardel
273abb0f93cSkardel while (--ct >= 0) {
2744e3b3909Schristos const tOptionValue * pOV = *(poptv++);
275b3d6264cSchristos if (old_found) {
276b3d6264cSchristos res = pOV;
277abb0f93cSkardel break;
278abb0f93cSkardel }
279abb0f93cSkardel if (pOV == pPrevVal)
280b3d6264cSchristos old_found = true;
281abb0f93cSkardel }
282b3d6264cSchristos if (res == NULL)
283abb0f93cSkardel errno = ENOENT;
284b3d6264cSchristos } while (false);
285abb0f93cSkardel
286b3d6264cSchristos return res;
287abb0f93cSkardel }
288abb0f93cSkardel
289abb0f93cSkardel
290abb0f93cSkardel /*=export_func optionGetValue
291abb0f93cSkardel *
292abb0f93cSkardel * what: get a specific value from a hierarcical list
293abb0f93cSkardel * arg: + const tOptionValue * + pOptValue + a hierarchcal value +
294abb0f93cSkardel * arg: + char const * + valueName + name of value to get +
295abb0f93cSkardel *
296abb0f93cSkardel * ret_type: const tOptionValue *
297abb0f93cSkardel * ret_desc: a compound value structure
298abb0f93cSkardel *
299abb0f93cSkardel * doc:
300abb0f93cSkardel * This routine will find an entry in a nested value option or configurable.
301abb0f93cSkardel * If "valueName" is NULL, then the first entry is returned. Otherwise,
302abb0f93cSkardel * the first entry with a name that exactly matches the argument will be
303b3d6264cSchristos * returned. If there is no matching value, NULL is returned and errno is
304b3d6264cSchristos * set to ENOENT. If the provided option value is not a hierarchical value,
305b3d6264cSchristos * NULL is also returned and errno is set to EINVAL.
306abb0f93cSkardel *
307abb0f93cSkardel * err:
308abb0f93cSkardel * The returned result is NULL and errno is set:
309abb0f93cSkardel * @itemize @bullet
310abb0f93cSkardel * @item
311abb0f93cSkardel * @code{EINVAL} - the @code{pOptValue} does not point to a valid
312abb0f93cSkardel * hierarchical option value.
313abb0f93cSkardel * @item
314abb0f93cSkardel * @code{ENOENT} - no entry matched the given name.
315abb0f93cSkardel * @end itemize
316abb0f93cSkardel =*/
317b3d6264cSchristos tOptionValue const *
optionGetValue(tOptionValue const * oov,char const * vname)318b3d6264cSchristos optionGetValue(tOptionValue const * oov, char const * vname)
319abb0f93cSkardel {
320b3d6264cSchristos tArgList * arg_list;
3214e3b3909Schristos const tOptionValue * res = NULL;
322abb0f93cSkardel
323b3d6264cSchristos if ((oov == NULL) || (oov->valType != OPARG_TYPE_HIERARCHY)) {
324abb0f93cSkardel errno = EINVAL;
325b3d6264cSchristos return res;
326abb0f93cSkardel }
327b3d6264cSchristos arg_list = oov->v.nestVal;
328abb0f93cSkardel
329b3d6264cSchristos if (arg_list->useCt > 0) {
330b3d6264cSchristos int ct = arg_list->useCt;
331335f7552Schristos const void ** ovlist = VOIDP(arg_list->apzArgs);
332abb0f93cSkardel
333b3d6264cSchristos if (vname == NULL) {
3344e3b3909Schristos res = (const tOptionValue *)*ovlist;
335abb0f93cSkardel
336b3d6264cSchristos } else do {
3374e3b3909Schristos const tOptionValue * opt_val = *(ovlist++);
338b3d6264cSchristos if (strcmp(opt_val->pzName, vname) == 0) {
339b3d6264cSchristos res = opt_val;
340abb0f93cSkardel break;
341abb0f93cSkardel }
342abb0f93cSkardel } while (--ct > 0);
343abb0f93cSkardel }
344b3d6264cSchristos if (res == NULL)
345abb0f93cSkardel errno = ENOENT;
346b3d6264cSchristos return res;
347abb0f93cSkardel }
348abb0f93cSkardel
349abb0f93cSkardel /*=export_func optionNextValue
350abb0f93cSkardel *
351abb0f93cSkardel * what: get the next value from a hierarchical list
352abb0f93cSkardel * arg: + const tOptionValue * + pOptValue + a hierarchcal list value +
353abb0f93cSkardel * arg: + const tOptionValue * + pOldValue + a value from this list +
354abb0f93cSkardel *
355abb0f93cSkardel * ret_type: const tOptionValue *
356abb0f93cSkardel * ret_desc: a compound value structure
357abb0f93cSkardel *
358abb0f93cSkardel * doc:
359abb0f93cSkardel * This routine will return the next entry after the entry passed in. At the
360abb0f93cSkardel * end of the list, NULL will be returned. If the entry is not found on the
361abb0f93cSkardel * list, NULL will be returned and "@var{errno}" will be set to EINVAL.
362abb0f93cSkardel * The "@var{pOldValue}" must have been gotten from a prior call to this
363abb0f93cSkardel * routine or to "@code{opitonGetValue()}".
364abb0f93cSkardel *
365abb0f93cSkardel * err:
366abb0f93cSkardel * The returned result is NULL and errno is set:
367abb0f93cSkardel * @itemize @bullet
368abb0f93cSkardel * @item
369abb0f93cSkardel * @code{EINVAL} - the @code{pOptValue} does not point to a valid
370abb0f93cSkardel * hierarchical option value or @code{pOldValue} does not point to a
371abb0f93cSkardel * member of that option value.
372abb0f93cSkardel * @item
373abb0f93cSkardel * @code{ENOENT} - the supplied @code{pOldValue} pointed to the last entry.
374abb0f93cSkardel * @end itemize
375abb0f93cSkardel =*/
376abb0f93cSkardel tOptionValue const *
optionNextValue(tOptionValue const * ov_list,tOptionValue const * oov)377b3d6264cSchristos optionNextValue(tOptionValue const * ov_list,tOptionValue const * oov )
378abb0f93cSkardel {
379b3d6264cSchristos tArgList * arg_list;
3804e3b3909Schristos const tOptionValue * res = NULL;
381abb0f93cSkardel int err = EINVAL;
382abb0f93cSkardel
383b3d6264cSchristos if ((ov_list == NULL) || (ov_list->valType != OPARG_TYPE_HIERARCHY)) {
384abb0f93cSkardel errno = EINVAL;
385abb0f93cSkardel return NULL;
386abb0f93cSkardel }
387b3d6264cSchristos arg_list = ov_list->v.nestVal;
388abb0f93cSkardel {
389b3d6264cSchristos int ct = arg_list->useCt;
390335f7552Schristos const void ** o_list = VOIDP(arg_list->apzArgs);
391abb0f93cSkardel
392abb0f93cSkardel while (ct-- > 0) {
3934e3b3909Schristos const tOptionValue * nov = *(o_list++);
394b3d6264cSchristos if (nov == oov) {
395abb0f93cSkardel if (ct == 0) {
396abb0f93cSkardel err = ENOENT;
397abb0f93cSkardel
398abb0f93cSkardel } else {
399abb0f93cSkardel err = 0;
4004e3b3909Schristos res = (const tOptionValue *)*o_list;
401abb0f93cSkardel }
402abb0f93cSkardel break;
403abb0f93cSkardel }
404abb0f93cSkardel }
405abb0f93cSkardel }
406abb0f93cSkardel if (err != 0)
407abb0f93cSkardel errno = err;
408b3d6264cSchristos return res;
409abb0f93cSkardel }
410abb0f93cSkardel
4114305584aSkardel /**
412abb0f93cSkardel * Load a file containing presetting information (a configuration file).
413abb0f93cSkardel */
414abb0f93cSkardel static void
file_preset(tOptions * opts,char const * fname,int dir)4154305584aSkardel file_preset(tOptions * opts, char const * fname, int dir)
416abb0f93cSkardel {
417abb0f93cSkardel tmap_info_t cfgfile;
418abb0f93cSkardel tOptState optst = OPTSTATE_INITIALIZER(PRESET);
419b3d6264cSchristos opt_state_mask_t st_flags = optst.flags;
420b3d6264cSchristos opt_state_mask_t fl_save = opts->fOptSet;
4214305584aSkardel char * ftext =
4224305584aSkardel text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &cfgfile);
423abb0f93cSkardel
4244305584aSkardel if (TEXT_MMAP_FAILED_ADDR(ftext))
425abb0f93cSkardel return;
426abb0f93cSkardel
427b3d6264cSchristos /*
428b3d6264cSchristos * While processing config files, we ignore errors.
429b3d6264cSchristos */
430b3d6264cSchristos opts->fOptSet &= ~OPTPROC_ERRSTOP;
431b3d6264cSchristos
4324305584aSkardel if (dir == DIRECTION_CALLED) {
4334305584aSkardel st_flags = OPTST_DEFINED;
4344305584aSkardel dir = DIRECTION_PROCESS;
435abb0f93cSkardel }
436abb0f93cSkardel
437abb0f93cSkardel /*
438abb0f93cSkardel * IF this is called via "optionProcess", then we are presetting.
439abb0f93cSkardel * This is the default and the PRESETTING bit will be set.
440abb0f93cSkardel * If this is called via "optionFileLoad", then the bit is not set
441abb0f93cSkardel * and we consider stuff set herein to be "set" by the client program.
442abb0f93cSkardel */
4434305584aSkardel if ((opts->fOptSet & OPTPROC_PRESETTING) == 0)
4444305584aSkardel st_flags = OPTST_SET;
445abb0f93cSkardel
446abb0f93cSkardel do {
4474305584aSkardel optst.flags = st_flags;
448b3d6264cSchristos ftext = SPN_WHITESPACE_CHARS(ftext);
449abb0f93cSkardel
4504305584aSkardel if (IS_VAR_FIRST_CHAR(*ftext)) {
4514305584aSkardel ftext = handle_cfg(opts, &optst, ftext, dir);
452abb0f93cSkardel
4534305584aSkardel } else switch (*ftext) {
454abb0f93cSkardel case '<':
4554305584aSkardel if (IS_VAR_FIRST_CHAR(ftext[1]))
4564305584aSkardel ftext = handle_struct(opts, &optst, ftext, dir);
457abb0f93cSkardel
4584305584aSkardel else switch (ftext[1]) {
459abb0f93cSkardel case '?':
4604305584aSkardel ftext = handle_directive(opts, ftext);
461abb0f93cSkardel break;
462abb0f93cSkardel
463abb0f93cSkardel case '!':
4644305584aSkardel ftext = handle_comment(ftext);
465abb0f93cSkardel break;
466abb0f93cSkardel
467abb0f93cSkardel case '/':
4684305584aSkardel ftext = strchr(ftext + 2, '>');
4694305584aSkardel if (ftext++ != NULL)
470abb0f93cSkardel break;
471*4efa1683Schristos /*FALLTHROUGH*/
472abb0f93cSkardel default:
473b3d6264cSchristos ftext = NULL;
474abb0f93cSkardel }
475b3d6264cSchristos if (ftext == NULL)
476b3d6264cSchristos goto all_done;
477abb0f93cSkardel break;
478abb0f93cSkardel
479abb0f93cSkardel case '[':
4804305584aSkardel ftext = handle_section(opts, ftext);
481abb0f93cSkardel break;
482abb0f93cSkardel
483abb0f93cSkardel case '#':
484b3d6264cSchristos ftext = strchr(ftext + 1, NL);
485abb0f93cSkardel break;
486abb0f93cSkardel
487abb0f93cSkardel default:
488abb0f93cSkardel goto all_done; /* invalid format */
489abb0f93cSkardel }
4904305584aSkardel } while (ftext != NULL);
491abb0f93cSkardel
492abb0f93cSkardel all_done:
493abb0f93cSkardel text_munmap(&cfgfile);
494b3d6264cSchristos opts->fOptSet = fl_save;
495abb0f93cSkardel }
496abb0f93cSkardel
4974305584aSkardel /**
498b3d6264cSchristos * "txt" points to a "<!" sequence.
499abb0f93cSkardel * Theoretically, we should ensure that it begins with "<!--",
500abb0f93cSkardel * but actually I don't care that much. It ends with "-->".
501abb0f93cSkardel */
502abb0f93cSkardel static char *
handle_comment(char * txt)503b3d6264cSchristos handle_comment(char * txt)
504abb0f93cSkardel {
505b3d6264cSchristos char * pz = strstr(txt, "-->");
506abb0f93cSkardel if (pz != NULL)
507abb0f93cSkardel pz += 3;
508abb0f93cSkardel return pz;
509abb0f93cSkardel }
510abb0f93cSkardel
5114305584aSkardel /**
512b3d6264cSchristos * "txt" points to the start of some value name.
513abb0f93cSkardel * The end of the entry is the end of the line that is not preceded by
514abb0f93cSkardel * a backslash escape character. The string value is always processed
515abb0f93cSkardel * in "cooked" mode.
516abb0f93cSkardel */
517abb0f93cSkardel static char *
handle_cfg(tOptions * opts,tOptState * ost,char * txt,int dir)518b3d6264cSchristos handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir)
519abb0f93cSkardel {
520b3d6264cSchristos char * pzName = txt++;
521b3d6264cSchristos char * pzEnd = strchr(txt, NL);
522abb0f93cSkardel
523abb0f93cSkardel if (pzEnd == NULL)
524b3d6264cSchristos return txt + strlen(txt);
525abb0f93cSkardel
526b3d6264cSchristos txt = SPN_VALUE_NAME_CHARS(txt);
527b3d6264cSchristos txt = SPN_WHITESPACE_CHARS(txt);
528b3d6264cSchristos if (txt > pzEnd) {
529abb0f93cSkardel name_only:
530abb0f93cSkardel *pzEnd++ = NUL;
531b3d6264cSchristos load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
532abb0f93cSkardel return pzEnd;
533abb0f93cSkardel }
534abb0f93cSkardel
535abb0f93cSkardel /*
536abb0f93cSkardel * Either the first character after the name is a ':' or '=',
537abb0f93cSkardel * or else we must have skipped over white space. Anything else
538abb0f93cSkardel * is an invalid format and we give up parsing the text.
539abb0f93cSkardel */
540b3d6264cSchristos if ((*txt == '=') || (*txt == ':')) {
541b3d6264cSchristos txt = SPN_WHITESPACE_CHARS(txt+1);
542b3d6264cSchristos if (txt > pzEnd)
543abb0f93cSkardel goto name_only;
544b3d6264cSchristos } else if (! IS_WHITESPACE_CHAR(txt[-1]))
545abb0f93cSkardel return NULL;
546abb0f93cSkardel
547abb0f93cSkardel /*
548abb0f93cSkardel * IF the value is continued, remove the backslash escape and push "pzEnd"
549abb0f93cSkardel * on to a newline *not* preceded by a backslash.
550abb0f93cSkardel */
551abb0f93cSkardel if (pzEnd[-1] == '\\') {
552abb0f93cSkardel char * pcD = pzEnd-1;
553abb0f93cSkardel char * pcS = pzEnd;
554abb0f93cSkardel
555abb0f93cSkardel for (;;) {
556abb0f93cSkardel char ch = *(pcS++);
557abb0f93cSkardel switch (ch) {
558abb0f93cSkardel case NUL:
559abb0f93cSkardel pcS = NULL;
560b3d6264cSchristos /* FALLTHROUGH */
561abb0f93cSkardel
562b3d6264cSchristos case NL:
563abb0f93cSkardel *pcD = NUL;
564abb0f93cSkardel pzEnd = pcS;
565abb0f93cSkardel goto copy_done;
566abb0f93cSkardel
567abb0f93cSkardel case '\\':
568b3d6264cSchristos if (*pcS == NL)
569abb0f93cSkardel ch = *(pcS++);
570abb0f93cSkardel /* FALLTHROUGH */
571abb0f93cSkardel default:
572abb0f93cSkardel *(pcD++) = ch;
573abb0f93cSkardel }
574abb0f93cSkardel } copy_done:;
575abb0f93cSkardel
576abb0f93cSkardel } else {
577abb0f93cSkardel /*
578abb0f93cSkardel * The newline was not preceded by a backslash. NUL it out
579abb0f93cSkardel */
580abb0f93cSkardel *(pzEnd++) = NUL;
581abb0f93cSkardel }
582abb0f93cSkardel
583abb0f93cSkardel /*
584abb0f93cSkardel * "pzName" points to what looks like text for one option/configurable.
585abb0f93cSkardel * It is NUL terminated. Process it.
586abb0f93cSkardel */
587b3d6264cSchristos load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
588abb0f93cSkardel
589abb0f93cSkardel return pzEnd;
590abb0f93cSkardel }
591abb0f93cSkardel
5924305584aSkardel /**
593b3d6264cSchristos * "txt" points to a "<?" sequence.
5944305584aSkardel * We handle "<?program" and "<?auto-options" directives.
5954305584aSkardel * All others are treated as comments.
596b3d6264cSchristos *
597b3d6264cSchristos * @param[in,out] opts program option descriptor
598b3d6264cSchristos * @param[in] txt scanning pointer
599b3d6264cSchristos * @returns the next character to look at
600abb0f93cSkardel */
601abb0f93cSkardel static char *
handle_directive(tOptions * opts,char * txt)602b3d6264cSchristos handle_directive(tOptions * opts, char * txt)
603abb0f93cSkardel {
6044305584aSkardel # define DIRECTIVE_TABLE \
6054305584aSkardel _dt_(zCfgProg, program_directive) \
6064305584aSkardel _dt_(zCfgAO_Flags, aoflags_directive)
607abb0f93cSkardel
6084305584aSkardel typedef char * (directive_func_t)(tOptions *, char *);
6094305584aSkardel # define _dt_(_s, _fn) _fn,
6104305584aSkardel static directive_func_t * dir_disp[] = {
6114305584aSkardel DIRECTIVE_TABLE
6124305584aSkardel };
6134305584aSkardel # undef _dt_
6144305584aSkardel
6154305584aSkardel # define _dt_(_s, _fn) 1 +
6164305584aSkardel static int const dir_ct = DIRECTIVE_TABLE 0;
6174305584aSkardel static char const * dir_names[DIRECTIVE_TABLE 0];
6184305584aSkardel # undef _dt_
6194305584aSkardel
6204305584aSkardel int ix;
6214305584aSkardel
6224305584aSkardel if (dir_names[0] == NULL) {
6234305584aSkardel ix = 0;
6244305584aSkardel # define _dt_(_s, _fn) dir_names[ix++] = _s;
6254305584aSkardel DIRECTIVE_TABLE;
6264305584aSkardel # undef _dt_
6274305584aSkardel }
6284305584aSkardel
6294305584aSkardel for (ix = 0; ix < dir_ct; ix++) {
6304305584aSkardel size_t len = strlen(dir_names[ix]);
631b3d6264cSchristos if ( (strncmp(txt + 2, dir_names[ix], len) == 0)
632b3d6264cSchristos && (! IS_VALUE_NAME_CHAR(txt[len+2])) )
633b3d6264cSchristos return dir_disp[ix](opts, txt + len + 2);
6344305584aSkardel }
6354305584aSkardel
6364305584aSkardel /*
6374305584aSkardel * We don't know what this is. Skip it.
6384305584aSkardel */
639b3d6264cSchristos txt = strchr(txt+2, '>');
640b3d6264cSchristos if (txt != NULL)
641b3d6264cSchristos txt++;
642b3d6264cSchristos return txt;
643b3d6264cSchristos # undef DIRECTIVE_TABLE
644abb0f93cSkardel }
645abb0f93cSkardel
6464305584aSkardel /**
647b3d6264cSchristos * handle AutoOpts mode flags.
648b3d6264cSchristos *
649b3d6264cSchristos * @param[in,out] opts program option descriptor
650b3d6264cSchristos * @param[in] txt scanning pointer
651b3d6264cSchristos * @returns the next character to look at
6524305584aSkardel */
6534305584aSkardel static char *
aoflags_directive(tOptions * opts,char * txt)654b3d6264cSchristos aoflags_directive(tOptions * opts, char * txt)
6554305584aSkardel {
656b3d6264cSchristos char * pz;
6574305584aSkardel
658b3d6264cSchristos pz = SPN_WHITESPACE_CHARS(txt+1);
659b3d6264cSchristos txt = strchr(pz, '>');
660b3d6264cSchristos if (txt != NULL) {
6614305584aSkardel
662b3d6264cSchristos size_t len = (unsigned)(txt - pz);
6634305584aSkardel char * ftxt = AGALOC(len + 1, "aoflags");
6644305584aSkardel
6654305584aSkardel memcpy(ftxt, pz, len);
6664305584aSkardel ftxt[len] = NUL;
667b3d6264cSchristos set_usage_flags(opts, ftxt);
6684305584aSkardel AGFREE(ftxt);
6694305584aSkardel
670b3d6264cSchristos txt++;
6714305584aSkardel }
6724305584aSkardel
673b3d6264cSchristos return txt;
6744305584aSkardel }
6754305584aSkardel
6764305584aSkardel /**
6774305584aSkardel * handle program segmentation of config file.
678b3d6264cSchristos *
679b3d6264cSchristos * @param[in,out] opts program option descriptor
680b3d6264cSchristos * @param[in] txt scanning pointer
681b3d6264cSchristos * @returns the next character to look at
6824305584aSkardel */
6834305584aSkardel static char *
program_directive(tOptions * opts,char * txt)684b3d6264cSchristos program_directive(tOptions * opts, char * txt)
6854305584aSkardel {
6864305584aSkardel static char const ttlfmt[] = "<?";
6874305584aSkardel size_t ttl_len = sizeof(ttlfmt) + strlen(zCfgProg);
6884305584aSkardel char * ttl = AGALOC(ttl_len, "prog title");
689b3d6264cSchristos size_t name_len = strlen(opts->pzProgName);
6904305584aSkardel
6914305584aSkardel memcpy(ttl, ttlfmt, sizeof(ttlfmt) - 1);
6924305584aSkardel memcpy(ttl + sizeof(ttlfmt) - 1, zCfgProg, ttl_len - (sizeof(ttlfmt) - 1));
693abb0f93cSkardel
694abb0f93cSkardel do {
695b3d6264cSchristos txt = SPN_WHITESPACE_CHARS(txt+1);
6964305584aSkardel
697b3d6264cSchristos if ( (strneqvcmp(txt, opts->pzProgName, (int)name_len) == 0)
698b3d6264cSchristos && (IS_END_XML_TOKEN_CHAR(txt[name_len])) ) {
699b3d6264cSchristos txt += name_len;
700abb0f93cSkardel break;
701abb0f93cSkardel }
702abb0f93cSkardel
703b3d6264cSchristos txt = strstr(txt, ttl);
704b3d6264cSchristos } while (txt != NULL);
705abb0f93cSkardel
7064305584aSkardel AGFREE(ttl);
707b3d6264cSchristos if (txt != NULL)
7084305584aSkardel for (;;) {
709b3d6264cSchristos if (*txt == NUL) {
710b3d6264cSchristos txt = NULL;
7114305584aSkardel break;
7124305584aSkardel }
713b3d6264cSchristos if (*(txt++) == '>')
7144305584aSkardel break;
7154305584aSkardel }
7164305584aSkardel
717b3d6264cSchristos return txt;
718abb0f93cSkardel }
719abb0f93cSkardel
7204305584aSkardel /**
721b3d6264cSchristos * "txt" points to a '[' character.
722abb0f93cSkardel * The "traditional" [PROG_NAME] segmentation of the config file.
723abb0f93cSkardel * Do not ever mix with the "<?program prog-name>" variation.
724b3d6264cSchristos *
725b3d6264cSchristos * @param[in,out] opts program option descriptor
726b3d6264cSchristos * @param[in] txt scanning pointer
727b3d6264cSchristos * @returns the next character to look at
728abb0f93cSkardel */
729abb0f93cSkardel static char *
handle_section(tOptions * opts,char * txt)730b3d6264cSchristos handle_section(tOptions * opts, char * txt)
731abb0f93cSkardel {
732b3d6264cSchristos size_t len = strlen(opts->pzPROGNAME);
733b3d6264cSchristos if ( (strncmp(txt+1, opts->pzPROGNAME, len) == 0)
734b3d6264cSchristos && (txt[len+1] == ']'))
735b3d6264cSchristos return strchr(txt + len + 2, NL);
736abb0f93cSkardel
737abb0f93cSkardel if (len > 16)
738abb0f93cSkardel return NULL;
739abb0f93cSkardel
740abb0f93cSkardel {
741abb0f93cSkardel char z[24];
742b3d6264cSchristos sprintf(z, "[%s]", opts->pzPROGNAME);
743b3d6264cSchristos txt = strstr(txt, z);
744abb0f93cSkardel }
745abb0f93cSkardel
746b3d6264cSchristos if (txt != NULL)
747b3d6264cSchristos txt = strchr(txt, NL);
748b3d6264cSchristos return txt;
749abb0f93cSkardel }
750abb0f93cSkardel
7514305584aSkardel /**
7524305584aSkardel * parse XML encodings
7534305584aSkardel */
7544305584aSkardel static int
parse_xml_encoding(char ** ppz)7554305584aSkardel parse_xml_encoding(char ** ppz)
7564305584aSkardel {
7574305584aSkardel # define XMLTABLE \
7584305584aSkardel _xmlNm_(amp, '&') \
7594305584aSkardel _xmlNm_(lt, '<') \
7604305584aSkardel _xmlNm_(gt, '>') \
7614305584aSkardel _xmlNm_(ff, '\f') \
7624305584aSkardel _xmlNm_(ht, '\t') \
7634305584aSkardel _xmlNm_(cr, '\r') \
7644305584aSkardel _xmlNm_(vt, '\v') \
7654305584aSkardel _xmlNm_(bel, '\a') \
766b3d6264cSchristos _xmlNm_(nl, NL) \
7674305584aSkardel _xmlNm_(space, ' ') \
7684305584aSkardel _xmlNm_(quot, '"') \
7694305584aSkardel _xmlNm_(apos, '\'')
770abb0f93cSkardel
7714305584aSkardel static struct {
7724305584aSkardel char const * const nm_str;
7734305584aSkardel unsigned short nm_len;
7744305584aSkardel short nm_val;
7754305584aSkardel } const xml_names[] = {
7764305584aSkardel # define _xmlNm_(_n, _v) { #_n ";", sizeof(#_n), _v },
7774305584aSkardel XMLTABLE
7784305584aSkardel # undef _xmlNm_
7794305584aSkardel # undef XMLTABLE
7804305584aSkardel };
7814305584aSkardel
7824305584aSkardel static int const nm_ct = sizeof(xml_names) / sizeof(xml_names[0]);
7834305584aSkardel int base = 10;
7844305584aSkardel
7854305584aSkardel char * pz = *ppz;
7864305584aSkardel
7874305584aSkardel if (*pz == '#') {
7884305584aSkardel pz++;
7894305584aSkardel goto parse_number;
7904305584aSkardel }
7914305584aSkardel
7924305584aSkardel if (IS_DEC_DIGIT_CHAR(*pz)) {
7934305584aSkardel unsigned long v;
7944305584aSkardel
7954305584aSkardel parse_number:
7964305584aSkardel switch (*pz) {
7974305584aSkardel case 'x': case 'X':
7984305584aSkardel /*
7994305584aSkardel * Some forms specify hex with: &#xNN;
8004305584aSkardel */
8014305584aSkardel base = 16;
8024305584aSkardel pz++;
8034305584aSkardel break;
8044305584aSkardel
8054305584aSkardel case '0':
8064305584aSkardel /*
8074305584aSkardel *  is hex and  is decimal. Cool.
8084305584aSkardel * Ya gotta love it.
8094305584aSkardel */
8104305584aSkardel if (pz[1] == '0')
8114305584aSkardel base = 16;
8124305584aSkardel break;
8134305584aSkardel }
8144305584aSkardel
8154305584aSkardel v = strtoul(pz, &pz, base);
8164305584aSkardel if ((*pz != ';') || (v > 0x7F))
8174305584aSkardel return NUL;
8184305584aSkardel *ppz = pz + 1;
8194305584aSkardel return (int)v;
8204305584aSkardel }
8214305584aSkardel
8224305584aSkardel {
8234305584aSkardel int ix = 0;
8244305584aSkardel do {
8254305584aSkardel if (strncmp(pz, xml_names[ix].nm_str, xml_names[ix].nm_len)
8264305584aSkardel == 0) {
8274305584aSkardel *ppz = pz + xml_names[ix].nm_len;
8284305584aSkardel return xml_names[ix].nm_val;
8294305584aSkardel }
8304305584aSkardel } while (++ix < nm_ct);
8314305584aSkardel }
8324305584aSkardel
8334305584aSkardel return NUL;
8344305584aSkardel }
8354305584aSkardel
8364305584aSkardel /**
8374305584aSkardel * Find the end marker for the named section of XML.
8384305584aSkardel * Trim that text there, trimming trailing white space for all modes
8394305584aSkardel * except for OPTION_LOAD_UNCOOKED.
8404305584aSkardel */
8414305584aSkardel static char *
trim_xml_text(char * intxt,char const * pznm,tOptionLoadMode mode)842b3d6264cSchristos trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode)
8434305584aSkardel {
8444305584aSkardel static char const fmt[] = "</%s>";
8454305584aSkardel size_t len = strlen(pznm) + sizeof(fmt) - 2 /* for %s */;
846b3d6264cSchristos char * etext;
8474305584aSkardel
848b3d6264cSchristos {
849b3d6264cSchristos char z[64], *pz = z;
850b3d6264cSchristos if (len >= sizeof(z))
8514305584aSkardel pz = AGALOC(len, "scan name");
8524305584aSkardel
853b3d6264cSchristos len = (size_t)sprintf(pz, fmt, pznm);
854b3d6264cSchristos *intxt = ' ';
855b3d6264cSchristos etext = strstr(intxt, pz);
8564305584aSkardel if (pz != z) AGFREE(pz);
857b3d6264cSchristos }
8584305584aSkardel
859b3d6264cSchristos if (etext == NULL)
860b3d6264cSchristos return etext;
861b3d6264cSchristos
862b3d6264cSchristos {
863b3d6264cSchristos char * result = etext + len;
8644305584aSkardel
8654305584aSkardel if (mode != OPTION_LOAD_UNCOOKED)
866b3d6264cSchristos etext = SPN_WHITESPACE_BACK(intxt, etext);
8674305584aSkardel
868b3d6264cSchristos *etext = NUL;
869b3d6264cSchristos return result;
870b3d6264cSchristos }
8714305584aSkardel }
8724305584aSkardel
8734305584aSkardel /**
8744305584aSkardel */
8754305584aSkardel static void
cook_xml_text(char * pzData)8764305584aSkardel cook_xml_text(char * pzData)
8774305584aSkardel {
8784305584aSkardel char * pzs = pzData;
8794305584aSkardel char * pzd = pzData;
8804305584aSkardel char bf[4];
8814305584aSkardel bf[2] = NUL;
8824305584aSkardel
8834305584aSkardel for (;;) {
8844305584aSkardel int ch = ((int)*(pzs++)) & 0xFF;
8854305584aSkardel switch (ch) {
8864305584aSkardel case NUL:
8874305584aSkardel *pzd = NUL;
8884305584aSkardel return;
8894305584aSkardel
8904305584aSkardel case '&':
8914305584aSkardel ch = parse_xml_encoding(&pzs);
892b3d6264cSchristos *(pzd++) = (char)ch;
8934305584aSkardel if (ch == NUL)
8944305584aSkardel return;
8954305584aSkardel break;
8964305584aSkardel
8974305584aSkardel case '%':
8984305584aSkardel bf[0] = *(pzs++);
8994305584aSkardel bf[1] = *(pzs++);
9004305584aSkardel if ((bf[0] == NUL) || (bf[1] == NUL)) {
9014305584aSkardel *pzd = NUL;
9024305584aSkardel return;
9034305584aSkardel }
9044305584aSkardel
905b3d6264cSchristos ch = (int)strtoul(bf, NULL, 16);
9064305584aSkardel /* FALLTHROUGH */
9074305584aSkardel
9084305584aSkardel default:
909b3d6264cSchristos *(pzd++) = (char)ch;
9104305584aSkardel }
9114305584aSkardel }
9124305584aSkardel }
9134305584aSkardel
9144305584aSkardel /**
915b3d6264cSchristos * "txt" points to a '<' character, followed by an alpha.
916abb0f93cSkardel * The end of the entry is either the "/>" following the name, or else a
917abb0f93cSkardel * "</name>" string.
918abb0f93cSkardel */
919abb0f93cSkardel static char *
handle_struct(tOptions * opts,tOptState * ost,char * txt,int dir)920b3d6264cSchristos handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir)
921abb0f93cSkardel {
922abb0f93cSkardel tOptionLoadMode mode = option_load_mode;
923abb0f93cSkardel tOptionValue valu;
924abb0f93cSkardel
925b3d6264cSchristos char * pzName = ++txt;
926abb0f93cSkardel char * pzData;
927abb0f93cSkardel char * pcNulPoint;
928abb0f93cSkardel
929b3d6264cSchristos txt = SPN_VALUE_NAME_CHARS(txt);
930b3d6264cSchristos pcNulPoint = txt;
931abb0f93cSkardel valu.valType = OPARG_TYPE_STRING;
932abb0f93cSkardel
933b3d6264cSchristos switch (*txt) {
934abb0f93cSkardel case ' ':
935abb0f93cSkardel case '\t':
9364e3b3909Schristos txt = VOIDP(parse_attrs(
9374e3b3909Schristos opts, SPN_WHITESPACE_CHARS(txt), &mode, &valu));
938b3d6264cSchristos if (txt == NULL)
939b3d6264cSchristos return txt;
940b3d6264cSchristos if (*txt == '>')
941abb0f93cSkardel break;
942b3d6264cSchristos if (*txt != '/')
943abb0f93cSkardel return NULL;
944abb0f93cSkardel /* FALLTHROUGH */
945abb0f93cSkardel
946abb0f93cSkardel case '/':
947b3d6264cSchristos if (txt[1] != '>')
948abb0f93cSkardel return NULL;
949b3d6264cSchristos *txt = NUL;
950b3d6264cSchristos txt += 2;
951b3d6264cSchristos load_opt_line(opts, ost, pzName, dir, mode);
952b3d6264cSchristos return txt;
953abb0f93cSkardel
954abb0f93cSkardel case '>':
955abb0f93cSkardel break;
956abb0f93cSkardel
957abb0f93cSkardel default:
958b3d6264cSchristos txt = strchr(txt, '>');
959b3d6264cSchristos if (txt != NULL)
960b3d6264cSchristos txt++;
961b3d6264cSchristos return txt;
962abb0f93cSkardel }
963abb0f93cSkardel
964abb0f93cSkardel /*
965b3d6264cSchristos * If we are here, we have a value. "txt" points to a closing angle
966abb0f93cSkardel * bracket. Separate the name from the value for a moment.
967abb0f93cSkardel */
968abb0f93cSkardel *pcNulPoint = NUL;
969b3d6264cSchristos pzData = ++txt;
970b3d6264cSchristos txt = trim_xml_text(txt, pzName, mode);
971b3d6264cSchristos if (txt == NULL)
972b3d6264cSchristos return txt;
973abb0f93cSkardel
974abb0f93cSkardel /*
975b3d6264cSchristos * Rejoin the name and value for parsing by "load_opt_line()".
976b3d6264cSchristos * Erase any attributes parsed by "parse_attrs()".
977abb0f93cSkardel */
978b3d6264cSchristos memset(pcNulPoint, ' ', (size_t)(pzData - pcNulPoint));
979abb0f93cSkardel
980abb0f93cSkardel /*
9814305584aSkardel * If we are getting a "string" value that is to be cooked,
9824305584aSkardel * then process the XML-ish &xx; XML-ish and %XX hex characters.
983abb0f93cSkardel */
9844305584aSkardel if ( (valu.valType == OPARG_TYPE_STRING)
9854305584aSkardel && (mode == OPTION_LOAD_COOKED))
9864305584aSkardel cook_xml_text(pzData);
987abb0f93cSkardel
988abb0f93cSkardel /*
989abb0f93cSkardel * "pzName" points to what looks like text for one option/configurable.
990abb0f93cSkardel * It is NUL terminated. Process it.
991abb0f93cSkardel */
992b3d6264cSchristos load_opt_line(opts, ost, pzName, dir, mode);
993abb0f93cSkardel
994b3d6264cSchristos return txt;
995abb0f93cSkardel }
996abb0f93cSkardel
9974305584aSkardel /**
998abb0f93cSkardel * Load a configuration file. This may be invoked either from
999abb0f93cSkardel * scanning the "homerc" list, or from a specific file request.
1000abb0f93cSkardel * (see "optionFileLoad()", the implementation for --load-opts)
1001abb0f93cSkardel */
1002abb0f93cSkardel LOCAL void
intern_file_load(tOptions * opts)1003b3d6264cSchristos intern_file_load(tOptions * opts)
1004abb0f93cSkardel {
1005abb0f93cSkardel uint32_t svfl;
1006abb0f93cSkardel int idx;
1007abb0f93cSkardel int inc;
1008b3d6264cSchristos char f_name[ AG_PATH_MAX+1 ];
1009abb0f93cSkardel
1010b3d6264cSchristos if (opts->papzHomeList == NULL)
1011abb0f93cSkardel return;
1012abb0f93cSkardel
1013b3d6264cSchristos svfl = opts->fOptSet;
1014abb0f93cSkardel inc = DIRECTION_PRESET;
1015abb0f93cSkardel
1016abb0f93cSkardel /*
1017abb0f93cSkardel * Never stop on errors in config files.
1018abb0f93cSkardel */
1019b3d6264cSchristos opts->fOptSet &= ~OPTPROC_ERRSTOP;
1020abb0f93cSkardel
1021abb0f93cSkardel /*
1022abb0f93cSkardel * Find the last RC entry (highest priority entry)
1023abb0f93cSkardel */
1024b3d6264cSchristos for (idx = 0; opts->papzHomeList[ idx+1 ] != NULL; ++idx) ;
1025abb0f93cSkardel
1026abb0f93cSkardel /*
1027abb0f93cSkardel * For every path in the home list, ... *TWICE* We start at the last
1028abb0f93cSkardel * (highest priority) entry, work our way down to the lowest priority,
1029abb0f93cSkardel * handling the immediate options.
1030abb0f93cSkardel * Then we go back up, doing the normal options.
1031abb0f93cSkardel */
1032abb0f93cSkardel for (;;) {
1033b3d6264cSchristos struct stat sb;
1034b3d6264cSchristos cch_t * path;
1035abb0f93cSkardel
1036abb0f93cSkardel /*
1037abb0f93cSkardel * IF we've reached the bottom end, change direction
1038abb0f93cSkardel */
1039abb0f93cSkardel if (idx < 0) {
1040abb0f93cSkardel inc = DIRECTION_PROCESS;
1041abb0f93cSkardel idx = 0;
1042abb0f93cSkardel }
1043abb0f93cSkardel
1044b3d6264cSchristos path = opts->papzHomeList[ idx ];
1045abb0f93cSkardel
1046abb0f93cSkardel /*
1047abb0f93cSkardel * IF we've reached the top end, bail out
1048abb0f93cSkardel */
1049b3d6264cSchristos if (path == NULL)
1050abb0f93cSkardel break;
1051abb0f93cSkardel
1052abb0f93cSkardel idx += inc;
1053abb0f93cSkardel
1054b3d6264cSchristos if (! optionMakePath(f_name, (int)sizeof(f_name),
1055b3d6264cSchristos path, opts->pzProgPath))
1056abb0f93cSkardel continue;
1057abb0f93cSkardel
1058abb0f93cSkardel /*
1059abb0f93cSkardel * IF the file name we constructed is a directory,
1060abb0f93cSkardel * THEN append the Resource Configuration file name
1061abb0f93cSkardel * ELSE we must have the complete file name
1062abb0f93cSkardel */
1063b3d6264cSchristos if (stat(f_name, &sb) != 0)
1064abb0f93cSkardel continue; /* bogus name - skip the home list entry */
1065abb0f93cSkardel
1066b3d6264cSchristos if (S_ISDIR(sb.st_mode)) {
1067b3d6264cSchristos size_t len = strlen(f_name);
1068b3d6264cSchristos size_t nln = strlen(opts->pzRcName) + 1;
1069b3d6264cSchristos char * pz = f_name + len;
1070abb0f93cSkardel
1071b3d6264cSchristos if (len + 1 + nln >= sizeof(f_name))
1072abb0f93cSkardel continue;
1073abb0f93cSkardel
1074abb0f93cSkardel if (pz[-1] != DIRCH)
1075abb0f93cSkardel *(pz++) = DIRCH;
1076b3d6264cSchristos memcpy(pz, opts->pzRcName, nln);
1077abb0f93cSkardel }
1078abb0f93cSkardel
1079b3d6264cSchristos file_preset(opts, f_name, inc);
1080abb0f93cSkardel
1081abb0f93cSkardel /*
1082abb0f93cSkardel * IF we are now to skip config files AND we are presetting,
1083abb0f93cSkardel * THEN change direction. We must go the other way.
1084abb0f93cSkardel */
1085abb0f93cSkardel {
1086b3d6264cSchristos tOptDesc * od = opts->pOptDesc + opts->specOptIdx.save_opts + 1;
1087b3d6264cSchristos if (DISABLED_OPT(od) && PRESETTING(inc)) {
1088abb0f93cSkardel idx -= inc; /* go back and reprocess current file */
1089abb0f93cSkardel inc = DIRECTION_PROCESS;
1090abb0f93cSkardel }
1091abb0f93cSkardel }
1092abb0f93cSkardel } /* twice for every path in the home list, ... */
1093abb0f93cSkardel
1094b3d6264cSchristos opts->fOptSet = svfl;
1095abb0f93cSkardel }
1096abb0f93cSkardel
1097abb0f93cSkardel /*=export_func optionFileLoad
1098abb0f93cSkardel *
1099abb0f93cSkardel * what: Load the locatable config files, in order
1100abb0f93cSkardel *
1101b3d6264cSchristos * arg: + tOptions * + opts + program options descriptor +
1102b3d6264cSchristos * arg: + char const * + prog + program name +
1103abb0f93cSkardel *
1104abb0f93cSkardel * ret_type: int
1105abb0f93cSkardel * ret_desc: 0 -> SUCCESS, -1 -> FAILURE
1106abb0f93cSkardel *
1107abb0f93cSkardel * doc:
1108abb0f93cSkardel *
1109abb0f93cSkardel * This function looks in all the specified directories for a configuration
1110abb0f93cSkardel * file ("rc" file or "ini" file) and processes any found twice. The first
1111abb0f93cSkardel * time through, they are processed in reverse order (last file first). At
1112abb0f93cSkardel * that time, only "immediate action" configurables are processed. For
1113abb0f93cSkardel * example, if the last named file specifies not processing any more
1114abb0f93cSkardel * configuration files, then no more configuration files will be processed.
1115abb0f93cSkardel * Such an option in the @strong{first} named directory will have no effect.
1116abb0f93cSkardel *
1117abb0f93cSkardel * Once the immediate action configurables have been handled, then the
1118abb0f93cSkardel * directories are handled in normal, forward order. In that way, later
1119abb0f93cSkardel * config files can override the settings of earlier config files.
1120abb0f93cSkardel *
1121abb0f93cSkardel * See the AutoOpts documentation for a thorough discussion of the
1122abb0f93cSkardel * config file format.
1123abb0f93cSkardel *
1124abb0f93cSkardel * Configuration files not found or not decipherable are simply ignored.
1125abb0f93cSkardel *
1126abb0f93cSkardel * err: Returns the value, "-1" if the program options descriptor
1127abb0f93cSkardel * is out of date or indecipherable. Otherwise, the value "0" will
1128abb0f93cSkardel * always be returned.
1129abb0f93cSkardel =*/
1130abb0f93cSkardel int
optionFileLoad(tOptions * opts,char const * prog)1131b3d6264cSchristos optionFileLoad(tOptions * opts, char const * prog)
1132abb0f93cSkardel {
1133b3d6264cSchristos if (! SUCCESSFUL(validate_struct(opts, prog)))
1134abb0f93cSkardel return -1;
1135abb0f93cSkardel
1136b3d6264cSchristos /*
1137b3d6264cSchristos * The pointer to the program name is "const". However, the
1138b3d6264cSchristos * structure is in writable memory, so we coerce the address
1139b3d6264cSchristos * of this pointer to point to writable memory.
1140b3d6264cSchristos */
11414305584aSkardel {
11424e3b3909Schristos char const ** pp = VOIDP(&(opts->pzProgName));
1143b3d6264cSchristos *pp = prog;
11444305584aSkardel }
11454305584aSkardel
1146b3d6264cSchristos intern_file_load(opts);
1147abb0f93cSkardel return 0;
1148abb0f93cSkardel }
1149abb0f93cSkardel
1150abb0f93cSkardel /*=export_func optionLoadOpt
1151abb0f93cSkardel * private:
1152abb0f93cSkardel *
1153abb0f93cSkardel * what: Load an option rc/ini file
1154b3d6264cSchristos * arg: + tOptions * + opts + program options descriptor +
1155b3d6264cSchristos * arg: + tOptDesc * + odesc + the descriptor for this arg +
1156abb0f93cSkardel *
1157abb0f93cSkardel * doc:
1158abb0f93cSkardel * Processes the options found in the file named with
1159b3d6264cSchristos * odesc->optArg.argString.
1160abb0f93cSkardel =*/
1161abb0f93cSkardel void
optionLoadOpt(tOptions * opts,tOptDesc * odesc)1162b3d6264cSchristos optionLoadOpt(tOptions * opts, tOptDesc * odesc)
1163abb0f93cSkardel {
1164abb0f93cSkardel struct stat sb;
1165abb0f93cSkardel
1166b3d6264cSchristos if (opts <= OPTPROC_EMIT_LIMIT)
1167b3d6264cSchristos return;
1168b3d6264cSchristos
1169abb0f93cSkardel /*
1170abb0f93cSkardel * IF the option is not being disabled, THEN load the file. There must
1171abb0f93cSkardel * be a file. (If it is being disabled, then the disablement processing
1172abb0f93cSkardel * already took place. It must be done to suppress preloading of ini/rc
1173abb0f93cSkardel * files.)
1174abb0f93cSkardel */
1175b3d6264cSchristos if ( DISABLED_OPT(odesc)
1176b3d6264cSchristos || ((odesc->fOptState & OPTST_RESET) != 0))
1177abb0f93cSkardel return;
1178abb0f93cSkardel
1179b3d6264cSchristos if (stat(odesc->optArg.argString, &sb) != 0) {
1180b3d6264cSchristos if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
1181abb0f93cSkardel return;
1182abb0f93cSkardel
1183b3d6264cSchristos fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
1184abb0f93cSkardel /* NOT REACHED */
1185abb0f93cSkardel }
1186abb0f93cSkardel
1187abb0f93cSkardel if (! S_ISREG(sb.st_mode)) {
1188b3d6264cSchristos if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
1189abb0f93cSkardel return;
1190b3d6264cSchristos errno = EINVAL;
1191b3d6264cSchristos fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
1192abb0f93cSkardel /* NOT REACHED */
1193abb0f93cSkardel }
1194abb0f93cSkardel
1195b3d6264cSchristos file_preset(opts, odesc->optArg.argString, DIRECTION_CALLED);
1196abb0f93cSkardel }
1197abb0f93cSkardel
11984305584aSkardel /**
1199abb0f93cSkardel * Parse the various attributes of an XML-styled config file entry
1200b3d6264cSchristos *
1201b3d6264cSchristos * @returns NULL on failure, otherwise the scan point
1202abb0f93cSkardel */
1203b3d6264cSchristos LOCAL char const *
parse_attrs(tOptions * opts,char const * txt,tOptionLoadMode * pMode,tOptionValue * pType)1204b3d6264cSchristos parse_attrs(tOptions * opts, char const * txt, tOptionLoadMode * pMode,
1205abb0f93cSkardel tOptionValue * pType)
1206abb0f93cSkardel {
1207b3d6264cSchristos size_t len = 0;
1208abb0f93cSkardel
1209b3d6264cSchristos for (;;) {
1210b3d6264cSchristos len = (size_t)(SPN_LOWER_CASE_CHARS(txt) - txt);
1211abb0f93cSkardel
1212b3d6264cSchristos /*
1213b3d6264cSchristos * The enumeration used in this switch is derived from this switch
1214b3d6264cSchristos * statement itself. The "find_option_xat_attribute_cmd" function
1215b3d6264cSchristos * will return XAT_CMD_MEMBERS for the "txt" string value
1216b3d6264cSchristos * "members", etc.
1217b3d6264cSchristos */
1218b3d6264cSchristos switch (find_option_xat_attribute_cmd(txt, len)) {
1219b3d6264cSchristos case XAT_CMD_TYPE:
1220b3d6264cSchristos txt = parse_value(txt+len, pType);
1221abb0f93cSkardel break;
1222abb0f93cSkardel
1223b3d6264cSchristos case XAT_CMD_WORDS:
1224b3d6264cSchristos txt = parse_keyword(opts, txt+len, pType);
1225abb0f93cSkardel break;
1226abb0f93cSkardel
1227b3d6264cSchristos case XAT_CMD_MEMBERS:
1228b3d6264cSchristos txt = parse_set_mem(opts, txt+len, pType);
1229abb0f93cSkardel break;
1230abb0f93cSkardel
1231b3d6264cSchristos case XAT_CMD_COOKED:
1232b3d6264cSchristos txt += len;
1233b3d6264cSchristos if (! IS_END_XML_TOKEN_CHAR(*txt))
1234abb0f93cSkardel goto invalid_kwd;
1235abb0f93cSkardel
1236abb0f93cSkardel *pMode = OPTION_LOAD_COOKED;
1237abb0f93cSkardel break;
1238abb0f93cSkardel
1239b3d6264cSchristos case XAT_CMD_UNCOOKED:
1240b3d6264cSchristos txt += len;
1241b3d6264cSchristos if (! IS_END_XML_TOKEN_CHAR(*txt))
1242abb0f93cSkardel goto invalid_kwd;
1243abb0f93cSkardel
1244abb0f93cSkardel *pMode = OPTION_LOAD_UNCOOKED;
1245abb0f93cSkardel break;
1246abb0f93cSkardel
1247b3d6264cSchristos case XAT_CMD_KEEP:
1248b3d6264cSchristos txt += len;
1249b3d6264cSchristos if (! IS_END_XML_TOKEN_CHAR(*txt))
1250abb0f93cSkardel goto invalid_kwd;
1251abb0f93cSkardel
1252abb0f93cSkardel *pMode = OPTION_LOAD_KEEP;
1253abb0f93cSkardel break;
1254abb0f93cSkardel
1255abb0f93cSkardel default:
1256b3d6264cSchristos case XAT_INVALID_CMD:
1257abb0f93cSkardel invalid_kwd:
1258abb0f93cSkardel pType->valType = OPARG_TYPE_NONE;
1259b3d6264cSchristos return skip_unkn(txt);
1260abb0f93cSkardel }
1261abb0f93cSkardel
1262b3d6264cSchristos if (txt == NULL)
1263b3d6264cSchristos return NULL;
1264b3d6264cSchristos txt = SPN_WHITESPACE_CHARS(txt);
1265b3d6264cSchristos switch (*txt) {
1266b3d6264cSchristos case '/': pType->valType = OPARG_TYPE_NONE;
1267b3d6264cSchristos /* FALLTHROUGH */
1268b3d6264cSchristos case '>': return txt;
1269b3d6264cSchristos }
1270b3d6264cSchristos if (! IS_LOWER_CASE_CHAR(*txt))
1271b3d6264cSchristos return NULL;
1272b3d6264cSchristos }
1273b3d6264cSchristos }
1274abb0f93cSkardel
12754305584aSkardel /**
1276b3d6264cSchristos * "txt" points to the character after "words=".
1277abb0f93cSkardel * What should follow is a name of a keyword (enumeration) list.
1278b3d6264cSchristos *
1279b3d6264cSchristos * @param opts unused
1280b3d6264cSchristos * @param[in] txt keyword to skip over
1281b3d6264cSchristos * @param type unused value type
1282b3d6264cSchristos * @returns pointer after skipped text
1283abb0f93cSkardel */
1284b3d6264cSchristos static char const *
parse_keyword(tOptions * opts,char const * txt,tOptionValue * typ)1285b3d6264cSchristos parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ)
1286abb0f93cSkardel {
1287b3d6264cSchristos (void)opts;
1288b3d6264cSchristos (void)typ;
1289b3d6264cSchristos
1290b3d6264cSchristos return skip_unkn(txt);
1291abb0f93cSkardel }
1292abb0f93cSkardel
12934305584aSkardel /**
1294b3d6264cSchristos * "txt" points to the character after "members="
1295abb0f93cSkardel * What should follow is a name of a "set membership".
1296abb0f93cSkardel * A collection of bit flags.
1297b3d6264cSchristos *
1298b3d6264cSchristos * @param opts unused
1299b3d6264cSchristos * @param[in] txt keyword to skip over
1300b3d6264cSchristos * @param type unused value type
1301b3d6264cSchristos * @returns pointer after skipped text
1302abb0f93cSkardel */
1303b3d6264cSchristos static char const *
parse_set_mem(tOptions * opts,char const * txt,tOptionValue * typ)1304b3d6264cSchristos parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ)
1305abb0f93cSkardel {
1306b3d6264cSchristos (void)opts;
1307b3d6264cSchristos (void)typ;
1308b3d6264cSchristos
1309b3d6264cSchristos return skip_unkn(txt);
1310abb0f93cSkardel }
1311abb0f93cSkardel
13124305584aSkardel /**
1313b3d6264cSchristos * parse the type. The keyword "type" was found, now figure out
1314b3d6264cSchristos * the type that follows the type.
1315b3d6264cSchristos *
1316b3d6264cSchristos * @param[in] txt points to the '=' character after the "type" keyword.
1317b3d6264cSchristos * @param[out] typ where to store the type found
1318b3d6264cSchristos * @returns the next byte after the type name
1319abb0f93cSkardel */
1320b3d6264cSchristos static char const *
parse_value(char const * txt,tOptionValue * typ)1321b3d6264cSchristos parse_value(char const * txt, tOptionValue * typ)
1322abb0f93cSkardel {
1323abb0f93cSkardel size_t len = 0;
1324abb0f93cSkardel
1325b3d6264cSchristos if (*(txt++) != '=')
1326abb0f93cSkardel goto woops;
1327abb0f93cSkardel
1328b3d6264cSchristos len = (size_t)(SPN_OPTION_NAME_CHARS(txt) - txt);
1329abb0f93cSkardel
1330b3d6264cSchristos if ((len == 0) || (! IS_END_XML_TOKEN_CHAR(txt[len]))) {
1331abb0f93cSkardel woops:
1332b3d6264cSchristos typ->valType = OPARG_TYPE_NONE;
1333b3d6264cSchristos return skip_unkn(txt + len);
1334abb0f93cSkardel }
1335abb0f93cSkardel
1336b3d6264cSchristos /*
1337b3d6264cSchristos * The enumeration used in this switch is derived from this switch
1338b3d6264cSchristos * statement itself. The "find_option_value_type_cmd" function
1339b3d6264cSchristos * will return VTP_CMD_INTEGER for the "txt" string value
1340b3d6264cSchristos * "integer", etc.
1341b3d6264cSchristos */
1342b3d6264cSchristos switch (find_option_value_type_cmd(txt, len)) {
1343abb0f93cSkardel default:
1344b3d6264cSchristos case VTP_INVALID_CMD: goto woops;
1345abb0f93cSkardel
1346b3d6264cSchristos case VTP_CMD_STRING:
1347b3d6264cSchristos typ->valType = OPARG_TYPE_STRING;
1348abb0f93cSkardel break;
1349abb0f93cSkardel
1350b3d6264cSchristos case VTP_CMD_INTEGER:
1351b3d6264cSchristos typ->valType = OPARG_TYPE_NUMERIC;
1352abb0f93cSkardel break;
1353abb0f93cSkardel
1354b3d6264cSchristos case VTP_CMD_BOOL:
1355b3d6264cSchristos case VTP_CMD_BOOLEAN:
1356b3d6264cSchristos typ->valType = OPARG_TYPE_BOOLEAN;
1357abb0f93cSkardel break;
1358abb0f93cSkardel
1359b3d6264cSchristos case VTP_CMD_KEYWORD:
1360b3d6264cSchristos typ->valType = OPARG_TYPE_ENUMERATION;
1361abb0f93cSkardel break;
1362abb0f93cSkardel
1363b3d6264cSchristos case VTP_CMD_SET:
1364b3d6264cSchristos case VTP_CMD_SET_MEMBERSHIP:
1365b3d6264cSchristos typ->valType = OPARG_TYPE_MEMBERSHIP;
1366abb0f93cSkardel break;
1367abb0f93cSkardel
1368b3d6264cSchristos case VTP_CMD_NESTED:
1369b3d6264cSchristos case VTP_CMD_HIERARCHY:
1370b3d6264cSchristos typ->valType = OPARG_TYPE_HIERARCHY;
1371abb0f93cSkardel }
1372abb0f93cSkardel
1373b3d6264cSchristos return txt + len;
1374abb0f93cSkardel }
1375abb0f93cSkardel
1376b3d6264cSchristos /** @}
1377b3d6264cSchristos *
1378abb0f93cSkardel * Local Variables:
1379abb0f93cSkardel * mode: C
1380abb0f93cSkardel * c-file-style: "stroustrup"
1381abb0f93cSkardel * indent-tabs-mode: nil
1382abb0f93cSkardel * End:
1383abb0f93cSkardel * end of autoopts/configfile.c */
1384