Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * filter.c
4 : : * Implementation of simple filter file parser
5 : : *
6 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/bin/pg_dump/filter.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres_fe.h"
15 : :
16 : : #include "common/logging.h"
17 : : #include "common/string.h"
18 : : #include "filter.h"
19 : : #include "lib/stringinfo.h"
20 : : #include "pqexpbuffer.h"
21 : :
22 : : #define is_keyword_str(cstr, str, bytes) \
23 : : ((strlen(cstr) == (bytes)) && (pg_strncasecmp((cstr), (str), (bytes)) == 0))
24 : :
25 : : /*
26 : : * Following routines are called from pg_dump, pg_dumpall and pg_restore.
27 : : * Since the implementation of exit_nicely is application specific, each
28 : : * application need to pass a function pointer to the exit_nicely function to
29 : : * use for exiting on errors.
30 : : */
31 : :
32 : : /*
33 : : * Opens filter's file and initialize fstate structure.
34 : : */
35 : : void
647 dgustafsson@postgres 36 :CBC 41 : filter_init(FilterStateData *fstate, const char *filename, exit_function f_exit)
37 : : {
38 : 41 : fstate->filename = filename;
39 : 41 : fstate->lineno = 0;
40 : 41 : fstate->exit_nicely = f_exit;
41 : 41 : initStringInfo(&fstate->linebuff);
42 : :
43 [ + - ]: 41 : if (strcmp(filename, "-") != 0)
44 : : {
45 : 41 : fstate->fp = fopen(filename, "r");
46 [ - + ]: 41 : if (!fstate->fp)
47 : : {
647 dgustafsson@postgres 48 :UBC 0 : pg_log_error("could not open filter file \"%s\": %m", filename);
49 : 0 : fstate->exit_nicely(1);
50 : : }
51 : : }
52 : : else
53 : 0 : fstate->fp = stdin;
647 dgustafsson@postgres 54 :CBC 41 : }
55 : :
56 : : /*
57 : : * Release allocated resources for the given filter.
58 : : */
59 : : void
60 : 30 : filter_free(FilterStateData *fstate)
61 : : {
62 [ - + ]: 30 : if (!fstate)
647 dgustafsson@postgres 63 :UBC 0 : return;
64 : :
647 dgustafsson@postgres 65 :CBC 30 : free(fstate->linebuff.data);
66 : 30 : fstate->linebuff.data = NULL;
67 : :
68 [ + - + - ]: 30 : if (fstate->fp && fstate->fp != stdin)
69 : : {
70 [ - + ]: 30 : if (fclose(fstate->fp) != 0)
647 dgustafsson@postgres 71 :UBC 0 : pg_log_error("could not close filter file \"%s\": %m", fstate->filename);
72 : :
647 dgustafsson@postgres 73 :CBC 30 : fstate->fp = NULL;
74 : : }
75 : : }
76 : :
77 : : /*
78 : : * Translate FilterObjectType enum to string. The main purpose is for error
79 : : * message formatting.
80 : : */
81 : : const char *
82 : 5 : filter_object_type_name(FilterObjectType fot)
83 : : {
84 [ - + - - : 5 : switch (fot)
+ + - - -
- - - - ]
85 : : {
647 dgustafsson@postgres 86 :UBC 0 : case FILTER_OBJECT_TYPE_NONE:
87 : 0 : return "comment or empty line";
647 dgustafsson@postgres 88 :CBC 2 : case FILTER_OBJECT_TYPE_TABLE_DATA:
89 : 2 : return "table data";
647 dgustafsson@postgres 90 :UBC 0 : case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
91 : 0 : return "table data and children";
92 : 0 : case FILTER_OBJECT_TYPE_DATABASE:
93 : 0 : return "database";
647 dgustafsson@postgres 94 :CBC 2 : case FILTER_OBJECT_TYPE_EXTENSION:
95 : 2 : return "extension";
96 : 1 : case FILTER_OBJECT_TYPE_FOREIGN_DATA:
97 : 1 : return "foreign data";
647 dgustafsson@postgres 98 :UBC 0 : case FILTER_OBJECT_TYPE_FUNCTION:
99 : 0 : return "function";
100 : 0 : case FILTER_OBJECT_TYPE_INDEX:
101 : 0 : return "index";
102 : 0 : case FILTER_OBJECT_TYPE_SCHEMA:
103 : 0 : return "schema";
104 : 0 : case FILTER_OBJECT_TYPE_TABLE:
105 : 0 : return "table";
106 : 0 : case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
107 : 0 : return "table and children";
108 : 0 : case FILTER_OBJECT_TYPE_TRIGGER:
109 : 0 : return "trigger";
110 : : }
111 : :
112 : : /* should never get here */
113 : 0 : pg_unreachable();
114 : : }
115 : :
116 : : /*
117 : : * Returns true when keyword is one of supported object types, and
118 : : * set related objtype. Returns false, when keyword is not assigned
119 : : * with known object type.
120 : : */
121 : : static bool
647 dgustafsson@postgres 122 :CBC 43 : get_object_type(const char *keyword, int size, FilterObjectType *objtype)
123 : : {
124 [ + + + + ]: 43 : if (is_keyword_str("table_data", keyword, size))
125 : 3 : *objtype = FILTER_OBJECT_TYPE_TABLE_DATA;
126 [ + + + - ]: 40 : else if (is_keyword_str("table_data_and_children", keyword, size))
127 : 1 : *objtype = FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN;
128 [ + + + + ]: 39 : else if (is_keyword_str("database", keyword, size))
129 : 2 : *objtype = FILTER_OBJECT_TYPE_DATABASE;
130 [ + + + - ]: 37 : else if (is_keyword_str("extension", keyword, size))
131 : 4 : *objtype = FILTER_OBJECT_TYPE_EXTENSION;
132 [ + + + - ]: 33 : else if (is_keyword_str("foreign_data", keyword, size))
133 : 2 : *objtype = FILTER_OBJECT_TYPE_FOREIGN_DATA;
134 [ + + + - ]: 31 : else if (is_keyword_str("function", keyword, size))
135 : 2 : *objtype = FILTER_OBJECT_TYPE_FUNCTION;
136 [ + + + + ]: 29 : else if (is_keyword_str("index", keyword, size))
137 : 1 : *objtype = FILTER_OBJECT_TYPE_INDEX;
138 [ + + + - ]: 28 : else if (is_keyword_str("schema", keyword, size))
139 : 5 : *objtype = FILTER_OBJECT_TYPE_SCHEMA;
140 [ + + + - ]: 23 : else if (is_keyword_str("table", keyword, size))
141 : 18 : *objtype = FILTER_OBJECT_TYPE_TABLE;
142 [ + + + - ]: 5 : else if (is_keyword_str("table_and_children", keyword, size))
143 : 2 : *objtype = FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN;
144 [ + + + - ]: 3 : else if (is_keyword_str("trigger", keyword, size))
145 : 1 : *objtype = FILTER_OBJECT_TYPE_TRIGGER;
146 : : else
147 : 2 : return false;
148 : :
149 : 41 : return true;
150 : : }
151 : :
152 : :
153 : : void
154 : 11 : pg_log_filter_error(FilterStateData *fstate, const char *fmt,...)
155 : : {
156 : : va_list argp;
157 : : char buf[256];
158 : :
159 : 11 : va_start(argp, fmt);
160 : 11 : vsnprintf(buf, sizeof(buf), fmt, argp);
161 : 11 : va_end(argp);
162 : :
443 peter@eisentraut.org 163 [ - + ]: 11 : if (fstate->fp == stdin)
443 peter@eisentraut.org 164 :UBC 0 : pg_log_error("invalid format in filter read from standard input on line %d: %s",
165 : : fstate->lineno, buf);
166 : : else
443 peter@eisentraut.org 167 :CBC 11 : pg_log_error("invalid format in filter read from file \"%s\" on line %d: %s",
168 : : fstate->filename, fstate->lineno, buf);
647 dgustafsson@postgres 169 : 11 : }
170 : :
171 : : /*
172 : : * filter_get_keyword - read the next filter keyword from buffer
173 : : *
174 : : * Search for keywords (strings of non-whitespace characters) in the passed
175 : : * in line buffer. Returns NULL when the buffer is empty or no keyword exists.
176 : : * The length of the found keyword is returned in the size parameter.
177 : : */
178 : : static const char *
179 : 88 : filter_get_keyword(const char **line, int *size)
180 : : {
181 : 88 : const char *ptr = *line;
182 : 88 : const char *result = NULL;
183 : :
184 : : /* The passed buffer must not be NULL */
29 fujii@postgresql.org 185 [ - + ]: 88 : Assert(*line != NULL);
186 : :
187 : : /* Set returned length preemptively in case no keyword is found */
647 dgustafsson@postgres 188 : 88 : *size = 0;
189 : :
190 : : /* Skip initial whitespace */
646 191 [ + + ]: 133 : while (isspace((unsigned char) *ptr))
647 192 : 45 : ptr++;
193 : :
194 : : /* Grab one keyword that's the string of non-whitespace characters */
29 fujii@postgresql.org 195 [ + - + - ]: 88 : if (*ptr != '\0' && !isspace((unsigned char) *ptr))
196 : : {
647 dgustafsson@postgres 197 : 88 : result = ptr++;
198 : :
29 fujii@postgresql.org 199 [ + + + + ]: 629 : while (*ptr != '\0' && !isspace((unsigned char) *ptr))
647 dgustafsson@postgres 200 : 541 : ptr++;
201 : :
202 : 88 : *size = ptr - result;
203 : : }
204 : :
205 : 88 : *line = ptr;
206 : :
207 : 88 : return result;
208 : : }
209 : :
210 : : /*
211 : : * read_quoted_string - read quoted possibly multi line string
212 : : *
213 : : * Reads a quoted string which can span over multiple lines and returns a
214 : : * pointer to next char after ending double quotes; it will exit on errors.
215 : : */
216 : : static const char *
217 : 7 : read_quoted_string(FilterStateData *fstate,
218 : : const char *str,
219 : : PQExpBuffer pattern)
220 : : {
221 : 7 : appendPQExpBufferChar(pattern, '"');
222 : 7 : str++;
223 : :
224 : : while (1)
225 : : {
226 : : /*
227 : : * We can ignore \r or \n chars because the string is read by
228 : : * pg_get_line_buf, so these chars should be just trailing chars.
229 : : */
230 [ + - + + ]: 70 : if (*str == '\r' || *str == '\n')
231 : : {
232 : 4 : str++;
233 : 4 : continue;
234 : : }
235 : :
236 [ + + ]: 66 : if (*str == '\0')
237 : : {
238 [ - + ]: 4 : Assert(fstate->linebuff.data);
239 : :
240 [ - + ]: 4 : if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
241 : : {
647 dgustafsson@postgres 242 [ # # ]:UBC 0 : if (ferror(fstate->fp))
243 : 0 : pg_log_error("could not read from filter file \"%s\": %m",
244 : : fstate->filename);
245 : : else
246 : 0 : pg_log_filter_error(fstate, _("unexpected end of file"));
247 : :
248 : 0 : fstate->exit_nicely(1);
249 : : }
250 : :
647 dgustafsson@postgres 251 :CBC 4 : str = fstate->linebuff.data;
252 : :
253 : 4 : appendPQExpBufferChar(pattern, '\n');
254 : 4 : fstate->lineno++;
255 : : }
256 : :
257 [ + + ]: 66 : if (*str == '"')
258 : : {
259 : 7 : appendPQExpBufferChar(pattern, '"');
260 : 7 : str++;
261 : :
262 [ - + ]: 7 : if (*str == '"')
263 : : {
647 dgustafsson@postgres 264 :UBC 0 : appendPQExpBufferChar(pattern, '"');
265 : 0 : str++;
266 : : }
267 : : else
647 dgustafsson@postgres 268 :CBC 7 : break;
269 : : }
270 [ + + ]: 59 : else if (*str == '\\')
271 : : {
272 : 4 : str++;
273 [ + - ]: 4 : if (*str == 'n')
274 : 4 : appendPQExpBufferChar(pattern, '\n');
647 dgustafsson@postgres 275 [ # # ]:UBC 0 : else if (*str == '\\')
276 : 0 : appendPQExpBufferChar(pattern, '\\');
277 : :
647 dgustafsson@postgres 278 :CBC 4 : str++;
279 : : }
280 : : else
281 : 55 : appendPQExpBufferChar(pattern, *str++);
282 : : }
283 : :
284 : 7 : return str;
285 : : }
286 : :
287 : : /*
288 : : * read_pattern - reads on object pattern from input
289 : : *
290 : : * This function will parse any valid identifier (quoted or not, qualified or
291 : : * not), which can also includes the full signature for routines.
292 : : * Note that this function takes special care to sanitize the detected
293 : : * identifier (removing extraneous whitespaces or other unnecessary
294 : : * characters). This is necessary as most backup/restore filtering functions
295 : : * only recognize identifiers if they are written exactly the same way as
296 : : * they are output by the server.
297 : : *
298 : : * Returns a pointer to next character after the found identifier and exits
299 : : * on error.
300 : : */
301 : : static const char *
302 : 41 : read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
303 : : {
304 : 41 : bool skip_space = true;
305 : 41 : bool found_space = false;
306 : :
307 : : /* Skip initial whitespace */
646 308 [ + + ]: 82 : while (isspace((unsigned char) *str))
647 309 : 41 : str++;
310 : :
311 [ + + ]: 41 : if (*str == '\0')
312 : : {
313 : 1 : pg_log_filter_error(fstate, _("missing object name pattern"));
314 : 1 : fstate->exit_nicely(1);
315 : : }
316 : :
317 [ + + + + ]: 93 : while (*str && *str != '#')
318 : : {
646 319 [ + + + + : 348 : while (*str && !isspace((unsigned char) *str) && !strchr("#,.()\"", *str))
+ + ]
320 : : {
321 : : /*
322 : : * Append space only when it is allowed, and when it was found in
323 : : * original string.
324 : : */
647 325 [ + + + - ]: 295 : if (!skip_space && found_space)
326 : : {
327 : 3 : appendPQExpBufferChar(pattern, ' ');
328 : 3 : skip_space = true;
329 : : }
330 : :
331 : 295 : appendPQExpBufferChar(pattern, *str++);
332 : : }
333 : :
334 : 53 : skip_space = false;
335 : :
336 [ + + ]: 53 : if (*str == '"')
337 : : {
338 [ - + ]: 7 : if (found_space)
647 dgustafsson@postgres 339 :UBC 0 : appendPQExpBufferChar(pattern, ' ');
340 : :
647 dgustafsson@postgres 341 :CBC 7 : str = read_quoted_string(fstate, str, pattern);
342 : : }
343 [ + + ]: 46 : else if (*str == ',')
344 : : {
345 : 1 : appendPQExpBufferStr(pattern, ", ");
346 : 1 : skip_space = true;
347 : 1 : str++;
348 : : }
349 [ + + + + ]: 45 : else if (*str && strchr(".()", *str))
350 : : {
351 : 7 : appendPQExpBufferChar(pattern, *str++);
352 : 7 : skip_space = true;
353 : : }
354 : :
355 : 53 : found_space = false;
356 : :
357 : : /* skip ending whitespaces */
646 358 [ + + ]: 95 : while (isspace((unsigned char) *str))
359 : : {
647 360 : 42 : found_space = true;
361 : 42 : str++;
362 : : }
363 : : }
364 : :
365 : 40 : return str;
366 : : }
367 : :
368 : : /*
369 : : * filter_read_item - Read command/type/pattern triplet from a filter file
370 : : *
371 : : * This will parse one filter item from the filter file, and while it is a
372 : : * row based format a pattern may span more than one line due to how object
373 : : * names can be constructed. The expected format of the filter file is:
374 : : *
375 : : * <command> <object_type> <pattern>
376 : : *
377 : : * command can be "include" or "exclude".
378 : : *
379 : : * Supported object types are described by enum FilterObjectType
380 : : * (see function get_object_type).
381 : : *
382 : : * pattern can be any possibly-quoted and possibly-qualified identifier. It
383 : : * follows the same rules as other object include and exclude functions so it
384 : : * can also use wildcards.
385 : : *
386 : : * Returns true when one filter item was successfully read and parsed. When
387 : : * object name contains \n chars, then more than one line from input file can
388 : : * be processed. Returns false when the filter file reaches EOF. In case of
389 : : * error, the function will emit an appropriate error message and exit.
390 : : */
391 : : bool
392 : 82 : filter_read_item(FilterStateData *fstate,
393 : : char **objname,
394 : : FilterCommandType *comtype,
395 : : FilterObjectType *objtype)
396 : : {
397 [ + + ]: 82 : if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
398 : : {
399 : 52 : const char *str = fstate->linebuff.data;
400 : : const char *keyword;
401 : : int size;
402 : : PQExpBufferData pattern;
403 : :
404 : 52 : fstate->lineno++;
405 : :
406 : : /* Skip initial white spaces */
646 407 [ + + ]: 63 : while (isspace((unsigned char) *str))
647 408 : 11 : str++;
409 : :
410 : : /*
411 : : * Skip empty lines or lines where the first non-whitespace character
412 : : * is a hash indicating a comment.
413 : : */
414 [ + + + + ]: 52 : if (*str != '\0' && *str != '#')
415 : : {
416 : : /*
417 : : * First we expect sequence of two keywords, {include|exclude}
418 : : * followed by the object type to operate on.
419 : : */
420 : 45 : keyword = filter_get_keyword(&str, &size);
421 [ - + ]: 45 : if (!keyword)
422 : : {
647 dgustafsson@postgres 423 :UBC 0 : pg_log_filter_error(fstate,
424 : 0 : _("no filter command found (expected \"include\" or \"exclude\")"));
425 : 0 : fstate->exit_nicely(1);
426 : : }
427 : :
647 dgustafsson@postgres 428 [ + + + + ]:CBC 45 : if (is_keyword_str("include", keyword, size))
429 : 26 : *comtype = FILTER_COMMAND_TYPE_INCLUDE;
430 [ + + + - ]: 19 : else if (is_keyword_str("exclude", keyword, size))
431 : 17 : *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
432 : : else
433 : : {
434 : 2 : pg_log_filter_error(fstate,
435 : 2 : _("invalid filter command (expected \"include\" or \"exclude\")"));
436 : 2 : fstate->exit_nicely(1);
437 : : }
438 : :
439 : 43 : keyword = filter_get_keyword(&str, &size);
440 [ - + ]: 43 : if (!keyword)
441 : : {
647 dgustafsson@postgres 442 :UBC 0 : pg_log_filter_error(fstate, _("missing filter object type"));
443 : 0 : fstate->exit_nicely(1);
444 : : }
445 : :
647 dgustafsson@postgres 446 [ + + ]:CBC 43 : if (!get_object_type(keyword, size, objtype))
447 : : {
448 : 2 : pg_log_filter_error(fstate,
449 : 2 : _("unsupported filter object type: \"%.*s\""), size, keyword);
450 : 2 : fstate->exit_nicely(1);
451 : : }
452 : :
453 : 41 : initPQExpBuffer(&pattern);
454 : :
455 : 41 : str = read_pattern(fstate, str, &pattern);
456 : 40 : *objname = pattern.data;
457 : : }
458 : : else
459 : : {
460 : 7 : *objname = NULL;
461 : 7 : *comtype = FILTER_COMMAND_TYPE_NONE;
462 : 7 : *objtype = FILTER_OBJECT_TYPE_NONE;
463 : : }
464 : :
465 : 47 : return true;
466 : : }
467 : :
468 [ - + ]: 30 : if (ferror(fstate->fp))
469 : : {
647 dgustafsson@postgres 470 :UBC 0 : pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
471 : 0 : fstate->exit_nicely(1);
472 : : }
473 : :
647 dgustafsson@postgres 474 :CBC 30 : return false;
475 : : }
|