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

Generated by: LCOV version 1.14