1
2 /*
3 * $Id: nested.c,v 4.14 2007/02/04 17:44:12 bkorb Exp $
4 * Time-stamp: "2007-01-26 11:04:35 bkorb"
5 *
6 * Automated Options Nested Values module.
7 */
8
9 /*
10 * Automated Options copyright 1992-2007 Bruce Korb
11 *
12 * Automated Options is free software.
13 * You may redistribute it and/or modify it under the terms of the
14 * GNU General Public License, as published by the Free Software
15 * Foundation; either version 2, or (at your option) any later version.
16 *
17 * Automated Options is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with Automated Options. See the file "COPYING". If not,
24 * write to: The Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor,
26 * Boston, MA 02110-1301, USA.
27 *
28 * As a special exception, Bruce Korb gives permission for additional
29 * uses of the text contained in his release of AutoOpts.
30 *
31 * The exception is that, if you link the AutoOpts library with other
32 * files to produce an executable, this does not by itself cause the
33 * resulting executable to be covered by the GNU General Public License.
34 * Your use of that executable is in no way restricted on account of
35 * linking the AutoOpts library code into it.
36 *
37 * This exception does not however invalidate any other reasons why
38 * the executable file might be covered by the GNU General Public License.
39 *
40 * This exception applies only to the code released by Bruce Korb under
41 * the name AutoOpts. If you copy code from other sources under the
42 * General Public License into a copy of AutoOpts, as the General Public
43 * License permits, the exception does not apply to the code that you add
44 * in this way. To avoid misleading anyone as to the status of such
45 * modified files, you must delete this exception notice from them.
46 *
47 * If you write modifications of your own for AutoOpts, it is your choice
48 * whether to permit this exception to apply to your modifications.
49 * If you do not wish that, delete this exception notice.
50 */
51 /* = = = START-STATIC-FORWARD = = = */
52 /* static forward declarations maintained by :mkfwd */
53 static void
54 removeBackslashes( char* pzSrc );
55
56 static char const*
57 scanQuotedString( char const* pzTxt );
58
59 static tOptionValue*
60 addStringValue( void** pp, char const* pzName, size_t nameLen,
61 char const* pzValue, size_t dataLen );
62
63 static tOptionValue*
64 addBoolValue( void** pp, char const* pzName, size_t nameLen,
65 char const* pzValue, size_t dataLen );
66
67 static tOptionValue*
68 addNumberValue( void** pp, char const* pzName, size_t nameLen,
69 char const* pzValue, size_t dataLen );
70
71 static tOptionValue*
72 addNestedValue( void** pp, char const* pzName, size_t nameLen,
73 char* pzValue, size_t dataLen );
74
75 static char const*
76 scanNameEntry(char const* pzName, tOptionValue* pRes);
77
78 static char const*
79 scanXmlEntry( char const* pzName, tOptionValue* pRes );
80
81 static void
82 unloadNestedArglist( tArgList* pAL );
83
84 static void
85 sortNestedList( tArgList* pAL );
86 /* = = = END-STATIC-FORWARD = = = */
87
88 /* removeBackslashes
89 *
90 * This function assumes that all newline characters were preceeded by
91 * backslashes that need removal.
92 */
93 static void
removeBackslashes(char * pzSrc)94 removeBackslashes( char* pzSrc )
95 {
96 char* pzD = strchr(pzSrc, '\n');
97
98 if (pzD == NULL)
99 return;
100 *--pzD = '\n';
101
102 for (;;) {
103 char ch = ((*pzD++) = *(pzSrc++));
104 switch (ch) {
105 case '\n': *--pzD = ch; break;
106 case NUL: return;
107 default:
108 ;
109 }
110 }
111 }
112
113
114 /* scanQuotedString
115 *
116 * Find the end of a quoted string, skipping escaped quote characters.
117 */
118 static char const*
scanQuotedString(char const * pzTxt)119 scanQuotedString( char const* pzTxt )
120 {
121 char q = *(pzTxt++); /* remember the type of quote */
122
123 for (;;) {
124 char ch = *(pzTxt++);
125 if (ch == NUL)
126 return pzTxt-1;
127
128 if (ch == q)
129 return pzTxt;
130
131 if (ch == '\\') {
132 ch = *(pzTxt++);
133 /*
134 * IF the next character is NUL, drop the backslash, too.
135 */
136 if (ch == NUL)
137 return pzTxt - 2;
138
139 /*
140 * IF the quote character or the escape character were escaped,
141 * then skip both, as long as the string does not end.
142 */
143 if ((ch == q) || (ch == '\\')) {
144 if (*(pzTxt++) == NUL)
145 return pzTxt-1;
146 }
147 }
148 }
149 }
150
151
152 /* addStringValue
153 *
154 * Associate a name with either a string or no value.
155 */
156 static tOptionValue*
addStringValue(void ** pp,char const * pzName,size_t nameLen,char const * pzValue,size_t dataLen)157 addStringValue( void** pp, char const* pzName, size_t nameLen,
158 char const* pzValue, size_t dataLen )
159 {
160 tOptionValue* pNV;
161 size_t sz = nameLen + dataLen + sizeof(*pNV);
162
163 pNV = AGALOC( sz, "option name/str value pair" );
164 if (pNV == NULL)
165 return NULL;
166
167 if (pzValue == NULL) {
168 pNV->valType = OPARG_TYPE_NONE;
169 pNV->pzName = pNV->v.strVal;
170
171 } else {
172 pNV->valType = OPARG_TYPE_STRING;
173 if (dataLen > 0)
174 memcpy( pNV->v.strVal, pzValue, dataLen );
175 pNV->v.strVal[dataLen] = NUL;
176 pNV->pzName = pNV->v.strVal + dataLen + 1;
177 }
178
179 memcpy( pNV->pzName, pzName, nameLen );
180 pNV->pzName[ nameLen ] = NUL;
181 addArgListEntry( pp, pNV );
182 return pNV;
183 }
184
185
186 /* addBoolValue
187 *
188 * Associate a name with either a string or no value.
189 */
190 static tOptionValue*
addBoolValue(void ** pp,char const * pzName,size_t nameLen,char const * pzValue,size_t dataLen)191 addBoolValue( void** pp, char const* pzName, size_t nameLen,
192 char const* pzValue, size_t dataLen )
193 {
194 tOptionValue* pNV;
195 size_t sz = nameLen + sizeof(*pNV) + 1;
196
197 pNV = AGALOC( sz, "option name/bool value pair" );
198 if (pNV == NULL)
199 return NULL;
200 while (isspace( (int)*pzValue ) && (dataLen > 0)) {
201 dataLen--; pzValue++;
202 }
203 if (dataLen == 0)
204 pNV->v.boolVal = 0;
205 else if (isdigit( (int)*pzValue ))
206 pNV->v.boolVal = atoi( pzValue );
207 else switch (*pzValue) {
208 case 'f':
209 case 'F':
210 case 'n':
211 case 'N':
212 pNV->v.boolVal = 0; break;
213 default:
214 pNV->v.boolVal = 1;
215 }
216
217 pNV->valType = OPARG_TYPE_BOOLEAN;
218 pNV->pzName = (char*)(pNV + 1);
219 memcpy( pNV->pzName, pzName, nameLen );
220 pNV->pzName[ nameLen ] = NUL;
221 addArgListEntry( pp, pNV );
222 return pNV;
223 }
224
225
226 /* addNumberValue
227 *
228 * Associate a name with either a string or no value.
229 */
230 static tOptionValue*
addNumberValue(void ** pp,char const * pzName,size_t nameLen,char const * pzValue,size_t dataLen)231 addNumberValue( void** pp, char const* pzName, size_t nameLen,
232 char const* pzValue, size_t dataLen )
233 {
234 tOptionValue* pNV;
235 size_t sz = nameLen + sizeof(*pNV) + 1;
236
237 pNV = AGALOC( sz, "option name/bool value pair" );
238 if (pNV == NULL)
239 return NULL;
240 while (isspace( (int)*pzValue ) && (dataLen > 0)) {
241 dataLen--; pzValue++;
242 }
243 if (dataLen == 0)
244 pNV->v.boolVal = 0;
245 else
246 pNV->v.boolVal = atoi( pzValue );
247
248 pNV->valType = OPARG_TYPE_NUMERIC;
249 pNV->pzName = (char*)(pNV + 1);
250 memcpy( pNV->pzName, pzName, nameLen );
251 pNV->pzName[ nameLen ] = NUL;
252 addArgListEntry( pp, pNV );
253 return pNV;
254 }
255
256
257 /* addNestedValue
258 *
259 * Associate a name with either a string or no value.
260 */
261 static tOptionValue*
addNestedValue(void ** pp,char const * pzName,size_t nameLen,char * pzValue,size_t dataLen)262 addNestedValue( void** pp, char const* pzName, size_t nameLen,
263 char* pzValue, size_t dataLen )
264 {
265 tOptionValue* pNV;
266
267 if (dataLen == 0) {
268 size_t sz = nameLen + sizeof(*pNV) + 1;
269 pNV = AGALOC( sz, "empty nested value pair" );
270 if (pNV == NULL)
271 return NULL;
272 pNV->v.nestVal = NULL;
273 pNV->valType = OPARG_TYPE_HIERARCHY;
274 pNV->pzName = (char*)(pNV + 1);
275 memcpy( pNV->pzName, pzName, nameLen );
276 pNV->pzName[ nameLen ] = NUL;
277
278 } else {
279 pNV = optionLoadNested( pzValue, pzName, nameLen );
280 }
281
282 if (pNV != NULL)
283 addArgListEntry( pp, pNV );
284
285 return pNV;
286 }
287
288
289 /* scanNameEntry
290 *
291 * We have an entry that starts with a name. Find the end of it, cook it
292 * (if called for) and create the name/value association.
293 */
294 static char const*
scanNameEntry(char const * pzName,tOptionValue * pRes)295 scanNameEntry(char const* pzName, tOptionValue* pRes)
296 {
297 tOptionValue* pNV;
298 char const * pzScan = pzName+1;
299 char const * pzVal;
300 size_t nameLen = 1;
301 size_t dataLen = 0;
302
303 while (ISNAMECHAR( (int)*pzScan )) { pzScan++; nameLen++; }
304
305 while (isspace( (int)*pzScan )) {
306 char ch = *(pzScan++);
307 if ((ch == '\n') || (ch == ',')) {
308 addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL,(size_t)0);
309 return pzScan - 1;
310 }
311 }
312
313 switch (*pzScan) {
314 case '=':
315 case ':':
316 while (isspace( (int)*++pzScan )) ;
317 switch (*pzScan) {
318 case ',': goto comma_char;
319 case '"':
320 case '\'': goto quote_char;
321 case NUL: goto nul_byte;
322 default: goto default_char;
323 }
324
325 case ',':
326 comma_char:
327 pzScan++;
328 /* FALLTHROUGH */
329
330 case NUL:
331 nul_byte:
332 addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
333 break;
334
335 case '"':
336 case '\'':
337 quote_char:
338 pzVal = pzScan;
339 pzScan = scanQuotedString( pzScan );
340 dataLen = pzScan - pzVal;
341 pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, pzVal,
342 dataLen );
343 if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
344 ao_string_cook( pNV->v.strVal, NULL );
345 break;
346
347 default:
348 default_char:
349 /*
350 * We have found some strange text value. It ends with a newline
351 * or a comma.
352 */
353 pzVal = pzScan;
354 for (;;) {
355 char ch = *(pzScan++);
356 switch (ch) {
357 case NUL:
358 pzScan--;
359 dataLen = pzScan - pzVal;
360 goto string_done;
361 /* FALLTHROUGH */
362
363 case '\n':
364 if ( (pzScan > pzVal + 2)
365 && (pzScan[-2] == '\\')
366 && (pzScan[ 0] != NUL))
367 continue;
368 /* FALLTHROUGH */
369
370 case ',':
371 dataLen = (pzScan - pzVal) - 1;
372 string_done:
373 pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen,
374 pzVal, dataLen );
375 if (pNV != NULL)
376 removeBackslashes( pNV->v.strVal );
377 goto leave_scan_name;
378 }
379 }
380 break;
381 } leave_scan_name:;
382
383 return pzScan;
384 }
385
386
387 /* scanXmlEntry
388 *
389 * We've found a '<' character. We ignore this if it is a comment or a
390 * directive. If it is something else, then whatever it is we are looking
391 * at is bogus. Returning NULL stops processing.
392 */
393 static char const*
scanXmlEntry(char const * pzName,tOptionValue * pRes)394 scanXmlEntry( char const* pzName, tOptionValue* pRes )
395 {
396 size_t nameLen = 1, valLen = 0;
397 char const* pzScan = ++pzName;
398 char const* pzVal;
399 tOptionValue valu;
400 tOptionValue* pNewVal;
401 tOptionLoadMode save_mode = option_load_mode;
402
403 if (! isalpha((int)*pzName)) {
404 switch (*pzName) {
405 default:
406 pzName = NULL;
407 break;
408
409 case '!':
410 pzName = strstr( pzName, "-->" );
411 if (pzName != NULL)
412 pzName += 3;
413 break;
414
415 case '?':
416 pzName = strchr( pzName, '>' );
417 if (pzName != NULL)
418 pzName++;
419 break;
420 }
421 return pzName;
422 }
423
424 while (isalpha( (int)*++pzScan )) nameLen++;
425 if (nameLen > 64)
426 return NULL;
427 valu.valType = OPARG_TYPE_STRING;
428
429 switch (*pzScan) {
430 case ' ':
431 case '\t':
432 pzScan = parseAttributes(
433 NULL, (char*)pzScan, &option_load_mode, &valu );
434 if (*pzScan == '>') {
435 pzScan++;
436 break;
437 }
438
439 if (*pzScan != '/') {
440 option_load_mode = save_mode;
441 return NULL;
442 }
443 /* FALLTHROUGH */
444
445 case '/':
446 if (*++pzScan != '>') {
447 option_load_mode = save_mode;
448 return NULL;
449 }
450 addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
451 option_load_mode = save_mode;
452 return pzScan+2;
453
454 default:
455 option_load_mode = save_mode;
456 return NULL;
457
458 case '>':
459 pzScan++;
460 break;
461 }
462
463 pzVal = pzScan;
464
465 {
466 char z[68];
467 char* pzD = z;
468 int ct = nameLen;
469 char const* pzS = pzName;
470
471 *(pzD++) = '<';
472 *(pzD++) = '/';
473
474 do {
475 *(pzD++) = *(pzS++);
476 } while (--ct > 0);
477 *(pzD++) = '>';
478 *pzD = NUL;
479
480 pzScan = strstr( pzScan, z );
481 if (pzScan == NULL) {
482 option_load_mode = save_mode;
483 return NULL;
484 }
485 valLen = (pzScan - pzVal);
486 pzScan += nameLen + 3;
487 while (isspace( (int)*pzScan )) pzScan++;
488 }
489
490 switch (valu.valType) {
491 case OPARG_TYPE_NONE:
492 addStringValue( &(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
493 break;
494
495 case OPARG_TYPE_STRING:
496 pNewVal = addStringValue(
497 &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);
498
499 if (option_load_mode == OPTION_LOAD_KEEP)
500 break;
501 mungeString( pNewVal->v.strVal, option_load_mode );
502 break;
503
504 case OPARG_TYPE_BOOLEAN:
505 addBoolValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );
506 break;
507
508 case OPARG_TYPE_NUMERIC:
509 addNumberValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );
510 break;
511
512 case OPARG_TYPE_HIERARCHY:
513 {
514 char* pz = AGALOC( valLen+1, "hierarchical scan" );
515 if (pz == NULL)
516 break;
517 memcpy( pz, pzVal, valLen );
518 pz[valLen] = NUL;
519 addNestedValue( &(pRes->v.nestVal), pzName, nameLen, pz, valLen );
520 AGFREE(pz);
521 break;
522 }
523
524 case OPARG_TYPE_ENUMERATION:
525 case OPARG_TYPE_MEMBERSHIP:
526 default:
527 break;
528 }
529
530 option_load_mode = save_mode;
531 return pzScan;
532 }
533
534
535 /* unloadNestedArglist
536 *
537 * Deallocate a list of option arguments. This must have been gotten from
538 * a hierarchical option argument, not a stacked list of strings. It is
539 * an internal call, so it is not validated. The caller is responsible for
540 * knowing what they are doing.
541 */
542 static void
unloadNestedArglist(tArgList * pAL)543 unloadNestedArglist( tArgList* pAL )
544 {
545 int ct = pAL->useCt;
546 tCC** ppNV = pAL->apzArgs;
547
548 while (ct-- > 0) {
549 tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++);
550 if (pNV->valType == OPARG_TYPE_HIERARCHY)
551 unloadNestedArglist( pNV->v.nestVal );
552 AGFREE( pNV );
553 }
554
555 AGFREE( (void*)pAL );
556 }
557
558
559 /*=export_func optionUnloadNested
560 *
561 * what: Deallocate the memory for a nested value
562 * arg: + tOptionValue const * + pOptVal + the hierarchical value +
563 *
564 * doc:
565 * A nested value needs to be deallocated. The pointer passed in should
566 * have been gotten from a call to @code{configFileLoad()} (See
567 * @pxref{libopts-configFileLoad}).
568 =*/
569 void
optionUnloadNested(tOptionValue const * pOV)570 optionUnloadNested( tOptionValue const * pOV )
571 {
572 if (pOV == NULL) return;
573 if (pOV->valType != OPARG_TYPE_HIERARCHY) {
574 errno = EINVAL;
575 return;
576 }
577
578 unloadNestedArglist( pOV->v.nestVal );
579
580 AGFREE( (void*)pOV );
581 }
582
583
584 /* sortNestedList
585 *
586 * This is a _stable_ sort. The entries are sorted alphabetically,
587 * but within entries of the same name the ordering is unchanged.
588 * Typically, we also hope the input is sorted.
589 */
590 static void
sortNestedList(tArgList * pAL)591 sortNestedList( tArgList* pAL )
592 {
593 int ix;
594 int lm = pAL->useCt;
595
596 /*
597 * This loop iterates "useCt" - 1 times.
598 */
599 for (ix = 0; ++ix < lm;) {
600 int iy = ix-1;
601 tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]);
602 tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]);
603
604 /*
605 * For as long as the new entry precedes the "old" entry,
606 * move the old pointer. Stop before trying to extract the
607 * "-1" entry.
608 */
609 while (strcmp( pOldNV->pzName, pNewNV->pzName ) > 0) {
610 pAL->apzArgs[iy+1] = (void*)pOldNV;
611 pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]);
612 if (iy < 0)
613 break;
614 }
615
616 /*
617 * Always store the pointer. Sometimes it is redundant,
618 * but the redundancy is cheaper than a test and branch sequence.
619 */
620 pAL->apzArgs[iy+1] = (void*)pNewNV;
621 }
622 }
623
624
625 /* optionLoadNested
626 * private:
627 *
628 * what: parse a hierarchical option argument
629 * arg: + char const* + pzTxt + the text to scan +
630 * arg: + char const* + pzName + the name for the text +
631 * arg: + size_t + nameLen + the length of "name" +
632 *
633 * ret_type: tOptionValue*
634 * ret_desc: An allocated, compound value structure
635 *
636 * doc:
637 * A block of text represents a series of values. It may be an
638 * entire configuration file, or it may be an argument to an
639 * option that takes a hierarchical value.
640 */
641 LOCAL tOptionValue*
optionLoadNested(char const * pzTxt,char const * pzName,size_t nameLen)642 optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen)
643 {
644 tOptionValue* pRes;
645 tArgList* pAL;
646
647 /*
648 * Make sure we have some data and we have space to put what we find.
649 */
650 if (pzTxt == NULL) {
651 errno = EINVAL;
652 return NULL;
653 }
654 while (isspace( (int)*pzTxt )) pzTxt++;
655 if (*pzTxt == NUL) {
656 errno = ENOENT;
657 return NULL;
658 }
659 pRes = AGALOC( sizeof(*pRes) + nameLen + 1, "nested args" );
660 if (pRes == NULL) {
661 errno = ENOMEM;
662 return NULL;
663 }
664 pRes->valType = OPARG_TYPE_HIERARCHY;
665 pRes->pzName = (char*)(pRes + 1);
666 memcpy( pRes->pzName, pzName, nameLen );
667 pRes->pzName[ nameLen ] = NUL;
668
669 pAL = AGALOC( sizeof(*pAL), "nested arg list" );
670 if (pAL == NULL) {
671 AGFREE( pRes );
672 return NULL;
673 }
674 pRes->v.nestVal = pAL;
675 pAL->useCt = 0;
676 pAL->allocCt = MIN_ARG_ALLOC_CT;
677
678 /*
679 * Scan until we hit a NUL.
680 */
681 do {
682 while (isspace( (int)*pzTxt )) pzTxt++;
683 if (isalpha( (int)*pzTxt )) {
684 pzTxt = scanNameEntry( pzTxt, pRes );
685 }
686 else switch (*pzTxt) {
687 case NUL: goto scan_done;
688 case '<': pzTxt = scanXmlEntry( pzTxt, pRes );
689 if (*pzTxt == ',') pzTxt++; break;
690 case '#': pzTxt = strchr( pzTxt, '\n' ); break;
691 default: goto woops;
692 }
693 } while (pzTxt != NULL); scan_done:;
694
695 pAL = pRes->v.nestVal;
696 if (pAL->useCt != 0) {
697 sortNestedList( pAL );
698 return pRes;
699 }
700
701 woops:
702 AGFREE( pRes->v.nestVal );
703 AGFREE( pRes );
704 return NULL;
705 }
706
707
708 /*=export_func optionNestedVal
709 * private:
710 *
711 * what: parse a hierarchical option argument
712 * arg: + tOptions* + pOpts + program options descriptor +
713 * arg: + tOptDesc* + pOptDesc + the descriptor for this arg +
714 *
715 * doc:
716 * Nested value was found on the command line
717 =*/
718 void
optionNestedVal(tOptions * pOpts,tOptDesc * pOD)719 optionNestedVal( tOptions* pOpts, tOptDesc* pOD )
720 {
721 tOptionValue* pOV = optionLoadNested(
722 pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name));
723
724 if (pOV != NULL)
725 addArgListEntry( &(pOD->optCookie), (void*)pOV );
726 }
727 /*
728 * Local Variables:
729 * mode: C
730 * c-file-style: "stroustrup"
731 * indent-tabs-mode: nil
732 * End:
733 * end of autoopts/nested.c */
734