LCOV - code coverage report
Current view: top level - src/bin/pg_dump - filter.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 78.9 % 194 153
Test Date: 2026-03-03 13:15:30 Functions: 100.0 % 9 9
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           41 : filter_init(FilterStateData *fstate, const char *filename, exit_function f_exit)
      37              : {
      38           41 :     fstate->filename = filename;
      39           41 :     fstate->lineno = 0;
      40           41 :     fstate->exit_nicely = f_exit;
      41           41 :     initStringInfo(&fstate->linebuff);
      42              : 
      43           41 :     if (strcmp(filename, "-") != 0)
      44              :     {
      45           41 :         fstate->fp = fopen(filename, "r");
      46           41 :         if (!fstate->fp)
      47              :         {
      48            0 :             pg_log_error("could not open filter file \"%s\": %m", filename);
      49            0 :             fstate->exit_nicely(1);
      50              :         }
      51              :     }
      52              :     else
      53            0 :         fstate->fp = stdin;
      54           41 : }
      55              : 
      56              : /*
      57              :  * Release allocated resources for the given filter.
      58              :  */
      59              : void
      60           30 : filter_free(FilterStateData *fstate)
      61              : {
      62           30 :     if (!fstate)
      63            0 :         return;
      64              : 
      65           30 :     free(fstate->linebuff.data);
      66           30 :     fstate->linebuff.data = NULL;
      67              : 
      68           30 :     if (fstate->fp && fstate->fp != stdin)
      69              :     {
      70           30 :         if (fclose(fstate->fp) != 0)
      71            0 :             pg_log_error("could not close filter file \"%s\": %m", fstate->filename);
      72              : 
      73           30 :         fstate->fp = NULL;
      74              :     }
      75              : }
      76              : 
      77              : /*
      78              :  * Translate FilterObjectType enum to string. The main purpose is for error
      79              :  * message formatting.
      80              :  */
      81              : const char *
      82            5 : filter_object_type_name(FilterObjectType fot)
      83              : {
      84            5 :     switch (fot)
      85              :     {
      86            0 :         case FILTER_OBJECT_TYPE_NONE:
      87            0 :             return "comment or empty line";
      88            2 :         case FILTER_OBJECT_TYPE_TABLE_DATA:
      89            2 :             return "table data";
      90            0 :         case FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN:
      91            0 :             return "table data and children";
      92            0 :         case FILTER_OBJECT_TYPE_DATABASE:
      93            0 :             return "database";
      94            2 :         case FILTER_OBJECT_TYPE_EXTENSION:
      95            2 :             return "extension";
      96            1 :         case FILTER_OBJECT_TYPE_FOREIGN_DATA:
      97            1 :             return "foreign data";
      98            0 :         case FILTER_OBJECT_TYPE_FUNCTION:
      99            0 :             return "function";
     100            0 :         case FILTER_OBJECT_TYPE_INDEX:
     101            0 :             return "index";
     102            0 :         case FILTER_OBJECT_TYPE_SCHEMA:
     103            0 :             return "schema";
     104            0 :         case FILTER_OBJECT_TYPE_TABLE:
     105            0 :             return "table";
     106            0 :         case FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN:
     107            0 :             return "table and children";
     108            0 :         case FILTER_OBJECT_TYPE_TRIGGER:
     109            0 :             return "trigger";
     110              :     }
     111              : 
     112              :     /* should never get here */
     113            0 :     pg_unreachable();
     114              : }
     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           43 : get_object_type(const char *keyword, int size, FilterObjectType *objtype)
     123              : {
     124           43 :     if (is_keyword_str("table_data", keyword, size))
     125            3 :         *objtype = FILTER_OBJECT_TYPE_TABLE_DATA;
     126           40 :     else if (is_keyword_str("table_data_and_children", keyword, size))
     127            1 :         *objtype = FILTER_OBJECT_TYPE_TABLE_DATA_AND_CHILDREN;
     128           39 :     else if (is_keyword_str("database", keyword, size))
     129            2 :         *objtype = FILTER_OBJECT_TYPE_DATABASE;
     130           37 :     else if (is_keyword_str("extension", keyword, size))
     131            4 :         *objtype = FILTER_OBJECT_TYPE_EXTENSION;
     132           33 :     else if (is_keyword_str("foreign_data", keyword, size))
     133            2 :         *objtype = FILTER_OBJECT_TYPE_FOREIGN_DATA;
     134           31 :     else if (is_keyword_str("function", keyword, size))
     135            2 :         *objtype = FILTER_OBJECT_TYPE_FUNCTION;
     136           29 :     else if (is_keyword_str("index", keyword, size))
     137            1 :         *objtype = FILTER_OBJECT_TYPE_INDEX;
     138           28 :     else if (is_keyword_str("schema", keyword, size))
     139            5 :         *objtype = FILTER_OBJECT_TYPE_SCHEMA;
     140           23 :     else if (is_keyword_str("table", keyword, size))
     141           18 :         *objtype = FILTER_OBJECT_TYPE_TABLE;
     142            5 :     else if (is_keyword_str("table_and_children", keyword, size))
     143            2 :         *objtype = FILTER_OBJECT_TYPE_TABLE_AND_CHILDREN;
     144            3 :     else if (is_keyword_str("trigger", keyword, size))
     145            1 :         *objtype = FILTER_OBJECT_TYPE_TRIGGER;
     146              :     else
     147            2 :         return false;
     148              : 
     149           41 :     return true;
     150              : }
     151              : 
     152              : 
     153              : void
     154           11 : pg_log_filter_error(FilterStateData *fstate, const char *fmt,...)
     155              : {
     156              :     va_list     argp;
     157              :     char        buf[256];
     158              : 
     159           11 :     va_start(argp, fmt);
     160           11 :     vsnprintf(buf, sizeof(buf), fmt, argp);
     161           11 :     va_end(argp);
     162              : 
     163           11 :     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           11 :         pg_log_error("invalid format in filter read from file \"%s\" on line %d: %s",
     168              :                      fstate->filename, fstate->lineno, buf);
     169           11 : }
     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           88 : filter_get_keyword(const char **line, int *size)
     180              : {
     181           88 :     const char *ptr = *line;
     182           88 :     const char *result = NULL;
     183              : 
     184              :     /* The passed buffer must not be NULL */
     185              :     Assert(*line != NULL);
     186              : 
     187              :     /* Set returned length preemptively in case no keyword is found */
     188           88 :     *size = 0;
     189              : 
     190              :     /* Skip initial whitespace */
     191          133 :     while (isspace((unsigned char) *ptr))
     192           45 :         ptr++;
     193              : 
     194              :     /* Grab one keyword that's the string of non-whitespace characters */
     195           88 :     if (*ptr != '\0' && !isspace((unsigned char) *ptr))
     196              :     {
     197           88 :         result = ptr++;
     198              : 
     199          629 :         while (*ptr != '\0' && !isspace((unsigned char) *ptr))
     200          541 :             ptr++;
     201              : 
     202           88 :         *size = ptr - result;
     203              :     }
     204              : 
     205           88 :     *line = ptr;
     206              : 
     207           88 :     return result;
     208              : }
     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            7 : read_quoted_string(FilterStateData *fstate,
     218              :                    const char *str,
     219              :                    PQExpBuffer pattern)
     220              : {
     221            7 :     appendPQExpBufferChar(pattern, '"');
     222            7 :     str++;
     223              : 
     224              :     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           70 :         if (*str == '\r' || *str == '\n')
     231              :         {
     232            4 :             str++;
     233            4 :             continue;
     234              :         }
     235              : 
     236           66 :         if (*str == '\0')
     237              :         {
     238              :             Assert(fstate->linebuff.data);
     239              : 
     240            4 :             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              :             }
     250              : 
     251            4 :             str = fstate->linebuff.data;
     252              : 
     253            4 :             appendPQExpBufferChar(pattern, '\n');
     254            4 :             fstate->lineno++;
     255              :         }
     256              : 
     257           66 :         if (*str == '"')
     258              :         {
     259            7 :             appendPQExpBufferChar(pattern, '"');
     260            7 :             str++;
     261              : 
     262            7 :             if (*str == '"')
     263              :             {
     264            0 :                 appendPQExpBufferChar(pattern, '"');
     265            0 :                 str++;
     266              :             }
     267              :             else
     268            7 :                 break;
     269              :         }
     270           59 :         else if (*str == '\\')
     271              :         {
     272            4 :             str++;
     273            4 :             if (*str == 'n')
     274            4 :                 appendPQExpBufferChar(pattern, '\n');
     275            0 :             else if (*str == '\\')
     276            0 :                 appendPQExpBufferChar(pattern, '\\');
     277              : 
     278            4 :             str++;
     279              :         }
     280              :         else
     281           55 :             appendPQExpBufferChar(pattern, *str++);
     282              :     }
     283              : 
     284            7 :     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           41 : read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
     303              : {
     304           41 :     bool        skip_space = true;
     305           41 :     bool        found_space = false;
     306              : 
     307              :     /* Skip initial whitespace */
     308           82 :     while (isspace((unsigned char) *str))
     309           41 :         str++;
     310              : 
     311           41 :     if (*str == '\0')
     312              :     {
     313            1 :         pg_log_filter_error(fstate, _("missing object name pattern"));
     314            1 :         fstate->exit_nicely(1);
     315              :     }
     316              : 
     317           93 :     while (*str && *str != '#')
     318              :     {
     319          348 :         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          295 :             if (!skip_space && found_space)
     326              :             {
     327            3 :                 appendPQExpBufferChar(pattern, ' ');
     328            3 :                 skip_space = true;
     329              :             }
     330              : 
     331          295 :             appendPQExpBufferChar(pattern, *str++);
     332              :         }
     333              : 
     334           53 :         skip_space = false;
     335              : 
     336           53 :         if (*str == '"')
     337              :         {
     338            7 :             if (found_space)
     339            0 :                 appendPQExpBufferChar(pattern, ' ');
     340              : 
     341            7 :             str = read_quoted_string(fstate, str, pattern);
     342              :         }
     343           46 :         else if (*str == ',')
     344              :         {
     345            1 :             appendPQExpBufferStr(pattern, ", ");
     346            1 :             skip_space = true;
     347            1 :             str++;
     348              :         }
     349           45 :         else if (*str && strchr(".()", *str))
     350              :         {
     351            7 :             appendPQExpBufferChar(pattern, *str++);
     352            7 :             skip_space = true;
     353              :         }
     354              : 
     355           53 :         found_space = false;
     356              : 
     357              :         /* skip ending whitespaces */
     358           95 :         while (isspace((unsigned char) *str))
     359              :         {
     360           42 :             found_space = true;
     361           42 :             str++;
     362              :         }
     363              :     }
     364              : 
     365           40 :     return str;
     366              : }
     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           82 : filter_read_item(FilterStateData *fstate,
     393              :                  char **objname,
     394              :                  FilterCommandType *comtype,
     395              :                  FilterObjectType *objtype)
     396              : {
     397           82 :     if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
     398              :     {
     399           52 :         const char *str = fstate->linebuff.data;
     400              :         const char *keyword;
     401              :         int         size;
     402              :         PQExpBufferData pattern;
     403              : 
     404           52 :         fstate->lineno++;
     405              : 
     406              :         /* Skip initial white spaces */
     407           63 :         while (isspace((unsigned char) *str))
     408           11 :             str++;
     409              : 
     410              :         /*
     411              :          * Skip empty lines or lines where the first non-whitespace character
     412              :          * is a hash indicating a comment.
     413              :          */
     414           52 :         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           45 :             keyword = filter_get_keyword(&str, &size);
     421           45 :             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              :             }
     427              : 
     428           45 :             if (is_keyword_str("include", keyword, size))
     429           26 :                 *comtype = FILTER_COMMAND_TYPE_INCLUDE;
     430           19 :             else if (is_keyword_str("exclude", keyword, size))
     431           17 :                 *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
     432              :             else
     433              :             {
     434            2 :                 pg_log_filter_error(fstate,
     435            2 :                                     _("invalid filter command (expected \"include\" or \"exclude\")"));
     436            2 :                 fstate->exit_nicely(1);
     437              :             }
     438              : 
     439           43 :             keyword = filter_get_keyword(&str, &size);
     440           43 :             if (!keyword)
     441              :             {
     442            0 :                 pg_log_filter_error(fstate, _("missing filter object type"));
     443            0 :                 fstate->exit_nicely(1);
     444              :             }
     445              : 
     446           43 :             if (!get_object_type(keyword, size, objtype))
     447              :             {
     448            2 :                 pg_log_filter_error(fstate,
     449            2 :                                     _("unsupported filter object type: \"%.*s\""), size, keyword);
     450            2 :                 fstate->exit_nicely(1);
     451              :             }
     452              : 
     453           41 :             initPQExpBuffer(&pattern);
     454              : 
     455           41 :             str = read_pattern(fstate, str, &pattern);
     456           40 :             *objname = pattern.data;
     457              :         }
     458              :         else
     459              :         {
     460            7 :             *objname = NULL;
     461            7 :             *comtype = FILTER_COMMAND_TYPE_NONE;
     462            7 :             *objtype = FILTER_OBJECT_TYPE_NONE;
     463              :         }
     464              : 
     465           47 :         return true;
     466              :     }
     467              : 
     468           30 :     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              :     }
     473              : 
     474           30 :     return false;
     475              : }
        

Generated by: LCOV version 2.0-1