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
This commit is contained in:
parent
5209c0ba0b
commit
0c9d84427f
@ -24,7 +24,7 @@ static bool parseAclItem(const char *item, const char *type,
|
|||||||
const char *name, const char *subname, int remoteVersion,
|
const char *name, const char *subname, int remoteVersion,
|
||||||
PQExpBuffer grantee, PQExpBuffer grantor,
|
PQExpBuffer grantee, PQExpBuffer grantor,
|
||||||
PQExpBuffer privs, PQExpBuffer privswgo);
|
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,
|
static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
|
||||||
const char *subname);
|
const char *subname);
|
||||||
|
|
||||||
@ -39,7 +39,8 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
|
|||||||
* TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
|
* TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
|
||||||
* FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
|
* FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
|
||||||
* acls: the ACL string fetched from the database
|
* 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
|
* owner: username of object owner (will be passed through fmtId); can be
|
||||||
* NULL or empty string to indicate "no owner known"
|
* NULL or empty string to indicate "no owner known"
|
||||||
* prefix: string to prefix to each generated command; typically empty
|
* 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.
|
* Returns true if okay, false if could not parse the acl string.
|
||||||
* The resulting commands (if any) are appended to the contents of 'sql'.
|
* 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 "
|
* Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES "
|
||||||
* or something similar, and name is an empty string.
|
* or something similar, and name is an empty string.
|
||||||
*
|
*
|
||||||
@ -56,15 +63,19 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
|
|||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
buildACLCommands(const char *name, const char *subname, const char *nspname,
|
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,
|
const char *owner, const char *prefix, int remoteVersion,
|
||||||
PQExpBuffer sql)
|
PQExpBuffer sql)
|
||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
char **aclitems = NULL;
|
char **aclitems = NULL;
|
||||||
char **raclitems = NULL;
|
char **baseitems = NULL;
|
||||||
|
char **grantitems = NULL;
|
||||||
|
char **revokeitems = NULL;
|
||||||
int naclitems = 0;
|
int naclitems = 0;
|
||||||
int nraclitems = 0;
|
int nbaseitems = 0;
|
||||||
|
int ngrantitems = 0;
|
||||||
|
int nrevokeitems = 0;
|
||||||
int i;
|
int i;
|
||||||
PQExpBuffer grantee,
|
PQExpBuffer grantee,
|
||||||
grantor,
|
grantor,
|
||||||
@ -72,37 +83,88 @@ buildACLCommands(const char *name, const char *subname, const char *nspname,
|
|||||||
privswgo;
|
privswgo;
|
||||||
PQExpBuffer firstsql,
|
PQExpBuffer firstsql,
|
||||||
secondsql;
|
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 */
|
return true; /* object has default permissions */
|
||||||
|
|
||||||
/* treat empty-string owner same as NULL */
|
/* treat empty-string owner same as NULL */
|
||||||
if (owner && *owner == '\0')
|
if (owner && *owner == '\0')
|
||||||
owner = NULL;
|
owner = NULL;
|
||||||
|
|
||||||
if (strlen(acls) != 0)
|
/* Parse the acls array */
|
||||||
|
if (!parsePGArray(acls, &aclitems, &naclitems))
|
||||||
{
|
{
|
||||||
if (!parsePGArray(acls, &aclitems, &naclitems))
|
if (aclitems)
|
||||||
|
free(aclitems);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the baseacls, if provided */
|
||||||
|
if (baseacls && *baseacls != '\0')
|
||||||
|
{
|
||||||
|
if (!parsePGArray(baseacls, &baseitems, &nbaseitems))
|
||||||
{
|
{
|
||||||
if (aclitems)
|
if (aclitems)
|
||||||
free(aclitems);
|
free(aclitems);
|
||||||
|
if (baseitems)
|
||||||
|
free(baseitems);
|
||||||
return false;
|
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)
|
if (strcmp(aclitems[i], baseitems[j]) == 0)
|
||||||
free(aclitems);
|
{
|
||||||
if (raclitems)
|
found = true;
|
||||||
free(raclitems);
|
break;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
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();
|
grantee = createPQExpBuffer();
|
||||||
grantor = createPQExpBuffer();
|
grantor = createPQExpBuffer();
|
||||||
privs = 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.
|
* 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();
|
firstsql = createPQExpBuffer();
|
||||||
secondsql = createPQExpBuffer();
|
secondsql = createPQExpBuffer();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For pre-9.6 systems, we always start with REVOKE ALL FROM PUBLIC, as we
|
* If we weren't given baseacls information, we just revoke everything and
|
||||||
* don't wish to make any assumptions about what the default ACLs are, and
|
* then grant what's listed in the ACL. This avoids having to embed
|
||||||
* we do not collect them during the dump phase (and racls will always be
|
* detailed knowledge about what the defaults are/were, and it's not very
|
||||||
* the empty set, see above).
|
* expensive since servers lacking acldefault() are now rare.
|
||||||
*
|
*
|
||||||
* For 9.6 and later, if any revoke ACLs have been provided, then include
|
* Otherwise, we need only revoke what's listed in revokeitems.
|
||||||
* them in 'firstsql'.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
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);
|
appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix);
|
||||||
if (subname)
|
if (subname)
|
||||||
appendPQExpBuffer(firstsql, "(%s)", subname);
|
appendPQExpBuffer(firstsql, "(%s)", subname);
|
||||||
@ -161,13 +194,24 @@ buildACLCommands(const char *name, const char *subname, const char *nspname,
|
|||||||
if (nspname && *nspname)
|
if (nspname && *nspname)
|
||||||
appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
|
appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
|
||||||
appendPQExpBuffer(firstsql, "%s FROM PUBLIC;\n", name);
|
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
|
else
|
||||||
{
|
{
|
||||||
/* Scan individual REVOKE ACL items */
|
/* 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))
|
grantee, grantor, privs, NULL))
|
||||||
{
|
{
|
||||||
ok = false;
|
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
|
* 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
|
* public privileges are added in new versions: the REVOKE ALL will revoke
|
||||||
* them, leading to behavior different from what the old version had,
|
* 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);
|
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,
|
if (parseAclItem(grantitems[i], type, name, subname, remoteVersion,
|
||||||
grantee, grantor, privs, privswgo))
|
grantee, grantor, privs, privswgo))
|
||||||
{
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grantor->len == 0 && owner)
|
|
||||||
printfPQExpBuffer(grantor, "%s", owner);
|
|
||||||
|
|
||||||
if (privs->len > 0 || privswgo->len > 0)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Prior to 9.6, we had to handle owner privileges in a special
|
* If the grantor isn't the owner, we'll need to use SET SESSION
|
||||||
* manner by first REVOKE'ing the rights and then GRANT'ing them
|
* AUTHORIZATION to become the grantor. Issue the SET/RESET only
|
||||||
* after. With 9.6 and above, what we need to REVOKE and what we
|
* if there's something useful to do.
|
||||||
* need to GRANT is figured out when we dump and stashed into
|
|
||||||
* "racls" and "acls", respectively. See above.
|
|
||||||
*/
|
*/
|
||||||
if (remoteVersion < 90600 && owner
|
if (privs->len > 0 || privswgo->len > 0)
|
||||||
&& strcmp(grantee->data, owner) == 0
|
|
||||||
&& strcmp(grantor->data, owner) == 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
|
if (grantor->len > 0
|
||||||
&& (!owner || strcmp(owner, grantor->data) != 0))
|
&& (!owner || strcmp(owner, grantor->data) != 0))
|
||||||
appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n",
|
appendPQExpBuffer(thissql, "SET SESSION AUTHORIZATION %s;\n",
|
||||||
fmtId(grantor->data));
|
fmtId(grantor->data));
|
||||||
|
|
||||||
if (privs->len > 0)
|
if (privs->len > 0)
|
||||||
{
|
{
|
||||||
appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ",
|
appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
|
||||||
prefix, privs->data, type);
|
prefix, privs->data, type);
|
||||||
if (nspname && *nspname)
|
if (nspname && *nspname)
|
||||||
appendPQExpBuffer(secondsql, "%s.", fmtId(nspname));
|
appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
|
||||||
appendPQExpBuffer(secondsql, "%s TO ", name);
|
appendPQExpBuffer(thissql, "%s TO ", name);
|
||||||
if (grantee->len == 0)
|
if (grantee->len == 0)
|
||||||
appendPQExpBufferStr(secondsql, "PUBLIC;\n");
|
appendPQExpBufferStr(thissql, "PUBLIC;\n");
|
||||||
else if (strncmp(grantee->data, "group ",
|
else if (strncmp(grantee->data, "group ",
|
||||||
strlen("group ")) == 0)
|
strlen("group ")) == 0)
|
||||||
appendPQExpBuffer(secondsql, "GROUP %s;\n",
|
appendPQExpBuffer(thissql, "GROUP %s;\n",
|
||||||
fmtId(grantee->data + strlen("group ")));
|
fmtId(grantee->data + strlen("group ")));
|
||||||
else
|
else
|
||||||
appendPQExpBuffer(secondsql, "%s;\n", fmtId(grantee->data));
|
appendPQExpBuffer(thissql, "%s;\n", fmtId(grantee->data));
|
||||||
}
|
}
|
||||||
if (privswgo->len > 0)
|
if (privswgo->len > 0)
|
||||||
{
|
{
|
||||||
appendPQExpBuffer(secondsql, "%sGRANT %s ON %s ",
|
appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
|
||||||
prefix, privswgo->data, type);
|
prefix, privswgo->data, type);
|
||||||
if (nspname && *nspname)
|
if (nspname && *nspname)
|
||||||
appendPQExpBuffer(secondsql, "%s.", fmtId(nspname));
|
appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
|
||||||
appendPQExpBuffer(secondsql, "%s TO ", name);
|
appendPQExpBuffer(thissql, "%s TO ", name);
|
||||||
if (grantee->len == 0)
|
if (grantee->len == 0)
|
||||||
appendPQExpBufferStr(secondsql, "PUBLIC");
|
appendPQExpBufferStr(thissql, "PUBLIC");
|
||||||
else if (strncmp(grantee->data, "group ",
|
else if (strncmp(grantee->data, "group ",
|
||||||
strlen("group ")) == 0)
|
strlen("group ")) == 0)
|
||||||
appendPQExpBuffer(secondsql, "GROUP %s",
|
appendPQExpBuffer(thissql, "GROUP %s",
|
||||||
fmtId(grantee->data + strlen("group ")));
|
fmtId(grantee->data + strlen("group ")));
|
||||||
else
|
else
|
||||||
appendPQExpBufferStr(secondsql, fmtId(grantee->data));
|
appendPQExpBufferStr(thissql, fmtId(grantee->data));
|
||||||
appendPQExpBufferStr(secondsql, " WITH GRANT OPTION;\n");
|
appendPQExpBufferStr(thissql, " WITH GRANT OPTION;\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grantor->len > 0
|
if (grantor->len > 0
|
||||||
&& (!owner || strcmp(owner, grantor->data) != 0))
|
&& (!owner || strcmp(owner, grantor->data) != 0))
|
||||||
appendPQExpBufferStr(secondsql, "RESET SESSION AUTHORIZATION;\n");
|
appendPQExpBufferStr(thissql, "RESET SESSION AUTHORIZATION;\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
/*
|
/* parseAclItem failed, give up */
|
||||||
* For systems prior to 9.6, if we didn't find any owner privs, the owner
|
ok = false;
|
||||||
* must have revoked 'em all.
|
break;
|
||||||
*
|
}
|
||||||
* 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyPQExpBuffer(grantee);
|
destroyPQExpBuffer(grantee);
|
||||||
@ -361,19 +355,23 @@ buildACLCommands(const char *name, const char *subname, const char *nspname,
|
|||||||
|
|
||||||
if (aclitems)
|
if (aclitems)
|
||||||
free(aclitems);
|
free(aclitems);
|
||||||
|
if (baseitems)
|
||||||
if (raclitems)
|
free(baseitems);
|
||||||
free(raclitems);
|
if (grantitems)
|
||||||
|
free(grantitems);
|
||||||
|
if (revokeitems)
|
||||||
|
free(revokeitems);
|
||||||
|
|
||||||
return ok;
|
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)
|
* type: the object type (TABLES, FUNCTIONS, etc)
|
||||||
* nspname: schema name, or NULL for global default privileges
|
* nspname: schema name, or NULL for global default privileges
|
||||||
* acls: the ACL string fetched from the database
|
* 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)
|
* owner: username of privileges owner (will be passed through fmtId)
|
||||||
* remoteVersion: version of database
|
* remoteVersion: version of database
|
||||||
*
|
*
|
||||||
@ -382,8 +380,7 @@ buildACLCommands(const char *name, const char *subname, const char *nspname,
|
|||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
buildDefaultACLCommands(const char *type, const char *nspname,
|
buildDefaultACLCommands(const char *type, const char *nspname,
|
||||||
const char *acls, const char *racls,
|
const char *acls, const char *acldefault,
|
||||||
const char *initacls, const char *initracls,
|
|
||||||
const char *owner,
|
const char *owner,
|
||||||
int remoteVersion,
|
int remoteVersion,
|
||||||
PQExpBuffer sql)
|
PQExpBuffer sql)
|
||||||
@ -403,21 +400,12 @@ buildDefaultACLCommands(const char *type, const char *nspname,
|
|||||||
if (nspname)
|
if (nspname)
|
||||||
appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
|
appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
|
||||||
|
|
||||||
if (strlen(initacls) != 0 || strlen(initracls) != 0)
|
/*
|
||||||
{
|
* There's no such thing as initprivs for a default ACL, so the base ACL
|
||||||
appendPQExpBufferStr(sql, "SELECT pg_catalog.binary_upgrade_set_record_init_privs(true);\n");
|
* is always just the object-type-specific default.
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!buildACLCommands("", NULL, NULL, type,
|
if (!buildACLCommands("", NULL, NULL, type,
|
||||||
acls, racls, owner,
|
acls, acldefault, owner,
|
||||||
prefix->data, remoteVersion, sql))
|
prefix->data, remoteVersion, sql))
|
||||||
{
|
{
|
||||||
destroyPQExpBuffer(prefix);
|
destroyPQExpBuffer(prefix);
|
||||||
@ -467,7 +455,7 @@ parseAclItem(const char *item, const char *type,
|
|||||||
buf = pg_strdup(item);
|
buf = pg_strdup(item);
|
||||||
|
|
||||||
/* user or group name is string up to = */
|
/* user or group name is string up to = */
|
||||||
eqpos = copyAclUserName(grantee, buf);
|
eqpos = dequoteAclUserName(grantee, buf);
|
||||||
if (*eqpos != '=')
|
if (*eqpos != '=')
|
||||||
{
|
{
|
||||||
pg_free(buf);
|
pg_free(buf);
|
||||||
@ -479,7 +467,7 @@ parseAclItem(const char *item, const char *type,
|
|||||||
if (slpos)
|
if (slpos)
|
||||||
{
|
{
|
||||||
*slpos++ = '\0';
|
*slpos++ = '\0';
|
||||||
slpos = copyAclUserName(grantor, slpos);
|
slpos = dequoteAclUserName(grantor, slpos);
|
||||||
if (*slpos != '\0')
|
if (*slpos != '\0')
|
||||||
{
|
{
|
||||||
pg_free(buf);
|
pg_free(buf);
|
||||||
@ -603,13 +591,46 @@ do { \
|
|||||||
return true;
|
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,
|
* 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.
|
* 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.
|
* The name is taken to end at an unquoted '=' or end of string.
|
||||||
|
* Note: unlike quoteAclUserName(), this first clears the output buffer.
|
||||||
*/
|
*/
|
||||||
static char *
|
static char *
|
||||||
copyAclUserName(PQExpBuffer output, char *input)
|
dequoteAclUserName(PQExpBuffer output, char *input)
|
||||||
{
|
{
|
||||||
resetPQExpBuffer(output);
|
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.
|
* Detect whether the given GUC variable is of GUC_LIST_QUOTE type.
|
||||||
|
@ -37,26 +37,22 @@
|
|||||||
|
|
||||||
|
|
||||||
extern bool buildACLCommands(const char *name, const char *subname, const char *nspname,
|
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,
|
const char *owner, const char *prefix, int remoteVersion,
|
||||||
PQExpBuffer sql);
|
PQExpBuffer sql);
|
||||||
extern bool buildDefaultACLCommands(const char *type, const char *nspname,
|
extern bool buildDefaultACLCommands(const char *type, const char *nspname,
|
||||||
const char *acls, const char *racls,
|
const char *acls, const char *acldefault,
|
||||||
const char *initacls, const char *initracls,
|
|
||||||
const char *owner,
|
const char *owner,
|
||||||
int remoteVersion,
|
int remoteVersion,
|
||||||
PQExpBuffer sql);
|
PQExpBuffer sql);
|
||||||
|
|
||||||
|
extern void quoteAclUserName(PQExpBuffer output, const char *input);
|
||||||
|
|
||||||
extern void buildShSecLabelQuery(const char *catalog_name,
|
extern void buildShSecLabelQuery(const char *catalog_name,
|
||||||
Oid objectId, PQExpBuffer sql);
|
Oid objectId, PQExpBuffer sql);
|
||||||
extern void emitShSecLabels(PGconn *conn, PGresult *res,
|
extern void emitShSecLabels(PGconn *conn, PGresult *res,
|
||||||
PQExpBuffer buffer, const char *objtype, const char *objname);
|
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 variable_is_guc_list_quote(const char *name);
|
||||||
|
|
||||||
extern bool SplitGUCList(char *rawstring, char separator,
|
extern bool SplitGUCList(char *rawstring, char separator,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -146,16 +146,36 @@ typedef struct _dumpableObject
|
|||||||
int allocDeps; /* allocated size of dependencies[] */
|
int allocDeps; /* allocated size of dependencies[] */
|
||||||
} DumpableObject;
|
} DumpableObject;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Object types that have ACLs must store them in a DumpableAcl sub-struct,
|
||||||
|
* which must immediately follow the DumpableObject base struct.
|
||||||
|
*
|
||||||
|
* Note: when dumping from a pre-9.2 server, which lacks the acldefault()
|
||||||
|
* function, acldefault will be NULL or empty.
|
||||||
|
*/
|
||||||
|
typedef struct _dumpableAcl
|
||||||
|
{
|
||||||
|
char *acl; /* the object's actual ACL string */
|
||||||
|
char *acldefault; /* default ACL for the object's type & owner */
|
||||||
|
/* these fields come from the object's pg_init_privs entry, if any: */
|
||||||
|
char privtype; /* entry type, 'i' or 'e'; 0 if no entry */
|
||||||
|
char *initprivs; /* the object's initial ACL string, or NULL */
|
||||||
|
} DumpableAcl;
|
||||||
|
|
||||||
|
/* Generic struct that can be used to access any object type having an ACL */
|
||||||
|
typedef struct _dumpableObjectWithAcl
|
||||||
|
{
|
||||||
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
|
} DumpableObjectWithAcl;
|
||||||
|
|
||||||
typedef struct _namespaceInfo
|
typedef struct _namespaceInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
bool create; /* CREATE SCHEMA, or just set owner? */
|
bool create; /* CREATE SCHEMA, or just set owner? */
|
||||||
Oid nspowner;
|
Oid nspowner;
|
||||||
char *rolname; /* name of owner, or empty string */
|
char *rolname; /* name of owner, or empty string */
|
||||||
char *nspacl;
|
|
||||||
char *rnspacl;
|
|
||||||
char *initnspacl;
|
|
||||||
char *initrnspacl;
|
|
||||||
} NamespaceInfo;
|
} NamespaceInfo;
|
||||||
|
|
||||||
typedef struct _extensionInfo
|
typedef struct _extensionInfo
|
||||||
@ -171,6 +191,7 @@ typedef struct _extensionInfo
|
|||||||
typedef struct _typeInfo
|
typedef struct _typeInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: dobj.name is the raw pg_type.typname entry. ftypname is the
|
* Note: dobj.name is the raw pg_type.typname entry. ftypname is the
|
||||||
@ -179,10 +200,6 @@ typedef struct _typeInfo
|
|||||||
*/
|
*/
|
||||||
char *ftypname;
|
char *ftypname;
|
||||||
char *rolname; /* name of owner, or empty string */
|
char *rolname; /* name of owner, or empty string */
|
||||||
char *typacl;
|
|
||||||
char *rtypacl;
|
|
||||||
char *inittypacl;
|
|
||||||
char *initrtypacl;
|
|
||||||
Oid typelem;
|
Oid typelem;
|
||||||
Oid typrelid;
|
Oid typrelid;
|
||||||
char typrelkind; /* 'r', 'v', 'c', etc */
|
char typrelkind; /* 'r', 'v', 'c', etc */
|
||||||
@ -207,15 +224,12 @@ typedef struct _shellTypeInfo
|
|||||||
typedef struct _funcInfo
|
typedef struct _funcInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
char *rolname; /* name of owner, or empty string */
|
char *rolname; /* name of owner, or empty string */
|
||||||
Oid lang;
|
Oid lang;
|
||||||
int nargs;
|
int nargs;
|
||||||
Oid *argtypes;
|
Oid *argtypes;
|
||||||
Oid prorettype;
|
Oid prorettype;
|
||||||
char *proacl;
|
|
||||||
char *rproacl;
|
|
||||||
char *initproacl;
|
|
||||||
char *initrproacl;
|
|
||||||
} FuncInfo;
|
} FuncInfo;
|
||||||
|
|
||||||
/* AggInfo is a superset of FuncInfo */
|
/* AggInfo is a superset of FuncInfo */
|
||||||
@ -270,11 +284,8 @@ typedef struct _tableInfo
|
|||||||
* These fields are collected for every table in the database.
|
* These fields are collected for every table in the database.
|
||||||
*/
|
*/
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
char *rolname; /* name of owner, or empty string */
|
char *rolname; /* name of owner, or empty string */
|
||||||
char *relacl;
|
|
||||||
char *rrelacl;
|
|
||||||
char *initrelacl;
|
|
||||||
char *initrrelacl;
|
|
||||||
char relkind;
|
char relkind;
|
||||||
char relpersistence; /* relation persistence */
|
char relpersistence; /* relation persistence */
|
||||||
bool relispopulated; /* relation is populated */
|
bool relispopulated; /* relation is populated */
|
||||||
@ -286,6 +297,7 @@ typedef struct _tableInfo
|
|||||||
bool hasindex; /* does it have any indexes? */
|
bool hasindex; /* does it have any indexes? */
|
||||||
bool hasrules; /* does it have any rules? */
|
bool hasrules; /* does it have any rules? */
|
||||||
bool hastriggers; /* does it have any triggers? */
|
bool hastriggers; /* does it have any triggers? */
|
||||||
|
bool hascolumnACLs; /* do any columns have non-default ACLs? */
|
||||||
bool rowsec; /* is row security enabled? */
|
bool rowsec; /* is row security enabled? */
|
||||||
bool forcerowsec; /* is row security forced? */
|
bool forcerowsec; /* is row security forced? */
|
||||||
bool hasoids; /* does it have OIDs? */
|
bool hasoids; /* does it have OIDs? */
|
||||||
@ -478,14 +490,11 @@ typedef struct _constraintInfo
|
|||||||
typedef struct _procLangInfo
|
typedef struct _procLangInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
bool lanpltrusted;
|
bool lanpltrusted;
|
||||||
Oid lanplcallfoid;
|
Oid lanplcallfoid;
|
||||||
Oid laninline;
|
Oid laninline;
|
||||||
Oid lanvalidator;
|
Oid lanvalidator;
|
||||||
char *lanacl;
|
|
||||||
char *rlanacl;
|
|
||||||
char *initlanacl;
|
|
||||||
char *initrlanacl;
|
|
||||||
char *lanowner; /* name of owner, or empty string */
|
char *lanowner; /* name of owner, or empty string */
|
||||||
} ProcLangInfo;
|
} ProcLangInfo;
|
||||||
|
|
||||||
@ -550,49 +559,37 @@ typedef struct _cfgInfo
|
|||||||
typedef struct _fdwInfo
|
typedef struct _fdwInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
char *rolname;
|
char *rolname;
|
||||||
char *fdwhandler;
|
char *fdwhandler;
|
||||||
char *fdwvalidator;
|
char *fdwvalidator;
|
||||||
char *fdwoptions;
|
char *fdwoptions;
|
||||||
char *fdwacl;
|
|
||||||
char *rfdwacl;
|
|
||||||
char *initfdwacl;
|
|
||||||
char *initrfdwacl;
|
|
||||||
} FdwInfo;
|
} FdwInfo;
|
||||||
|
|
||||||
typedef struct _foreignServerInfo
|
typedef struct _foreignServerInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
char *rolname;
|
char *rolname;
|
||||||
Oid srvfdw;
|
Oid srvfdw;
|
||||||
char *srvtype;
|
char *srvtype;
|
||||||
char *srvversion;
|
char *srvversion;
|
||||||
char *srvacl;
|
|
||||||
char *rsrvacl;
|
|
||||||
char *initsrvacl;
|
|
||||||
char *initrsrvacl;
|
|
||||||
char *srvoptions;
|
char *srvoptions;
|
||||||
} ForeignServerInfo;
|
} ForeignServerInfo;
|
||||||
|
|
||||||
typedef struct _defaultACLInfo
|
typedef struct _defaultACLInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
char *defaclrole;
|
char *defaclrole;
|
||||||
char defaclobjtype;
|
char defaclobjtype;
|
||||||
char *defaclacl;
|
|
||||||
char *rdefaclacl;
|
|
||||||
char *initdefaclacl;
|
|
||||||
char *initrdefaclacl;
|
|
||||||
} DefaultACLInfo;
|
} DefaultACLInfo;
|
||||||
|
|
||||||
typedef struct _blobInfo
|
typedef struct _blobInfo
|
||||||
{
|
{
|
||||||
DumpableObject dobj;
|
DumpableObject dobj;
|
||||||
|
DumpableAcl dacl;
|
||||||
char *rolname;
|
char *rolname;
|
||||||
char *blobacl;
|
|
||||||
char *rblobacl;
|
|
||||||
char *initblobacl;
|
|
||||||
char *initrblobacl;
|
|
||||||
} BlobInfo;
|
} BlobInfo;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1166,55 +1166,12 @@ dumpTablespaces(PGconn *conn)
|
|||||||
/*
|
/*
|
||||||
* Get all tablespaces except built-in ones (which we assume are named
|
* Get all tablespaces except built-in ones (which we assume are named
|
||||||
* pg_xxx)
|
* pg_xxx)
|
||||||
*
|
|
||||||
* For the tablespace ACLs, as of 9.6, we extract both the positive (as
|
|
||||||
* spcacl) and negative (as rspcacl) ACLs, relative to the default ACL for
|
|
||||||
* tablespaces, which are then passed to buildACLCommands() below.
|
|
||||||
*
|
|
||||||
* See buildACLQueries() and buildACLCommands().
|
|
||||||
*
|
|
||||||
* 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 we do not support initial privileges (pg_init_privs) on
|
|
||||||
* tablespaces, so this logic cannot make use of buildACLQueries().
|
|
||||||
*/
|
*/
|
||||||
if (server_version >= 90600)
|
if (server_version >= 90200)
|
||||||
res = executeQuery(conn, "SELECT oid, spcname, "
|
res = executeQuery(conn, "SELECT oid, spcname, "
|
||||||
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
||||||
"pg_catalog.pg_tablespace_location(oid), "
|
"pg_catalog.pg_tablespace_location(oid), "
|
||||||
"(SELECT array_agg(acl ORDER BY row_n) FROM "
|
"spcacl, acldefault('t', spcowner) AS acldefault, "
|
||||||
" (SELECT acl, row_n FROM "
|
|
||||||
" unnest(coalesce(spcacl,acldefault('t',spcowner))) "
|
|
||||||
" WITH ORDINALITY AS perm(acl,row_n) "
|
|
||||||
" WHERE NOT EXISTS ( "
|
|
||||||
" SELECT 1 "
|
|
||||||
" FROM unnest(acldefault('t',spcowner)) "
|
|
||||||
" AS init(init_acl) "
|
|
||||||
" WHERE acl = init_acl)) AS spcacls) "
|
|
||||||
" AS spcacl, "
|
|
||||||
"(SELECT array_agg(acl ORDER BY row_n) FROM "
|
|
||||||
" (SELECT acl, row_n FROM "
|
|
||||||
" unnest(acldefault('t',spcowner)) "
|
|
||||||
" WITH ORDINALITY AS initp(acl,row_n) "
|
|
||||||
" WHERE NOT EXISTS ( "
|
|
||||||
" SELECT 1 "
|
|
||||||
" FROM unnest(coalesce(spcacl,acldefault('t',spcowner))) "
|
|
||||||
" AS permp(orig_acl) "
|
|
||||||
" WHERE acl = orig_acl)) AS rspcacls) "
|
|
||||||
" AS rspcacl, "
|
|
||||||
"array_to_string(spcoptions, ', '),"
|
|
||||||
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
|
||||||
"FROM pg_catalog.pg_tablespace "
|
|
||||||
"WHERE spcname !~ '^pg_' "
|
|
||||||
"ORDER BY 1");
|
|
||||||
else if (server_version >= 90200)
|
|
||||||
res = executeQuery(conn, "SELECT oid, spcname, "
|
|
||||||
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
|
||||||
"pg_catalog.pg_tablespace_location(oid), "
|
|
||||||
"spcacl, '' as rspcacl, "
|
|
||||||
"array_to_string(spcoptions, ', '),"
|
"array_to_string(spcoptions, ', '),"
|
||||||
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
||||||
"FROM pg_catalog.pg_tablespace "
|
"FROM pg_catalog.pg_tablespace "
|
||||||
@ -1223,7 +1180,7 @@ dumpTablespaces(PGconn *conn)
|
|||||||
else if (server_version >= 90000)
|
else if (server_version >= 90000)
|
||||||
res = executeQuery(conn, "SELECT oid, spcname, "
|
res = executeQuery(conn, "SELECT oid, spcname, "
|
||||||
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
||||||
"spclocation, spcacl, '' as rspcacl, "
|
"spclocation, spcacl, NULL AS acldefault, "
|
||||||
"array_to_string(spcoptions, ', '),"
|
"array_to_string(spcoptions, ', '),"
|
||||||
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
||||||
"FROM pg_catalog.pg_tablespace "
|
"FROM pg_catalog.pg_tablespace "
|
||||||
@ -1232,7 +1189,7 @@ dumpTablespaces(PGconn *conn)
|
|||||||
else if (server_version >= 80200)
|
else if (server_version >= 80200)
|
||||||
res = executeQuery(conn, "SELECT oid, spcname, "
|
res = executeQuery(conn, "SELECT oid, spcname, "
|
||||||
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
||||||
"spclocation, spcacl, '' as rspcacl, null, "
|
"spclocation, spcacl, NULL AS acldefault, null, "
|
||||||
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
"pg_catalog.shobj_description(oid, 'pg_tablespace') "
|
||||||
"FROM pg_catalog.pg_tablespace "
|
"FROM pg_catalog.pg_tablespace "
|
||||||
"WHERE spcname !~ '^pg_' "
|
"WHERE spcname !~ '^pg_' "
|
||||||
@ -1240,7 +1197,7 @@ dumpTablespaces(PGconn *conn)
|
|||||||
else
|
else
|
||||||
res = executeQuery(conn, "SELECT oid, spcname, "
|
res = executeQuery(conn, "SELECT oid, spcname, "
|
||||||
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
"pg_catalog.pg_get_userbyid(spcowner) AS spcowner, "
|
||||||
"spclocation, spcacl, '' as rspcacl, "
|
"spclocation, spcacl, NULL AS acldefault, "
|
||||||
"null, null "
|
"null, null "
|
||||||
"FROM pg_catalog.pg_tablespace "
|
"FROM pg_catalog.pg_tablespace "
|
||||||
"WHERE spcname !~ '^pg_' "
|
"WHERE spcname !~ '^pg_' "
|
||||||
@ -1257,7 +1214,7 @@ dumpTablespaces(PGconn *conn)
|
|||||||
char *spcowner = PQgetvalue(res, i, 2);
|
char *spcowner = PQgetvalue(res, i, 2);
|
||||||
char *spclocation = PQgetvalue(res, i, 3);
|
char *spclocation = PQgetvalue(res, i, 3);
|
||||||
char *spcacl = PQgetvalue(res, i, 4);
|
char *spcacl = PQgetvalue(res, i, 4);
|
||||||
char *rspcacl = PQgetvalue(res, i, 5);
|
char *acldefault = PQgetvalue(res, i, 5);
|
||||||
char *spcoptions = PQgetvalue(res, i, 6);
|
char *spcoptions = PQgetvalue(res, i, 6);
|
||||||
char *spccomment = PQgetvalue(res, i, 7);
|
char *spccomment = PQgetvalue(res, i, 7);
|
||||||
char *fspcname;
|
char *fspcname;
|
||||||
@ -1276,9 +1233,11 @@ dumpTablespaces(PGconn *conn)
|
|||||||
appendPQExpBuffer(buf, "ALTER TABLESPACE %s SET (%s);\n",
|
appendPQExpBuffer(buf, "ALTER TABLESPACE %s SET (%s);\n",
|
||||||
fspcname, spcoptions);
|
fspcname, spcoptions);
|
||||||
|
|
||||||
|
/* tablespaces can't have initprivs */
|
||||||
|
|
||||||
if (!skip_acls &&
|
if (!skip_acls &&
|
||||||
!buildACLCommands(fspcname, NULL, NULL, "TABLESPACE",
|
!buildACLCommands(fspcname, NULL, NULL, "TABLESPACE",
|
||||||
spcacl, rspcacl,
|
spcacl, acldefault,
|
||||||
spcowner, "", server_version, buf))
|
spcowner, "", server_version, buf))
|
||||||
{
|
{
|
||||||
pg_log_error("could not parse ACL list (%s) for tablespace \"%s\"",
|
pg_log_error("could not parse ACL list (%s) for tablespace \"%s\"",
|
||||||
|
@ -726,6 +726,69 @@ parsePGArray(const char *atext, char ***itemarray, int *nitems)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append one element to the text representation of a 1-dimensional Postgres
|
||||||
|
* array.
|
||||||
|
*
|
||||||
|
* The caller must provide the initial '{' and closing '}' of the array.
|
||||||
|
* This function handles all else, including insertion of commas and
|
||||||
|
* quoting of values.
|
||||||
|
*
|
||||||
|
* We assume that typdelim is ','.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
appendPGArray(PQExpBuffer buffer, const char *value)
|
||||||
|
{
|
||||||
|
bool needquote;
|
||||||
|
const char *tmp;
|
||||||
|
|
||||||
|
if (buffer->data[buffer->len - 1] != '{')
|
||||||
|
appendPQExpBufferChar(buffer, ',');
|
||||||
|
|
||||||
|
/* Decide if we need quotes; this should match array_out()'s choices. */
|
||||||
|
if (value[0] == '\0')
|
||||||
|
needquote = true; /* force quotes for empty string */
|
||||||
|
else if (pg_strcasecmp(value, "NULL") == 0)
|
||||||
|
needquote = true; /* force quotes for literal NULL */
|
||||||
|
else
|
||||||
|
needquote = false;
|
||||||
|
|
||||||
|
if (!needquote)
|
||||||
|
{
|
||||||
|
for (tmp = value; *tmp; tmp++)
|
||||||
|
{
|
||||||
|
char ch = *tmp;
|
||||||
|
|
||||||
|
if (ch == '"' || ch == '\\' ||
|
||||||
|
ch == '{' || ch == '}' || ch == ',' ||
|
||||||
|
/* these match array_isspace(): */
|
||||||
|
ch == ' ' || ch == '\t' || ch == '\n' ||
|
||||||
|
ch == '\r' || ch == '\v' || ch == '\f')
|
||||||
|
{
|
||||||
|
needquote = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needquote)
|
||||||
|
{
|
||||||
|
appendPQExpBufferChar(buffer, '"');
|
||||||
|
for (tmp = value; *tmp; tmp++)
|
||||||
|
{
|
||||||
|
char ch = *tmp;
|
||||||
|
|
||||||
|
if (ch == '"' || ch == '\\')
|
||||||
|
appendPQExpBufferChar(buffer, '\\');
|
||||||
|
appendPQExpBufferChar(buffer, ch);
|
||||||
|
}
|
||||||
|
appendPQExpBufferChar(buffer, '"');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
appendPQExpBufferStr(buffer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Format a reloptions array and append it to the given buffer.
|
* Format a reloptions array and append it to the given buffer.
|
||||||
*
|
*
|
||||||
|
@ -46,6 +46,7 @@ extern void appendConnStrVal(PQExpBuffer buf, const char *str);
|
|||||||
extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname);
|
extern void appendPsqlMetaConnect(PQExpBuffer buf, const char *dbname);
|
||||||
|
|
||||||
extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems);
|
extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems);
|
||||||
|
extern void appendPGArray(PQExpBuffer buffer, const char *value);
|
||||||
|
|
||||||
extern bool appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
|
extern bool appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
|
||||||
const char *prefix, int encoding, bool std_strings);
|
const char *prefix, int encoding, bool std_strings);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user