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