LCOV - code coverage report
Current view: top level - src/bin/pg_dump - filter.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 78.9 % 194 153
Test Date: 2026-07-11 05:15:30 Functions: 100.0 % 9 9
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 73.5 % 155 114

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

Generated by: LCOV version 2.0-1