Line data Source code
1 : %top{
2 : /*
3 : * Scanner for the configuration file
4 : *
5 : * Copyright (c) 2000-2025, PostgreSQL Global Development Group
6 : *
7 : * src/backend/utils/misc/guc-file.l
8 : */
9 :
10 : #include "postgres.h"
11 :
12 : #include <ctype.h>
13 : #include <unistd.h>
14 :
15 : #include "common/file_utils.h"
16 : #include "guc_internal.h"
17 : #include "mb/pg_wchar.h"
18 : #include "miscadmin.h"
19 : #include "storage/fd.h"
20 : #include "utils/conffiles.h"
21 : #include "utils/memutils.h"
22 : }
23 :
24 :
25 : %{
26 : /*
27 : * flex emits a yy_fatal_error() function that it calls in response to
28 : * critical errors like malloc failure, file I/O errors, and detection of
29 : * internal inconsistency. That function prints a message and calls exit().
30 : * Mutate it to instead call our handler, which jumps out of the parser.
31 : */
32 : #undef fprintf
33 : #define fprintf(file, fmt, msg) GUC_flex_fatal(msg)
34 :
35 : enum
36 : {
37 : GUC_ID = 1,
38 : GUC_STRING = 2,
39 : GUC_INTEGER = 3,
40 : GUC_REAL = 4,
41 : GUC_EQUALS = 5,
42 : GUC_UNQUOTED_STRING = 6,
43 : GUC_QUALIFIED_ID = 7,
44 : GUC_EOL = 99,
45 : GUC_ERROR = 100
46 : };
47 :
48 : static unsigned int ConfigFileLineno;
49 : static const char *GUC_flex_fatal_errmsg;
50 : static sigjmp_buf *GUC_flex_fatal_jmp;
51 :
52 : static void FreeConfigVariable(ConfigVariable *item);
53 :
54 : static int GUC_flex_fatal(const char *msg);
55 :
56 : /* LCOV_EXCL_START */
57 :
58 : %}
59 :
60 : %option reentrant
61 : %option 8bit
62 : %option never-interactive
63 : %option nodefault
64 : %option noinput
65 : %option nounput
66 : %option noyywrap
67 : %option warn
68 : %option prefix="GUC_yy"
69 :
70 :
71 : SIGN ("-"|"+")
72 : DIGIT [0-9]
73 : HEXDIGIT [0-9a-fA-F]
74 :
75 : UNIT_LETTER [a-zA-Z]
76 :
77 : INTEGER {SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}*
78 :
79 : EXPONENT [Ee]{SIGN}?{DIGIT}+
80 : REAL {SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?
81 :
82 : LETTER [A-Za-z_\200-\377]
83 : LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
84 :
85 : ID {LETTER}{LETTER_OR_DIGIT}*
86 : QUALIFIED_ID {ID}"."{ID}
87 :
88 : UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
89 : STRING \'([^'\\\n]|\\.|\'\')*\'
90 :
91 : %%
92 :
93 : \n ConfigFileLineno++; return GUC_EOL;
94 : [ \t\r]+ /* eat whitespace */
95 : #.* /* eat comment (.* matches anything until newline) */
96 :
97 : {ID} return GUC_ID;
98 : {QUALIFIED_ID} return GUC_QUALIFIED_ID;
99 : {STRING} return GUC_STRING;
100 : {UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
101 : {INTEGER} return GUC_INTEGER;
102 : {REAL} return GUC_REAL;
103 : = return GUC_EQUALS;
104 :
105 : . return GUC_ERROR;
106 :
107 : %%
108 :
109 : /* LCOV_EXCL_STOP */
110 :
111 : /*
112 : * Exported function to read and process the configuration file. The
113 : * parameter indicates in what context the file is being read --- either
114 : * postmaster startup (including standalone-backend startup) or SIGHUP.
115 : * All options mentioned in the configuration file are set to new values.
116 : * If a hard error occurs, no values will be changed. (There can also be
117 : * errors that prevent just one value from being changed.)
118 : */
119 : void
120 4682 : ProcessConfigFile(GucContext context)
121 : {
122 : int elevel;
123 : MemoryContext config_cxt;
124 : MemoryContext caller_cxt;
125 :
126 : /*
127 : * Config files are processed on startup (by the postmaster only) and on
128 : * SIGHUP (by the postmaster and its children)
129 : */
130 : Assert((context == PGC_POSTMASTER && !IsUnderPostmaster) ||
131 : context == PGC_SIGHUP);
132 :
133 : /*
134 : * To avoid cluttering the log, only the postmaster bleats loudly about
135 : * problems with the config file.
136 : */
137 4682 : elevel = IsUnderPostmaster ? DEBUG2 : LOG;
138 :
139 : /*
140 : * This function is usually called within a process-lifespan memory
141 : * context. To ensure that any memory leaked during GUC processing does
142 : * not accumulate across repeated SIGHUP cycles, do the work in a private
143 : * context that we can free at exit.
144 : */
145 4682 : config_cxt = AllocSetContextCreate(CurrentMemoryContext,
146 : "config file processing",
147 : ALLOCSET_DEFAULT_SIZES);
148 4682 : caller_cxt = MemoryContextSwitchTo(config_cxt);
149 :
150 : /*
151 : * Read and apply the config file. We don't need to examine the result.
152 : */
153 4682 : (void) ProcessConfigFileInternal(context, true, elevel);
154 :
155 : /* Clean up */
156 4678 : MemoryContextSwitchTo(caller_cxt);
157 4678 : MemoryContextDelete(config_cxt);
158 4678 : }
159 :
160 : /*
161 : * Read and parse a single configuration file. This function recurses
162 : * to handle "include" directives.
163 : *
164 : * If "strict" is true, treat failure to open the config file as an error,
165 : * otherwise just skip the file.
166 : *
167 : * calling_file/calling_lineno identify the source of the request.
168 : * Pass NULL/0 if not recursing from an inclusion request.
169 : *
170 : * See ParseConfigFp for further details. This one merely adds opening the
171 : * config file rather than working from a caller-supplied file descriptor,
172 : * and absolute-ifying the path name if necessary.
173 : */
174 : bool
175 7554 : ParseConfigFile(const char *config_file, bool strict,
176 : const char *calling_file, int calling_lineno,
177 : int depth, int elevel,
178 : ConfigVariable **head_p,
179 : ConfigVariable **tail_p)
180 : {
181 : char *abs_path;
182 7554 : bool OK = true;
183 : FILE *fp;
184 :
185 : /*
186 : * Reject file name that is all-blank (including empty), as that leads to
187 : * confusion --- we'd try to read the containing directory as a file.
188 : */
189 7554 : if (strspn(config_file, " \t\r\n") == strlen(config_file))
190 : {
191 0 : ereport(elevel,
192 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
193 : errmsg("empty configuration file name: \"%s\"",
194 : config_file)));
195 0 : record_config_file_error("empty configuration file name",
196 : calling_file, calling_lineno,
197 : head_p, tail_p);
198 0 : return false;
199 : }
200 :
201 : /*
202 : * Reject too-deep include nesting depth. This is just a safety check to
203 : * avoid dumping core due to stack overflow if an include file loops back
204 : * to itself. The maximum nesting depth is pretty arbitrary.
205 : */
206 7554 : if (depth > CONF_FILE_MAX_DEPTH)
207 : {
208 0 : ereport(elevel,
209 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
210 : errmsg("could not open configuration file \"%s\": maximum nesting depth exceeded",
211 : config_file)));
212 0 : record_config_file_error("nesting depth exceeded",
213 : calling_file, calling_lineno,
214 : head_p, tail_p);
215 0 : return false;
216 : }
217 :
218 7554 : abs_path = AbsoluteConfigLocation(config_file, calling_file);
219 :
220 : /*
221 : * Reject direct recursion. Indirect recursion is also possible, but it's
222 : * harder to detect and so doesn't seem worth the trouble. (We test at
223 : * this step because the canonicalization done by AbsoluteConfigLocation
224 : * makes it more likely that a simple strcmp comparison will match.)
225 : */
226 7554 : if (calling_file && strcmp(abs_path, calling_file) == 0)
227 : {
228 0 : ereport(elevel,
229 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
230 : errmsg("configuration file recursion in \"%s\"",
231 : calling_file)));
232 0 : record_config_file_error("configuration file recursion",
233 : calling_file, calling_lineno,
234 : head_p, tail_p);
235 0 : pfree(abs_path);
236 0 : return false;
237 : }
238 :
239 7554 : fp = AllocateFile(abs_path, "r");
240 7554 : if (!fp)
241 : {
242 180 : if (strict)
243 : {
244 0 : ereport(elevel,
245 : (errcode_for_file_access(),
246 : errmsg("could not open configuration file \"%s\": %m",
247 : abs_path)));
248 0 : record_config_file_error(psprintf("could not open file \"%s\"",
249 : abs_path),
250 : calling_file, calling_lineno,
251 : head_p, tail_p);
252 0 : OK = false;
253 : }
254 : else
255 : {
256 180 : ereport(LOG,
257 : (errmsg("skipping missing configuration file \"%s\"",
258 : abs_path)));
259 : }
260 180 : goto cleanup;
261 : }
262 :
263 7374 : OK = ParseConfigFp(fp, abs_path, depth, elevel, head_p, tail_p);
264 :
265 7554 : cleanup:
266 7554 : if (fp)
267 7374 : FreeFile(fp);
268 7554 : pfree(abs_path);
269 :
270 7554 : return OK;
271 : }
272 :
273 : /*
274 : * Capture an error message in the ConfigVariable list returned by
275 : * config file parsing.
276 : */
277 : void
278 0 : record_config_file_error(const char *errmsg,
279 : const char *config_file,
280 : int lineno,
281 : ConfigVariable **head_p,
282 : ConfigVariable **tail_p)
283 : {
284 : ConfigVariable *item;
285 :
286 0 : item = palloc(sizeof *item);
287 0 : item->name = NULL;
288 0 : item->value = NULL;
289 0 : item->errmsg = pstrdup(errmsg);
290 0 : item->filename = config_file ? pstrdup(config_file) : NULL;
291 0 : item->sourceline = lineno;
292 0 : item->ignore = true;
293 0 : item->applied = false;
294 0 : item->next = NULL;
295 0 : if (*head_p == NULL)
296 0 : *head_p = item;
297 : else
298 0 : (*tail_p)->next = item;
299 0 : *tail_p = item;
300 0 : }
301 :
302 : /*
303 : * Flex fatal errors bring us here. Stash the error message and jump back to
304 : * ParseConfigFp(). Assume all msg arguments point to string constants; this
305 : * holds for all currently known flex versions. Otherwise, we would need to
306 : * copy the message.
307 : *
308 : * We return "int" since this takes the place of calls to fprintf().
309 : */
310 : static int
311 0 : GUC_flex_fatal(const char *msg)
312 : {
313 0 : GUC_flex_fatal_errmsg = msg;
314 0 : siglongjmp(*GUC_flex_fatal_jmp, 1);
315 : return 0; /* keep compiler quiet */
316 : }
317 :
318 : /*
319 : * Read and parse a single configuration file. This function recurses
320 : * to handle "include" directives.
321 : *
322 : * Input parameters:
323 : * fp: file pointer from AllocateFile for the configuration file to parse
324 : * config_file: absolute or relative path name of the configuration file
325 : * depth: recursion depth (should be CONF_FILE_START_DEPTH in the outermost
326 : * call)
327 : * elevel: error logging level to use
328 : * Input/Output parameters:
329 : * head_p, tail_p: head and tail of linked list of name/value pairs
330 : *
331 : * *head_p and *tail_p must be initialized, either to NULL or valid pointers
332 : * to a ConfigVariable list, before calling the outer recursion level. Any
333 : * name-value pairs read from the input file(s) will be appended to the list.
334 : * Error reports will also be appended to the list, if elevel < ERROR.
335 : *
336 : * Returns TRUE if successful, FALSE if an error occurred. The error has
337 : * already been ereport'd, it is only necessary for the caller to clean up
338 : * its own state and release the ConfigVariable list.
339 : *
340 : * Note: if elevel >= ERROR then an error will not return control to the
341 : * caller, so there is no need to check the return value in that case.
342 : *
343 : * Note: this function is used to parse not only postgresql.conf, but
344 : * various other configuration files that use the same "name = value"
345 : * syntax. Hence, do not do anything here or in the subsidiary routines
346 : * ParseConfigFile/ParseConfigDirectory that assumes we are processing
347 : * GUCs specifically.
348 : */
349 : bool
350 12426 : ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
351 : ConfigVariable **head_p, ConfigVariable **tail_p)
352 : {
353 12426 : volatile bool OK = true;
354 12426 : unsigned int save_ConfigFileLineno = ConfigFileLineno;
355 12426 : sigjmp_buf *save_GUC_flex_fatal_jmp = GUC_flex_fatal_jmp;
356 : sigjmp_buf flex_fatal_jmp;
357 : yyscan_t scanner;
358 : struct yyguts_t *yyg; /* needed for yytext macro */
359 12426 : volatile YY_BUFFER_STATE lex_buffer = NULL;
360 : int errorcount;
361 : int token;
362 :
363 12426 : if (sigsetjmp(flex_fatal_jmp, 1) == 0)
364 12426 : GUC_flex_fatal_jmp = &flex_fatal_jmp;
365 : else
366 : {
367 : /*
368 : * Regain control after a fatal, internal flex error. It may have
369 : * corrupted parser state. Consequently, abandon the file, but trust
370 : * that the state remains sane enough for yy_delete_buffer().
371 : */
372 0 : elog(elevel, "%s at file \"%s\" line %u",
373 : GUC_flex_fatal_errmsg, config_file, ConfigFileLineno);
374 0 : record_config_file_error(GUC_flex_fatal_errmsg,
375 : config_file, ConfigFileLineno,
376 : head_p, tail_p);
377 0 : OK = false;
378 0 : goto cleanup;
379 : }
380 :
381 : /*
382 : * Parse
383 : */
384 12426 : ConfigFileLineno = 1;
385 12426 : errorcount = 0;
386 :
387 12426 : if (yylex_init(&scanner) != 0)
388 0 : elog(elevel, "yylex_init() failed: %m");
389 12426 : yyg = (struct yyguts_t *) scanner;
390 :
391 12426 : lex_buffer = yy_create_buffer(fp, YY_BUF_SIZE, scanner);
392 12426 : yy_switch_to_buffer(lex_buffer, scanner);
393 :
394 : /* This loop iterates once per logical line */
395 3810116 : while ((token = yylex(scanner)))
396 : {
397 3797690 : char *opt_name = NULL;
398 3797690 : char *opt_value = NULL;
399 : ConfigVariable *item;
400 :
401 3797690 : if (token == GUC_EOL) /* empty or comment line */
402 3645030 : continue;
403 :
404 : /* first token on line is option name */
405 152660 : if (token != GUC_ID && token != GUC_QUALIFIED_ID)
406 0 : goto parse_error;
407 152660 : opt_name = pstrdup(yytext);
408 :
409 : /* next we have an optional equal sign; discard if present */
410 152660 : token = yylex(scanner);
411 152660 : if (token == GUC_EQUALS)
412 152548 : token = yylex(scanner);
413 :
414 : /* now we must have the option value */
415 152660 : if (token != GUC_ID &&
416 44668 : token != GUC_STRING &&
417 200 : token != GUC_INTEGER &&
418 200 : token != GUC_REAL &&
419 : token != GUC_UNQUOTED_STRING)
420 0 : goto parse_error;
421 152660 : if (token == GUC_STRING) /* strip quotes and escapes */
422 64832 : opt_value = DeescapeQuotedString(yytext);
423 : else
424 87828 : opt_value = pstrdup(yytext);
425 :
426 : /* now we'd like an end of line, or possibly EOF */
427 152660 : token = yylex(scanner);
428 152660 : if (token != GUC_EOL)
429 : {
430 0 : if (token != 0)
431 0 : goto parse_error;
432 : /* treat EOF like \n for line numbering purposes, cf bug 4752 */
433 0 : ConfigFileLineno++;
434 : }
435 :
436 : /* OK, process the option name and value */
437 152660 : if (guc_name_compare(opt_name, "include_dir") == 0)
438 : {
439 : /*
440 : * An include_dir directive isn't a variable and should be
441 : * processed immediately.
442 : */
443 0 : if (!ParseConfigDirectory(opt_value,
444 0 : config_file, ConfigFileLineno - 1,
445 : depth + 1, elevel,
446 : head_p, tail_p))
447 0 : OK = false;
448 0 : yy_switch_to_buffer(lex_buffer, scanner);
449 0 : pfree(opt_name);
450 0 : pfree(opt_value);
451 : }
452 152660 : else if (guc_name_compare(opt_name, "include_if_exists") == 0)
453 : {
454 : /*
455 : * An include_if_exists directive isn't a variable and should be
456 : * processed immediately.
457 : */
458 0 : if (!ParseConfigFile(opt_value, false,
459 0 : config_file, ConfigFileLineno - 1,
460 : depth + 1, elevel,
461 : head_p, tail_p))
462 0 : OK = false;
463 0 : yy_switch_to_buffer(lex_buffer, scanner);
464 0 : pfree(opt_name);
465 0 : pfree(opt_value);
466 : }
467 152660 : else if (guc_name_compare(opt_name, "include") == 0)
468 : {
469 : /*
470 : * An include directive isn't a variable and should be processed
471 : * immediately.
472 : */
473 112 : if (!ParseConfigFile(opt_value, true,
474 112 : config_file, ConfigFileLineno - 1,
475 : depth + 1, elevel,
476 : head_p, tail_p))
477 0 : OK = false;
478 112 : yy_switch_to_buffer(lex_buffer, scanner);
479 112 : pfree(opt_name);
480 112 : pfree(opt_value);
481 : }
482 : else
483 : {
484 : /* ordinary variable, append to list */
485 152548 : item = palloc(sizeof *item);
486 152548 : item->name = opt_name;
487 152548 : item->value = opt_value;
488 152548 : item->errmsg = NULL;
489 152548 : item->filename = pstrdup(config_file);
490 152548 : item->sourceline = ConfigFileLineno - 1;
491 152548 : item->ignore = false;
492 152548 : item->applied = false;
493 152548 : item->next = NULL;
494 152548 : if (*head_p == NULL)
495 9338 : *head_p = item;
496 : else
497 143210 : (*tail_p)->next = item;
498 152548 : *tail_p = item;
499 : }
500 :
501 : /* break out of loop if read EOF, else loop for next line */
502 152660 : if (token == 0)
503 0 : break;
504 152660 : continue;
505 :
506 0 : parse_error:
507 : /* release storage if we allocated any on this line */
508 0 : if (opt_name)
509 0 : pfree(opt_name);
510 0 : if (opt_value)
511 0 : pfree(opt_value);
512 :
513 : /* report the error */
514 0 : if (token == GUC_EOL || token == 0)
515 : {
516 0 : ereport(elevel,
517 : (errcode(ERRCODE_SYNTAX_ERROR),
518 : errmsg("syntax error in file \"%s\" line %u, near end of line",
519 : config_file, ConfigFileLineno - 1)));
520 0 : record_config_file_error("syntax error",
521 0 : config_file, ConfigFileLineno - 1,
522 : head_p, tail_p);
523 : }
524 : else
525 : {
526 0 : ereport(elevel,
527 : (errcode(ERRCODE_SYNTAX_ERROR),
528 : errmsg("syntax error in file \"%s\" line %u, near token \"%s\"",
529 : config_file, ConfigFileLineno, yytext)));
530 0 : record_config_file_error("syntax error",
531 : config_file, ConfigFileLineno,
532 : head_p, tail_p);
533 : }
534 0 : OK = false;
535 0 : errorcount++;
536 :
537 : /*
538 : * To avoid producing too much noise when fed a totally bogus file,
539 : * give up after 100 syntax errors per file (an arbitrary number).
540 : * Also, if we're only logging the errors at DEBUG level anyway, might
541 : * as well give up immediately. (This prevents postmaster children
542 : * from bloating the logs with duplicate complaints.)
543 : */
544 0 : if (errorcount >= 100 || elevel <= DEBUG1)
545 : {
546 0 : ereport(elevel,
547 : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
548 : errmsg("too many syntax errors found, abandoning file \"%s\"",
549 : config_file)));
550 0 : break;
551 : }
552 :
553 : /* resync to next end-of-line or EOF */
554 0 : while (token != GUC_EOL && token != 0)
555 0 : token = yylex(scanner);
556 : /* break out of loop on EOF */
557 0 : if (token == 0)
558 0 : break;
559 : }
560 :
561 12426 : cleanup:
562 12426 : yy_delete_buffer(lex_buffer, scanner);
563 12426 : yylex_destroy(scanner);
564 : /* Each recursion level must save and restore these static variables. */
565 12426 : ConfigFileLineno = save_ConfigFileLineno;
566 12426 : GUC_flex_fatal_jmp = save_GUC_flex_fatal_jmp;
567 12426 : return OK;
568 : }
569 :
570 : /*
571 : * Read and parse all config files in a subdirectory in alphabetical order
572 : *
573 : * includedir is the absolute or relative path to the subdirectory to scan.
574 : *
575 : * calling_file/calling_lineno identify the source of the request.
576 : * Pass NULL/0 if not recursing from an inclusion request.
577 : *
578 : * See ParseConfigFp for further details.
579 : */
580 : bool
581 0 : ParseConfigDirectory(const char *includedir,
582 : const char *calling_file, int calling_lineno,
583 : int depth, int elevel,
584 : ConfigVariable **head_p,
585 : ConfigVariable **tail_p)
586 : {
587 : char *err_msg;
588 : char **filenames;
589 : int num_filenames;
590 :
591 0 : filenames = GetConfFilesInDir(includedir, calling_file, elevel,
592 : &num_filenames, &err_msg);
593 :
594 0 : if (!filenames)
595 : {
596 0 : record_config_file_error(err_msg, calling_file, calling_lineno, head_p,
597 : tail_p);
598 0 : return false;
599 : }
600 :
601 0 : for (int i = 0; i < num_filenames; i++)
602 : {
603 0 : if (!ParseConfigFile(filenames[i], true,
604 : calling_file, calling_lineno,
605 : depth, elevel,
606 : head_p, tail_p))
607 0 : return false;
608 : }
609 :
610 0 : return true;
611 : }
612 :
613 : /*
614 : * Free a list of ConfigVariables, including the names and the values
615 : */
616 : void
617 5052 : FreeConfigVariables(ConfigVariable *list)
618 : {
619 : ConfigVariable *item;
620 :
621 5052 : item = list;
622 27016 : while (item)
623 : {
624 21964 : ConfigVariable *next = item->next;
625 :
626 21964 : FreeConfigVariable(item);
627 21964 : item = next;
628 : }
629 5052 : }
630 :
631 : /*
632 : * Free a single ConfigVariable
633 : */
634 : static void
635 21964 : FreeConfigVariable(ConfigVariable *item)
636 : {
637 21964 : if (item->name)
638 21964 : pfree(item->name);
639 21964 : if (item->value)
640 21964 : pfree(item->value);
641 21964 : if (item->errmsg)
642 0 : pfree(item->errmsg);
643 21964 : if (item->filename)
644 21964 : pfree(item->filename);
645 21964 : pfree(item);
646 21964 : }
647 :
648 :
649 : /*
650 : * DeescapeQuotedString
651 : *
652 : * Strip the quotes surrounding the given string, and collapse any embedded
653 : * '' sequences and backslash escapes.
654 : *
655 : * The string returned is palloc'd and should eventually be pfree'd by the
656 : * caller.
657 : *
658 : * This is exported because it is also used by the bootstrap scanner.
659 : */
660 : char *
661 720320 : DeescapeQuotedString(const char *s)
662 : {
663 : char *newStr;
664 : int len,
665 : i,
666 : j;
667 :
668 : /* We just Assert that there are leading and trailing quotes */
669 : Assert(s != NULL && s[0] == '\'');
670 720320 : len = strlen(s);
671 : Assert(len >= 2);
672 : Assert(s[len - 1] == '\'');
673 :
674 : /* Skip the leading quote; we'll handle the trailing quote below */
675 720320 : s++, len--;
676 :
677 : /* Since len still includes trailing quote, this is enough space */
678 720320 : newStr = palloc(len);
679 :
680 13580574 : for (i = 0, j = 0; i < len; i++)
681 : {
682 12860254 : if (s[i] == '\\')
683 : {
684 0 : i++;
685 0 : switch (s[i])
686 : {
687 0 : case 'b':
688 0 : newStr[j] = '\b';
689 0 : break;
690 0 : case 'f':
691 0 : newStr[j] = '\f';
692 0 : break;
693 0 : case 'n':
694 0 : newStr[j] = '\n';
695 0 : break;
696 0 : case 'r':
697 0 : newStr[j] = '\r';
698 0 : break;
699 0 : case 't':
700 0 : newStr[j] = '\t';
701 0 : break;
702 0 : case '0':
703 : case '1':
704 : case '2':
705 : case '3':
706 : case '4':
707 : case '5':
708 : case '6':
709 : case '7':
710 : {
711 : int k;
712 0 : long octVal = 0;
713 :
714 0 : for (k = 0;
715 0 : s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
716 0 : k++)
717 0 : octVal = (octVal << 3) + (s[i + k] - '0');
718 0 : i += k - 1;
719 0 : newStr[j] = ((char) octVal);
720 : }
721 0 : break;
722 0 : default:
723 0 : newStr[j] = s[i];
724 0 : break;
725 : } /* switch */
726 : }
727 12860254 : else if (s[i] == '\'' && s[i + 1] == '\'')
728 : {
729 : /* doubled quote becomes just one quote */
730 5636 : newStr[j] = s[++i];
731 : }
732 : else
733 12854618 : newStr[j] = s[i];
734 12860254 : j++;
735 : }
736 :
737 : /* We copied the ending quote to newStr, so replace with \0 */
738 : Assert(j > 0 && j <= len);
739 720320 : newStr[--j] = '\0';
740 :
741 720320 : return newStr;
742 : }
|