From 0c9d84427f441602425b0e18be5cfe751d1d8ebe Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 6 Dec 2021 12:39:45 -0500 Subject: [PATCH] Rethink pg_dump's handling of object ACLs. Throw away most of the existing logic for this, as it was very inefficient thanks to expensive sub-selects executed to collect ACL data that we very possibly would have no interest in dumping. Reduce the ACL handling in the initial per-object-type queries to be just collection of the catalog ACL fields, as it was originally. Fetch pg_init_privs data separately in a single scan of that catalog, and do the merging calculations on the client side. Remove the separate code path used for pre-9.6 source servers; there is no good reason to treat them differently from newer servers that happen to have empty pg_init_privs. Discussion: https://postgr.es/m/2273648.1634764485@sss.pgh.pa.us Discussion: https://postgr.es/m/7d7eb6128f40401d81b3b7a898b6b4de@W2012-02.nidsa.loc --- src/bin/pg_dump/dumputils.c | 516 +++++------- src/bin/pg_dump/dumputils.h | 14 +- src/bin/pg_dump/pg_dump.c | 1199 +++++++++++---------------- src/bin/pg_dump/pg_dump.h | 69 +- src/bin/pg_dump/pg_dumpall.c | 59 +- src/fe_utils/string_utils.c | 63 ++ src/include/fe_utils/string_utils.h | 1 + 7 files changed, 784 insertions(+), 1137 deletions(-) diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index ea67e52a3f..6e216313c6 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -24,7 +24,7 @@ static bool parseAclItem(const char *item, const char *type, const char *name, const char *subname, int remoteVersion, PQExpBuffer grantee, PQExpBuffer grantor, PQExpBuffer privs, PQExpBuffer privswgo); -static char *copyAclUserName(PQExpBuffer output, char *input); +static char *dequoteAclUserName(PQExpBuffer output, char *input); static void AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname); @@ -39,7 +39,8 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE, * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT) * acls: the ACL string fetched from the database - * racls: the ACL string of any initial-but-now-revoked privileges + * baseacls: the initial ACL string for this object; can be + * NULL or empty string to indicate "not available from server" * owner: username of object owner (will be passed through fmtId); can be * NULL or empty string to indicate "no owner known" * prefix: string to prefix to each generated command; typically empty @@ -48,6 +49,12 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, * Returns true if okay, false if could not parse the acl string. * The resulting commands (if any) are appended to the contents of 'sql'. * + * baseacls is typically the result of acldefault() for the object's type + * and owner. However, if there is a pg_init_privs entry for the object, + * it should instead be the initprivs ACLs. When acls is itself a + * pg_init_privs entry, baseacls is what to dump that relative to; then + * it can be either an acldefault() value or an empty ACL "{}". + * * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES " * or something similar, and name is an empty string. * @@ -56,15 +63,19 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword, */ bool buildACLCommands(const char *name, const char *subname, const char *nspname, - const char *type, const char *acls, const char *racls, + const char *type, const char *acls, const char *baseacls, const char *owner, const char *prefix, int remoteVersion, PQExpBuffer sql) { bool ok = true; char **aclitems = NULL; - char **raclitems = NULL; + char **baseitems = NULL; + char **grantitems = NULL; + char **revokeitems = NULL; int naclitems = 0; - int nraclitems = 0; + int nbaseitems = 0; + int ngrantitems = 0; + int nrevokeitems = 0; int i; PQExpBuffer grantee, grantor, @@ -72,37 +83,88 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, privswgo; PQExpBuffer firstsql, secondsql; - bool found_owner_privs = false; - if (strlen(acls) == 0 && strlen(racls) == 0) + /* + * If the acl was NULL (initial default state), we need do nothing. Note + * that this is distinguishable from all-privileges-revoked, which will + * look like an empty array ("{}"). + */ + if (acls == NULL || *acls == '\0') return true; /* object has default permissions */ /* treat empty-string owner same as NULL */ if (owner && *owner == '\0') owner = NULL; - if (strlen(acls) != 0) + /* Parse the acls array */ + if (!parsePGArray(acls, &aclitems, &naclitems)) + { + if (aclitems) + free(aclitems); + return false; + } + + /* Parse the baseacls, if provided */ + if (baseacls && *baseacls != '\0') { - if (!parsePGArray(acls, &aclitems, &naclitems)) + if (!parsePGArray(baseacls, &baseitems, &nbaseitems)) { if (aclitems) free(aclitems); + if (baseitems) + free(baseitems); return false; } } - if (strlen(racls) != 0) + /* + * Compare the actual ACL with the base ACL, extracting the privileges + * that need to be granted (i.e., are in the actual ACL but not the base + * ACL) and the ones that need to be revoked (the reverse). We use plain + * string comparisons to check for matches. In principle that could be + * fooled by extraneous issues such as whitespace, but since all these + * strings are the work of aclitemout(), it should be OK in practice. + * Besides, a false mismatch will just cause the output to be a little + * more verbose than it really needed to be. + * + * (If we weren't given a base ACL, this stanza winds up with all the + * ACL's items in grantitems and nothing in revokeitems. It's not worth + * special-casing that.) + */ + grantitems = (char **) pg_malloc(naclitems * sizeof(char *)); + for (i = 0; i < naclitems; i++) { - if (!parsePGArray(racls, &raclitems, &nraclitems)) + bool found = false; + + for (int j = 0; j < nbaseitems; j++) { - if (aclitems) - free(aclitems); - if (raclitems) - free(raclitems); - return false; + if (strcmp(aclitems[i], baseitems[j]) == 0) + { + found = true; + break; + } } + if (!found) + grantitems[ngrantitems++] = aclitems[i]; } + revokeitems = (char **) pg_malloc(nbaseitems * sizeof(char *)); + for (i = 0; i < nbaseitems; i++) + { + bool found = false; + for (int j = 0; j < naclitems; j++) + { + if (strcmp(baseitems[i], aclitems[j]) == 0) + { + found = true; + break; + } + } + if (!found) + revokeitems[nrevokeitems++] = baseitems[i]; + } + + /* Prepare working buffers */ grantee = createPQExpBuffer(); grantor = createPQExpBuffer(); privs = createPQExpBuffer(); @@ -110,50 +172,21 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, /* * At the end, these two will be pasted together to form the result. - * - * For older systems we use these to ensure that the owner privileges go - * before the other ones, as a GRANT could create the default entry for - * the object, which generally includes all rights for the owner. In more - * recent versions we normally handle this because the owner rights come - * first in the ACLs, but older versions might have them after the PUBLIC - * privileges. - * - * For 9.6 and later systems, much of this changes. With 9.6, we check - * the default privileges for the objects at dump time and create two sets - * of ACLs- "racls" which are the ACLs to REVOKE from the object (as the - * object may have initial privileges on it, along with any default ACLs - * which are not part of the current set of privileges), and regular - * "acls", which are the ACLs to GRANT to the object. We handle the - * REVOKEs first, followed by the GRANTs. */ firstsql = createPQExpBuffer(); secondsql = createPQExpBuffer(); /* - * For pre-9.6 systems, we always start with REVOKE ALL FROM PUBLIC, as we - * don't wish to make any assumptions about what the default ACLs are, and - * we do not collect them during the dump phase (and racls will always be - * the empty set, see above). - * - * For 9.6 and later, if any revoke ACLs have been provided, then include - * them in 'firstsql'. + * If we weren't given baseacls information, we just revoke everything and + * then grant what's listed in the ACL. This avoids having to embed + * detailed knowledge about what the defaults are/were, and it's not very + * expensive since servers lacking acldefault() are now rare. * - * Revoke ACLs happen when an object starts out life with a set of - * privileges (eg: GRANT SELECT ON pg_class TO PUBLIC;) and the user has - * decided to revoke those rights. Since those objects come into being - * with those default privileges, we have to revoke them to match what the - * current state of affairs is. Note that we only started explicitly - * tracking such initial rights in 9.6, and prior to that all initial - * rights are actually handled by the simple 'REVOKE ALL .. FROM PUBLIC' - * case, for initdb-created objects. Prior to 9.6, we didn't handle - * extensions correctly, but we do now by tracking their initial - * privileges, in the same way we track initdb initial privileges, see - * pg_init_privs. + * Otherwise, we need only revoke what's listed in revokeitems. */ - if (remoteVersion < 90600) + if (baseacls == NULL || *baseacls == '\0') { - Assert(nraclitems == 0); - + /* We assume the old defaults only involved the owner and PUBLIC */ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); @@ -161,13 +194,24 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, if (nspname && *nspname) appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); appendPQExpBuffer(firstsql, "%s FROM PUBLIC;\n", name); + if (owner) + { + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); + if (subname) + appendPQExpBuffer(firstsql, "(%s)", subname); + appendPQExpBuffer(firstsql, " ON %s ", type); + if (nspname && *nspname) + appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); + appendPQExpBuffer(firstsql, "%s FROM %s;\n", name, fmtId(owner)); + } } else { /* Scan individual REVOKE ACL items */ - for (i = 0; i < nraclitems; i++) + for (i = 0; i < nrevokeitems; i++) { - if (!parseAclItem(raclitems[i], type, name, subname, remoteVersion, + if (!parseAclItem(revokeitems[i], + type, name, subname, remoteVersion, grantee, grantor, privs, NULL)) { ok = false; @@ -195,6 +239,10 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, } /* + * At this point we have issued REVOKE statements for all initial and + * default privileges that are no longer present on the object, so we are + * almost ready to GRANT the privileges listed in grantitems[]. + * * We still need some hacking though to cover the case where new default * public privileges are added in new versions: the REVOKE ALL will revoke * them, leading to behavior different from what the old version had, @@ -208,146 +256,92 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, prefix, type, name); } - /* Scan individual ACL items */ - for (i = 0; i < naclitems; i++) + /* + * Scan individual ACL items to be granted. + * + * The order in which privileges appear in the ACL string (the order they + * have been GRANT'd in, which the backend maintains) must be preserved to + * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on + * those are dumped in the correct order. However, some old server + * versions will show grants to PUBLIC before the owner's own grants; for + * consistency's sake, force the owner's grants to be output first. + */ + for (i = 0; i < ngrantitems; i++) { - if (!parseAclItem(aclitems[i], type, name, subname, remoteVersion, - grantee, grantor, privs, privswgo)) - { - ok = false; - break; - } - - if (grantor->len == 0 && owner) - printfPQExpBuffer(grantor, "%s", owner); - - if (privs->len > 0 || privswgo->len > 0) + if (parseAclItem(grantitems[i], type, name, subname, remoteVersion, + grantee, grantor, privs, privswgo)) { /* - * Prior to 9.6, we had to handle owner privileges in a special - * manner by first REVOKE'ing the rights and then GRANT'ing them - * after. With 9.6 and above, what we need to REVOKE and what we - * need to GRANT is figured out when we dump and stashed into - * "racls" and "acls", respectively. See above. + * If the grantor isn't the owner, we'll need to use SET SESSION + * AUTHORIZATION to become the grantor. Issue the SET/RESET only + * if there's something useful to do. */ - if (remoteVersion < 90600 && owner - && strcmp(grantee->data, owner) == 0 - && strcmp(grantor->data, owner) == 0) + if (privs->len > 0 || privswgo->len > 0) { - found_owner_privs = true; + PQExpBuffer thissql; + + /* Set owner as grantor if that's not explicit in the ACL */ + if (grantor->len == 0 && owner) + printfPQExpBuffer(grantor, "%s", owner); + + /* Make sure owner's own grants are output before others */ + if (owner && + strcmp(grantee->data, owner) == 0 && + strcmp(grantor->data, owner) == 0) + thissql = firstsql; + else + thissql = secondsql; - /* - * For the owner, the default privilege level is ALL WITH - * GRANT OPTION. - */ - if (strcmp(privswgo->data, "ALL") != 0) - { - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s ", type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, "%s FROM %s;\n", - name, fmtId(grantee->data)); - if (privs->len > 0) - { - appendPQExpBuffer(firstsql, - "%sGRANT %s ON %s ", - prefix, privs->data, type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, - "%s TO %s;\n", - name, fmtId(grantee->data)); - } - if (privswgo->len > 0) - { - appendPQExpBuffer(firstsql, - "%sGRANT %s ON %s ", - prefix, privswgo->data, type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, - "%s TO %s WITH GRANT OPTION;\n", - name, fmtId(grantee->data)); - } - } - } - else - { - /* - * For systems prior to 9.6, we can assume we are starting - * from no privs at this point. - * - * For 9.6 and above, at this point we have issued REVOKE - * statements for all initial and default privileges which are - * no longer present on the object (as they were passed in as - * 'racls') and we can simply GRANT the rights which are in - * 'acls'. - */ if (grantor->len > 0 && (!owner || strcmp(owner, grantor->data) != 0)) - appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n", + appendPQExpBuffer(thissql, "SET SESSION AUTHORIZATION %s;\n", fmtId(grantor->data)); if (privs->len > 0) { - appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ", + appendPQExpBuffer(thissql, "%sGRANT %s ON %s ", prefix, privs->data, type); if (nspname && *nspname) - appendPQExpBuffer(secondsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(secondsql, "%s TO ", name); + appendPQExpBuffer(thissql, "%s.", fmtId(nspname)); + appendPQExpBuffer(thissql, "%s TO ", name); if (grantee->len == 0) - appendPQExpBufferStr(secondsql, "PUBLIC;\n"); + appendPQExpBufferStr(thissql, "PUBLIC;\n"); else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) - appendPQExpBuffer(secondsql, "GROUP %s;\n", + appendPQExpBuffer(thissql, "GROUP %s;\n", fmtId(grantee->data + strlen("group "))); else - appendPQExpBuffer(secondsql, "%s;\n", fmtId(grantee->data)); + appendPQExpBuffer(thissql, "%s;\n", fmtId(grantee->data)); } if (privswgo->len > 0) { - appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ", + appendPQExpBuffer(thissql, "%sGRANT %s ON %s ", prefix, privswgo->data, type); if (nspname && *nspname) - appendPQExpBuffer(secondsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(secondsql, "%s TO ", name); + appendPQExpBuffer(thissql, "%s.", fmtId(nspname)); + appendPQExpBuffer(thissql, "%s TO ", name); if (grantee->len == 0) - appendPQExpBufferStr(secondsql, "PUBLIC"); + appendPQExpBufferStr(thissql, "PUBLIC"); else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) - appendPQExpBuffer(secondsql, "GROUP %s", + appendPQExpBuffer(thissql, "GROUP %s", fmtId(grantee->data + strlen("group "))); else - appendPQExpBufferStr(secondsql, fmtId(grantee->data)); - appendPQExpBufferStr(secondsql, " WITH GRANT OPTION;\n"); + appendPQExpBufferStr(thissql, fmtId(grantee->data)); + appendPQExpBufferStr(thissql, " WITH GRANT OPTION;\n"); } if (grantor->len > 0 && (!owner || strcmp(owner, grantor->data) != 0)) - appendPQExpBufferStr(secondsql, "RESET SESSION AUTHORIZATION;\n"); + appendPQExpBufferStr(thissql, "RESET SESSION AUTHORIZATION;\n"); } } - } - - /* - * For systems prior to 9.6, if we didn't find any owner privs, the owner - * must have revoked 'em all. - * - * For 9.6 and above, we handle this through the 'racls'. See above. - */ - if (remoteVersion < 90600 && !found_owner_privs && owner) - { - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s ", type); - if (nspname && *nspname) - appendPQExpBuffer(firstsql, "%s.", fmtId(nspname)); - appendPQExpBuffer(firstsql, "%s FROM %s;\n", - name, fmtId(owner)); + else + { + /* parseAclItem failed, give up */ + ok = false; + break; + } } destroyPQExpBuffer(grantee); @@ -361,19 +355,23 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, if (aclitems) free(aclitems); - - if (raclitems) - free(raclitems); + if (baseitems) + free(baseitems); + if (grantitems) + free(grantitems); + if (revokeitems) + free(revokeitems); return ok; } /* - * Build ALTER DEFAULT PRIVILEGES command(s) for single pg_default_acl entry. + * Build ALTER DEFAULT PRIVILEGES command(s) for a single pg_default_acl entry. * * type: the object type (TABLES, FUNCTIONS, etc) * nspname: schema name, or NULL for global default privileges * acls: the ACL string fetched from the database + * acldefault: the appropriate default ACL for the object type and owner * owner: username of privileges owner (will be passed through fmtId) * remoteVersion: version of database * @@ -382,8 +380,7 @@ buildACLCommands(const char *name, const char *subname, const char *nspname, */ bool buildDefaultACLCommands(const char *type, const char *nspname, - const char *acls, const char *racls, - const char *initacls, const char *initracls, + const char *acls, const char *acldefault, const char *owner, int remoteVersion, PQExpBuffer sql) @@ -403,21 +400,12 @@ buildDefaultACLCommands(const char *type, const char *nspname, if (nspname) appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname)); - if (strlen(initacls) != 0 || strlen(initracls) != 0) - { - appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\n"); - if (!buildACLCommands("", NULL, NULL, type, - initacls, initracls, owner, - prefix->data, remoteVersion, sql)) - { - destroyPQExpBuffer(prefix); - return false; - } - appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(false);\n"); - } - + /* + * There's no such thing as initprivs for a default ACL, so the base ACL + * is always just the object-type-specific default. + */ if (!buildACLCommands("", NULL, NULL, type, - acls, racls, owner, + acls, acldefault, owner, prefix->data, remoteVersion, sql)) { destroyPQExpBuffer(prefix); @@ -467,7 +455,7 @@ parseAclItem(const char *item, const char *type, buf = pg_strdup(item); /* user or group name is string up to = */ - eqpos = copyAclUserName(grantee, buf); + eqpos = dequoteAclUserName(grantee, buf); if (*eqpos != '=') { pg_free(buf); @@ -479,7 +467,7 @@ parseAclItem(const char *item, const char *type, if (slpos) { *slpos++ = '\0'; - slpos = copyAclUserName(grantor, slpos); + slpos = dequoteAclUserName(grantor, slpos); if (*slpos != '\0') { pg_free(buf); @@ -603,13 +591,46 @@ do { \ return true; } +/* + * Transfer the role name at *input into the output buffer, adding + * quoting according to the same rules as putid() in backend's acl.c. + */ +void +quoteAclUserName(PQExpBuffer output, const char *input) +{ + const char *src; + bool safe = true; + + for (src = input; *src; src++) + { + /* This test had better match what putid() does */ + if (!isalnum((unsigned char) *src) && *src != '_') + { + safe = false; + break; + } + } + if (!safe) + appendPQExpBufferChar(output, '"'); + for (src = input; *src; src++) + { + /* A double quote character in a username is encoded as "" */ + if (*src == '"') + appendPQExpBufferChar(output, '"'); + appendPQExpBufferChar(output, *src); + } + if (!safe) + appendPQExpBufferChar(output, '"'); +} + /* * Transfer a user or group name starting at *input into the output buffer, * dequoting if needed. Returns a pointer to just past the input name. * The name is taken to end at an unquoted '=' or end of string. + * Note: unlike quoteAclUserName(), this first clears the output buffer. */ static char * -copyAclUserName(PQExpBuffer output, char *input) +dequoteAclUserName(PQExpBuffer output, char *input) { resetPQExpBuffer(output); @@ -708,137 +729,6 @@ emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer, } } -/* - * buildACLQueries - * - * Build the subqueries to extract out the correct set of ACLs to be - * GRANT'd and REVOKE'd for the specific kind of object, accounting for any - * initial privileges (from pg_init_privs) and based on if we are in binary - * upgrade mode or not. - * - * Also builds subqueries to extract out the set of ACLs to go from the object - * default privileges to the privileges in pg_init_privs, if we are in binary - * upgrade mode, so that those privileges can be set up and recorded in the new - * cluster before the regular privileges are added on top of those. - */ -void -buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery, - PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery, - const char *acl_column, const char *acl_owner, - const char *initprivs_expr, - const char *obj_kind, bool binary_upgrade) -{ - /* - * To get the delta from what the permissions were at creation time - * (either initdb or CREATE EXTENSION) vs. what they are now, we have to - * look at two things: - * - * What privileges have been added, which we calculate by extracting all - * the current privileges (using the set of default privileges for the - * object type if current privileges are NULL) and then removing those - * which existed at creation time (again, using the set of default - * privileges for the object type if there were no creation time - * privileges). - * - * What privileges have been removed, which we calculate by extracting the - * privileges as they were at creation time (or the default privileges, as - * above), and then removing the current privileges (or the default - * privileges, if current privileges are NULL). - * - * As a good cross-check, both directions of these checks should result in - * the empty set if both the current ACL and the initial privs are NULL - * (meaning, in practice, that the default ACLs were there at init time - * and is what the current privileges are). - * - * We always perform this delta on all ACLs and expect that by the time - * these are run the initial privileges will be in place, even in a binary - * upgrade situation (see below). - * - * Finally, the order in which privileges are in the ACL string (the order - * they been GRANT'd in, which the backend maintains) must be preserved to - * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on - * those are dumped in the correct order. - */ - printfPQExpBuffer(acl_subquery, - "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " - "(SELECT acl, row_n FROM " - "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " - "WITH ORDINALITY AS perm(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM " - "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " - "AS init(init_acl) WHERE acl = init_acl)) as foo)", - acl_column, - obj_kind, - acl_owner, - initprivs_expr, - obj_kind, - acl_owner); - - printfPQExpBuffer(racl_subquery, - "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " - "(SELECT acl, row_n FROM " - "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " - "WITH ORDINALITY AS initp(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM " - "pg_catalog.unnest(coalesce(%s,pg_catalog.acldefault(%s,%s))) " - "AS permp(orig_acl) WHERE acl = orig_acl)) as foo)", - initprivs_expr, - obj_kind, - acl_owner, - acl_column, - obj_kind, - acl_owner); - - /* - * In binary upgrade mode we don't run the extension script but instead - * dump out the objects independently and then recreate them. To preserve - * the initial privileges which were set on extension objects, we need to - * grab the set of GRANT and REVOKE commands necessary to get from the - * default privileges of an object to the initial privileges as recorded - * in pg_init_privs. - * - * These will then be run ahead of the regular ACL commands, which were - * calculated using the queries above, inside of a block which sets a flag - * to indicate that the backend should record the results of these GRANT - * and REVOKE statements into pg_init_privs. This is how we preserve the - * contents of that catalog across binary upgrades. - */ - if (binary_upgrade) - { - printfPQExpBuffer(init_acl_subquery, - "CASE WHEN privtype = 'e' THEN " - "(SELECT pg_catalog.array_agg(acl ORDER BY row_n) FROM " - "(SELECT acl, row_n FROM pg_catalog.unnest(%s) " - "WITH ORDINALITY AS initp(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM " - "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) " - "AS privm(orig_acl) WHERE acl = orig_acl)) as foo) END", - initprivs_expr, - obj_kind, - acl_owner); - - printfPQExpBuffer(init_racl_subquery, - "CASE WHEN privtype = 'e' THEN " - "(SELECT pg_catalog.array_agg(acl) FROM " - "(SELECT acl, row_n FROM " - "pg_catalog.unnest(pg_catalog.acldefault(%s,%s)) " - "WITH ORDINALITY AS privp(acl,row_n) " - "WHERE NOT EXISTS ( " - "SELECT 1 FROM pg_catalog.unnest(%s) " - "AS initp(init_acl) WHERE acl = init_acl)) as foo) END", - obj_kind, - acl_owner, - initprivs_expr); - } - else - { - printfPQExpBuffer(init_acl_subquery, "NULL"); - printfPQExpBuffer(init_racl_subquery, "NULL"); - } -} /* * Detect whether the given GUC variable is of GUC_LIST_QUOTE type. diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index f5465f19ae..fac7a05c91 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -37,26 +37,22 @@ extern bool buildACLCommands(const char *name, const char *subname, const char *nspname, - const char *type, const char *acls, const char *racls, + const char *type, const char *acls, const char *baseacls, const char *owner, const char *prefix, int remoteVersion, PQExpBuffer sql); extern bool buildDefaultACLCommands(const char *type, const char *nspname, - const char *acls, const char *racls, - const char *initacls, const char *initracls, + const char *acls, const char *acldefault, const char *owner, int remoteVersion, PQExpBuffer sql); + +extern void quoteAclUserName(PQExpBuffer output, const char *input); + extern void buildShSecLabelQuery(const char *catalog_name, Oid objectId, PQExpBuffer sql); extern void emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer, const char *objtype, const char *objname); -extern void buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery, - PQExpBuffer init_acl_subquery, PQExpBuffer init_racl_subquery, - const char *acl_column, const char *acl_owner, - const char *initprivs_expr, - const char *obj_kind, bool binary_upgrade); - extern bool variable_is_guc_list_quote(const char *name); extern bool SplitGUCList(char *rawstring, char separator, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 15f55cbb19..75ea57266e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -179,6 +179,7 @@ static NamespaceInfo *findNamespace(Oid nsoid); static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo); static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo); static void guessConstraintInheritance(TableInfo *tblinfo, int numTables); +static void getAdditionalACLs(Archive *fout); static void dumpCommentExtended(Archive *fout, const char *type, const char *name, const char *namespace, const char *owner, CatalogId catalogId, @@ -248,8 +249,7 @@ static void dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo); static DumpId dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId, const char *type, const char *name, const char *subname, const char *nspname, const char *owner, - const char *acls, const char *racls, - const char *initacls, const char *initracls); + const DumpableAcl *dacl); static void getDependencies(Archive *fout); static void BuildArchiveDependencies(Archive *fout); @@ -888,8 +888,10 @@ main(int argc, char **argv) getDependencies(fout); /* - * Collect comments and security labels, if wanted. + * Collect ACLs, comments, and security labels, if wanted. */ + if (!dopt.aclsSkip) + getAdditionalACLs(fout); if (!dopt.no_comments) collectComments(fout); if (!dopt.no_security_labels) @@ -2859,19 +2861,18 @@ dumpDatabase(Archive *fout) i_frozenxid, i_minmxid, i_datacl, - i_rdatacl, + i_acldefault, i_datistemplate, i_datconnlimit, i_tablespace; CatalogId dbCatId; DumpId dbDumpId; + DumpableAcl dbdacl; const char *datname, *dba, *encoding, *collate, *ctype, - *datacl, - *rdatacl, *datistemplate, *datconnlimit, *tablespace; @@ -2883,40 +2884,14 @@ dumpDatabase(Archive *fout) /* * Fetch the database-level properties for this database. - * - * The order in which privileges are in the ACL string (the order they - * have been GRANT'd in, which the backend maintains) must be preserved to - * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on - * those are dumped in the correct order. Note that initial privileges - * (pg_init_privs) are not supported on databases, so this logic cannot - * make use of buildACLQueries(). */ - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90300) { appendPQExpBuffer(dbQry, "SELECT tableoid, oid, datname, " "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "datcollate, datctype, datfrozenxid, datminmxid, " - "(SELECT array_agg(acl ORDER BY row_n) FROM " - " (SELECT acl, row_n FROM " - " unnest(coalesce(datacl,acldefault('d',datdba))) " - " WITH ORDINALITY AS perm(acl,row_n) " - " WHERE NOT EXISTS ( " - " SELECT 1 " - " FROM unnest(acldefault('d',datdba)) " - " AS init(init_acl) " - " WHERE acl = init_acl)) AS datacls) " - " AS datacl, " - "(SELECT array_agg(acl ORDER BY row_n) FROM " - " (SELECT acl, row_n FROM " - " unnest(acldefault('d',datdba)) " - " WITH ORDINALITY AS initp(acl,row_n) " - " WHERE NOT EXISTS ( " - " SELECT 1 " - " FROM unnest(coalesce(datacl,acldefault('d',datdba))) " - " AS permp(orig_acl) " - " WHERE acl = orig_acl)) AS rdatacls) " - " AS rdatacl, " + "datacl, acldefault('d', datdba) AS acldefault, " "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2925,13 +2900,14 @@ dumpDatabase(Archive *fout) "WHERE datname = current_database()", username_subquery); } - else if (fout->remoteVersion >= 90300) + else if (fout->remoteVersion >= 90200) { appendPQExpBuffer(dbQry, "SELECT tableoid, oid, datname, " "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " - "datcollate, datctype, datfrozenxid, datminmxid, " - "datacl, '' as rdatacl, datistemplate, datconnlimit, " + "datcollate, datctype, datfrozenxid, 0 AS datminmxid, " + "datacl, acldefault('d', datdba) AS acldefault, " + "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2945,7 +2921,8 @@ dumpDatabase(Archive *fout) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "datcollate, datctype, datfrozenxid, 0 AS datminmxid, " - "datacl, '' as rdatacl, datistemplate, datconnlimit, " + "datacl, NULL AS acldefault, " + "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2959,7 +2936,8 @@ dumpDatabase(Archive *fout) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, " - "datacl, '' as rdatacl, datistemplate, datconnlimit, " + "datacl, NULL AS acldefault, " + "datistemplate, datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " "shobj_description(oid, 'pg_database') AS description " @@ -2973,8 +2951,8 @@ dumpDatabase(Archive *fout) "(%s datdba) AS dba, " "pg_encoding_to_char(encoding) AS encoding, " "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, " - "datacl, '' as rdatacl, datistemplate, " - "-1 as datconnlimit, " + "datacl, NULL AS acldefault, " + "datistemplate, -1 AS datconnlimit, " "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace " "FROM pg_database " "WHERE datname = current_database()", @@ -2993,7 +2971,7 @@ dumpDatabase(Archive *fout) i_frozenxid = PQfnumber(res, "datfrozenxid"); i_minmxid = PQfnumber(res, "datminmxid"); i_datacl = PQfnumber(res, "datacl"); - i_rdatacl = PQfnumber(res, "rdatacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_datistemplate = PQfnumber(res, "datistemplate"); i_datconnlimit = PQfnumber(res, "datconnlimit"); i_tablespace = PQfnumber(res, "tablespace"); @@ -3007,8 +2985,8 @@ dumpDatabase(Archive *fout) ctype = PQgetvalue(res, 0, i_ctype); frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid)); minmxid = atooid(PQgetvalue(res, 0, i_minmxid)); - datacl = PQgetvalue(res, 0, i_datacl); - rdatacl = PQgetvalue(res, 0, i_rdatacl); + dbdacl.acl = PQgetvalue(res, 0, i_datacl); + dbdacl.acldefault = PQgetvalue(res, 0, i_acldefault); datistemplate = PQgetvalue(res, 0, i_datistemplate); datconnlimit = PQgetvalue(res, 0, i_datconnlimit); tablespace = PQgetvalue(res, 0, i_tablespace); @@ -3146,9 +3124,12 @@ dumpDatabase(Archive *fout) * Dump ACL if any. Note that we do not support initial privileges * (pg_init_privs) on databases. */ + dbdacl.privtype = 0; + dbdacl.initprivs = NULL; + dumpACL(fout, dbDumpId, InvalidDumpId, "DATABASE", qdatname, NULL, NULL, - dba, datacl, rdatacl, "", ""); + dba, &dbdacl); /* * Now construct a DATABASE PROPERTIES archive entry to restore any @@ -3470,59 +3451,30 @@ getBlobs(Archive *fout) int i_oid; int i_lomowner; int i_lomacl; - int i_rlomacl; - int i_initlomacl; - int i_initrlomacl; + int i_acldefault; pg_log_info("reading large objects"); /* Fetch BLOB OIDs, and owner/ACL data if >= 9.0 */ - if (fout->remoteVersion >= 90600) + if (fout->remoteVersion >= 90200) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer init_acl_subquery = createPQExpBuffer(); - PQExpBuffer init_racl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery, - init_racl_subquery, "l.lomacl", "l.lomowner", - "pip.initprivs", "'L'", dopt->binary_upgrade); - appendPQExpBuffer(blobQry, - "SELECT l.oid, (%s l.lomowner) AS rolname, " - "%s AS lomacl, " - "%s AS rlomacl, " - "%s AS initlomacl, " - "%s AS initrlomacl " - "FROM pg_largeobject_metadata l " - "LEFT JOIN pg_init_privs pip ON " - "(l.oid = pip.objoid " - "AND pip.classoid = 'pg_largeobject'::regclass " - "AND pip.objsubid = 0) ", - username_subquery, - acl_subquery->data, - racl_subquery->data, - init_acl_subquery->data, - init_racl_subquery->data); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(init_acl_subquery); - destroyPQExpBuffer(init_racl_subquery); + "SELECT oid, (%s lomowner) AS rolname, lomacl, " + "acldefault('L', lomowner) AS acldefault " + "FROM pg_largeobject_metadata", + username_subquery); } else if (fout->remoteVersion >= 90000) appendPQExpBuffer(blobQry, "SELECT oid, (%s lomowner) AS rolname, lomacl, " - "NULL AS rlomacl, NULL AS initlomacl, " - "NULL AS initrlomacl " - " FROM pg_largeobject_metadata", + "NULL AS acldefault " + "FROM pg_largeobject_metadata", username_subquery); else appendPQExpBufferStr(blobQry, "SELECT DISTINCT loid AS oid, " "NULL::name AS rolname, NULL::oid AS lomacl, " - "NULL::oid AS rlomacl, NULL::oid AS initlomacl, " - "NULL::oid AS initrlomacl " + "NULL::oid AS acldefault " " FROM pg_largeobject"); res = ExecuteSqlQuery(fout, blobQry->data, PGRES_TUPLES_OK); @@ -3530,9 +3482,7 @@ getBlobs(Archive *fout) i_oid = PQfnumber(res, "oid"); i_lomowner = PQfnumber(res, "rolname"); i_lomacl = PQfnumber(res, "lomacl"); - i_rlomacl = PQfnumber(res, "rlomacl"); - i_initlomacl = PQfnumber(res, "initlomacl"); - i_initrlomacl = PQfnumber(res, "initrlomacl"); + i_acldefault = PQfnumber(res, "acldefault"); ntups = PQntuples(res); @@ -3549,20 +3499,17 @@ getBlobs(Archive *fout) AssignDumpId(&binfo[i].dobj); binfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_oid)); + binfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_lomacl)); + binfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + binfo[i].dacl.privtype = 0; + binfo[i].dacl.initprivs = NULL; binfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_lomowner)); - binfo[i].blobacl = pg_strdup(PQgetvalue(res, i, i_lomacl)); - binfo[i].rblobacl = pg_strdup(PQgetvalue(res, i, i_rlomacl)); - binfo[i].initblobacl = pg_strdup(PQgetvalue(res, i, i_initlomacl)); - binfo[i].initrblobacl = pg_strdup(PQgetvalue(res, i, i_initrlomacl)); /* Blobs have data */ binfo[i].dobj.components |= DUMP_COMPONENT_DATA; /* Mark whether blob has an ACL */ - if (!(PQgetisnull(res, i, i_lomacl) && - PQgetisnull(res, i, i_rlomacl) && - PQgetisnull(res, i, i_initlomacl) && - PQgetisnull(res, i, i_initrlomacl))) + if (!PQgetisnull(res, i, i_lomacl)) binfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* @@ -3638,8 +3585,7 @@ dumpBlob(Archive *fout, const BlobInfo *binfo) if (binfo->dobj.dump & DUMP_COMPONENT_ACL) dumpACL(fout, binfo->dobj.dumpId, InvalidDumpId, "LARGE OBJECT", binfo->dobj.name, NULL, - NULL, binfo->rolname, binfo->blobacl, binfo->rblobacl, - binfo->initblobacl, binfo->initrblobacl); + NULL, binfo->rolname, &binfo->dacl); destroyPQExpBuffer(cquery); destroyPQExpBuffer(dquery); @@ -4999,7 +4945,6 @@ binary_upgrade_extension_member(PQExpBuffer upgrade_buffer, NamespaceInfo * getNamespaces(Archive *fout, int *numNamespaces) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -5011,9 +4956,7 @@ getNamespaces(Archive *fout, int *numNamespaces) int i_nspowner; int i_rolname; int i_nspacl; - int i_rnspacl; - int i_initnspacl; - int i_initrnspacl; + int i_acldefault; query = createPQExpBuffer(); @@ -5021,67 +4964,18 @@ getNamespaces(Archive *fout, int *numNamespaces) * we fetch all namespaces including system ones, so that every object we * read in can be linked to a containing namespace. */ - if (fout->remoteVersion >= 90600) - { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer init_acl_subquery = createPQExpBuffer(); - PQExpBuffer init_racl_subquery = createPQExpBuffer(); - - /* - * Bypass pg_init_privs.initprivs for the public schema, for several - * reasons. First, dropping and recreating the schema detaches it - * from its pg_init_privs row, but an empty destination database - * starts with this ACL nonetheless. Second, we support dump/reload - * of public schema ownership changes. ALTER SCHEMA OWNER filters - * nspacl through aclnewowner(), but initprivs continues to reflect - * the initial owner. Hence, synthesize the value that nspacl will - * have after the restore's ALTER SCHEMA OWNER. Third, this makes the - * destination database match the source's ACL, even if the latter was - * an initdb-default ACL, which changed in v15. An upgrade pulls in - * changes to most system object ACLs that the DBA had not customized. - * We've made the public schema depart from that, because changing its - * ACL so easily breaks applications. - */ - buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery, - init_racl_subquery, "n.nspacl", "n.nspowner", - "CASE WHEN n.nspname = 'public' THEN array[" - " format('%s=UC/%s', " - " n.nspowner::regrole, n.nspowner::regrole)," - " format('=U/%s', n.nspowner::regrole)]::aclitem[] " - "ELSE pip.initprivs END", - "'n'", dopt->binary_upgrade); - + if (fout->remoteVersion >= 90200) appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, " "n.nspowner, " "(%s nspowner) AS rolname, " - "%s as nspacl, " - "%s as rnspacl, " - "%s as initnspacl, " - "%s as initrnspacl " - "FROM pg_namespace n " - "LEFT JOIN pg_init_privs pip " - "ON (n.oid = pip.objoid " - "AND pip.classoid = 'pg_namespace'::regclass " - "AND pip.objsubid = 0", - username_subquery, - acl_subquery->data, - racl_subquery->data, - init_acl_subquery->data, - init_racl_subquery->data); - - appendPQExpBufferStr(query, ") "); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(init_acl_subquery); - destroyPQExpBuffer(init_racl_subquery); - } + "n.nspacl, " + "acldefault('n', n.nspowner) AS acldefault " + "FROM pg_namespace n", + username_subquery); else appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, nspowner, " "(%s nspowner) AS rolname, " - "nspacl, NULL as rnspacl, " - "NULL AS initnspacl, NULL as initrnspacl " + "nspacl, NULL AS acldefault " "FROM pg_namespace", username_subquery); @@ -5097,9 +4991,7 @@ getNamespaces(Archive *fout, int *numNamespaces) i_nspowner = PQfnumber(res, "nspowner"); i_rolname = PQfnumber(res, "rolname"); i_nspacl = PQfnumber(res, "nspacl"); - i_rnspacl = PQfnumber(res, "rnspacl"); - i_initnspacl = PQfnumber(res, "initnspacl"); - i_initrnspacl = PQfnumber(res, "initrnspacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -5108,23 +5000,61 @@ getNamespaces(Archive *fout, int *numNamespaces) nsinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); AssignDumpId(&nsinfo[i].dobj); nsinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_nspname)); + nsinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_nspacl)); + nsinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + nsinfo[i].dacl.privtype = 0; + nsinfo[i].dacl.initprivs = NULL; nsinfo[i].nspowner = atooid(PQgetvalue(res, i, i_nspowner)); nsinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); - nsinfo[i].nspacl = pg_strdup(PQgetvalue(res, i, i_nspacl)); - nsinfo[i].rnspacl = pg_strdup(PQgetvalue(res, i, i_rnspacl)); - nsinfo[i].initnspacl = pg_strdup(PQgetvalue(res, i, i_initnspacl)); - nsinfo[i].initrnspacl = pg_strdup(PQgetvalue(res, i, i_initrnspacl)); /* Decide whether to dump this namespace */ selectDumpableNamespace(&nsinfo[i], fout); /* Mark whether namespace has an ACL */ - if (!(PQgetisnull(res, i, i_nspacl) && - PQgetisnull(res, i, i_rnspacl) && - PQgetisnull(res, i, i_initnspacl) && - PQgetisnull(res, i, i_initrnspacl))) + if (!PQgetisnull(res, i, i_nspacl)) + nsinfo[i].dobj.components |= DUMP_COMPONENT_ACL; + + /* + * We ignore any pg_init_privs.initprivs entry for the public schema + * and assume a predetermined default, for several reasons. First, + * dropping and recreating the schema removes its pg_init_privs entry, + * but an empty destination database starts with this ACL nonetheless. + * Second, we support dump/reload of public schema ownership changes. + * ALTER SCHEMA OWNER filters nspacl through aclnewowner(), but + * initprivs continues to reflect the initial owner. Hence, + * synthesize the value that nspacl will have after the restore's + * ALTER SCHEMA OWNER. Third, this makes the destination database + * match the source's ACL, even if the latter was an initdb-default + * ACL, which changed in v15. An upgrade pulls in changes to most + * system object ACLs that the DBA had not customized. We've made the + * public schema depart from that, because changing its ACL so easily + * breaks applications. + */ + if (strcmp(nsinfo[i].dobj.name, "public") == 0) + { + PQExpBuffer aclarray = createPQExpBuffer(); + PQExpBuffer aclitem = createPQExpBuffer(); + + /* Standard ACL as of v15 is {owner=UC/owner,=U/owner} */ + appendPQExpBufferChar(aclarray, '{'); + quoteAclUserName(aclitem, nsinfo[i].rolname); + appendPQExpBufferStr(aclitem, "=UC/"); + quoteAclUserName(aclitem, nsinfo[i].rolname); + appendPGArray(aclarray, aclitem->data); + resetPQExpBuffer(aclitem); + appendPQExpBufferStr(aclitem, "=U/"); + quoteAclUserName(aclitem, nsinfo[i].rolname); + appendPGArray(aclarray, aclitem->data); + appendPQExpBufferChar(aclarray, '}'); + + nsinfo[i].dacl.privtype = 'i'; + nsinfo[i].dacl.initprivs = pstrdup(aclarray->data); nsinfo[i].dobj.components |= DUMP_COMPONENT_ACL; + destroyPQExpBuffer(aclarray); + destroyPQExpBuffer(aclitem); + } + if (strlen(nsinfo[i].rolname) == 0) pg_log_warning("owner of schema \"%s\" appears to be invalid", nsinfo[i].dobj.name); @@ -5247,7 +5177,6 @@ getExtensions(Archive *fout, int *numExtensions) TypeInfo * getTypes(Archive *fout, int *numTypes) { - DumpOptions *dopt = fout->dopt; PGresult *res; int ntups; int i; @@ -5259,9 +5188,7 @@ getTypes(Archive *fout, int *numTypes) int i_typname; int i_typnamespace; int i_typacl; - int i_rtypacl; - int i_inittypacl; - int i_initrtypacl; + int i_acldefault; int i_rolname; int i_typelem; int i_typrelid; @@ -5285,52 +5212,11 @@ getTypes(Archive *fout, int *numTypes) * cost of the subselect probe for all standard types. This would have to * be revisited if the backend ever allows renaming of array types. */ - - if (fout->remoteVersion >= 90600) - { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); - - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "t.typacl", "t.typowner", - "pip.initprivs", "'T'", dopt->binary_upgrade); - - appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, " - "t.typnamespace, " - "%s AS typacl, " - "%s AS rtypacl, " - "%s AS inittypacl, " - "%s AS initrtypacl, " - "(%s t.typowner) AS rolname, " - "t.typelem, t.typrelid, " - "CASE WHEN t.typrelid = 0 THEN ' '::\"char\" " - "ELSE (SELECT relkind FROM pg_class WHERE oid = t.typrelid) END AS typrelkind, " - "t.typtype, t.typisdefined, " - "t.typname[0] = '_' AND t.typelem != 0 AND " - "(SELECT typarray FROM pg_type te WHERE oid = t.typelem) = t.oid AS isarray " - "FROM pg_type t " - "LEFT JOIN pg_init_privs pip ON " - "(t.oid = pip.objoid " - "AND pip.classoid = 'pg_type'::regclass " - "AND pip.objsubid = 0) ", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, - username_subquery); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); - } - else if (fout->remoteVersion >= 90200) + if (fout->remoteVersion >= 90200) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " - "typnamespace, typacl, NULL as rtypacl, " - "NULL AS inittypacl, NULL AS initrtypacl, " + "typnamespace, typacl, " + "acldefault('T', typowner) AS acldefault, " "(%s typowner) AS rolname, " "typelem, typrelid, " "CASE WHEN typrelid = 0 THEN ' '::\"char\" " @@ -5344,8 +5230,7 @@ getTypes(Archive *fout, int *numTypes) else if (fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " - "typnamespace, NULL AS typacl, NULL as rtypacl, " - "NULL AS inittypacl, NULL AS initrtypacl, " + "typnamespace, NULL AS typacl, NULL AS acldefault, " "(%s typowner) AS rolname, " "typelem, typrelid, " "CASE WHEN typrelid = 0 THEN ' '::\"char\" " @@ -5359,8 +5244,7 @@ getTypes(Archive *fout, int *numTypes) else { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " - "typnamespace, NULL AS typacl, NULL as rtypacl, " - "NULL AS inittypacl, NULL AS initrtypacl, " + "typnamespace, NULL AS typacl, NULL AS acldefault, " "(%s typowner) AS rolname, " "typelem, typrelid, " "CASE WHEN typrelid = 0 THEN ' '::\"char\" " @@ -5382,9 +5266,7 @@ getTypes(Archive *fout, int *numTypes) i_typname = PQfnumber(res, "typname"); i_typnamespace = PQfnumber(res, "typnamespace"); i_typacl = PQfnumber(res, "typacl"); - i_rtypacl = PQfnumber(res, "rtypacl"); - i_inittypacl = PQfnumber(res, "inittypacl"); - i_initrtypacl = PQfnumber(res, "initrtypacl"); + i_acldefault = PQfnumber(res, "acldefault"); i_rolname = PQfnumber(res, "rolname"); i_typelem = PQfnumber(res, "typelem"); i_typrelid = PQfnumber(res, "typrelid"); @@ -5402,12 +5284,12 @@ getTypes(Archive *fout, int *numTypes) tyinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_typname)); tyinfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_typnamespace))); + tyinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_typacl)); + tyinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + tyinfo[i].dacl.privtype = 0; + tyinfo[i].dacl.initprivs = NULL; tyinfo[i].ftypname = NULL; /* may get filled later */ tyinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); - tyinfo[i].typacl = pg_strdup(PQgetvalue(res, i, i_typacl)); - tyinfo[i].rtypacl = pg_strdup(PQgetvalue(res, i, i_rtypacl)); - tyinfo[i].inittypacl = pg_strdup(PQgetvalue(res, i, i_inittypacl)); - tyinfo[i].initrtypacl = pg_strdup(PQgetvalue(res, i, i_initrtypacl)); tyinfo[i].typelem = atooid(PQgetvalue(res, i, i_typelem)); tyinfo[i].typrelid = atooid(PQgetvalue(res, i, i_typrelid)); tyinfo[i].typrelkind = *PQgetvalue(res, i, i_typrelkind); @@ -5433,10 +5315,7 @@ getTypes(Archive *fout, int *numTypes) selectDumpableType(&tyinfo[i], fout); /* Mark whether type has an ACL */ - if (!(PQgetisnull(res, i, i_typacl) && - PQgetisnull(res, i, i_rtypacl) && - PQgetisnull(res, i, i_inittypacl) && - PQgetisnull(res, i, i_initrtypacl))) + if (!PQgetisnull(res, i, i_typacl)) tyinfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* @@ -5963,9 +5842,7 @@ getAggregates(Archive *fout, int *numAggs) int i_proargtypes; int i_rolname; int i_aggacl; - int i_raggacl; - int i_initaggacl; - int i_initraggacl; + int i_acldefault; /* * Find all interesting aggregates. See comment in getFuncs() for the @@ -5973,16 +5850,8 @@ getAggregates(Archive *fout, int *numAggs) */ if (fout->remoteVersion >= 90600) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); const char *agg_check; - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "p.proacl", "p.proowner", - "pip.initprivs", "'f'", dopt->binary_upgrade); - agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'" : "p.proisagg"); @@ -5991,10 +5860,8 @@ getAggregates(Archive *fout, int *numAggs) "p.pronamespace AS aggnamespace, " "p.pronargs, p.proargtypes, " "(%s p.proowner) AS rolname, " - "%s AS aggacl, " - "%s AS raggacl, " - "%s AS initaggacl, " - "%s AS initraggacl " + "p.proacl AS aggacl, " + "acldefault('f', p.proowner) AS acldefault " "FROM pg_proc p " "LEFT JOIN pg_init_privs pip ON " "(p.oid = pip.objoid " @@ -6006,10 +5873,6 @@ getAggregates(Archive *fout, int *numAggs) "WHERE nspname = 'pg_catalog') OR " "p.proacl IS DISTINCT FROM pip.initprivs", username_subquery, - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, agg_check); if (dopt->binary_upgrade) appendPQExpBufferStr(query, @@ -6019,11 +5882,29 @@ getAggregates(Archive *fout, int *numAggs) "refclassid = 'pg_extension'::regclass AND " "deptype = 'e')"); appendPQExpBufferChar(query, ')'); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); + } + else if (fout->remoteVersion >= 90200) + { + appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, " + "pronamespace AS aggnamespace, " + "pronargs, proargtypes, " + "(%s proowner) AS rolname, " + "proacl AS aggacl, " + "acldefault('f', proowner) AS acldefault " + "FROM pg_proc p " + "WHERE proisagg AND (" + "pronamespace != " + "(SELECT oid FROM pg_namespace " + "WHERE nspname = 'pg_catalog')", + username_subquery); + if (dopt->binary_upgrade) + appendPQExpBufferStr(query, + " OR EXISTS(SELECT 1 FROM pg_depend WHERE " + "classid = 'pg_proc'::regclass AND " + "objid = p.oid AND " + "refclassid = 'pg_extension'::regclass AND " + "deptype = 'e')"); + appendPQExpBufferChar(query, ')'); } else if (fout->remoteVersion >= 80200) { @@ -6032,8 +5913,7 @@ getAggregates(Archive *fout, int *numAggs) "pronargs, proargtypes, " "(%s proowner) AS rolname, " "proacl AS aggacl, " - "NULL AS raggacl, " - "NULL AS initaggacl, NULL AS initraggacl " + "NULL AS acldefault " "FROM pg_proc p " "WHERE proisagg AND (" "pronamespace != " @@ -6057,8 +5937,7 @@ getAggregates(Archive *fout, int *numAggs) "proargtypes, " "(%s proowner) AS rolname, " "proacl AS aggacl, " - "NULL AS raggacl, " - "NULL AS initaggacl, NULL AS initraggacl " + "NULL AS acldefault " "FROM pg_proc " "WHERE proisagg " "AND pronamespace != " @@ -6081,9 +5960,7 @@ getAggregates(Archive *fout, int *numAggs) i_proargtypes = PQfnumber(res, "proargtypes"); i_rolname = PQfnumber(res, "rolname"); i_aggacl = PQfnumber(res, "aggacl"); - i_raggacl = PQfnumber(res, "raggacl"); - i_initaggacl = PQfnumber(res, "initaggacl"); - i_initraggacl = PQfnumber(res, "initraggacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -6094,16 +5971,16 @@ getAggregates(Archive *fout, int *numAggs) agginfo[i].aggfn.dobj.name = pg_strdup(PQgetvalue(res, i, i_aggname)); agginfo[i].aggfn.dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_aggnamespace))); + agginfo[i].aggfn.dacl.acl = pg_strdup(PQgetvalue(res, i, i_aggacl)); + agginfo[i].aggfn.dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + agginfo[i].aggfn.dacl.privtype = 0; + agginfo[i].aggfn.dacl.initprivs = NULL; agginfo[i].aggfn.rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); if (strlen(agginfo[i].aggfn.rolname) == 0) pg_log_warning("owner of aggregate function \"%s\" appears to be invalid", agginfo[i].aggfn.dobj.name); agginfo[i].aggfn.lang = InvalidOid; /* not currently interesting */ agginfo[i].aggfn.prorettype = InvalidOid; /* not saved */ - agginfo[i].aggfn.proacl = pg_strdup(PQgetvalue(res, i, i_aggacl)); - agginfo[i].aggfn.rproacl = pg_strdup(PQgetvalue(res, i, i_raggacl)); - agginfo[i].aggfn.initproacl = pg_strdup(PQgetvalue(res, i, i_initaggacl)); - agginfo[i].aggfn.initrproacl = pg_strdup(PQgetvalue(res, i, i_initraggacl)); agginfo[i].aggfn.nargs = atoi(PQgetvalue(res, i, i_pronargs)); if (agginfo[i].aggfn.nargs == 0) agginfo[i].aggfn.argtypes = NULL; @@ -6119,10 +5996,7 @@ getAggregates(Archive *fout, int *numAggs) selectDumpableObject(&(agginfo[i].aggfn.dobj), fout); /* Mark whether aggregate has an ACL */ - if (!(PQgetisnull(res, i, i_aggacl) && - PQgetisnull(res, i, i_raggacl) && - PQgetisnull(res, i, i_initaggacl) && - PQgetisnull(res, i, i_initraggacl))) + if (!PQgetisnull(res, i, i_aggacl)) agginfo[i].aggfn.dobj.components |= DUMP_COMPONENT_ACL; } @@ -6159,9 +6033,7 @@ getFuncs(Archive *fout, int *numFuncs) int i_proargtypes; int i_prorettype; int i_proacl; - int i_rproacl; - int i_initproacl; - int i_initrproacl; + int i_acldefault; /* * Find all interesting functions. This is a bit complicated: @@ -6183,30 +6055,20 @@ getFuncs(Archive *fout, int *numFuncs) * to gather the information about them, though they won't be dumped if * they are built-in. Also, in 9.6 and up, include functions in * pg_catalog if they have an ACL different from what's shown in - * pg_init_privs. + * pg_init_privs (so we have to join to pg_init_privs; annoying). */ if (fout->remoteVersion >= 90600) { - PQExpBuffer acl_subquery = createPQExpBuffer(); - PQExpBuffer racl_subquery = createPQExpBuffer(); - PQExpBuffer initacl_subquery = createPQExpBuffer(); - PQExpBuffer initracl_subquery = createPQExpBuffer(); const char *not_agg_check; - buildACLQueries(acl_subquery, racl_subquery, initacl_subquery, - initracl_subquery, "p.proacl", "p.proowner", - "pip.initprivs", "'f'", dopt->binary_upgrade); - not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'" : "NOT p.proisagg"); appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.proname, p.prolang, " "p.pronargs, p.proargtypes, p.prorettype, " - "%s AS proacl, " - "%s AS rproacl, " - "%s AS initproacl, " - "%s AS initrproacl, " + "p.proacl, " + "acldefault('f', p.proowner) AS acldefault, " "p.pronamespace, " "(%s p.proowner) AS rolname " "FROM pg_proc p " @@ -6229,10 +6091,6 @@ getFuncs(Archive *fout, int *numFuncs) "\n WHERE pg_transform.oid > %u AND " "\n (p.oid = pg_transform.trffromsql" "\n OR p.oid = pg_transform.trftosql))", - acl_subquery->data, - racl_subquery->data, - initacl_subquery->data, - initracl_subquery->data, username_subquery, not_agg_check, g_last_builtin_oid, @@ -6247,23 +6105,23 @@ getFuncs(Archive *fout, int *numFuncs) appendPQExpBufferStr(query, "\n OR p.proacl IS DISTINCT FROM pip.initprivs"); appendPQExpBufferChar(query, ')'); - - destroyPQExpBuffer(acl_subquery); - destroyPQExpBuffer(racl_subquery); - destroyPQExpBuffer(initacl_subquery); - destroyPQExpBuffer(initracl_subquery); } else { + const char *acldefault_call; + + acldefault_call = (fout->remoteVersion >= 90200 ? + "acldefault('f', proowner)" : "NULL"); + appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " "pronargs, proargtypes, prorettype, proacl, " - "NULL as rproacl, " - "NULL as initproacl, NULL AS initrproacl, " + "%s AS acldefault, " "pronamespace, " "(%s proowner) AS rolname " "FROM pg_proc p " "WHERE NOT proisagg", + acldefault_call, username_subquery); if (fout->remoteVersion >= 90200) appendPQExpBufferStr(query, @@ -6316,9 +6174,7 @@ getFuncs(Archive *fout, int *numFuncs) i_proargtypes = PQfnumber(res, "proargtypes"); i_prorettype = PQfnumber(res, "prorettype"); i_proacl = PQfnumber(res, "proacl"); - i_rproacl = PQfnumber(res, "rproacl"); - i_initproacl = PQfnumber(res, "initproacl"); - i_initrproacl = PQfnumber(res, "initrproacl"); + i_acldefault = PQfnumber(res, "acldefault"); for (i = 0; i < ntups; i++) { @@ -6329,13 +6185,13 @@ getFuncs(Archive *fout, int *numFuncs) finfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_proname)); finfo[i].dobj.namespace = findNamespace(atooid(PQgetvalue(res, i, i_pronamespace))); + finfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_proacl)); + finfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + finfo[i].dacl.privtype = 0; + finfo[i].dacl.initprivs = NULL; finfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); finfo[i].lang = atooid(PQgetvalue(res, i, i_prolang)); finfo[i].prorettype = atooid(PQgetvalue(res, i, i_prorettype)); - finfo[i].proacl = pg_strdup(PQgetvalue(res, i, i_proacl)); - finfo[i].rproacl = pg_strdup(PQgetvalue(res, i, i_rproacl)); - finfo[i].initproacl = pg_strdup(PQgetvalue(res, i, i_initproacl)); - finfo[i].initrproacl = pg_strdup(PQgetvalue(res, i, i_initrproacl)); finfo[i].nargs = atoi(PQgetvalue(res, i, i_pronargs)); if (finfo[i].nargs == 0) finfo[i].argtypes = NULL; @@ -6350,10 +6206,7 @@ getFuncs(Archive *fout, int *numFuncs) selectDumpableObject(&(finfo[i].dobj), fout); /* Mark whether function has an ACL */ - if (!(PQgetisnull(res, i, i_proacl) && - PQgetisnull(res, i, i_rproacl) && - PQgetisnull(res, i, i_initproacl) && - PQgetisnull(res, i, i_initrproacl))) + if (!PQgetisnull(res, i, i_proacl)) finfo[i].dobj.components |= DUMP_COMPONENT_ACL; if (strlen(finfo[i].rolname) == 0) @@ -6418,10 +6271,7 @@ getTables(Archive *fout, int *numTables) int i_amname; int i_is_identity_sequence; int i_relacl; - int i_rrelacl; - int i_initrelacl; - int i_initrrelacl; - int i_changed_acl; + int i_acldefault; int i_partkeydef; int i_ispartition; int i_partb