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