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