LCOV - code coverage report
Current view: top level - src/bin/pg_dump - filter.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 216 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 9 0
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.3.2-1