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 : 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 22 : }
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 176 : filter_get_keyword(const char **line, int *size)
180 : {
181 176 : const char *ptr = *line;
182 176 : const char *result = NULL;
183 :
184 : /* Set returned length preemptively in case no keyword is found */
185 176 : *size = 0;
186 :
187 : /* Skip initial whitespace */
188 266 : while (isspace((unsigned char) *ptr))
189 90 : ptr++;
190 :
191 176 : if (isalpha((unsigned char) *ptr))
192 : {
193 176 : result = ptr++;
194 :
195 1244 : while (isalpha((unsigned char) *ptr) || *ptr == '_')
196 1068 : ptr++;
197 :
198 176 : *size = ptr - result;
199 : }
200 :
201 176 : *line = ptr;
202 :
203 176 : return result;
204 : }
205 :
206 : /*
207 : * read_quoted_string - 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 14 : read_quoted_string(FilterStateData *fstate,
214 : const char *str,
215 : PQExpBuffer pattern)
216 : {
217 14 : appendPQExpBufferChar(pattern, '"');
218 14 : 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 140 : if (*str == '\r' || *str == '\n')
227 : {
228 8 : str++;
229 8 : continue;
230 : }
231 :
232 132 : if (*str == '\0')
233 : {
234 : Assert(fstate->linebuff.data);
235 :
236 8 : if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
237 : {
238 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 :
247 8 : str = fstate->linebuff.data;
248 :
249 8 : appendPQExpBufferChar(pattern, '\n');
250 8 : fstate->lineno++;
251 : }
252 :
253 132 : if (*str == '"')
254 : {
255 14 : appendPQExpBufferChar(pattern, '"');
256 14 : str++;
257 :
258 14 : if (*str == '"')
259 : {
260 0 : appendPQExpBufferChar(pattern, '"');
261 0 : str++;
262 : }
263 : else
264 14 : break;
265 : }
266 118 : else if (*str == '\\')
267 : {
268 8 : str++;
269 8 : if (*str == 'n')
270 8 : appendPQExpBufferChar(pattern, '\n');
271 0 : else if (*str == '\\')
272 0 : appendPQExpBufferChar(pattern, '\\');
273 :
274 8 : str++;
275 : }
276 : else
277 110 : appendPQExpBufferChar(pattern, *str++);
278 : }
279 :
280 14 : 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 82 : read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
299 : {
300 82 : bool skip_space = true;
301 82 : bool found_space = false;
302 :
303 : /* Skip initial whitespace */
304 164 : while (isspace((unsigned char) *str))
305 82 : str++;
306 :
307 82 : if (*str == '\0')
308 : {
309 2 : pg_log_filter_error(fstate, _("missing object name pattern"));
310 2 : fstate->exit_nicely(1);
311 : }
312 :
313 186 : while (*str && *str != '#')
314 : {
315 696 : 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 : */
321 590 : if (!skip_space && found_space)
322 : {
323 6 : appendPQExpBufferChar(pattern, ' ');
324 6 : skip_space = true;
325 : }
326 :
327 590 : appendPQExpBufferChar(pattern, *str++);
328 : }
329 :
330 106 : skip_space = false;
331 :
332 106 : if (*str == '"')
333 : {
334 14 : if (found_space)
335 0 : appendPQExpBufferChar(pattern, ' ');
336 :
337 14 : str = read_quoted_string(fstate, str, pattern);
338 : }
339 92 : else if (*str == ',')
340 : {
341 2 : appendPQExpBufferStr(pattern, ", ");
342 2 : skip_space = true;
343 2 : str++;
344 : }
345 90 : else if (*str && strchr(".()", *str))
346 : {
347 14 : appendPQExpBufferChar(pattern, *str++);
348 14 : skip_space = true;
349 : }
350 :
351 106 : found_space = false;
352 :
353 : /* skip ending whitespaces */
354 190 : while (isspace((unsigned char) *str))
355 : {
356 84 : found_space = true;
357 84 : str++;
358 : }
359 : }
360 :
361 80 : 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 164 : filter_read_item(FilterStateData *fstate,
389 : char **objname,
390 : FilterCommandType *comtype,
391 : FilterObjectType *objtype)
392 : {
393 164 : if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
394 : {
395 104 : const char *str = fstate->linebuff.data;
396 : const char *keyword;
397 : int size;
398 : PQExpBufferData pattern;
399 :
400 104 : fstate->lineno++;
401 :
402 : /* Skip initial white spaces */
403 126 : while (isspace((unsigned char) *str))
404 22 : str++;
405 :
406 : /*
407 : * Skip empty lines or lines where the first non-whitespace character
408 : * is a hash indicating a comment.
409 : */
410 104 : 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 90 : keyword = filter_get_keyword(&str, &size);
417 90 : if (!keyword)
418 : {
419 0 : pg_log_filter_error(fstate,
420 0 : _("no filter command found (expected \"include\" or \"exclude\")"));
421 0 : fstate->exit_nicely(1);
422 : }
423 :
424 90 : if (is_keyword_str("include", keyword, size))
425 54 : *comtype = FILTER_COMMAND_TYPE_INCLUDE;
426 36 : else if (is_keyword_str("exclude", keyword, size))
427 32 : *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
428 : else
429 : {
430 4 : pg_log_filter_error(fstate,
431 4 : _("invalid filter command (expected \"include\" or \"exclude\")"));
432 4 : fstate->exit_nicely(1);
433 : }
434 :
435 86 : keyword = filter_get_keyword(&str, &size);
436 86 : if (!keyword)
437 : {
438 0 : pg_log_filter_error(fstate, _("missing filter object type"));
439 0 : fstate->exit_nicely(1);
440 : }
441 :
442 86 : if (!get_object_type(keyword, size, objtype))
443 : {
444 4 : pg_log_filter_error(fstate,
445 4 : _("unsupported filter object type: \"%.*s\""), size, keyword);
446 4 : fstate->exit_nicely(1);
447 : }
448 :
449 82 : initPQExpBuffer(&pattern);
450 :
451 82 : str = read_pattern(fstate, str, &pattern);
452 80 : *objname = pattern.data;
453 : }
454 : else
455 : {
456 14 : *objname = NULL;
457 14 : *comtype = FILTER_COMMAND_TYPE_NONE;
458 14 : *objtype = FILTER_OBJECT_TYPE_NONE;
459 : }
460 :
461 94 : return true;
462 : }
463 :
464 60 : if (ferror(fstate->fp))
465 : {
466 0 : pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
467 0 : fstate->exit_nicely(1);
468 : }
469 :
470 60 : return false;
471 : }
|