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