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