LCOV - code coverage report
Current view: top level - src/bin/pg_dump - filter.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 153 194 78.9 %
Date: 2024-11-21 08:14:44 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-2024, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  * IDENTIFICATION
      10             :  *    src/bin/pg_dump/filter.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : #include "postgres_fe.h"
      15             : 
      16             : #include "common/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 (limited to ascii alphabetic characters) in
     175             :  * the passed in line buffer. Returns NULL when the buffer is empty or the first
     176             :  * char is not alpha. The char '_' is allowed, except as the first character.
     177             :  * The length of the found keyword is returned in the size parameter.
     178             :  */
     179             : static const char *
     180         176 : filter_get_keyword(const char **line, int *size)
     181             : {
     182         176 :     const char *ptr = *line;
     183         176 :     const char *result = NULL;
     184             : 
     185             :     /* Set returned length preemptively in case no keyword is found */
     186         176 :     *size = 0;
     187             : 
     188             :     /* Skip initial whitespace */
     189         266 :     while (isspace((unsigned char) *ptr))
     190          90 :         ptr++;
     191             : 
     192         176 :     if (isalpha((unsigned char) *ptr))
     193             :     {
     194         176 :         result = ptr++;
     195             : 
     196        1244 :         while (isalpha((unsigned char) *ptr) || *ptr == '_')
     197        1068 :             ptr++;
     198             : 
     199         176 :         *size = ptr - result;
     200             :     }
     201             : 
     202         176 :     *line = ptr;
     203             : 
     204         176 :     return result;
     205             : }
     206             : 
     207             : /*
     208             :  * read_quoted_string - read quoted possibly multi line string
     209             :  *
     210             :  * Reads a quoted string which can span over multiple lines and returns a
     211             :  * pointer to next char after ending double quotes; it will exit on errors.
     212             :  */
     213             : static const char *
     214          14 : read_quoted_string(FilterStateData *fstate,
     215             :                    const char *str,
     216             :                    PQExpBuffer pattern)
     217             : {
     218          14 :     appendPQExpBufferChar(pattern, '"');
     219          14 :     str++;
     220             : 
     221             :     while (1)
     222             :     {
     223             :         /*
     224             :          * We can ignore \r or \n chars because the string is read by
     225             :          * pg_get_line_buf, so these chars should be just trailing chars.
     226             :          */
     227         140 :         if (*str == '\r' || *str == '\n')
     228             :         {
     229           8 :             str++;
     230           8 :             continue;
     231             :         }
     232             : 
     233         132 :         if (*str == '\0')
     234             :         {
     235             :             Assert(fstate->linebuff.data);
     236             : 
     237           8 :             if (!pg_get_line_buf(fstate->fp, &fstate->linebuff))
     238             :             {
     239           0 :                 if (ferror(fstate->fp))
     240           0 :                     pg_log_error("could not read from filter file \"%s\": %m",
     241             :                                  fstate->filename);
     242             :                 else
     243           0 :                     pg_log_filter_error(fstate, _("unexpected end of file"));
     244             : 
     245           0 :                 fstate->exit_nicely(1);
     246             :             }
     247             : 
     248           8 :             str = fstate->linebuff.data;
     249             : 
     250           8 :             appendPQExpBufferChar(pattern, '\n');
     251           8 :             fstate->lineno++;
     252             :         }
     253             : 
     254         132 :         if (*str == '"')
     255             :         {
     256          14 :             appendPQExpBufferChar(pattern, '"');
     257          14 :             str++;
     258             : 
     259          14 :             if (*str == '"')
     260             :             {
     261           0 :                 appendPQExpBufferChar(pattern, '"');
     262           0 :                 str++;
     263             :             }
     264             :             else
     265          14 :                 break;
     266             :         }
     267         118 :         else if (*str == '\\')
     268             :         {
     269           8 :             str++;
     270           8 :             if (*str == 'n')
     271           8 :                 appendPQExpBufferChar(pattern, '\n');
     272           0 :             else if (*str == '\\')
     273           0 :                 appendPQExpBufferChar(pattern, '\\');
     274             : 
     275           8 :             str++;
     276             :         }
     277             :         else
     278         110 :             appendPQExpBufferChar(pattern, *str++);
     279             :     }
     280             : 
     281          14 :     return str;
     282             : }
     283             : 
     284             : /*
     285             :  * read_pattern - reads on object pattern from input
     286             :  *
     287             :  * This function will parse any valid identifier (quoted or not, qualified or
     288             :  * not), which can also includes the full signature for routines.
     289             :  * Note that this function takes special care to sanitize the detected
     290             :  * identifier (removing extraneous whitespaces or other unnecessary
     291             :  * characters).  This is necessary as most backup/restore filtering functions
     292             :  * only recognize identifiers if they are written exactly the same way as
     293             :  * they are output by the server.
     294             :  *
     295             :  * Returns a pointer to next character after the found identifier and exits
     296             :  * on error.
     297             :  */
     298             : static const char *
     299          82 : read_pattern(FilterStateData *fstate, const char *str, PQExpBuffer pattern)
     300             : {
     301          82 :     bool        skip_space = true;
     302          82 :     bool        found_space = false;
     303             : 
     304             :     /* Skip initial whitespace */
     305         164 :     while (isspace((unsigned char) *str))
     306          82 :         str++;
     307             : 
     308          82 :     if (*str == '\0')
     309             :     {
     310           2 :         pg_log_filter_error(fstate, _("missing object name pattern"));
     311           2 :         fstate->exit_nicely(1);
     312             :     }
     313             : 
     314         186 :     while (*str && *str != '#')
     315             :     {
     316         696 :         while (*str && !isspace((unsigned char) *str) && !strchr("#,.()\"", *str))
     317             :         {
     318             :             /*
     319             :              * Append space only when it is allowed, and when it was found in
     320             :              * original string.
     321             :              */
     322         590 :             if (!skip_space && found_space)
     323             :             {
     324           6 :                 appendPQExpBufferChar(pattern, ' ');
     325           6 :                 skip_space = true;
     326             :             }
     327             : 
     328         590 :             appendPQExpBufferChar(pattern, *str++);
     329             :         }
     330             : 
     331         106 :         skip_space = false;
     332             : 
     333         106 :         if (*str == '"')
     334             :         {
     335          14 :             if (found_space)
     336           0 :                 appendPQExpBufferChar(pattern, ' ');
     337             : 
     338          14 :             str = read_quoted_string(fstate, str, pattern);
     339             :         }
     340          92 :         else if (*str == ',')
     341             :         {
     342           2 :             appendPQExpBufferStr(pattern, ", ");
     343           2 :             skip_space = true;
     344           2 :             str++;
     345             :         }
     346          90 :         else if (*str && strchr(".()", *str))
     347             :         {
     348          14 :             appendPQExpBufferChar(pattern, *str++);
     349          14 :             skip_space = true;
     350             :         }
     351             : 
     352         106 :         found_space = false;
     353             : 
     354             :         /* skip ending whitespaces */
     355         190 :         while (isspace((unsigned char) *str))
     356             :         {
     357          84 :             found_space = true;
     358          84 :             str++;
     359             :         }
     360             :     }
     361             : 
     362          80 :     return str;
     363             : }
     364             : 
     365             : /*
     366             :  * filter_read_item - Read command/type/pattern triplet from a filter file
     367             :  *
     368             :  * This will parse one filter item from the filter file, and while it is a
     369             :  * row based format a pattern may span more than one line due to how object
     370             :  * names can be constructed.  The expected format of the filter file is:
     371             :  *
     372             :  * <command> <object_type> <pattern>
     373             :  *
     374             :  * command can be "include" or "exclude".
     375             :  *
     376             :  * Supported object types are described by enum FilterObjectType
     377             :  * (see function get_object_type).
     378             :  *
     379             :  * pattern can be any possibly-quoted and possibly-qualified identifier.  It
     380             :  * follows the same rules as other object include and exclude functions so it
     381             :  * can also use wildcards.
     382             :  *
     383             :  * Returns true when one filter item was successfully read and parsed.  When
     384             :  * object name contains \n chars, then more than one line from input file can
     385             :  * be processed.  Returns false when the filter file reaches EOF. In case of
     386             :  * error, the function will emit an appropriate error message and exit.
     387             :  */
     388             : bool
     389         164 : filter_read_item(FilterStateData *fstate,
     390             :                  char **objname,
     391             :                  FilterCommandType *comtype,
     392             :                  FilterObjectType *objtype)
     393             : {
     394         164 :     if (pg_get_line_buf(fstate->fp, &fstate->linebuff))
     395             :     {
     396         104 :         const char *str = fstate->linebuff.data;
     397             :         const char *keyword;
     398             :         int         size;
     399             :         PQExpBufferData pattern;
     400             : 
     401         104 :         fstate->lineno++;
     402             : 
     403             :         /* Skip initial white spaces */
     404         126 :         while (isspace((unsigned char) *str))
     405          22 :             str++;
     406             : 
     407             :         /*
     408             :          * Skip empty lines or lines where the first non-whitespace character
     409             :          * is a hash indicating a comment.
     410             :          */
     411         104 :         if (*str != '\0' && *str != '#')
     412             :         {
     413             :             /*
     414             :              * First we expect sequence of two keywords, {include|exclude}
     415             :              * followed by the object type to operate on.
     416             :              */
     417          90 :             keyword = filter_get_keyword(&str, &size);
     418          90 :             if (!keyword)
     419             :             {
     420           0 :                 pg_log_filter_error(fstate,
     421           0 :                                     _("no filter command found (expected \"include\" or \"exclude\")"));
     422           0 :                 fstate->exit_nicely(1);
     423             :             }
     424             : 
     425          90 :             if (is_keyword_str("include", keyword, size))
     426          54 :                 *comtype = FILTER_COMMAND_TYPE_INCLUDE;
     427          36 :             else if (is_keyword_str("exclude", keyword, size))
     428          32 :                 *comtype = FILTER_COMMAND_TYPE_EXCLUDE;
     429             :             else
     430             :             {
     431           4 :                 pg_log_filter_error(fstate,
     432           4 :                                     _("invalid filter command (expected \"include\" or \"exclude\")"));
     433           4 :                 fstate->exit_nicely(1);
     434             :             }
     435             : 
     436          86 :             keyword = filter_get_keyword(&str, &size);
     437          86 :             if (!keyword)
     438             :             {
     439           0 :                 pg_log_filter_error(fstate, _("missing filter object type"));
     440           0 :                 fstate->exit_nicely(1);
     441             :             }
     442             : 
     443          86 :             if (!get_object_type(keyword, size, objtype))
     444             :             {
     445           4 :                 pg_log_filter_error(fstate,
     446           4 :                                     _("unsupported filter object type: \"%.*s\""), size, keyword);
     447           4 :                 fstate->exit_nicely(1);
     448             :             }
     449             : 
     450          82 :             initPQExpBuffer(&pattern);
     451             : 
     452          82 :             str = read_pattern(fstate, str, &pattern);
     453          80 :             *objname = pattern.data;
     454             :         }
     455             :         else
     456             :         {
     457          14 :             *objname = NULL;
     458          14 :             *comtype = FILTER_COMMAND_TYPE_NONE;
     459          14 :             *objtype = FILTER_OBJECT_TYPE_NONE;
     460             :         }
     461             : 
     462          94 :         return true;
     463             :     }
     464             : 
     465          60 :     if (ferror(fstate->fp))
     466             :     {
     467           0 :         pg_log_error("could not read from filter file \"%s\": %m", fstate->filename);
     468           0 :         fstate->exit_nicely(1);
     469             :     }
     470             : 
     471          60 :     return false;
     472             : }

Generated by: LCOV version 1.14