Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * filter.c
4 : * Implementation of simple filter file parser
5 : *
6 : * Portions Copyright (c) 1996-2024, 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 (limited to ascii alphabetic characters) in
175 : * the passed in line buffer. Returns NULL when the buffer is empty or the first
176 : * char is not alpha. The char '_' is allowed, except as the first character.
177 : * The length of the found keyword is returned in the size parameter.
178 : */
179 : static const char *
180 176 : filter_get_keyword(const char **line, int *size)
181 : {
182 176 : const char *ptr = *line;
183 176 : const char *result = NULL;
184 :
185 : /* Set returned length preemptively in case no keyword is found */
186 176 : *size = 0;
187 :
188 : /* Skip initial whitespace */
189 266 : while (isspace((unsigned char) *ptr))
190 90 : ptr++;
191 :
192 176 : if (isalpha((unsigned char) *ptr))
193 : {
194 176 : result = ptr++;
195 :
196 1244 : while (isalpha((unsigned char) *ptr) || *ptr == '_')
197 1068 : ptr++;
198 :
199 176 : *size = ptr - result;
200 : }
201 :
202 176 : *line = ptr;
203 :
204 176 : return result;
205 : }
206 :
207 : /*
208 : * read_quoted_string - read quoted possibly multi line string
209 : *
210 : * Reads a quoted string which can span over multiple lines and returns a
211 : * pointer to next char after ending double quotes; it will exit on errors.
212 : */
213 : static const char *
214 14 : read_quoted_string(FilterStateData *fstate,
215 : const char *str,
216 : PQExpBuffer pattern)
217 : {
218 14 : appendPQExpBufferChar(pattern, '"');
219 14 : str++;
220 :
221 : while (1)
222 : {
223 : /*
224 : * We can ignore \r or \n chars because the string is read by
225 : * pg_get_line_buf, so these chars should be just trailing chars.
226 : */
227 140 : if (*str == '\r' || *str == '\n')
228 : {
229 8 : str++;
230 8 : continue;
231 : }
232 :
233 132 : if (*str == '\0')
234 : {
235 : Assert(fstate->linebuff.data);
236 :
237 8 : if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
238 : {
239 0 : if (ferror(fstate->fp))
240 0 : pg_log_error("could not read from filter file \"%s\": %m",
241 : fstate->filename);
242 : else
243 0 : pg_log_filter_error(fstate, _("unexpected end of file"));
244 :
245 0 : fstate->exit_nicely(1);
246 : }
247 :
248 8 : str = fstate->linebuff.data;
249 :
250 8 : appendPQExpBufferChar(pattern, '\n');
251 8 : fstate->lineno++;
252 : }
253 :
254 132 : if (*str == '"')
255 : {
256 14 : appendPQExpBufferChar(pattern, '"');
257 14 : str++;
258 :
259 14 : if (*str == '"')
260 : {
261 0 : appendPQExpBufferChar(pattern, '"');
262 0 : str++;
263 : }
264 : else
265 14 : break;
266 : }
267 118 : else if (*str == '\\')
268 : {
269 8 : str++;
270 8 : if (*str == 'n')
271 8 : appendPQExpBufferChar(pattern, '\n');
272 0 : else if (*str == '\\')
273 0 : appendPQExpBufferChar(pattern, '\\');
274 :
275 8 : str++;
276 : }
277 : else
278 110 : appendPQExpBufferChar(pattern, *str++);
279 : }
280 :
281 14 : return str;
282 : }
283 :
284 : /*
285 : * read_pattern - reads on object pattern from input
286 : *
287 : * This function will parse any valid identifier (quoted or not, qualified or
288 : * not), which can also includes the full signature for routines.
289 : * Note that this function takes special care to sanitize the detected
290 : * identifier (removing extraneous whitespaces or other unnecessary
291 : * characters). This is necessary as most backup/restore filtering functions
292 : * only recognize identifiers if they are written exactly the same way as
293 : * they are output by the server.
294 : *
295 : * Returns a pointer to next character after the found identifier and exits
296 : * on error.
297 : */
298 : static const char *
299 82 : read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
300 : {
301 82 : bool skip_space = true;
302 82 : bool found_space = false;
303 :
304 : /* Skip initial whitespace */
305 164 : while (isspace((unsigned char) *str))
306 82 : str++;
307 :
308 82 : if (*str == '\0')
309 : {
310 2 : pg_log_filter_error(fstate, _("missing object name pattern"));
311 2 : fstate->exit_nicely(1);
312 : }
313 :
314 186 : while (*str && *str != '#')
315 : {
316 696 : while (*str && !isspace((unsigned char) *str) && !strchr("#,.()\"", *str))
317 : {
318 : /*
319 : * Append space only when it is allowed, and when it was found in
320 : * original string.
321 : */
322 590 : if (!skip_space && found_space)
323 : {
324 6 : appendPQExpBufferChar(pattern, ' ');
325 6 : skip_space = true;
326 : }
327 :
328 590 : appendPQExpBufferChar(pattern, *str++);
329 : }
330 :
331 106 : skip_space = false;
332 :
333 106 : if (*str == '"')
334 : {
335 14 : if (found_space)
336 0 : appendPQExpBufferChar(pattern, ' ');
337 :
338 14 : str = read_quoted_string(fstate, str, pattern);
339 : }
340 92 : else if (*str == ',')
341 : {
342 2 : appendPQExpBufferStr(pattern, ", ");
343 2 : skip_space = true;
344 2 : str++;
345 : }
346 90 : else if (*str && strchr(".()", *str))
347 : {
348 14 : appendPQExpBufferChar(pattern, *str++);
349 14 : skip_space = true;
350 : }
351 :
352 106 : found_space = false;
353 :
354 : /* skip ending whitespaces */
355 190 : while (isspace((unsigned char) *str))
356 : {
357 84 : found_space = true;
358 84 : str++;
359 : }
360 : }
361 :
362 80 : return str;
363 : }
364 :
365 : /*
366 : * filter_read_item - Read command/type/pattern triplet from a filter file
367 : *
368 : * This will parse one filter item from the filter file, and while it is a
369 : * row based format a pattern may span more than one line due to how object
370 : * names can be constructed. The expected format of the filter file is:
371 : *
372 : * <command> <object_type> <pattern>
373 : *
374 : * command can be "include" or "exclude".
375 : *
376 : * Supported object types are described by enum FilterObjectType
377 : * (see function get_object_type).
378 : *
379 : * pattern can be any possibly-quoted and possibly-qualified identifier. It
380 : * follows the same rules as other object include and exclude functions so it
381 : * can also use wildcards.
382 : *
383 : * Returns true when one filter item was successfully read and parsed. When
384 : * object name contains \n chars, then more than one line from input file can
385 : * be processed. Returns false when the filter file reaches EOF. In case of
386 : * error, the function will emit an appropriate error message and exit.
387 : */
388 : bool
389 164 : filter_read_item(FilterStateData *fstate,
390 : char **objname,
391 : FilterCommandType *comtype,
392 : FilterObjectType *objtype)
393 : {
394 164 : if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
395 : {
396 104 : const char *str = fstate->linebuff.data;
397 : const char *keyword;
398 : int size;
399 : PQExpBufferData pattern;
400 :
401 104 : fstate->lineno++;
402 :
403 : /* Skip initial white spaces */
404 126 : while (isspace((unsigned char) *str))
405 22 : str++;
406 :
407 : /*
408 : * Skip empty lines or lines where the first non-whitespace character
409 : * is a hash indicating a comment.
410 : */
411 104 : if (*str != '\0' && *str != '#')
412 : {
413 : /*
414 : * First we expect sequence of two keywords, {include|exclude}
415 : * followed by the object type to operate on.
416 : */
417 90 : keyword = filter_get_keyword(&str, &size);
418 90 : if (!keyword)
419 : {
420 0 : pg_log_filter_error(fstate,
421 0 : _("no filter command found (expected \"include\" or \"exclude\")"));
422 0 : fstate->exit_nicely(1);
423 : }
424 :
425 90 : if (is_keyword_str("include", keyword, size))
426 54 : *comtype = FILTER_COMMAND_TYPE_INCLUDE;
427 36 : else if (is_keyword_str("exclude", keyword, size))
428 32 : *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
429 : else
430 : {
431 4 : pg_log_filter_error(fstate,
432 4 : _("invalid filter command (expected \"include\" or \"exclude\")"));
433 4 : fstate->exit_nicely(1);
434 : }
435 :
436 86 : keyword = filter_get_keyword(&str, &size);
437 86 : if (!keyword)
438 : {
439 0 : pg_log_filter_error(fstate, _("missing filter object type"));
440 0 : fstate->exit_nicely(1);
441 : }
442 :
443 86 : if (!get_object_type(keyword, size, objtype))
444 : {
445 4 : pg_log_filter_error(fstate,
446 4 : _("unsupported filter object type: \"%.*s\""), size, keyword);
447 4 : fstate->exit_nicely(1);
448 : }
449 :
450 82 : initPQExpBuffer(&pattern);
451 :
452 82 : str = read_pattern(fstate, str, &pattern);
453 80 : *objname = pattern.data;
454 : }
455 : else
456 : {
457 14 : *objname = NULL;
458 14 : *comtype = FILTER_COMMAND_TYPE_NONE;
459 14 : *objtype = FILTER_OBJECT_TYPE_NONE;
460 : }
461 :
462 94 : return true;
463 : }
464 :
465 60 : if (ferror(fstate->fp))
466 : {
467 0 : pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
468 0 : fstate->exit_nicely(1);
469 : }
470 :
471 60 : return false;
472 : }
|