LCOV - code coverage report
Current view: top level - src/bin/pg_dump - dumputils.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 82.1 % 324 266
Test Date: 2026-08-23 00:15:51 Functions: 100.0 % 14 14
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 61.5 % 426 262

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * Utility routines for SQL dumping
       4                 :             :  *
       5                 :             :  * Basically this is stuff that is useful in both pg_dump and pg_dumpall.
       6                 :             :  *
       7                 :             :  *
       8                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       9                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
      10                 :             :  *
      11                 :             :  * src/bin/pg_dump/dumputils.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             :  */
      15                 :             : #include "postgres_fe.h"
      16                 :             : 
      17                 :             : #include <ctype.h>
      18                 :             : 
      19                 :             : #include "common/file_perm.h"
      20                 :             : #include "common/logging.h"
      21                 :             : #include "dumputils.h"
      22                 :             : #include "fe_utils/string_utils.h"
      23                 :             : 
      24                 :             : static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      25                 :             : 
      26                 :             : static bool parseAclItem(const char *item, const char *type,
      27                 :             :                          const char *name, const char *subname, int remoteVersion,
      28                 :             :                          PQExpBuffer grantee, PQExpBuffer grantor,
      29                 :             :                          PQExpBuffer privs, PQExpBuffer privswgo);
      30                 :             : static char *dequoteAclUserName(PQExpBuffer output, char *input);
      31                 :             : static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
      32                 :             :                    const char *subname);
      33                 :             : 
      34                 :             : 
      35                 :             : /*
      36                 :             :  * Sanitize a string to be included in an SQL comment or TOC listing, by
      37                 :             :  * replacing any newlines with spaces.  This ensures each logical output line
      38                 :             :  * is in fact one physical output line, to prevent corruption of the dump
      39                 :             :  * (which could, in the worst case, present an SQL injection vulnerability
      40                 :             :  * if someone were to incautiously load a dump containing objects with
      41                 :             :  * maliciously crafted names).
      42                 :             :  *
      43                 :             :  * The result is a freshly malloc'd string.  If the input string is NULL,
      44                 :             :  * return a malloc'ed empty string, unless want_hyphen, in which case return a
      45                 :             :  * malloc'ed hyphen.
      46                 :             :  *
      47                 :             :  * Note that we currently don't bother to quote names, meaning that the name
      48                 :             :  * fields aren't automatically parseable.  "pg_restore -L" doesn't care because
      49                 :             :  * it only examines the dumpId field, but someday we might want to try harder.
      50                 :             :  */
      51                 :             : char *
      52                 :      117830 : sanitize_line(const char *str, bool want_hyphen)
      53                 :             : {
      54                 :             :     char       *result;
      55                 :             :     char       *s;
      56                 :             : 
      57         [ +  + ]:      117830 :     if (!str)
      58         [ +  + ]:        4683 :         return pg_strdup(want_hyphen ? "-" : "");
      59                 :             : 
      60                 :      113147 :     result = pg_strdup(str);
      61                 :             : 
      62         [ +  + ]:     1457170 :     for (s = result; *s != '\0'; s++)
      63                 :             :     {
      64   [ +  +  -  + ]:     1344023 :         if (*s == '\n' || *s == '\r')
      65                 :         194 :             *s = ' ';
      66                 :             :     }
      67                 :             : 
      68                 :      113147 :     return result;
      69                 :             : }
      70                 :             : 
      71                 :             : 
      72                 :             : /*
      73                 :             :  * Build GRANT/REVOKE command(s) for an object.
      74                 :             :  *
      75                 :             :  *  name: the object name, in the form to use in the commands (already quoted)
      76                 :             :  *  subname: the sub-object name, if any (already quoted); NULL if none
      77                 :             :  *  nspname: the namespace the object is in (NULL if none); not pre-quoted
      78                 :             :  *  type: the object type (as seen in GRANT command: must be one of
      79                 :             :  *      TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
      80                 :             :  *      FOREIGN DATA WRAPPER, SERVER, PARAMETER or LARGE OBJECT)
      81                 :             :  *  acls: the ACL string fetched from the database
      82                 :             :  *  baseacls: the initial ACL string for this object
      83                 :             :  *  owner: username of object owner (will be passed through fmtId); can be
      84                 :             :  *      NULL or empty string to indicate "no owner known"
      85                 :             :  *  prefix: string to prefix to each generated command; typically empty
      86                 :             :  *  remoteVersion: version of database
      87                 :             :  *
      88                 :             :  * Returns true if okay, false if could not parse the acl string.
      89                 :             :  * The resulting commands (if any) are appended to the contents of 'sql'.
      90                 :             :  *
      91                 :             :  * baseacls is typically the result of acldefault() for the object's type
      92                 :             :  * and owner.  However, if there is a pg_init_privs entry for the object,
      93                 :             :  * it should instead be the initprivs ACLs.  When acls is itself a
      94                 :             :  * pg_init_privs entry, baseacls is what to dump that relative to; then
      95                 :             :  * it can be either an acldefault() value or an empty ACL "{}".
      96                 :             :  *
      97                 :             :  * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES "
      98                 :             :  * or something similar, and name is an empty string.
      99                 :             :  *
     100                 :             :  * Note: beware of passing a fmtId() result directly as 'name' or 'subname',
     101                 :             :  * since this routine uses fmtId() internally.
     102                 :             :  */
     103                 :             : bool
     104                 :       32160 : buildACLCommands(const char *name, const char *subname, const char *nspname,
     105                 :             :                  const char *type, const char *acls, const char *baseacls,
     106                 :             :                  const char *owner, const char *prefix, int remoteVersion,
     107                 :             :                  PQExpBuffer sql)
     108                 :             : {
     109                 :       32160 :     bool        ok = true;
     110                 :       32160 :     char      **aclitems = NULL;
     111                 :       32160 :     char      **baseitems = NULL;
     112                 :       32160 :     char      **grantitems = NULL;
     113                 :       32160 :     char      **revokeitems = NULL;
     114                 :       32160 :     int         naclitems = 0;
     115                 :       32160 :     int         nbaseitems = 0;
     116                 :       32160 :     int         ngrantitems = 0;
     117                 :       32160 :     int         nrevokeitems = 0;
     118                 :             :     int         i;
     119                 :             :     PQExpBuffer grantee,
     120                 :             :                 grantor,
     121                 :             :                 privs,
     122                 :             :                 privswgo;
     123                 :             :     PQExpBuffer firstsql,
     124                 :             :                 secondsql;
     125                 :             : 
     126                 :             :     /*
     127                 :             :      * If the acl was NULL (initial default state), we need do nothing.  Note
     128                 :             :      * that this is distinguishable from all-privileges-revoked, which will
     129                 :             :      * look like an empty array ("{}").
     130                 :             :      */
     131   [ +  -  +  + ]:       32160 :     if (acls == NULL || *acls == '\0')
     132                 :         119 :         return true;            /* object has default permissions */
     133                 :             : 
     134                 :             :     /* treat empty-string owner same as NULL */
     135   [ +  -  -  + ]:       32041 :     if (owner && *owner == '\0')
     136                 :           0 :         owner = NULL;
     137                 :             : 
     138                 :             :     /* Parse the acls array */
     139         [ -  + ]:       32041 :     if (!parsePGArray(acls, &aclitems, &naclitems))
     140                 :             :     {
     141                 :           0 :         free(aclitems);
     142                 :           0 :         return false;
     143                 :             :     }
     144                 :             : 
     145                 :             :     /* Parse the baseacls too */
     146         [ -  + ]:       32041 :     if (!parsePGArray(baseacls, &baseitems, &nbaseitems))
     147                 :             :     {
     148                 :           0 :         free(aclitems);
     149                 :           0 :         free(baseitems);
     150                 :           0 :         return false;
     151                 :             :     }
     152                 :             : 
     153                 :             :     /*
     154                 :             :      * Compare the actual ACL with the base ACL, extracting the privileges
     155                 :             :      * that need to be granted (i.e., are in the actual ACL but not the base
     156                 :             :      * ACL) and the ones that need to be revoked (the reverse).  We use plain
     157                 :             :      * string comparisons to check for matches.  In principle that could be
     158                 :             :      * fooled by extraneous issues such as whitespace, but since all these
     159                 :             :      * strings are the work of aclitemout(), it should be OK in practice.
     160                 :             :      * Besides, a false mismatch will just cause the output to be a little
     161                 :             :      * more verbose than it really needed to be.
     162                 :             :      */
     163                 :       32041 :     grantitems = pg_malloc_array(char *, naclitems);
     164         [ +  + ]:       88937 :     for (i = 0; i < naclitems; i++)
     165                 :             :     {
     166                 :       56896 :         bool        found = false;
     167                 :             : 
     168         [ +  + ]:       82265 :         for (int j = 0; j < nbaseitems; j++)
     169                 :             :         {
     170         [ +  + ]:       80374 :             if (strcmp(aclitems[i], baseitems[j]) == 0)
     171                 :             :             {
     172                 :       55005 :                 found = true;
     173                 :       55005 :                 break;
     174                 :             :             }
     175                 :             :         }
     176         [ +  + ]:       56896 :         if (!found)
     177                 :        1891 :             grantitems[ngrantitems++] = aclitems[i];
     178                 :             :     }
     179                 :       32041 :     revokeitems = pg_malloc_array(char *, nbaseitems);
     180         [ +  + ]:       87417 :     for (i = 0; i < nbaseitems; i++)
     181                 :             :     {
     182                 :       55376 :         bool        found = false;
     183                 :             : 
     184         [ +  + ]:       80013 :         for (int j = 0; j < naclitems; j++)
     185                 :             :         {
     186         [ +  + ]:       79642 :             if (strcmp(baseitems[i], aclitems[j]) == 0)
     187                 :             :             {
     188                 :       55005 :                 found = true;
     189                 :       55005 :                 break;
     190                 :             :             }
     191                 :             :         }
     192         [ +  + ]:       55376 :         if (!found)
     193                 :         371 :             revokeitems[nrevokeitems++] = baseitems[i];
     194                 :             :     }
     195                 :             : 
     196                 :             :     /* Prepare working buffers */
     197                 :       32041 :     grantee = createPQExpBuffer();
     198                 :       32041 :     grantor = createPQExpBuffer();
     199                 :       32041 :     privs = createPQExpBuffer();
     200                 :       32041 :     privswgo = createPQExpBuffer();
     201                 :             : 
     202                 :             :     /*
     203                 :             :      * At the end, these two will be pasted together to form the result.
     204                 :             :      */
     205                 :       32041 :     firstsql = createPQExpBuffer();
     206                 :       32041 :     secondsql = createPQExpBuffer();
     207                 :             : 
     208                 :             :     /*
     209                 :             :      * Build REVOKE statements for ACLs listed in revokeitems[].
     210                 :             :      */
     211         [ +  + ]:       32412 :     for (i = 0; i < nrevokeitems; i++)
     212                 :             :     {
     213         [ -  + ]:         371 :         if (!parseAclItem(revokeitems[i],
     214                 :             :                           type, name, subname, remoteVersion,
     215                 :             :                           grantee, grantor, privs, NULL))
     216                 :             :         {
     217                 :           0 :             ok = false;
     218                 :           0 :             break;
     219                 :             :         }
     220                 :             : 
     221         [ +  - ]:         371 :         if (privs->len > 0)
     222                 :             :         {
     223                 :         371 :             appendPQExpBuffer(firstsql, "%sREVOKE %s ON %s ",
     224                 :             :                               prefix, privs->data, type);
     225   [ +  +  +  - ]:         371 :             if (nspname && *nspname)
     226                 :         206 :                 appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
     227   [ +  -  +  + ]:         371 :             if (name && *name)
     228                 :         297 :                 appendPQExpBuffer(firstsql, "%s ", name);
     229                 :         371 :             appendPQExpBufferStr(firstsql, "FROM ");
     230         [ +  + ]:         371 :             if (grantee->len == 0)
     231                 :         191 :                 appendPQExpBufferStr(firstsql, "PUBLIC;\n");
     232                 :             :             else
     233                 :         180 :                 appendPQExpBuffer(firstsql, "%s;\n",
     234                 :         180 :                                   fmtId(grantee->data));
     235                 :             :         }
     236                 :             :     }
     237                 :             : 
     238                 :             :     /*
     239                 :             :      * At this point we have issued REVOKE statements for all initial and
     240                 :             :      * default privileges that are no longer present on the object, so we are
     241                 :             :      * almost ready to GRANT the privileges listed in grantitems[].
     242                 :             :      *
     243                 :             :      * We still need some hacking though to cover the case where new default
     244                 :             :      * public privileges are added in new versions: the REVOKE ALL will revoke
     245                 :             :      * them, leading to behavior different from what the old version had,
     246                 :             :      * which is generally not what's wanted.  So add back default privs if the
     247                 :             :      * source database is too old to have had that particular priv.  (As of
     248                 :             :      * right now, no such cases exist in supported versions.)
     249                 :             :      */
     250                 :             : 
     251                 :             :     /*
     252                 :             :      * Scan individual ACL items to be granted.
     253                 :             :      *
     254                 :             :      * The order in which privileges appear in the ACL string (the order they
     255                 :             :      * have been GRANT'd in, which the backend maintains) must be preserved to
     256                 :             :      * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on
     257                 :             :      * those are dumped in the correct order.  However, some old server
     258                 :             :      * versions will show grants to PUBLIC before the owner's own grants; for
     259                 :             :      * consistency's sake, force the owner's grants to be output first.
     260                 :             :      */
     261         [ +  + ]:       33932 :     for (i = 0; i < ngrantitems; i++)
     262                 :             :     {
     263         [ +  - ]:        1891 :         if (parseAclItem(grantitems[i], type, name, subname, remoteVersion,
     264                 :             :                          grantee, grantor, privs, privswgo))
     265                 :             :         {
     266                 :             :             /*
     267                 :             :              * If the grantor isn't the owner, we'll need to use SET SESSION
     268                 :             :              * AUTHORIZATION to become the grantor.  Issue the SET/RESET only
     269                 :             :              * if there's something useful to do.
     270                 :             :              */
     271   [ +  +  +  - ]:        1891 :             if (privs->len > 0 || privswgo->len > 0)
     272                 :             :             {
     273                 :             :                 PQExpBuffer thissql;
     274                 :             : 
     275                 :             :                 /* Set owner as grantor if that's not explicit in the ACL */
     276   [ -  +  -  - ]:        1891 :                 if (grantor->len == 0 && owner)
     277                 :           0 :                     printfPQExpBuffer(grantor, "%s", owner);
     278                 :             : 
     279                 :             :                 /* Make sure owner's own grants are output before others */
     280         [ +  - ]:        1891 :                 if (owner &&
     281         [ +  + ]:        1891 :                     strcmp(grantee->data, owner) == 0 &&
     282         [ +  - ]:         124 :                     strcmp(grantor->data, owner) == 0)
     283                 :         124 :                     thissql = firstsql;
     284                 :             :                 else
     285                 :        1767 :                     thissql = secondsql;
     286                 :             : 
     287         [ +  - ]:        1891 :                 if (grantor->len > 0
     288   [ +  -  -  + ]:        1891 :                     && (!owner || strcmp(owner, grantor->data) != 0))
     289                 :           0 :                     appendPQExpBuffer(thissql, "SET SESSION AUTHORIZATION %s;\n",
     290                 :           0 :                                       fmtId(grantor->data));
     291                 :             : 
     292         [ +  + ]:        1891 :                 if (privs->len > 0)
     293                 :             :                 {
     294                 :        1889 :                     appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
     295                 :             :                                       prefix, privs->data, type);
     296   [ +  +  +  - ]:        1889 :                     if (nspname && *nspname)
     297                 :        1605 :                         appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
     298   [ +  -  +  + ]:        1889 :                     if (name && *name)
     299                 :        1791 :                         appendPQExpBuffer(thissql, "%s ", name);
     300                 :        1889 :                     appendPQExpBufferStr(thissql, "TO ");
     301         [ +  + ]:        1889 :                     if (grantee->len == 0)
     302                 :        1103 :                         appendPQExpBufferStr(thissql, "PUBLIC;\n");
     303                 :             :                     else
     304                 :         786 :                         appendPQExpBuffer(thissql, "%s;\n", fmtId(grantee->data));
     305                 :             :                 }
     306         [ +  + ]:        1891 :                 if (privswgo->len > 0)
     307                 :             :                 {
     308                 :          20 :                     appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
     309                 :             :                                       prefix, privswgo->data, type);
     310   [ +  +  +  - ]:          20 :                     if (nspname && *nspname)
     311                 :          19 :                         appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
     312   [ +  -  +  - ]:          20 :                     if (name && *name)
     313                 :          20 :                         appendPQExpBuffer(thissql, "%s ", name);
     314                 :          20 :                     appendPQExpBufferStr(thissql, "TO ");
     315         [ -  + ]:          20 :                     if (grantee->len == 0)
     316                 :           0 :                         appendPQExpBufferStr(thissql, "PUBLIC");
     317                 :             :                     else
     318                 :          20 :                         appendPQExpBufferStr(thissql, fmtId(grantee->data));
     319                 :          20 :                     appendPQExpBufferStr(thissql, " WITH GRANT OPTION;\n");
     320                 :             :                 }
     321                 :             : 
     322         [ +  - ]:        1891 :                 if (grantor->len > 0
     323   [ +  -  -  + ]:        1891 :                     && (!owner || strcmp(owner, grantor->data) != 0))
     324                 :           0 :                     appendPQExpBufferStr(thissql, "RESET SESSION AUTHORIZATION;\n");
     325                 :             :             }
     326                 :             :         }
     327                 :             :         else
     328                 :             :         {
     329                 :             :             /* parseAclItem failed, give up */
     330                 :           0 :             ok = false;
     331                 :           0 :             break;
     332                 :             :         }
     333                 :             :     }
     334                 :             : 
     335                 :       32041 :     destroyPQExpBuffer(grantee);
     336                 :       32041 :     destroyPQExpBuffer(grantor);
     337                 :       32041 :     destroyPQExpBuffer(privs);
     338                 :       32041 :     destroyPQExpBuffer(privswgo);
     339                 :             : 
     340                 :       32041 :     appendPQExpBuffer(sql, "%s%s", firstsql->data, secondsql->data);
     341                 :       32041 :     destroyPQExpBuffer(firstsql);
     342                 :       32041 :     destroyPQExpBuffer(secondsql);
     343                 :             : 
     344                 :       32041 :     free(aclitems);
     345                 :       32041 :     free(baseitems);
     346                 :       32041 :     pg_free(grantitems);
     347                 :       32041 :     pg_free(revokeitems);
     348                 :             : 
     349                 :       32041 :     return ok;
     350                 :             : }
     351                 :             : 
     352                 :             : /*
     353                 :             :  * Build ALTER DEFAULT PRIVILEGES command(s) for a single pg_default_acl entry.
     354                 :             :  *
     355                 :             :  *  type: the object type (TABLES, FUNCTIONS, etc)
     356                 :             :  *  nspname: schema name, or NULL for global default privileges
     357                 :             :  *  acls: the ACL string fetched from the database
     358                 :             :  *  acldefault: the appropriate default ACL for the object type and owner
     359                 :             :  *  owner: username of privileges owner (will be passed through fmtId)
     360                 :             :  *  remoteVersion: version of database
     361                 :             :  *
     362                 :             :  * Returns true if okay, false if could not parse the acl string.
     363                 :             :  * The resulting commands (if any) are appended to the contents of 'sql'.
     364                 :             :  */
     365                 :             : bool
     366                 :         140 : buildDefaultACLCommands(const char *type, const char *nspname,
     367                 :             :                         const char *acls, const char *acldefault,
     368                 :             :                         const char *owner,
     369                 :             :                         int remoteVersion,
     370                 :             :                         PQExpBuffer sql)
     371                 :             : {
     372                 :             :     PQExpBuffer prefix;
     373                 :             : 
     374                 :         140 :     prefix = createPQExpBuffer();
     375                 :             : 
     376                 :             :     /*
     377                 :             :      * We incorporate the target role directly into the command, rather than
     378                 :             :      * playing around with SET ROLE or anything like that.  This is so that a
     379                 :             :      * permissions error leads to nothing happening, rather than changing
     380                 :             :      * default privileges for the wrong user.
     381                 :             :      */
     382                 :         140 :     appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s ",
     383                 :             :                       fmtId(owner));
     384         [ +  + ]:         140 :     if (nspname)
     385                 :          66 :         appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
     386                 :             : 
     387                 :             :     /*
     388                 :             :      * There's no such thing as initprivs for a default ACL, so the base ACL
     389                 :             :      * is always just the object-type-specific default.
     390                 :             :      */
     391         [ -  + ]:         140 :     if (!buildACLCommands("", NULL, NULL, type,
     392                 :             :                           acls, acldefault, owner,
     393                 :         140 :                           prefix->data, remoteVersion, sql))
     394                 :             :     {
     395                 :           0 :         destroyPQExpBuffer(prefix);
     396                 :           0 :         return false;
     397                 :             :     }
     398                 :             : 
     399                 :         140 :     destroyPQExpBuffer(prefix);
     400                 :             : 
     401                 :         140 :     return true;
     402                 :             : }
     403                 :             : 
     404                 :             : /*
     405                 :             :  * This will parse an aclitem string, having the general form
     406                 :             :  *      username=privilegecodes/grantor
     407                 :             :  *
     408                 :             :  * Returns true on success, false on parse error.  On success, the components
     409                 :             :  * of the string are returned in the PQExpBuffer parameters.
     410                 :             :  *
     411                 :             :  * The returned grantee string will be the dequoted username, or an empty
     412                 :             :  * string in the case of a grant to PUBLIC.  The returned grantor is the
     413                 :             :  * dequoted grantor name.  Privilege characters are translated to GRANT/REVOKE
     414                 :             :  * comma-separated privileges lists.  If "privswgo" is non-NULL, the result is
     415                 :             :  * separate lists for privileges with grant option ("privswgo") and without
     416                 :             :  * ("privs").  Otherwise, "privs" bears every relevant privilege, ignoring the
     417                 :             :  * grant option distinction.
     418                 :             :  *
     419                 :             :  * Note: for cross-version compatibility, it's important to use ALL to
     420                 :             :  * represent the privilege sets whenever appropriate.
     421                 :             :  */
     422                 :             : static bool
     423                 :        2262 : parseAclItem(const char *item, const char *type,
     424                 :             :              const char *name, const char *subname, int remoteVersion,
     425                 :             :              PQExpBuffer grantee, PQExpBuffer grantor,
     426                 :             :              PQExpBuffer privs, PQExpBuffer privswgo)
     427                 :             : {
     428                 :             :     char       *buf;
     429                 :        2262 :     bool        all_with_go = true;
     430                 :        2262 :     bool        all_without_go = true;
     431                 :             :     char       *eqpos;
     432                 :             :     char       *slpos;
     433                 :             :     char       *pos;
     434                 :             : 
     435                 :        2262 :     buf = pg_strdup(item);
     436                 :             : 
     437                 :             :     /* user or group name is string up to = */
     438                 :        2262 :     eqpos = dequoteAclUserName(grantee, buf);
     439         [ -  + ]:        2262 :     if (*eqpos != '=')
     440                 :             :     {
     441                 :           0 :         pg_free(buf);
     442                 :           0 :         return false;
     443                 :             :     }
     444                 :             : 
     445                 :             :     /* grantor should appear after / */
     446                 :        2262 :     slpos = strchr(eqpos + 1, '/');
     447         [ +  - ]:        2262 :     if (slpos)
     448                 :             :     {
     449                 :        2262 :         *slpos++ = '\0';
     450                 :        2262 :         slpos = dequoteAclUserName(grantor, slpos);
     451         [ -  + ]:        2262 :         if (*slpos != '\0')
     452                 :             :         {
     453                 :           0 :             pg_free(buf);
     454                 :           0 :             return false;
     455                 :             :         }
     456                 :             :     }
     457                 :             :     else
     458                 :             :     {
     459                 :           0 :         pg_free(buf);
     460                 :           0 :         return false;
     461                 :             :     }
     462                 :             : 
     463                 :             :     /* privilege codes */
     464                 :             : #define CONVERT_PRIV(code, keywd) \
     465                 :             : do { \
     466                 :             :     if ((pos = strchr(eqpos + 1, code))) \
     467                 :             :     { \
     468                 :             :         if (*(pos + 1) == '*' && privswgo != NULL) \
     469                 :             :         { \
     470                 :             :             AddAcl(privswgo, keywd, subname); \
     471                 :             :             all_without_go = false; \
     472                 :             :         } \
     473                 :             :         else \
     474                 :             :         { \
     475                 :             :             AddAcl(privs, keywd, subname); \
     476                 :             :             all_with_go = false; \
     477                 :             :         } \
     478                 :             :     } \
     479                 :             :     else \
     480                 :             :         all_with_go = all_without_go = false; \
     481                 :             : } while (0)
     482                 :             : 
     483                 :        2262 :     resetPQExpBuffer(privs);
     484                 :        2262 :     resetPQExpBuffer(privswgo);
     485                 :             : 
     486   [ +  +  +  + ]:        2262 :     if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 ||
     487   [ +  +  -  + ]:         774 :         strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0)
     488                 :             :     {
     489   [ +  +  -  +  :        1585 :         CONVERT_PRIV('r', "SELECT");
                   -  - ]
     490                 :             : 
     491         [ +  + ]:        1585 :         if (strcmp(type, "SEQUENCE") == 0 ||
     492         [ -  + ]:        1530 :             strcmp(type, "SEQUENCES") == 0)
     493                 :             :             /* sequence only */
     494   [ +  +  +  +  :         109 :             CONVERT_PRIV('U', "USAGE");
                   +  + ]
     495                 :             :         else
     496                 :             :         {
     497                 :             :             /* table only */
     498   [ +  +  -  +  :        1530 :             CONVERT_PRIV('a', "INSERT");
                   -  - ]
     499   [ +  +  -  +  :        1530 :             CONVERT_PRIV('x', "REFERENCES");
                   -  - ]
     500                 :             :             /* rest are not applicable to columns */
     501         [ +  + ]:        1530 :             if (subname == NULL)
     502                 :             :             {
     503   [ +  +  -  +  :         418 :                 CONVERT_PRIV('d', "DELETE");
                   -  - ]
     504   [ +  +  -  +  :         418 :                 CONVERT_PRIV('t', "TRIGGER");
                   -  - ]
     505   [ +  +  -  +  :         418 :                 CONVERT_PRIV('D', "TRUNCATE");
                   -  - ]
     506   [ +  +  -  +  :         418 :                 CONVERT_PRIV('m', "MAINTAIN");
                   -  - ]
     507                 :             :             }
     508                 :             :         }
     509                 :             : 
     510                 :             :         /* UPDATE */
     511   [ +  +  +  +  :        1694 :         CONVERT_PRIV('w', "UPDATE");
                   +  + ]
     512                 :             :     }
     513         [ +  + ]:         677 :     else if (strcmp(type, "PROPERTY GRAPH") == 0 ||
     514         [ -  + ]:         639 :              strcmp(type, "PROPERTY GRAPHS") == 0)
     515   [ +  -  -  +  :          76 :         CONVERT_PRIV('r', "SELECT");
                   -  - ]
     516         [ +  + ]:         639 :     else if (strcmp(type, "FUNCTION") == 0 ||
     517         [ +  + ]:         486 :              strcmp(type, "FUNCTIONS") == 0)
     518   [ +  -  +  +  :         436 :         CONVERT_PRIV('X', "EXECUTE");
                   +  + ]
     519         [ +  - ]:         421 :     else if (strcmp(type, "PROCEDURE") == 0 ||
     520         [ -  + ]:         421 :              strcmp(type, "PROCEDURES") == 0)
     521   [ #  #  #  #  :           0 :         CONVERT_PRIV('X', "EXECUTE");
                   #  # ]
     522         [ +  + ]:         421 :     else if (strcmp(type, "LANGUAGE") == 0)
     523   [ +  -  -  +  :          39 :         CONVERT_PRIV('U', "USAGE");
                   -  - ]
     524         [ +  + ]:         382 :     else if (strcmp(type, "SCHEMA") == 0 ||
     525         [ -  + ]:         276 :              strcmp(type, "SCHEMAS") == 0)
     526                 :             :     {
     527   [ +  +  -  +  :         106 :         CONVERT_PRIV('C', "CREATE");
                   -  - ]
     528   [ +  -  -  +  :         212 :         CONVERT_PRIV('U', "USAGE");
                   -  - ]
     529                 :             :     }
     530         [ +  + ]:         276 :     else if (strcmp(type, "DATABASE") == 0)
     531                 :             :     {
     532   [ +  +  -  +  :          32 :         CONVERT_PRIV('C', "CREATE");
                   -  - ]
     533   [ +  +  -  +  :          32 :         CONVERT_PRIV('c', "CONNECT");
                   -  - ]
     534   [ +  +  -  +  :          32 :         CONVERT_PRIV('T', "TEMPORARY");
                   -  - ]
     535                 :             :     }
     536         [ -  + ]:         244 :     else if (strcmp(type, "TABLESPACE") == 0)
     537   [ #  #  #  #  :           0 :         CONVERT_PRIV('C', "CREATE");
                   #  # ]
     538         [ +  + ]:         244 :     else if (strcmp(type, "TYPE") == 0 ||
     539         [ +  + ]:         111 :              strcmp(type, "TYPES") == 0)
     540   [ +  -  -  +  :         286 :         CONVERT_PRIV('U', "USAGE");
                   -  - ]
     541         [ +  + ]:         101 :     else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0)
     542   [ +  -  -  +  :          32 :         CONVERT_PRIV('U', "USAGE");
                   -  - ]
     543         [ +  + ]:          69 :     else if (strcmp(type, "FOREIGN SERVER") == 0)
     544   [ +  -  -  +  :          32 :         CONVERT_PRIV('U', "USAGE");
                   -  - ]
     545         [ -  + ]:          37 :     else if (strcmp(type, "FOREIGN TABLE") == 0)
     546   [ #  #  #  #  :           0 :         CONVERT_PRIV('r', "SELECT");
                   #  # ]
     547         [ +  + ]:          37 :     else if (strcmp(type, "PARAMETER") == 0)
     548                 :             :     {
     549   [ +  +  +  +  :           3 :         CONVERT_PRIV('s', "SET");
                   +  - ]
     550   [ +  -  +  +  :           3 :         CONVERT_PRIV('A', "ALTER SYSTEM");
                   +  - ]
     551                 :             :     }
     552         [ -  + ]:          34 :     else if (strcmp(type, "LARGE OBJECT") == 0 ||
     553         [ #  # ]:           0 :              strcmp(type, "LARGE OBJECTS") == 0)
     554                 :             :     {
     555   [ +  -  -  +  :          34 :         CONVERT_PRIV('r', "SELECT");
                   -  - ]
     556   [ +  -  -  +  :          68 :         CONVERT_PRIV('w', "UPDATE");
                   -  - ]
     557                 :             :     }
     558                 :             :     else
     559                 :           0 :         abort();
     560                 :             : 
     561                 :             : #undef CONVERT_PRIV
     562                 :             : 
     563         [ +  + ]:        2262 :     if (all_with_go)
     564                 :             :     {
     565                 :           2 :         resetPQExpBuffer(privs);
     566                 :           2 :         printfPQExpBuffer(privswgo, "ALL");
     567         [ -  + ]:           2 :         if (subname)
     568                 :           0 :             appendPQExpBuffer(privswgo, "(%s)", subname);
     569                 :             :     }
     570         [ +  + ]:        2260 :     else if (all_without_go)
     571                 :             :     {
     572                 :         632 :         resetPQExpBuffer(privswgo);
     573                 :         632 :         printfPQExpBuffer(privs, "ALL");
     574         [ -  + ]:         632 :         if (subname)
     575                 :           0 :             appendPQExpBuffer(privs, "(%s)", subname);
     576                 :             :     }
     577                 :             : 
     578                 :        2262 :     pg_free(buf);
     579                 :             : 
     580                 :        2262 :     return true;
     581                 :             : }
     582                 :             : 
     583                 :             : /*
     584                 :             :  * Transfer the role name at *input into the output buffer, adding
     585                 :             :  * quoting according to the same rules as putid() in backend's acl.c.
     586                 :             :  */
     587                 :             : void
     588                 :         570 : quoteAclUserName(PQExpBuffer output, const char *input)
     589                 :             : {
     590                 :             :     const char *src;
     591                 :         570 :     bool        safe = true;
     592                 :             : 
     593         [ +  + ]:        9783 :     for (src = input; *src; src++)
     594                 :             :     {
     595                 :             :         /* This test had better match what putid() does */
     596   [ +  +  +  + ]:        9372 :         if (!isalnum((unsigned char) *src) && *src != '_')
     597                 :             :         {
     598                 :         159 :             safe = false;
     599                 :         159 :             break;
     600                 :             :         }
     601                 :             :     }
     602         [ +  + ]:         570 :     if (!safe)
     603                 :         159 :         appendPQExpBufferChar(output, '"');
     604         [ +  + ]:       11214 :     for (src = input; *src; src++)
     605                 :             :     {
     606                 :             :         /* A double quote character in a username is encoded as "" */
     607         [ +  + ]:       10644 :         if (*src == '"')
     608                 :         159 :             appendPQExpBufferChar(output, '"');
     609                 :       10644 :         appendPQExpBufferChar(output, *src);
     610                 :             :     }
     611         [ +  + ]:         570 :     if (!safe)
     612                 :         159 :         appendPQExpBufferChar(output, '"');
     613                 :         570 : }
     614                 :             : 
     615                 :             : /*
     616                 :             :  * Transfer a user or group name starting at *input into the output buffer,
     617                 :             :  * dequoting if needed.  Returns a pointer to just past the input name.
     618                 :             :  * The name is taken to end at an unquoted '=' or end of string.
     619                 :             :  * Note: unlike quoteAclUserName(), this first clears the output buffer.
     620                 :             :  */
     621                 :             : static char *
     622                 :        4524 : dequoteAclUserName(PQExpBuffer output, char *input)
     623                 :             : {
     624                 :        4524 :     resetPQExpBuffer(output);
     625                 :             : 
     626   [ +  +  +  + ]:       43536 :     while (*input && *input != '=')
     627                 :             :     {
     628                 :             :         /*
     629                 :             :          * If user name isn't quoted, then just add it to the output buffer
     630                 :             :          */
     631         [ +  + ]:       39012 :         if (*input != '"')
     632                 :       38915 :             appendPQExpBufferChar(output, *input++);
     633                 :             :         else
     634                 :             :         {
     635                 :             :             /* Otherwise, it's a quoted username */
     636                 :          97 :             input++;
     637                 :             :             /* Loop until we come across an unescaped quote */
     638   [ +  +  +  + ]:        2328 :             while (!(*input == '"' && *(input + 1) != '"'))
     639                 :             :             {
     640         [ -  + ]:        2231 :                 if (*input == '\0')
     641                 :           0 :                     return input;   /* really a syntax error... */
     642                 :             : 
     643                 :             :                 /*
     644                 :             :                  * Quoting convention is to escape " as "".  Keep this code in
     645                 :             :                  * sync with putid() in backend's acl.c.
     646                 :             :                  */
     647   [ +  +  +  - ]:        2231 :                 if (*input == '"' && *(input + 1) == '"')
     648                 :          97 :                     input++;
     649                 :        2231 :                 appendPQExpBufferChar(output, *input++);
     650                 :             :             }
     651                 :          97 :             input++;
     652                 :             :         }
     653                 :             :     }
     654                 :        4524 :     return input;
     655                 :             : }
     656                 :             : 
     657                 :             : /*
     658                 :             :  * Append a privilege keyword to a keyword list, inserting comma if needed.
     659                 :             :  */
     660                 :             : static void
     661                 :        2906 : AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname)
     662                 :             : {
     663         [ +  + ]:        2906 :     if (aclbuf->len > 0)
     664                 :         626 :         appendPQExpBufferChar(aclbuf, ',');
     665                 :        2906 :     appendPQExpBufferStr(aclbuf, keyword);
     666         [ +  + ]:        2906 :     if (subname)
     667                 :        1112 :         appendPQExpBuffer(aclbuf, "(%s)", subname);
     668                 :        2906 : }
     669                 :             : 
     670                 :             : 
     671                 :             : /*
     672                 :             :  * buildShSecLabelQuery
     673                 :             :  *
     674                 :             :  * Build a query to retrieve security labels for a shared object.
     675                 :             :  * The object is identified by its OID plus the name of the catalog
     676                 :             :  * it can be found in (e.g., "pg_database" for database names).
     677                 :             :  * The query is appended to "sql".  (We don't execute it here so as to
     678                 :             :  * keep this file free of assumptions about how to deal with SQL errors.)
     679                 :             :  */
     680                 :             : void
     681                 :         184 : buildShSecLabelQuery(const char *catalog_name, Oid objectId,
     682                 :             :                      PQExpBuffer sql)
     683                 :             : {
     684                 :         184 :     appendPQExpBuffer(sql,
     685                 :             :                       "SELECT provider, label FROM pg_catalog.pg_shseclabel "
     686                 :             :                       "WHERE classoid = 'pg_catalog.%s'::pg_catalog.regclass "
     687                 :             :                       "AND objoid = '%u'", catalog_name, objectId);
     688                 :         184 : }
     689                 :             : 
     690                 :             : /*
     691                 :             :  * emitShSecLabels
     692                 :             :  *
     693                 :             :  * Construct SECURITY LABEL commands using the data retrieved by the query
     694                 :             :  * generated by buildShSecLabelQuery, and append them to "buffer".
     695                 :             :  * Here, the target object is identified by its type name (e.g. "DATABASE")
     696                 :             :  * and its name (not pre-quoted).
     697                 :             :  */
     698                 :             : void
     699                 :         184 : emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
     700                 :             :                 const char *objtype, const char *objname)
     701                 :             : {
     702                 :             :     int         i;
     703                 :             : 
     704         [ -  + ]:         184 :     for (i = 0; i < PQntuples(res); i++)
     705                 :             :     {
     706                 :           0 :         char       *provider = PQgetvalue(res, i, 0);
     707                 :           0 :         char       *label = PQgetvalue(res, i, 1);
     708                 :             : 
     709                 :             :         /* must use fmtId result before calling it again */
     710                 :           0 :         appendPQExpBuffer(buffer,
     711                 :             :                           "SECURITY LABEL FOR %s ON %s",
     712                 :             :                           fmtId(provider), objtype);
     713                 :           0 :         appendPQExpBuffer(buffer,
     714                 :             :                           " %s IS ",
     715                 :             :                           fmtId(objname));
     716                 :           0 :         appendStringLiteralConn(buffer, label, conn);
     717                 :           0 :         appendPQExpBufferStr(buffer, ";\n");
     718                 :             :     }
     719                 :         184 : }
     720                 :             : 
     721                 :             : 
     722                 :             : /*
     723                 :             :  * Detect whether the given GUC variable is of GUC_LIST_QUOTE type.
     724                 :             :  *
     725                 :             :  * It'd be better if we could inquire this directly from the backend; but even
     726                 :             :  * if there were a function for that, it could only tell us about variables
     727                 :             :  * currently known to guc.c, so that it'd be unsafe for extensions to declare
     728                 :             :  * GUC_LIST_QUOTE variables anyway.  Lacking a solution for that, it doesn't
     729                 :             :  * seem worth the work to do more than have this list, which must be kept in
     730                 :             :  * sync with the variables actually marked GUC_LIST_QUOTE in guc_parameters.dat.
     731                 :             :  */
     732                 :             : bool
     733                 :          70 : variable_is_guc_list_quote(const char *name)
     734                 :             : {
     735   [ +  +  +  - ]:         135 :     if (pg_strcasecmp(name, "local_preload_libraries") == 0 ||
     736         [ +  - ]:         130 :         pg_strcasecmp(name, "oauth_validator_libraries") == 0 ||
     737         [ +  + ]:         130 :         pg_strcasecmp(name, "output_plugin_libraries") == 0 ||
     738         [ +  - ]:         125 :         pg_strcasecmp(name, "search_path") == 0 ||
     739         [ +  - ]:         120 :         pg_strcasecmp(name, "session_preload_libraries") == 0 ||
     740         [ +  + ]:         120 :         pg_strcasecmp(name, "shared_preload_libraries") == 0 ||
     741         [ -  + ]:         115 :         pg_strcasecmp(name, "temp_tablespaces") == 0 ||
     742                 :          55 :         pg_strcasecmp(name, "unix_socket_directories") == 0)
     743                 :          15 :         return true;
     744                 :             :     else
     745                 :          55 :         return false;
     746                 :             : }
     747                 :             : 
     748                 :             : /*
     749                 :             :  * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands.
     750                 :             :  *
     751                 :             :  * Parse the contents of configitem (a "name=value" string), wrap it in
     752                 :             :  * a complete ALTER command, and append it to buf.
     753                 :             :  *
     754                 :             :  * type is DATABASE or ROLE, and name is the name of the database or role.
     755                 :             :  * If we need an "IN" clause, type2 and name2 similarly define what to put
     756                 :             :  * there; otherwise they should be NULL.
     757                 :             :  * conn is used only to determine string-literal quoting conventions.
     758                 :             :  */
     759                 :             : void
     760                 :          30 : makeAlterConfigCommand(PGconn *conn, const char *configitem,
     761                 :             :                        const char *type, const char *name,
     762                 :             :                        const char *type2, const char *name2,
     763                 :             :                        PQExpBuffer buf)
     764                 :             : {
     765                 :             :     char       *mine;
     766                 :             :     char       *pos;
     767                 :             : 
     768                 :             :     /* Parse the configitem.  If we can't find an "=", silently do nothing. */
     769                 :          30 :     mine = pg_strdup(configitem);
     770                 :          30 :     pos = strchr(mine, '=');
     771         [ -  + ]:          30 :     if (pos == NULL)
     772                 :             :     {
     773                 :           0 :         pg_free(mine);
     774                 :           0 :         return;
     775                 :             :     }
     776                 :          30 :     *pos++ = '\0';
     777                 :             : 
     778                 :             :     /* Build the command, with suitable quoting for everything. */
     779                 :          30 :     appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
     780   [ -  +  -  - ]:          30 :     if (type2 != NULL && name2 != NULL)
     781                 :           0 :         appendPQExpBuffer(buf, "IN %s %s ", type2, fmtId(name2));
     782                 :          30 :     appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine));
     783                 :             : 
     784                 :             :     /*
     785                 :             :      * Variables that are marked GUC_LIST_QUOTE were already fully quoted by
     786                 :             :      * flatten_set_variable_args() before they were put into the setconfig
     787                 :             :      * array.  However, because the quoting rules used there aren't exactly
     788                 :             :      * like SQL's, we have to break the list value apart and then quote the
     789                 :             :      * elements as string literals.  (The elements may be double-quoted as-is,
     790                 :             :      * but we can't just feed them to the SQL parser; it would do the wrong
     791                 :             :      * thing with elements that are zero-length or longer than NAMEDATALEN.)
     792                 :             :      * Also, we need a special case for empty lists.
     793                 :             :      *
     794                 :             :      * Variables that are not so marked should just be emitted as simple
     795                 :             :      * string literals.  If the variable is not known to
     796                 :             :      * variable_is_guc_list_quote(), we'll do that; this makes it unsafe to
     797                 :             :      * use GUC_LIST_QUOTE for extension variables.
     798                 :             :      */
     799         [ -  + ]:          30 :     if (variable_is_guc_list_quote(mine))
     800                 :             :     {
     801                 :             :         char      **namelist;
     802                 :             :         char      **nameptr;
     803                 :             : 
     804                 :             :         /* Parse string into list of identifiers */
     805                 :             :         /* this shouldn't fail really */
     806         [ #  # ]:           0 :         if (SplitGUCList(pos, ',', &namelist))
     807                 :             :         {
     808                 :             :             /* Special case: represent an empty list as NULL */
     809         [ #  # ]:           0 :             if (*namelist == NULL)
     810                 :           0 :                 appendPQExpBufferStr(buf, "NULL");
     811         [ #  # ]:           0 :             for (nameptr = namelist; *nameptr; nameptr++)
     812                 :             :             {
     813         [ #  # ]:           0 :                 if (nameptr != namelist)
     814                 :           0 :                     appendPQExpBufferStr(buf, ", ");
     815                 :           0 :                 appendStringLiteralConn(buf, *nameptr, conn);
     816                 :             :             }
     817                 :             :         }
     818                 :           0 :         pg_free(namelist);
     819                 :             :     }
     820                 :             :     else
     821                 :          30 :         appendStringLiteralConn(buf, pos, conn);
     822                 :             : 
     823                 :          30 :     appendPQExpBufferStr(buf, ";\n");
     824                 :             : 
     825                 :          30 :     pg_free(mine);
     826                 :             : }
     827                 :             : 
     828                 :             : /*
     829                 :             :  * create_or_open_dir
     830                 :             :  *
     831                 :             :  * This will create a new directory with the given dirname. If there is
     832                 :             :  * already an empty directory with that name, then use it.
     833                 :             :  */
     834                 :             : void
     835                 :          10 : create_or_open_dir(const char *dirname)
     836                 :             : {
     837                 :             :     int         ret;
     838                 :             : 
     839   [ -  +  -  - ]:          10 :     switch ((ret = pg_check_dir(dirname)))
     840                 :             :     {
     841                 :           0 :         case -1:
     842                 :             :             /* opendir failed but not with ENOENT */
     843                 :           0 :             pg_fatal("could not open directory \"%s\": %m", dirname);
     844                 :             :             break;
     845                 :          10 :         case 0:
     846                 :             :             /* directory does not exist */
     847         [ -  + ]:          10 :             if (mkdir(dirname, pg_dir_create_mode) < 0)
     848                 :           0 :                 pg_fatal("could not create directory \"%s\": %m", dirname);
     849                 :          10 :             break;
     850                 :           0 :         case 1:
     851                 :             :             /* exists and is empty, fix perms */
     852         [ #  # ]:           0 :             if (chmod(dirname, pg_dir_create_mode) != 0)
     853                 :           0 :                 pg_fatal("could not change permissions of directory \"%s\": %m",
     854                 :             :                          dirname);
     855                 :           0 :             break;
     856                 :           0 :         default:
     857                 :             :             /* exists and is not empty */
     858                 :           0 :             pg_fatal("directory \"%s\" is not empty", dirname);
     859                 :             :     }
     860                 :          10 : }
     861                 :             : 
     862                 :             : /*
     863                 :             :  * Generates a valid restrict key (i.e., an alphanumeric string) for use with
     864                 :             :  * psql's \restrict and \unrestrict meta-commands.  For safety, the value is
     865                 :             :  * chosen at random.
     866                 :             :  */
     867                 :             : char *
     868                 :         189 : generate_restrict_key(void)
     869                 :             : {
     870                 :             :     uint8       buf[64];
     871                 :         189 :     char       *ret = palloc(sizeof(buf));
     872                 :             : 
     873         [ -  + ]:         189 :     if (!pg_strong_random(buf, sizeof(buf)))
     874                 :           0 :         return NULL;
     875                 :             : 
     876         [ +  + ]:       12096 :     for (size_t i = 0; i < sizeof(buf) - 1; i++)
     877                 :             :     {
     878                 :       11907 :         uint8       idx = buf[i] % strlen(restrict_chars);
     879                 :             : 
     880                 :       11907 :         ret[i] = restrict_chars[idx];
     881                 :             :     }
     882                 :         189 :     ret[sizeof(buf) - 1] = '\0';
     883                 :             : 
     884                 :         189 :     return ret;
     885                 :             : }
     886                 :             : 
     887                 :             : /*
     888                 :             :  * Checks that a given restrict key (intended for use with psql's \restrict and
     889                 :             :  * \unrestrict meta-commands) contains only alphanumeric characters.
     890                 :             :  */
     891                 :             : bool
     892                 :         221 : valid_restrict_key(const char *restrict_key)
     893                 :             : {
     894                 :         221 :     return restrict_key != NULL &&
     895   [ +  -  +  - ]:         442 :         restrict_key[0] != '\0' &&
     896         [ +  - ]:         221 :         strspn(restrict_key, restrict_chars) == strlen(restrict_key);
     897                 :             : }
        

Generated by: LCOV version 2.0-1