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

Generated by: LCOV version 1.14