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

Generated by: LCOV version 1.16