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
36 82 : filter_init(FilterStateData *fstate, const char *filename, exit_function f_exit)
37 : {
38 82 : fstate->filename = filename;
39 82 : fstate->lineno = 0;
40 82 : fstate->exit_nicely = f_exit;
41 82 : initStringInfo(&fstate->linebuff);
42 :
43 82 : if (strcmp(filename, "-") != 0)
44 : {
45 82 : fstate->fp = fopen(filename, "r");
46 82 : if (!fstate->fp)
47 : {
48 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;
54 82 : }
55 :
56 : /*
57 : * Release allocated resources for the given filter.
58 : */
59 : void
60 60 : filter_free(FilterStateData *fstate)
61 : {
62 60 : if (!fstate)
63 0 : return;
64 :
65 60 : free(fstate->linebuff.data);
66 60 : fstate->linebuff.data = NULL;
67 :
68 60 : if (fstate->fp && fstate->fp != stdin)
69 : {
70 60 : if (fclose(fstate->fp) != 0)
71 0 : pg_log_error("could not close filter file \"%s\": %m", fstate->filename);
72 :
73 60 : 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 10 : filter_object_type_name(FilterObjectType fot)
83 : {
84 10 : switch (fot)
85 : {
86 0 : case FILTER_OBJECT_TYPE_NONE:
87 0 : return "comment or empty line";
88 4 : case FILTER_OBJECT_TYPE_TABLE_DATA:
89 4 : return "table data";
90 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";
94 4 : case FILTER_OBJECT_TYPE_EXTENSION:
95 4 : return "extension";
96 2 : case FILTER_OBJECT_TYPE_FOREIGN_DATA:
97 2 : return "foreign data";
98 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
122 86 : get_object_type(const char *keyword, int size, FilterObjectType *objtype)
123 : {
124 86 : if (is_keyword_str("table_data", keyword, size))
125 6 : *objtype = FILTER_OBJECT_TYPE_TABLE_DATA;
126 80 : else if (is_keyword_str("table_data_and_children", keyword, size))
127 2 : *objtype = FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN;
128 78 : else if (is_keyword_str("database", keyword, size))
129 4 : *objtype = FILTER_OBJECT_TYPE_DATABASE;
130 74 : else if (is_keyword_str("extension", keyword, size))
131 8 : *objtype = FILTER_OBJECT_TYPE_EXTENSION;
132 66 : else if (is_keyword_str("foreign_data", keyword, size))
133 4 : *objtype = FILTER_OBJECT_TYPE_FOREIGN_DATA;
134 62 : else if (is_keyword_str("function", keyword, size))
135 4 : *objtype = FILTER_OBJECT_TYPE_FUNCTION;
136 58 : else if (is_keyword_str("index", keyword, size))
137 2 : *objtype = FILTER_OBJECT_TYPE_INDEX;
138 56 : else if (is_keyword_str("schema", keyword, size))
139 10 : *objtype = FILTER_OBJECT_TYPE_SCHEMA;
140 46 : else if (is_keyword_str("table", keyword, size))
141 36 : *objtype = FILTER_OBJECT_TYPE_TABLE;
142 10 : else if (is_keyword_str("table_and_children", keyword, size))
143 4 : *objtype = FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN;
144 6 : else if (is_keyword_str("trigger", keyword, size))
145 2 : *objtype = FILTER_OBJECT_TYPE_TRIGGER;
146 : else
147 4 : return false;
148 :
149 82 : return true;
150 : }
151 :
152 :
153 : void
154 22 : pg_log_filter_error(FilterStateData *fstate, const char *fmt,...)
155 : {
156 : va_list argp;
157 : char buf[256];
158 :
159 22 : va_start(argp, fmt);
160 22 : vsnprintf(buf, sizeof(buf), fmt, argp);
161 22 : va_end(argp);
162 :
163 22 : if (fstate->fp == stdin)
164 0 : pg_log_error("invalid format in filter read from standard input on line %d: %s",
165 : fstate->lineno, buf);
166 : else
167 22 : pg_log_error("invalid format in filter read from file \"%s\" on line %d: %s",
168 : fstate->filename, fstate->lineno, buf);
169 22 : }
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 176 : filter_get_keyword(const char **line, int *size)
180 : {
181 176 : const char *ptr = *line;
182 176 : const char *result = NULL;
183 :
184 : /* The passed buffer must not be NULL */
185 : Assert(*line != NULL);
186 :
187 : /* Set returned length preemptively in case no keyword is found */
188 176 : *size = 0;
189 :
190 : /* Skip initial whitespace */
191 266 : while (isspace((unsigned char) *ptr))
192 90 : ptr++;
193 :
194 : /* Grab one keyword that's the string of non-whitespace characters */
195 176 : if (*ptr != '\0' && !isspace((unsigned char) *ptr))
196 : {
197 176 : result = ptr++;
198 :
199 1258 : while (*ptr != '\0' && !isspace((unsigned char) *ptr))
200 1082 : ptr++;
201 :
202 176 : *size = ptr - result;
203 : }
204 :
205 176 : *line = ptr;
206 :
207 176 : 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 14 : read_quoted_string(FilterStateData *fstate,
218 : const char *str,
219 : PQExpBuffer pattern)
220 : {
221 14 : appendPQExpBufferChar(pattern, '"');
222 14 : 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 140 : if (*str == '\r' || *str == '\n')
231 : {
232 8 : str++;
233 8 : continue;
234 : }
235 :
236 132 : if (*str == '\0')
237 : {
238 : Assert(fstate->linebuff.data);
239 :
240 8 : if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
241 : {
242 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 :
251 8 : str = fstate->linebuff.data;
252 :
253 8 : appendPQExpBufferChar(pattern, '\n');
254 8 : fstate->lineno++;
255 : }
256 :
257 132 : if (*str == '"')
258 : {
259 14 : appendPQExpBufferChar(pattern, '"');
260 14 : str++;
261 :
262 14 : if (*str == '"')
263 : {
264 0 : appendPQExpBufferChar(pattern, '"');
265 0 : str++;
266 : }
267 : else
268 14 : break;
269 : }
270 118 : else if (*str == '\\')
271 : {
272 8 : str++;
273 8 : if (*str == 'n')
274 8 : appendPQExpBufferChar(pattern, '\n');
275 0 : else if (*str == '\\')
276 0 : appendPQExpBufferChar(pattern, '\\');
277 :
278 8 : str++;
279 : }
280 : else
281 110 : appendPQExpBufferChar(pattern, *str++);
282 : }
283 :
284 14 : 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 82 : read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
303 : {
304 82 : bool skip_space = true;
305 82 : bool found_space = false;
306 :
307 : /* Skip initial whitespace */
308 164 : while (isspace((unsigned char) *str))
309 82 : str++;
310 :
311 82 : if (*str == '\0')
312 : {
313 2 : pg_log_filter_error(fstate, _("missing object name pattern"));
314 2 : fstate->exit_nicely(1);
315 : }
316 :
317 186 : while (*str && *str != '#')
318 : {
319 696 : 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 : */
325 590 : if (!skip_space && found_space)
326 : {
327 6 : appendPQExpBufferChar(pattern, ' ');
328 6 : skip_space = true;
329 : }
330 :
331 590 : appendPQExpBufferChar(pattern, *str++);
332 : }
333 :
334 106 : skip_space = false;
335 :
336 106 : if (*str == '"')
337 : {
338 14 : if (found_space)
339 0 : appendPQExpBufferChar(pattern, ' ');
340 :
341 14 : str = read_quoted_string(fstate, str, pattern);
342 : }
343 92 : else if (*str == ',')
344 : {
345 2 : appendPQExpBufferStr(pattern, ", ");
346 2 : skip_space = true;
347 2 : str++;
348 : }
349 90 : else if (*str && strchr(".()", *str))
350 : {
351 14 : appendPQExpBufferChar(pattern, *str++);
352 14 : skip_space = true;
353 : }
354 :
355 106 : found_space = false;
356 :
357 : /* skip ending whitespaces */
358 190 : while (isspace((unsigned char) *str))
359 : {
360 84 : found_space = true;
361 84 : str++;
362 : }
363 : }
364 :
365 80 : 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 164 : filter_read_item(FilterStateData *fstate,
393 : char **objname,
394 : FilterCommandType *comtype,
395 : FilterObjectType *objtype)
396 : {
397 164 : if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
398 : {
399 104 : const char *str = fstate->linebuff.data;
400 : const char *keyword;
401 : int size;
402 : PQExpBufferData pattern;
403 :
404 104 : fstate->lineno++;
405 :
406 : /* Skip initial white spaces */
407 126 : while (isspace((unsigned char) *str))
408 22 : str++;
409 :
410 : /*
411 : * Skip empty lines or lines where the first non-whitespace character
412 : * is a hash indicating a comment.
413 : */
414 104 : 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 90 : keyword = filter_get_keyword(&str, &size);
421 90 : if (!keyword)
422 : {
423 0 : pg_log_filter_error(fstate,
424 0 : _("no filter command found (expected \"include\" or \"exclude\")"));
425 0 : fstate->exit_nicely(1);
426 : }
427 :
428 90 : if (is_keyword_str("include", keyword, size))
429 52 : *comtype = FILTER_COMMAND_TYPE_INCLUDE;
430 38 : else if (is_keyword_str("exclude", keyword, size))
431 34 : *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
432 : else
433 : {
434 4 : pg_log_filter_error(fstate,
435 4 : _("invalid filter command (expected \"include\" or \"exclude\")"));
436 4 : fstate->exit_nicely(1);
437 : }
438 :
439 86 : keyword = filter_get_keyword(&str, &size);
440 86 : if (!keyword)
441 : {
442 0 : pg_log_filter_error(fstate, _("missing filter object type"));
443 0 : fstate->exit_nicely(1);
444 : }
445 :
446 86 : if (!get_object_type(keyword, size, objtype))
447 : {
448 4 : pg_log_filter_error(fstate,
449 4 : _("unsupported filter object type: \"%.*s\""), size, keyword);
450 4 : fstate->exit_nicely(1);
451 : }
452 :
453 82 : initPQExpBuffer(&pattern);
454 :
455 82 : str = read_pattern(fstate, str, &pattern);
456 80 : *objname = pattern.data;
457 : }
458 : else
459 : {
460 14 : *objname = NULL;
461 14 : *comtype = FILTER_COMMAND_TYPE_NONE;
462 14 : *objtype = FILTER_OBJECT_TYPE_NONE;
463 : }
464 :
465 94 : return true;
466 : }
467 :
468 60 : if (ferror(fstate->fp))
469 : {
470 0 : pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
471 0 : fstate->exit_nicely(1);
472 : }
473 :
474 60 : return false;
475 : }
|