1: /* This modules is based on the params.c module from Samba, written by Karl Auer
2: and much modifed by Christopher Hertel. */
3:
4: /*
5: * This program is free software; you can redistribute it and/or modify
6: * it under the terms of the GNU General Public License as published by
7: * the Free Software Foundation; either version 3 of the License, or
8: * (at your option) any later version.
9: *
10: * This program is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13: * GNU General Public License for more details.
14: *
15: * You should have received a copy of the GNU General Public License along
16: * with this program; if not, visit the http://fsf.org website.
17: */
18:
19: /* -------------------------------------------------------------------------- **
20: *
21: * Module name: params
22: *
23: * -------------------------------------------------------------------------- **
24: *
25: * This module performs lexical analysis and initial parsing of a
26: * Windows-like parameter file. It recognizes and handles four token
27: * types: section-name, parameter-name, parameter-value, and
28: * end-of-file. Comments and line continuation are handled
29: * internally.
30: *
31: * The entry point to the module is function pm_process(). This
32: * function opens the source file, calls the Parse() function to parse
33: * the input, and then closes the file when either the EOF is reached
34: * or a fatal error is encountered.
35: *
36: * A sample parameter file might look like this:
37: *
38: * [section one]
39: * parameter one = value string
40: * parameter two = another value
41: * [section two]
42: * new parameter = some value or t'other
43: *
44: * The parameter file is divided into sections by section headers:
45: * section names enclosed in square brackets (eg. [section one]).
46: * Each section contains parameter lines, each of which consist of a
47: * parameter name and value delimited by an equal sign. Roughly, the
48: * syntax is:
49: *
50: * <file> :== { <section> } EOF
51: *
52: * <section> :== <section header> { <parameter line> }
53: *
54: * <section header> :== '[' NAME ']'
55: *
56: * <parameter line> :== NAME '=' VALUE '\n'
57: *
58: * Blank lines and comment lines are ignored. Comment lines are lines
59: * beginning with either a semicolon (';') or a pound sign ('#').
60: *
61: * All whitespace in section names and parameter names is compressed
62: * to single spaces. Leading and trailing whitespace is stipped from
63: * both names and values.
64: *
65: * Only the first equals sign in a parameter line is significant.
66: * Parameter values may contain equals signs, square brackets and
67: * semicolons. Internal whitespace is retained in parameter values,
68: * with the exception of the '\r' character, which is stripped for
69: * historic reasons. Parameter names may not start with a left square
70: * bracket, an equal sign, a pound sign, or a semicolon, because these
71: * are used to identify other tokens.
72: *
73: * -------------------------------------------------------------------------- **
74: */
75:
76: #include "rsync.h"
77: #include "ifuncs.h"
78:
79: /* -------------------------------------------------------------------------- **
80: * Constants...
81: */
82:
83: #define BUFR_INC 1024
84:
85:
86: /* -------------------------------------------------------------------------- **
87: * Variables...
88: *
89: * bufr - pointer to a global buffer. This is probably a kludge,
90: * but it was the nicest kludge I could think of (for now).
91: * bSize - The size of the global buffer <bufr>.
92: */
93:
94: static char *bufr = NULL;
95: static int bSize = 0;
96:
97: /* -------------------------------------------------------------------------- **
98: * Functions...
99: */
100:
101: static int EatWhitespace( FILE *InFile )
102: /* ------------------------------------------------------------------------ **
103: * Scan past whitespace (see ctype(3C)) and return the first non-whitespace
104: * character, or newline, or EOF.
105: *
106: * Input: InFile - Input source.
107: *
108: * Output: The next non-whitespace character in the input stream.
109: *
110: * Notes: Because the config files use a line-oriented grammar, we
111: * explicitly exclude the newline character from the list of
112: * whitespace characters.
113: * - Note that both EOF (-1) and the nul character ('\0') are
114: * considered end-of-file markers.
115: *
116: * ------------------------------------------------------------------------ **
117: */
118: {
119: int c;
120:
121: for( c = getc( InFile ); isspace( c ) && ('\n' != c); c = getc( InFile ) )
122: ;
123: return( c );
124: } /* EatWhitespace */
125:
126: static int EatComment( FILE *InFile )
127: /* ------------------------------------------------------------------------ **
128: * Scan to the end of a comment.
129: *
130: * Input: InFile - Input source.
131: *
132: * Output: The character that marks the end of the comment. Normally,
133: * this will be a newline, but it *might* be an EOF.
134: *
135: * Notes: Because the config files use a line-oriented grammar, we
136: * explicitly exclude the newline character from the list of
137: * whitespace characters.
138: * - Note that both EOF (-1) and the nul character ('\0') are
139: * considered end-of-file markers.
140: *
141: * ------------------------------------------------------------------------ **
142: */
143: {
144: int c;
145:
146: for( c = getc( InFile ); ('\n'!=c) && (EOF!=c) && (c>0); c = getc( InFile ) )
147: ;
148: return( c );
149: } /* EatComment */
150:
151: static int Continuation( char *line, int pos )
152: /* ------------------------------------------------------------------------ **
153: * Scan backards within a string to discover if the last non-whitespace
154: * character is a line-continuation character ('\\').
155: *
156: * Input: line - A pointer to a buffer containing the string to be
157: * scanned.
158: * pos - This is taken to be the offset of the end of the
159: * string. This position is *not* scanned.
160: *
161: * Output: The offset of the '\\' character if it was found, or -1 to
162: * indicate that it was not.
163: *
164: * ------------------------------------------------------------------------ **
165: */
166: {
167: pos--;
168: while( pos >= 0 && isSpace(line + pos) )
169: pos--;
170:
171: return( ((pos >= 0) && ('\\' == line[pos])) ? pos : -1 );
172: } /* Continuation */
173:
174:
175: static BOOL Section( FILE *InFile, BOOL (*sfunc)(char *) )
176: /* ------------------------------------------------------------------------ **
177: * Scan a section name, and pass the name to function sfunc().
178: *
179: * Input: InFile - Input source.
180: * sfunc - Pointer to the function to be called if the section
181: * name is successfully read.
182: *
183: * Output: True if the section name was read and True was returned from
184: * <sfunc>. False if <sfunc> failed or if a lexical error was
185: * encountered.
186: *
187: * ------------------------------------------------------------------------ **
188: */
189: {
190: int c;
191: int i;
192: int end;
193: char *func = "params.c:Section() -";
194:
195: i = 0; /* <i> is the offset of the next free byte in bufr[] and */
196: end = 0; /* <end> is the current "end of string" offset. In most */
197: /* cases these will be the same, but if the last */
198: /* character written to bufr[] is a space, then <end> */
199: /* will be one less than <i>. */
200:
201: c = EatWhitespace( InFile ); /* We've already got the '['. Scan */
202: /* past initial white space. */
203:
204: while( (EOF != c) && (c > 0) )
205: {
206:
207: /* Check that the buffer is big enough for the next character. */
208: if( i > (bSize - 2) )
209: {
210: bSize += BUFR_INC;
211: bufr = realloc_array( bufr, char, bSize );
212: if( NULL == bufr )
213: {
214: rprintf(FLOG, "%s Memory re-allocation failure.", func);
215: return( False );
216: }
217: }
218:
219: /* Handle a single character. */
220: switch( c )
221: {
222: case ']': /* Found the closing bracket. */
223: bufr[end] = '\0';
224: if( 0 == end ) /* Don't allow an empty name. */
225: {
226: rprintf(FLOG, "%s Empty section name in configuration file.\n", func );
227: return( False );
228: }
229: if( !sfunc( bufr ) ) /* Got a valid name. Deal with it. */
230: return( False );
231: (void)EatComment( InFile ); /* Finish off the line. */
232: return( True );
233:
234: case '\n': /* Got newline before closing ']'. */
235: i = Continuation( bufr, i ); /* Check for line continuation. */
236: if( i < 0 )
237: {
238: bufr[end] = '\0';
239: rprintf(FLOG, "%s Badly formed line in configuration file: %s\n",
240: func, bufr );
241: return( False );
242: }
243: end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i);
244: c = getc( InFile ); /* Continue with next line. */
245: break;
246:
247: default: /* All else are a valid name chars. */
248: if( isspace( c ) ) /* One space per whitespace region. */
249: {
250: bufr[end] = ' ';
251: i = end + 1;
252: c = EatWhitespace( InFile );
253: }
254: else /* All others copy verbatim. */
255: {
256: bufr[i++] = c;
257: end = i;
258: c = getc( InFile );
259: }
260: }
261: }
262:
263: /* We arrive here if we've met the EOF before the closing bracket. */
264: rprintf(FLOG, "%s Unexpected EOF in the configuration file: %s\n", func, bufr );
265: return( False );
266: } /* Section */
267:
268: static BOOL Parameter( FILE *InFile, BOOL (*pfunc)(char *, char *), int c )
269: /* ------------------------------------------------------------------------ **
270: * Scan a parameter name and value, and pass these two fields to pfunc().
271: *
272: * Input: InFile - The input source.
273: * pfunc - A pointer to the function that will be called to
274: * process the parameter, once it has been scanned.
275: * c - The first character of the parameter name, which
276: * would have been read by Parse(). Unlike a comment
277: * line or a section header, there is no lead-in
278: * character that can be discarded.
279: *
280: * Output: True if the parameter name and value were scanned and processed
281: * successfully, else False.
282: *
283: * Notes: This function is in two parts. The first loop scans the
284: * parameter name. Internal whitespace is compressed, and an
285: * equal sign (=) terminates the token. Leading and trailing
286: * whitespace is discarded. The second loop scans the parameter
287: * value. When both have been successfully identified, they are
288: * passed to pfunc() for processing.
289: *
290: * ------------------------------------------------------------------------ **
291: */
292: {
293: int i = 0; /* Position within bufr. */
294: int end = 0; /* bufr[end] is current end-of-string. */
295: int vstart = 0; /* Starting position of the parameter value. */
296: char *func = "params.c:Parameter() -";
297:
298: /* Read the parameter name. */
299: while( 0 == vstart ) /* Loop until we've found the start of the value. */
300: {
301:
302: if( i > (bSize - 2) ) /* Ensure there's space for next char. */
303: {
304: bSize += BUFR_INC;
305: bufr = realloc_array( bufr, char, bSize );
306: if( NULL == bufr )
307: {
308: rprintf(FLOG, "%s Memory re-allocation failure.", func) ;
309: return( False );
310: }
311: }
312:
313: switch( c )
314: {
315: case '=': /* Equal sign marks end of param name. */
316: if( 0 == end ) /* Don't allow an empty name. */
317: {
318: rprintf(FLOG, "%s Invalid parameter name in config. file.\n", func );
319: return( False );
320: }
321: bufr[end++] = '\0'; /* Mark end of string & advance. */
322: i = end; /* New string starts here. */
323: vstart = end; /* New string is parameter value. */
324: bufr[i] = '\0'; /* New string is nul, for now. */
325: break;
326:
327: case '\n': /* Find continuation char, else error. */
328: i = Continuation( bufr, i );
329: if( i < 0 )
330: {
331: bufr[end] = '\0';
332: rprintf(FLOG, "%s Ignoring badly formed line in configuration file: %s\n",
333: func, bufr );
334: return( True );
335: }
336: end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i);
337: c = getc( InFile ); /* Read past eoln. */
338: break;
339:
340: case '\0': /* Shouldn't have EOF within param name. */
341: case EOF:
342: bufr[i] = '\0';
343: rprintf(FLOG, "%s Unexpected end-of-file at: %s\n", func, bufr );
344: return( True );
345:
346: default:
347: if( isspace( c ) ) /* One ' ' per whitespace region. */
348: {
349: bufr[end] = ' ';
350: i = end + 1;
351: c = EatWhitespace( InFile );
352: }
353: else /* All others verbatim. */
354: {
355: bufr[i++] = c;
356: end = i;
357: c = getc( InFile );
358: }
359: }
360: }
361:
362: /* Now parse the value. */
363: c = EatWhitespace( InFile ); /* Again, trim leading whitespace. */
364: while( (EOF !=c) && (c > 0) )
365: {
366:
367: if( i > (bSize - 2) ) /* Make sure there's enough room. */
368: {
369: bSize += BUFR_INC;
370: bufr = realloc_array( bufr, char, bSize );
371: if( NULL == bufr )
372: {
373: rprintf(FLOG, "%s Memory re-allocation failure.", func) ;
374: return( False );
375: }
376: }
377:
378: switch( c )
379: {
380: case '\r': /* Explicitly remove '\r' because the older */
381: c = getc( InFile ); /* version called fgets_slash() which also */
382: break; /* removes them. */
383:
384: case '\n': /* Marks end of value unless there's a '\'. */
385: i = Continuation( bufr, i );
386: if( i < 0 )
387: c = 0;
388: else
389: {
390: for( end = i; end >= 0 && isSpace(bufr + end); end-- )
391: ;
392: c = getc( InFile );
393: }
394: break;
395:
396: default: /* All others verbatim. Note that spaces do */
397: bufr[i++] = c; /* not advance <end>. This allows trimming */
398: if( !isspace( c ) ) /* of whitespace at the end of the line. */
399: end = i;
400: c = getc( InFile );
401: break;
402: }
403: }
404: bufr[end] = '\0'; /* End of value. */
405:
406: return( pfunc( bufr, &bufr[vstart] ) ); /* Pass name & value to pfunc(). */
407: } /* Parameter */
408:
409: static BOOL Parse( FILE *InFile,
410: BOOL (*sfunc)(char *),
411: BOOL (*pfunc)(char *, char *) )
412: /* ------------------------------------------------------------------------ **
413: * Scan & parse the input.
414: *
415: * Input: InFile - Input source.
416: * sfunc - Function to be called when a section name is scanned.
417: * See Section().
418: * pfunc - Function to be called when a parameter is scanned.
419: * See Parameter().
420: *
421: * Output: True if the file was successfully scanned, else False.
422: *
423: * Notes: The input can be viewed in terms of 'lines'. There are four
424: * types of lines:
425: * Blank - May contain whitespace, otherwise empty.
426: * Comment - First non-whitespace character is a ';' or '#'.
427: * The remainder of the line is ignored.
428: * Section - First non-whitespace character is a '['.
429: * Parameter - The default case.
430: *
431: * ------------------------------------------------------------------------ **
432: */
433: {
434: int c;
435:
436: c = EatWhitespace( InFile );
437: while( (EOF != c) && (c > 0) )
438: {
439: switch( c )
440: {
441: case '\n': /* Blank line. */
442: c = EatWhitespace( InFile );
443: break;
444:
445: case ';': /* Comment line. */
446: case '#':
447: c = EatComment( InFile );
448: break;
449:
450: case '[': /* Section Header. */
451: if (!sfunc) return True;
452: if( !Section( InFile, sfunc ) )
453: return( False );
454: c = EatWhitespace( InFile );
455: break;
456:
457: case '\\': /* Bogus backslash. */
458: c = EatWhitespace( InFile );
459: break;
460:
461: default: /* Parameter line. */
462: if( !Parameter( InFile, pfunc, c ) )
463: return( False );
464: c = EatWhitespace( InFile );
465: break;
466: }
467: }
468: return( True );
469: } /* Parse */
470:
471: static FILE *OpenConfFile( char *FileName )
472: /* ------------------------------------------------------------------------ **
473: * Open a configuration file.
474: *
475: * Input: FileName - The pathname of the config file to be opened.
476: *
477: * Output: A pointer of type (FILE *) to the opened file, or NULL if the
478: * file could not be opened.
479: *
480: * ------------------------------------------------------------------------ **
481: */
482: {
483: FILE *OpenedFile;
484: char *func = "params.c:OpenConfFile() -";
485:
486: if( NULL == FileName || 0 == *FileName )
487: {
488: rprintf(FLOG, "%s No configuration filename specified.\n", func);
489: return( NULL );
490: }
491:
492: OpenedFile = fopen( FileName, "r" );
493: if( NULL == OpenedFile )
494: {
495: rsyserr(FLOG, errno, "unable to open configuration file \"%s\"",
496: FileName);
497: }
498:
499: return( OpenedFile );
500: } /* OpenConfFile */
501:
502: BOOL pm_process( char *FileName,
503: BOOL (*sfunc)(char *),
504: BOOL (*pfunc)(char *, char *) )
505: /* ------------------------------------------------------------------------ **
506: * Process the named parameter file.
507: *
508: * Input: FileName - The pathname of the parameter file to be opened.
509: * sfunc - A pointer to a function that will be called when
510: * a section name is discovered.
511: * pfunc - A pointer to a function that will be called when
512: * a parameter name and value are discovered.
513: *
514: * Output: TRUE if the file was successfully parsed, else FALSE.
515: *
516: * ------------------------------------------------------------------------ **
517: */
518: {
519: int result;
520: FILE *InFile;
521: char *func = "params.c:pm_process() -";
522:
523: InFile = OpenConfFile( FileName ); /* Open the config file. */
524: if( NULL == InFile )
525: return( False );
526:
527: if( NULL != bufr ) /* If we already have a buffer */
528: result = Parse( InFile, sfunc, pfunc ); /* (recursive call), then just */
529: /* use it. */
530:
531: else /* If we don't have a buffer */
532: { /* allocate one, then parse, */
533: bSize = BUFR_INC; /* then free. */
534: bufr = new_array( char, bSize );
535: if( NULL == bufr )
536: {
537: rprintf(FLOG, "%s memory allocation failure.\n", func);
538: fclose(InFile);
539: return( False );
540: }
541: result = Parse( InFile, sfunc, pfunc );
542: free( bufr );
543: bufr = NULL;
544: bSize = 0;
545: }
546:
547: fclose(InFile);
548:
549: if( !result ) /* Generic failure. */
550: {
551: rprintf(FLOG, "%s Failed. Error returned from params.c:parse().\n", func);
552: return( False );
553: }
554:
555: return( True ); /* Generic success. */
556: } /* pm_process */
557:
558: /* -------------------------------------------------------------------------- */
559:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>