]> granicus.if.org Git - postgresql/blob - src/backend/utils/misc/guc-file.l
Rewrite ProcessConfigFile() to avoid misbehavior at EOF, as per report
[postgresql] / src / backend / utils / misc / guc-file.l
1 /* -*-pgsql-c-*- */
2 /*
3  * Scanner for the configuration file
4  *
5  * Copyright (c) 2000-2005, PostgreSQL Global Development Group
6  *
7  * $PostgreSQL: pgsql/src/backend/utils/misc/guc-file.l,v 1.33 2006/01/01 19:52:40 tgl Exp $
8  */
9
10 %{
11
12 #include "postgres.h"
13
14 #include <unistd.h>
15 #include <ctype.h>
16
17 #include "miscadmin.h"
18 #include "storage/fd.h"
19 #include "utils/guc.h"
20
21 /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
22 #undef fprintf
23 #define fprintf(file, fmt, msg)  ereport(ERROR, (errmsg_internal("%s", msg)))
24
25 static unsigned ConfigFileLineno;
26
27 enum {
28         GUC_ID = 1,
29         GUC_STRING = 2,
30         GUC_INTEGER = 3,
31         GUC_REAL = 4,
32         GUC_EQUALS = 5,
33         GUC_UNQUOTED_STRING = 6,
34         GUC_QUALIFIED_ID = 7,
35         GUC_EOL = 99,
36         GUC_ERROR = 100
37 };
38
39 /* prototype, so compiler is happy with our high warnings setting */
40 int GUC_yylex(void);
41 static char *GUC_scanstr(const char *s);
42 %}
43
44 %option 8bit
45 %option never-interactive
46 %option nodefault
47 %option nounput
48 %option noyywrap
49
50
51 SIGN            ("-"|"+")
52 DIGIT           [0-9]
53 HEXDIGIT        [0-9a-fA-F]
54
55 INTEGER         {SIGN}?({DIGIT}+|0x{HEXDIGIT}+)
56
57 EXPONENT        [Ee]{SIGN}?{DIGIT}+
58 REAL            {SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?
59
60 LETTER          [A-Za-z_\200-\377]
61 LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
62
63 ID              {LETTER}{LETTER_OR_DIGIT}*
64 QUALIFIED_ID    {ID}"."{ID}
65
66 UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
67 STRING          \'([^'\\\n]|\\.|\'\')*\'
68
69 %%
70
71 \n              ConfigFileLineno++; return GUC_EOL;
72 [ \t\r]+        /* eat whitespace */
73 #.*             /* eat comment (.* matches anything until newline) */
74
75 {ID}            return GUC_ID;
76 {QUALIFIED_ID}  return GUC_QUALIFIED_ID;
77 {STRING}        return GUC_STRING;
78 {UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
79 {INTEGER}       return GUC_INTEGER;
80 {REAL}          return GUC_REAL;
81 =               return GUC_EQUALS;
82
83 .               return GUC_ERROR;
84
85 %%
86
87
88 struct name_value_pair
89 {
90         char       *name;
91         char       *value;
92         struct name_value_pair *next;
93 };
94
95
96 /*
97  * Free a list of name/value pairs, including the names and the values
98  */
99 static void
100 free_name_value_list(struct name_value_pair * list)
101 {
102         struct name_value_pair *item;
103
104         item = list;
105         while (item)
106         {
107                 struct name_value_pair *save;
108
109                 save = item->next;
110                 pfree(item->name);
111                 pfree(item->value);
112                 pfree(item);
113                 item = save;
114         }
115 }
116
117
118 /*
119  * Official function to read and process the configuration file. The
120  * parameter indicates in what context the file is being read --- either
121  * postmaster startup (including standalone-backend startup) or SIGHUP.
122  * All options mentioned in the configuration file are set to new values.
123  * If an error occurs, no values will be changed.
124  */
125 void
126 ProcessConfigFile(GucContext context)
127 {
128         int                     elevel;
129         int                     token;
130         char       *opt_name, *opt_value;
131         struct name_value_pair *item, *head, *tail;
132         FILE       *fp;
133
134         Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP);
135
136         if (context == PGC_SIGHUP)
137         {
138                 /*
139                  * To avoid cluttering the log, only the postmaster bleats loudly
140                  * about problems with the config file.
141                  */
142                 elevel = IsUnderPostmaster ? DEBUG2 : LOG;
143         }
144         else
145                 elevel = ERROR;
146
147         fp = AllocateFile(ConfigFileName, "r");
148         if (!fp)
149         {
150                 ereport(elevel,
151                                 (errcode_for_file_access(),
152                                  errmsg("could not open configuration file \"%s\": %m",
153                                                 ConfigFileName)));
154                 return;
155         }
156
157         /*
158          * Parse
159          */
160         yyin = fp;
161         YY_FLUSH_BUFFER;                        /* in case we abandoned a prior scan */
162         head = tail = NULL;
163         opt_name = opt_value = NULL;
164         ConfigFileLineno = 1;
165
166         /* This loop iterates once per logical line */
167         while ((token = yylex()))
168         {
169                 if (token == GUC_EOL)   /* empty or comment line */
170                         continue;
171
172                 /* first token on line is option name */
173                 if (token != GUC_ID && token != GUC_QUALIFIED_ID)
174                         goto parse_error;
175                 opt_name = pstrdup(yytext);
176
177                 /* next we have an optional equal sign; discard if present */
178                 token = yylex();
179                 if (token == GUC_EQUALS)
180                         token = yylex();
181
182                 /* now we must have the option value */
183                 if (token != GUC_ID &&
184                         token != GUC_STRING && 
185                         token != GUC_INTEGER && 
186                         token != GUC_REAL && 
187                         token != GUC_UNQUOTED_STRING)
188                         goto parse_error;
189                 if (token == GUC_STRING)        /* strip quotes and escapes */
190                         opt_value = GUC_scanstr(yytext);
191                 else
192                         opt_value = pstrdup(yytext);
193
194                 /* now we'd like an end of line, or possibly EOF */
195                 token = yylex();
196                 if (token != GUC_EOL && token != 0)
197                         goto parse_error;
198
199                 /* OK, save the option name and value */
200                 if (strcmp(opt_name, "custom_variable_classes") == 0)
201                 {
202                         /*
203                          * This variable must be processed first as it controls
204                          * the validity of other variables; so apply immediately.
205                          */
206                         if (!set_config_option(opt_name, opt_value, context,
207                                                                    PGC_S_FILE, false, true))
208                         {
209                                 pfree(opt_name);
210                                 pfree(opt_value);
211                                 FreeFile(fp);
212                                 goto cleanup_exit;
213                         }
214                         pfree(opt_name);
215                         pfree(opt_value);
216                 }
217                 else
218                 {
219                         /* append to list */
220                         item = palloc(sizeof *item);
221                         item->name = opt_name;
222                         item->value = opt_value;
223                         item->next = NULL;
224                         if (!head)
225                                 head = item;
226                         else
227                                 tail->next = item;
228                         tail = item;
229                 }
230
231                 /* break out of loop if read EOF, else loop for next line */
232                 if (token == 0)
233                         break;
234         }
235
236         FreeFile(fp);
237
238         /*
239          * Check if all options are valid
240          */
241         for(item = head; item; item=item->next)
242         {
243                 if (!set_config_option(item->name, item->value, context,
244                                                            PGC_S_FILE, false, false))
245                         goto cleanup_exit;
246         }
247
248         /* If we got here all the options parsed okay, so apply them. */
249         for(item = head; item; item=item->next)
250         {
251                 set_config_option(item->name, item->value, context,
252                                                   PGC_S_FILE, false, true);
253         }
254
255  cleanup_exit:
256         free_name_value_list(head);
257         return;
258
259  parse_error:
260         FreeFile(fp);
261         free_name_value_list(head);
262         if (token == GUC_EOL || token == 0)
263                 ereport(elevel,
264                                 (errcode(ERRCODE_SYNTAX_ERROR),
265                                  errmsg("syntax error in file \"%s\" line %u, near end of line",
266                                                 ConfigFileName, ConfigFileLineno - 1)));
267         else
268                 ereport(elevel,
269                                 (errcode(ERRCODE_SYNTAX_ERROR),
270                                  errmsg("syntax error in file \"%s\" line %u, near token \"%s\"", 
271                                                 ConfigFileName, ConfigFileLineno, yytext)));
272 }
273
274
275
276 /*
277  *              scanstr
278  *
279  * Strip the quotes surrounding the given string, and collapse any embedded
280  * '' sequences and backslash escapes.
281  *
282  * the string returned is palloc'd and should eventually be pfree'd by the
283  * caller.
284  */
285 static char *
286 GUC_scanstr(const char *s)
287 {
288         char       *newStr;
289         int                     len,
290                                 i,
291                                 j;
292
293         Assert(s != NULL && s[0] == '\'');
294         len = strlen(s);
295         Assert(len >= 2);
296         Assert(s[len-1] == '\'');
297
298         /* Skip the leading quote; we'll handle the trailing quote below */
299         s++, len--;
300
301         /* Since len still includes trailing quote, this is enough space */
302         newStr = palloc(len);
303
304         for (i = 0, j = 0; i < len; i++)
305         {
306                 if (s[i] == '\\')
307                 {
308                         i++;
309                         switch (s[i])
310                         {
311                                 case 'b':
312                                         newStr[j] = '\b';
313                                         break;
314                                 case 'f':
315                                         newStr[j] = '\f';
316                                         break;
317                                 case 'n':
318                                         newStr[j] = '\n';
319                                         break;
320                                 case 'r':
321                                         newStr[j] = '\r';
322                                         break;
323                                 case 't':
324                                         newStr[j] = '\t';
325                                         break;
326                                 case '0':
327                                 case '1':
328                                 case '2':
329                                 case '3':
330                                 case '4':
331                                 case '5':
332                                 case '6':
333                                 case '7':
334                                         {
335                                                 int                     k;
336                                                 long            octVal = 0;
337
338                                                 for (k = 0;
339                                                          s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
340                                                          k++)
341                                                         octVal = (octVal << 3) + (s[i + k] - '0');
342                                                 i += k - 1;
343                                                 newStr[j] = ((char) octVal);
344                                         }
345                                         break;
346                                 default:
347                                         newStr[j] = s[i];
348                                         break;
349                         }                                       /* switch */
350                 }
351                 else if (s[i] == '\'' && s[i+1] == '\'')
352                 {
353                         /* doubled quote becomes just one quote */
354                         newStr[j] = s[++i];
355                 }
356                 else
357                         newStr[j] = s[i];
358                 j++;
359         }
360
361         /* We copied the ending quote to newStr, so replace with \0 */
362         Assert(j > 0 && j <= len);
363         newStr[--j] = '\0';
364
365         return newStr;
366 }