LCOV - code coverage report
Current view: top level - src/bin/pg_dump - dumputils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 270 321 84.1 %
Date: 2024-10-10 03:14:42 Functions: 11 11 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14