agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH] Draft implementation of USAGE privilege on PUBLICATION. 3+ messages / 2 participants [nested] [flat]
* [PATCH] Draft implementation of USAGE privilege on PUBLICATION. @ 2022-05-18 08:58 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 3+ messages in thread From: Antonin Houska @ 2022-05-18 08:58 UTC (permalink / raw) This privilege seems to be useful for databases from which multiple subscribers replicate the data. It should not be assumed that any publication can be exposed to any subscription. --- doc/src/sgml/ddl.sgml | 7 + doc/src/sgml/logical-replication.sgml | 4 +- doc/src/sgml/ref/grant.sgml | 7 +- doc/src/sgml/ref/revoke.sgml | 7 + src/backend/catalog/aclchk.c | 206 ++++++++++++++++++++ src/backend/commands/copyto.c | 114 +++++++++++ src/backend/commands/publicationcmds.c | 2 + src/backend/parser/gram.y | 8 + src/backend/replication/pgoutput/pgoutput.c | 13 +- src/backend/utils/adt/acl.c | 7 + src/bin/pg_dump/dumputils.c | 2 + src/bin/pg_dump/pg_dump.c | 28 ++- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/tab-complete.c | 3 + src/include/catalog/pg_publication.h | 6 + src/include/nodes/parsenodes.h | 4 +- src/include/utils/acl.h | 4 + 17 files changed, 413 insertions(+), 10 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index f2ac1ba0034..dc499c50756 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1935,6 +1935,13 @@ REVOKE ALL ON accounts FROM PUBLIC; statements that have previously performed this lookup, so this is not a completely secure way to prevent object access. </para> + <para> + For publications, allows logical replication via particular + publication. The user specified in + the <link linkend="sql-createsubscription"><command>CREATE + SUBSCRIPTION</command></link> command must have this privilege on all + publications listed in that command. + </para> <para> For sequences, allows use of the <function>currval</function> and <function>nextval</function> functions. diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 145ea71d61b..63194b34541 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -1156,7 +1156,9 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER <para> In order to be able to copy the initial table data, the role used for the replication connection must have the <literal>SELECT</literal> privilege on - a published table (or be a superuser). + a published table (or be a superuser). In addition, the role must have + the <literal>USAGE</literal> privilege on all the publications referenced + by particular subscription. </para> <para> diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index f744b05b55d..95b9c46137f 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -82,6 +82,11 @@ GRANT { { SET | ALTER SYSTEM } [, ... ] | ALL [ PRIVILEGES ] } TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] +GRANT { USAGE | ALL [ PRIVILEGES ] } + ON PUBLICATION <replaceable>pub_name</replaceable> [, ...] + TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ] + [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] + GRANT { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] } ON SCHEMA <replaceable>schema_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ] @@ -460,7 +465,7 @@ GRANT admins TO joe; </para> <para> - Privileges on databases, tablespaces, schemas, languages, and + Privileges on databases, tablespaces, schemas, languages, publications and configuration parameters are <productname>PostgreSQL</productname> extensions. </para> diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 62f19710369..c21ea4c9c69 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -104,6 +104,13 @@ REVOKE [ GRANT OPTION FOR ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] [ CASCADE | RESTRICT ] +REVOKE [ GRANT OPTION FOR ] + { USAGE | ALL [ PRIVILEGES ] } + ON PUBLICATION <replaceable>pub_name</replaceable> [, ...] + FROM <replaceable class="parameter">role_specification</replaceable> [, ...] + [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] + [ CASCADE | RESTRICT ] + REVOKE [ GRANT OPTION FOR ] { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] } ON SCHEMA <replaceable>schema_name</replaceable> [, ...] diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 5f1726c0957..2896c10415f 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -110,6 +110,7 @@ static void ExecGrant_ForeignServer(InternalGrant *grantStmt); static void ExecGrant_Function(InternalGrant *grantStmt); static void ExecGrant_Language(InternalGrant *grantStmt); static void ExecGrant_Largeobject(InternalGrant *grantStmt); +static void ExecGrant_Publication(InternalGrant *istmt); static void ExecGrant_Namespace(InternalGrant *grantStmt); static void ExecGrant_Tablespace(InternalGrant *grantStmt); static void ExecGrant_Type(InternalGrant *grantStmt); @@ -477,6 +478,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT; errormsg = gettext_noop("invalid privilege type %s for large object"); break; + case OBJECT_PUBLICATION: + all_privileges = ACL_ALL_RIGHTS_PUBLICATION; + errormsg = gettext_noop("invalid privilege type %s for publication"); + break; case OBJECT_SCHEMA: all_privileges = ACL_ALL_RIGHTS_SCHEMA; errormsg = gettext_noop("invalid privilege type %s for schema"); @@ -605,6 +610,9 @@ ExecGrantStmt_oids(InternalGrant *istmt) case OBJECT_LARGEOBJECT: ExecGrant_Largeobject(istmt); break; + case OBJECT_PUBLICATION: + ExecGrant_Publication(istmt); + break; case OBJECT_SCHEMA: ExecGrant_Namespace(istmt); break; @@ -715,6 +723,16 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant) objects = lappend_oid(objects, lobjOid); } break; + case OBJECT_PUBLICATION: + foreach(cell, objnames) + { + char *nspname = strVal(lfirst(cell)); + Oid oid; + + oid = get_publication_oid(nspname, false); + objects = lappend_oid(objects, oid); + } + break; case OBJECT_SCHEMA: foreach(cell, objnames) { @@ -2892,6 +2910,126 @@ ExecGrant_Largeobject(InternalGrant *istmt) table_close(relation, RowExclusiveLock); } +static void +ExecGrant_Publication(InternalGrant *istmt) +{ + Relation relation; + ListCell *cell; + + if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) + istmt->privileges = ACL_ALL_RIGHTS_PUBLICATION; + + relation = table_open(PublicationRelationId, RowExclusiveLock); + + foreach(cell, istmt->objects) + { + Oid pubid = lfirst_oid(cell); + HeapTuple tuple; + HeapTuple newtuple; + Form_pg_publication pg_pub_tuple; + Oid ownerId; + Datum aclDatum; + bool isnull; + AclMode avail_goptions; + AclMode this_privileges; + Acl *old_acl; + Acl *new_acl; + Oid grantorId; + Datum values[Natts_pg_publication]; + bool nulls[Natts_pg_publication]; + bool replaces[Natts_pg_publication]; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + + tuple = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for publication %u", pubid); + + pg_pub_tuple = (Form_pg_publication) GETSTRUCT(tuple); + ownerId = pg_pub_tuple->pubowner; + aclDatum = SysCacheGetAttr(PUBLICATIONOID, tuple, + Anum_pg_publication_pubacl, + &isnull); + + if (isnull) + { + old_acl = acldefault(OBJECT_PUBLICATION, ownerId); + /* There are no old member roles according to the catalogs */ + noldmembers = 0; + oldmembers = NULL; + } + else + { + old_acl = DatumGetAclPCopy(aclDatum); + /* Get the roles mentioned in the existing ACL */ + noldmembers = aclmembers(old_acl, &oldmembers); + } + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), istmt->privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); + + /* + * Restrict the privileges to what we can actually grant, and emit the + * standards-mandated warning and error messages. + */ + this_privileges = + restrict_and_check_grant(istmt->is_grant, avail_goptions, + istmt->all_privs, istmt->privileges, + pubid, grantorId, OBJECT_FUNCTION, + NameStr(pg_pub_tuple->pubname), + 0, NULL); + + /* + * Generate new ACL. + */ + new_acl = merge_acl_with_grant(old_acl, istmt->is_grant, + istmt->grant_option, istmt->behavior, + istmt->grantees, this_privileges, + grantorId, ownerId); + + /* + * We need the members of both old and new ACLs so we can correct the + * shared dependency information. + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + /* finished building new ACL value, now insert it */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + replaces[Anum_pg_publication_pubacl - 1] = true; + values[Anum_pg_publication_pubacl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, + nulls, replaces); + + CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); + + /* Update initial privileges for extensions */ + recordExtensionInitPriv(pubid, PublicationRelationId, 0, new_acl); + + /* Update the shared dependency ACL info */ + updateAclDependencies(PublicationRelationId, pubid, 0, + ownerId, + noldmembers, oldmembers, + nnewmembers, newmembers); + + ReleaseSysCache(tuple); + + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + } + + table_close(relation, RowExclusiveLock); +} + static void ExecGrant_Namespace(InternalGrant *istmt) { @@ -3856,6 +3994,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid, return pg_database_aclmask(table_oid, roleid, mask, how); case OBJECT_FUNCTION: return pg_proc_aclmask(table_oid, roleid, mask, how); + case OBJECT_PUBLICATION: + return pg_publication_aclmask(table_oid, roleid, mask, how); case OBJECT_LANGUAGE: return pg_language_aclmask(table_oid, roleid, mask, how); case OBJECT_LARGEOBJECT: @@ -4379,6 +4519,60 @@ pg_proc_aclmask(Oid proc_oid, Oid roleid, return result; } +/* + * Exported routine for examining a user's privileges for a publication. + */ +AclMode +pg_publication_aclmask(Oid pubid, Oid roleid, AclMode mask, + AclMaskHow how) +{ + AclMode result; + HeapTuple tuple; + Datum aclDatum; + bool isNull; + Acl *acl; + Oid ownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return mask; + + /* + * Get the publication's ACL from pg_publication + */ + tuple = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("publication with OID %u does not exist", pubid))); + + ownerId = ((Form_pg_publication) GETSTRUCT(tuple))->pubowner; + + aclDatum = SysCacheGetAttr(PUBLICATIONOID, tuple, Anum_pg_publication_pubacl, + &isNull); + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(OBJECT_PUBLICATION, ownerId); + aclDatum = (Datum) 0; + } + else + { + /* detoast ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + result = aclmask(acl, roleid, ownerId, mask, how); + + /* if we have a detoasted copy, free it */ + if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) + pfree(acl); + + ReleaseSysCache(tuple); + + return result; +} + /* * Exported routine for examining a user's privileges for a language */ @@ -5076,6 +5270,18 @@ pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode) return ACLCHECK_NO_PRIV; } +/* + * Exported routine for checking a user's access privileges to a publication + */ +AclResult +pg_publication_aclcheck(Oid pub_oid, Oid roleid, AclMode mode) +{ + if (pg_publication_aclmask(pub_oid, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + /* * Exported routine for checking a user's access privileges to a language */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index fca29a9a105..0d39ef77610 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -27,6 +27,7 @@ #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" +#include "executor/spi.h" #include "executor/tuptable.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" @@ -34,9 +35,11 @@ #include "miscadmin.h" #include "optimizer/optimizer.h" #include "pgstat.h" +#include "replication/walsender.h" #include "rewrite/rewriteHandler.h" #include "storage/fd.h" #include "tcop/tcopprot.h" +#include "utils/acl.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/partcache.h" @@ -129,6 +132,7 @@ static void CopySendEndOfRow(CopyToState cstate); static void CopySendInt32(CopyToState cstate, int32 val); static void CopySendInt16(CopyToState cstate, int16 val); +static void check_publication_privileges(List *relids); /* * Send copy start/stop messages for frontend copies. These have changed @@ -360,6 +364,7 @@ BeginCopyTo(ParseState *pstate, PROGRESS_COPY_COMMAND_TO, 0 }; + List *pubrelids = NIL; if (rel != NULL && rel->rd_rel->relkind != RELKIND_RELATION) { @@ -424,6 +429,13 @@ BeginCopyTo(ParseState *pstate, cstate->rel = rel; tupDesc = RelationGetDescr(cstate->rel); + + /* + * In walsender, we need to check the privileges of the related + * publications. + */ + if (am_walsender) + pubrelids = lappend_oid(pubrelids, RelationGetRelid(rel)); } else { @@ -553,9 +565,32 @@ BeginCopyTo(ParseState *pstate, */ ExecutorStart(cstate->queryDesc, 0); + /* + * In walsender, we need to check the privileges of the related + * publications. + */ + if (am_walsender) + { + ListCell *lc; + + foreach(lc, plan->rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + + if (rte->rtekind != RTE_RELATION) + continue; + + pubrelids = lappend_oid(pubrelids, rte->relid); + } + } + tupDesc = cstate->queryDesc->tupDesc; } + /* Check if publication privileges allow reading from the relations. */ + if (pubrelids) + check_publication_privileges(pubrelids); + /* Generate or convert list of attributes to process */ cstate->attnumlist = CopyGetAttnums(tupDesc, cstate->rel, attnamelist); @@ -1308,3 +1343,82 @@ CreateCopyDestReceiver(void) return (DestReceiver *) self; } + +static void +check_publication_privileges(List *relids) +{ + ListCell *lc; + StringInfoData bufOids; + StringInfoData bufQuery; + int i; + List *pubids = NIL; + + if (list_length(relids) == 0) + return; + + /* Construct a list of the OIDs */ + initStringInfo(&bufOids); + foreach(lc, relids) + { + Oid oid = lfirst_oid(lc); + + if (bufOids.len > 0) + appendStringInfoString(&bufOids, ", "); + appendStringInfo(&bufOids, "%u", oid); + } + + /* + * Construct the query to retrieve the publication names. (Similar query + * is in fetch_remote_table_info()). + */ + initStringInfo(&bufQuery); + appendStringInfo(&bufQuery, + "SELECT DISTINCT p.oid" + " FROM pg_publication p" + " LEFT OUTER JOIN pg_publication_rel pr" + " ON p.oid = pr.prpubid, " + " LATERAL pg_get_publication_tables(p.pubname) gpt" + " WHERE pr.prrelid IN (%s) AND gpt.relid IN (%s)", + bufOids.data, bufOids.data); + list_free(relids); + + /* Run the query to get the publication names. */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + if (SPI_exec(bufQuery.data, 0) != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed: %s", bufQuery.data); + + for (i = 0; i < SPI_processed; i++) + { + Datum oidD; + bool isnull; + + oidD = SPI_getbinval(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, + 1, + &isnull); + Assert(!isnull); + pubids = lappend_oid(pubids, DatumGetObjectId(oidD)); + } + + /* Finally, check the privileges. */ + foreach(lc, pubids) + { + Oid oid = lfirst_oid(lc); + + if (pg_publication_aclcheck(oid, GetUserId(), ACL_USAGE) != ACLCHECK_OK) + { + char *pubname = get_publication_name(oid, false); + + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for publication %s", + pubname))); + } + } + list_free(pubids); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); +} diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 8e645741e4e..bb7eb5726c4 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -822,6 +822,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) BoolGetDatum(pubactions.pubtruncate); values[Anum_pg_publication_pubviaroot - 1] = BoolGetDatum(publish_via_partition_root); + values[Anum_pg_publication_pubowner - 1] = ObjectIdGetDatum(GetUserId()); + nulls[Anum_pg_publication_pubacl - 1] = true; tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 989db0dbece..3b8dffc72ee 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -7720,6 +7720,14 @@ privilege_target: n->objs = $2; $$ = n; } + | PUBLICATION name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->targtype = ACL_TARGET_OBJECT; + n->objtype = OBJECT_PUBLICATION; + n->objs = $2; + $$ = n; + } | SCHEMA name_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 42c06af2391..d15969252da 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -19,12 +19,14 @@ #include "commands/defrem.h" #include "executor/executor.h" #include "fmgr.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" #include "replication/logical.h" #include "replication/logicalproto.h" #include "replication/origin.h" #include "replication/pgoutput.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/inval.h" #include "utils/lsyscache.h" @@ -2082,8 +2084,15 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) * We'll use the top-most relid across all publications. Also * track the ancestor level for this publication. */ - Oid pub_relid = relid; - int ancestor_level = 0; + Oid pub_relid = relid; + int ancestor_level = 0; + + /* Check if we have the usage privilege on the publication. */ + if (pg_publication_aclcheck(pub->oid, GetUserId(), ACL_USAGE) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for publication %s", + pub->name))); /* * If this is a FOR ALL TABLES publication, pick the partition diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 772c04155c3..fc3fd26a751 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -807,6 +807,10 @@ acldefault(ObjectType objtype, Oid ownerId) world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL; break; + case OBJECT_PUBLICATION: + world_default = ACL_USAGE; + owner_default = ACL_ALL_RIGHTS_PUBLICATION; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ @@ -904,6 +908,9 @@ acldefault_sql(PG_FUNCTION_ARGS) case 'T': objtype = OBJECT_TYPE; break; + case 'P': + objtype = OBJECT_PUBLICATION; + break; default: elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec); } diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 3e68dfc78f9..4c5b6d095ff 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -511,6 +511,8 @@ do { \ CONVERT_PRIV('r', "SELECT"); CONVERT_PRIV('w', "UPDATE"); } + else if (strcmp(type, "PUBLICATION") == 0) + CONVERT_PRIV('U', "USAGE"); else abort(); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 7cc9c72e492..7deae290507 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -3868,6 +3868,8 @@ getPublications(Archive *fout, int *numPublications) int i_pubdelete; int i_pubtruncate; int i_pubviaroot; + int i_pubacl; + int i_acldefault; int i, ntups; @@ -3882,23 +3884,29 @@ getPublications(Archive *fout, int *numPublications) resetPQExpBuffer(query); /* Get the publications. */ - if (fout->remoteVersion >= 130000) + if (fout->remoteVersion >= 150000) appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubacl, acldefault('P', p.pubowner) AS acldefault " + "FROM pg_publication p"); + else if (fout->remoteVersion >= 130000) + appendPQExpBuffer(query, + "SELECT p.tableoid, p.oid, p.pubname, " + "p.pubowner, " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot, '{}' AS pubacl, '{}' AS acldefault " "FROM pg_publication p"); else if (fout->remoteVersion >= 110000) appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot, '{}' AS pubacl, '{}' AS acldefault " "FROM pg_publication p"); else appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot, '{}' AS pubacl, '{}' AS acldefault " "FROM pg_publication p"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -3915,6 +3923,8 @@ getPublications(Archive *fout, int *numPublications) i_pubdelete = PQfnumber(res, "pubdelete"); i_pubtruncate = PQfnumber(res, "pubtruncate"); i_pubviaroot = PQfnumber(res, "pubviaroot"); + i_pubacl = PQfnumber(res, "pubacl"); + i_acldefault = PQfnumber(res, "acldefault"); pubinfo = pg_malloc(ntups * sizeof(PublicationInfo)); @@ -3939,6 +3949,11 @@ getPublications(Archive *fout, int *numPublications) (strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0); pubinfo[i].pubviaroot = (strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0); + pubinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_pubacl)); + pubinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + pubinfo[i].dacl.privtype = 0; + pubinfo[i].dacl.initprivs = NULL; + pubinfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* Decide whether we want to dump it */ selectDumpableObject(&(pubinfo[i].dobj), fout); @@ -4042,6 +4057,11 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) NULL, pubinfo->rolname, pubinfo->dobj.catId, 0, pubinfo->dobj.dumpId); + if (pubinfo->dobj.dump & DUMP_COMPONENT_ACL) + dumpACL(fout, pubinfo->dobj.dumpId, InvalidDumpId, "PUBLICATION", + pg_strdup(fmtId(pubinfo->dobj.name)), NULL, NULL, + pubinfo->rolname, &pubinfo->dacl); + destroyPQExpBuffer(delq); destroyPQExpBuffer(query); free(qpubname); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 1d21c2906f1..15168995baa 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -613,6 +613,7 @@ typedef struct _policyInfo typedef struct _PublicationInfo { DumpableObject dobj; + DumpableAcl dacl; const char *rolname; bool puballtables; bool pubinsert; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 55af9eb04e4..a6a2ec72ece 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3835,6 +3835,7 @@ psql_completion(const char *text, int start, int end) "LARGE OBJECT", "PARAMETER", "PROCEDURE", + "PUBLICATION", "ROUTINE", "SCHEMA", "SEQUENCE", @@ -3872,6 +3873,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_languages); else if (TailMatches("PROCEDURE")) COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures); + else if (TailMatches("PUBLICATION")) + COMPLETE_WITH_VERSIONED_QUERY(Query_for_list_of_publications); else if (TailMatches("ROUTINE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines); else if (TailMatches("SCHEMA")) diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 48205ba4293..00287fd3f90 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -54,6 +54,12 @@ CATALOG(pg_publication,6104,PublicationRelationId) /* true if partition changes are published using root schema */ bool pubviaroot; + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + /* NOTE: These fields are not present in a relcache entry's rd_rel field. */ + /* access permissions */ + aclitem pubacl[1] BKI_DEFAULT(_null_); +#endif } FormData_pg_publication; /* ---------------- diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 73f635b4553..f49dd92ae6d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -87,8 +87,8 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ #define ACL_REFERENCES (1<<5) #define ACL_TRIGGER (1<<6) #define ACL_EXECUTE (1<<7) /* for functions */ -#define ACL_USAGE (1<<8) /* for languages, namespaces, FDWs, and - * servers */ +#define ACL_USAGE (1<<8) /* for languages, namespaces, FDWs, servers + * and publications */ #define ACL_CREATE (1<<9) /* for namespaces and databases */ #define ACL_CREATE_TEMP (1<<10) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */ diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 48f7d72add5..d8408fe95e1 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -168,6 +168,7 @@ typedef struct ArrayType Acl; #define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) +#define ACL_ALL_RIGHTS_PUBLICATION (ACL_USAGE) /* operation codes for pg_*_aclmask */ typedef enum @@ -252,6 +253,8 @@ extern AclMode pg_parameter_acl_aclmask(Oid acl_oid, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_proc_aclmask(Oid proc_oid, Oid roleid, AclMode mask, AclMaskHow how); +extern AclMode pg_publication_aclmask(Oid pubid, Oid roleid, + AclMode mask, AclMaskHow how); extern AclMode pg_language_aclmask(Oid lang_oid, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_largeobject_aclmask_snapshot(Oid lobj_oid, Oid roleid, @@ -283,6 +286,7 @@ extern AclResult pg_parameter_aclcheck(const char *name, Oid roleid, extern AclResult pg_parameter_acl_aclcheck(Oid acl_oid, Oid roleid, AclMode mode); extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode); +extern AclResult pg_publication_aclcheck(Oid pub_oid, Oid roleid, AclMode mode); extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode); extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid, AclMode mode, Snapshot snapshot); -- 2.31.1 --=-=-=-- ^ permalink raw reply [nested|flat] 3+ messages in thread
* [PATCH] Draft implementation of USAGE privilege on PUBLICATION. @ 2022-05-18 08:58 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 3+ messages in thread From: Antonin Houska @ 2022-05-18 08:58 UTC (permalink / raw) This privilege seems to be useful for databases from which multiple subscribers replicate the data. It should not be assumed that any publication can be exposed to any subscription. --- doc/src/sgml/ddl.sgml | 7 + doc/src/sgml/logical-replication.sgml | 4 +- doc/src/sgml/ref/grant.sgml | 7 +- doc/src/sgml/ref/revoke.sgml | 7 + src/backend/catalog/aclchk.c | 206 ++++++++++++++++++++ src/backend/commands/copyto.c | 114 +++++++++++ src/backend/commands/publicationcmds.c | 2 + src/backend/parser/gram.y | 8 + src/backend/replication/pgoutput/pgoutput.c | 13 +- src/backend/utils/adt/acl.c | 7 + src/bin/pg_dump/dumputils.c | 2 + src/bin/pg_dump/pg_dump.c | 28 ++- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/tab-complete.c | 3 + src/include/catalog/pg_publication.h | 6 + src/include/nodes/parsenodes.h | 4 +- src/include/utils/acl.h | 4 + 17 files changed, 413 insertions(+), 10 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index f2ac1ba0034..dc499c50756 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1935,6 +1935,13 @@ REVOKE ALL ON accounts FROM PUBLIC; statements that have previously performed this lookup, so this is not a completely secure way to prevent object access. </para> + <para> + For publications, allows logical replication via particular + publication. The user specified in + the <link linkend="sql-createsubscription"><command>CREATE + SUBSCRIPTION</command></link> command must have this privilege on all + publications listed in that command. + </para> <para> For sequences, allows use of the <function>currval</function> and <function>nextval</function> functions. diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 145ea71d61b..63194b34541 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -1156,7 +1156,9 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER <para> In order to be able to copy the initial table data, the role used for the replication connection must have the <literal>SELECT</literal> privilege on - a published table (or be a superuser). + a published table (or be a superuser). In addition, the role must have + the <literal>USAGE</literal> privilege on all the publications referenced + by particular subscription. </para> <para> diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index f744b05b55d..95b9c46137f 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -82,6 +82,11 @@ GRANT { { SET | ALTER SYSTEM } [, ... ] | ALL [ PRIVILEGES ] } TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] +GRANT { USAGE | ALL [ PRIVILEGES ] } + ON PUBLICATION <replaceable>pub_name</replaceable> [, ...] + TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ] + [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] + GRANT { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] } ON SCHEMA <replaceable>schema_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ] @@ -460,7 +465,7 @@ GRANT admins TO joe; </para> <para> - Privileges on databases, tablespaces, schemas, languages, and + Privileges on databases, tablespaces, schemas, languages, publications and configuration parameters are <productname>PostgreSQL</productname> extensions. </para> diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 62f19710369..c21ea4c9c69 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -104,6 +104,13 @@ REVOKE [ GRANT OPTION FOR ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] [ CASCADE | RESTRICT ] +REVOKE [ GRANT OPTION FOR ] + { USAGE | ALL [ PRIVILEGES ] } + ON PUBLICATION <replaceable>pub_name</replaceable> [, ...] + FROM <replaceable class="parameter">role_specification</replaceable> [, ...] + [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] + [ CASCADE | RESTRICT ] + REVOKE [ GRANT OPTION FOR ] { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] } ON SCHEMA <replaceable>schema_name</replaceable> [, ...] diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 5f1726c0957..2896c10415f 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -110,6 +110,7 @@ static void ExecGrant_ForeignServer(InternalGrant *grantStmt); static void ExecGrant_Function(InternalGrant *grantStmt); static void ExecGrant_Language(InternalGrant *grantStmt); static void ExecGrant_Largeobject(InternalGrant *grantStmt); +static void ExecGrant_Publication(InternalGrant *istmt); static void ExecGrant_Namespace(InternalGrant *grantStmt); static void ExecGrant_Tablespace(InternalGrant *grantStmt); static void ExecGrant_Type(InternalGrant *grantStmt); @@ -477,6 +478,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT; errormsg = gettext_noop("invalid privilege type %s for large object"); break; + case OBJECT_PUBLICATION: + all_privileges = ACL_ALL_RIGHTS_PUBLICATION; + errormsg = gettext_noop("invalid privilege type %s for publication"); + break; case OBJECT_SCHEMA: all_privileges = ACL_ALL_RIGHTS_SCHEMA; errormsg = gettext_noop("invalid privilege type %s for schema"); @@ -605,6 +610,9 @@ ExecGrantStmt_oids(InternalGrant *istmt) case OBJECT_LARGEOBJECT: ExecGrant_Largeobject(istmt); break; + case OBJECT_PUBLICATION: + ExecGrant_Publication(istmt); + break; case OBJECT_SCHEMA: ExecGrant_Namespace(istmt); break; @@ -715,6 +723,16 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant) objects = lappend_oid(objects, lobjOid); } break; + case OBJECT_PUBLICATION: + foreach(cell, objnames) + { + char *nspname = strVal(lfirst(cell)); + Oid oid; + + oid = get_publication_oid(nspname, false); + objects = lappend_oid(objects, oid); + } + break; case OBJECT_SCHEMA: foreach(cell, objnames) { @@ -2892,6 +2910,126 @@ ExecGrant_Largeobject(InternalGrant *istmt) table_close(relation, RowExclusiveLock); } +static void +ExecGrant_Publication(InternalGrant *istmt) +{ + Relation relation; + ListCell *cell; + + if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) + istmt->privileges = ACL_ALL_RIGHTS_PUBLICATION; + + relation = table_open(PublicationRelationId, RowExclusiveLock); + + foreach(cell, istmt->objects) + { + Oid pubid = lfirst_oid(cell); + HeapTuple tuple; + HeapTuple newtuple; + Form_pg_publication pg_pub_tuple; + Oid ownerId; + Datum aclDatum; + bool isnull; + AclMode avail_goptions; + AclMode this_privileges; + Acl *old_acl; + Acl *new_acl; + Oid grantorId; + Datum values[Natts_pg_publication]; + bool nulls[Natts_pg_publication]; + bool replaces[Natts_pg_publication]; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + + tuple = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for publication %u", pubid); + + pg_pub_tuple = (Form_pg_publication) GETSTRUCT(tuple); + ownerId = pg_pub_tuple->pubowner; + aclDatum = SysCacheGetAttr(PUBLICATIONOID, tuple, + Anum_pg_publication_pubacl, + &isnull); + + if (isnull) + { + old_acl = acldefault(OBJECT_PUBLICATION, ownerId); + /* There are no old member roles according to the catalogs */ + noldmembers = 0; + oldmembers = NULL; + } + else + { + old_acl = DatumGetAclPCopy(aclDatum); + /* Get the roles mentioned in the existing ACL */ + noldmembers = aclmembers(old_acl, &oldmembers); + } + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), istmt->privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); + + /* + * Restrict the privileges to what we can actually grant, and emit the + * standards-mandated warning and error messages. + */ + this_privileges = + restrict_and_check_grant(istmt->is_grant, avail_goptions, + istmt->all_privs, istmt->privileges, + pubid, grantorId, OBJECT_FUNCTION, + NameStr(pg_pub_tuple->pubname), + 0, NULL); + + /* + * Generate new ACL. + */ + new_acl = merge_acl_with_grant(old_acl, istmt->is_grant, + istmt->grant_option, istmt->behavior, + istmt->grantees, this_privileges, + grantorId, ownerId); + + /* + * We need the members of both old and new ACLs so we can correct the + * shared dependency information. + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + /* finished building new ACL value, now insert it */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + replaces[Anum_pg_publication_pubacl - 1] = true; + values[Anum_pg_publication_pubacl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, + nulls, replaces); + + CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); + + /* Update initial privileges for extensions */ + recordExtensionInitPriv(pubid, PublicationRelationId, 0, new_acl); + + /* Update the shared dependency ACL info */ + updateAclDependencies(PublicationRelationId, pubid, 0, + ownerId, + noldmembers, oldmembers, + nnewmembers, newmembers); + + ReleaseSysCache(tuple); + + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + } + + table_close(relation, RowExclusiveLock); +} + static void ExecGrant_Namespace(InternalGrant *istmt) { @@ -3856,6 +3994,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid, return pg_database_aclmask(table_oid, roleid, mask, how); case OBJECT_FUNCTION: return pg_proc_aclmask(table_oid, roleid, mask, how); + case OBJECT_PUBLICATION: + return pg_publication_aclmask(table_oid, roleid, mask, how); case OBJECT_LANGUAGE: return pg_language_aclmask(table_oid, roleid, mask, how); case OBJECT_LARGEOBJECT: @@ -4379,6 +4519,60 @@ pg_proc_aclmask(Oid proc_oid, Oid roleid, return result; } +/* + * Exported routine for examining a user's privileges for a publication. + */ +AclMode +pg_publication_aclmask(Oid pubid, Oid roleid, AclMode mask, + AclMaskHow how) +{ + AclMode result; + HeapTuple tuple; + Datum aclDatum; + bool isNull; + Acl *acl; + Oid ownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return mask; + + /* + * Get the publication's ACL from pg_publication + */ + tuple = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("publication with OID %u does not exist", pubid))); + + ownerId = ((Form_pg_publication) GETSTRUCT(tuple))->pubowner; + + aclDatum = SysCacheGetAttr(PUBLICATIONOID, tuple, Anum_pg_publication_pubacl, + &isNull); + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(OBJECT_PUBLICATION, ownerId); + aclDatum = (Datum) 0; + } + else + { + /* detoast ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + result = aclmask(acl, roleid, ownerId, mask, how); + + /* if we have a detoasted copy, free it */ + if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) + pfree(acl); + + ReleaseSysCache(tuple); + + return result; +} + /* * Exported routine for examining a user's privileges for a language */ @@ -5076,6 +5270,18 @@ pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode) return ACLCHECK_NO_PRIV; } +/* + * Exported routine for checking a user's access privileges to a publication + */ +AclResult +pg_publication_aclcheck(Oid pub_oid, Oid roleid, AclMode mode) +{ + if (pg_publication_aclmask(pub_oid, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + /* * Exported routine for checking a user's access privileges to a language */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index fca29a9a105..0d39ef77610 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -27,6 +27,7 @@ #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" +#include "executor/spi.h" #include "executor/tuptable.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" @@ -34,9 +35,11 @@ #include "miscadmin.h" #include "optimizer/optimizer.h" #include "pgstat.h" +#include "replication/walsender.h" #include "rewrite/rewriteHandler.h" #include "storage/fd.h" #include "tcop/tcopprot.h" +#include "utils/acl.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/partcache.h" @@ -129,6 +132,7 @@ static void CopySendEndOfRow(CopyToState cstate); static void CopySendInt32(CopyToState cstate, int32 val); static void CopySendInt16(CopyToState cstate, int16 val); +static void check_publication_privileges(List *relids); /* * Send copy start/stop messages for frontend copies. These have changed @@ -360,6 +364,7 @@ BeginCopyTo(ParseState *pstate, PROGRESS_COPY_COMMAND_TO, 0 }; + List *pubrelids = NIL; if (rel != NULL && rel->rd_rel->relkind != RELKIND_RELATION) { @@ -424,6 +429,13 @@ BeginCopyTo(ParseState *pstate, cstate->rel = rel; tupDesc = RelationGetDescr(cstate->rel); + + /* + * In walsender, we need to check the privileges of the related + * publications. + */ + if (am_walsender) + pubrelids = lappend_oid(pubrelids, RelationGetRelid(rel)); } else { @@ -553,9 +565,32 @@ BeginCopyTo(ParseState *pstate, */ ExecutorStart(cstate->queryDesc, 0); + /* + * In walsender, we need to check the privileges of the related + * publications. + */ + if (am_walsender) + { + ListCell *lc; + + foreach(lc, plan->rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + + if (rte->rtekind != RTE_RELATION) + continue; + + pubrelids = lappend_oid(pubrelids, rte->relid); + } + } + tupDesc = cstate->queryDesc->tupDesc; } + /* Check if publication privileges allow reading from the relations. */ + if (pubrelids) + check_publication_privileges(pubrelids); + /* Generate or convert list of attributes to process */ cstate->attnumlist = CopyGetAttnums(tupDesc, cstate->rel, attnamelist); @@ -1308,3 +1343,82 @@ CreateCopyDestReceiver(void) return (DestReceiver *) self; } + +static void +check_publication_privileges(List *relids) +{ + ListCell *lc; + StringInfoData bufOids; + StringInfoData bufQuery; + int i; + List *pubids = NIL; + + if (list_length(relids) == 0) + return; + + /* Construct a list of the OIDs */ + initStringInfo(&bufOids); + foreach(lc, relids) + { + Oid oid = lfirst_oid(lc); + + if (bufOids.len > 0) + appendStringInfoString(&bufOids, ", "); + appendStringInfo(&bufOids, "%u", oid); + } + + /* + * Construct the query to retrieve the publication names. (Similar query + * is in fetch_remote_table_info()). + */ + initStringInfo(&bufQuery); + appendStringInfo(&bufQuery, + "SELECT DISTINCT p.oid" + " FROM pg_publication p" + " LEFT OUTER JOIN pg_publication_rel pr" + " ON p.oid = pr.prpubid, " + " LATERAL pg_get_publication_tables(p.pubname) gpt" + " WHERE pr.prrelid IN (%s) AND gpt.relid IN (%s)", + bufOids.data, bufOids.data); + list_free(relids); + + /* Run the query to get the publication names. */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + if (SPI_exec(bufQuery.data, 0) != SPI_OK_SELECT) + elog(ERROR, "SPI_exec failed: %s", bufQuery.data); + + for (i = 0; i < SPI_processed; i++) + { + Datum oidD; + bool isnull; + + oidD = SPI_getbinval(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, + 1, + &isnull); + Assert(!isnull); + pubids = lappend_oid(pubids, DatumGetObjectId(oidD)); + } + + /* Finally, check the privileges. */ + foreach(lc, pubids) + { + Oid oid = lfirst_oid(lc); + + if (pg_publication_aclcheck(oid, GetUserId(), ACL_USAGE) != ACLCHECK_OK) + { + char *pubname = get_publication_name(oid, false); + + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for publication %s", + pubname))); + } + } + list_free(pubids); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); +} diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 8e645741e4e..bb7eb5726c4 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -822,6 +822,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt) BoolGetDatum(pubactions.pubtruncate); values[Anum_pg_publication_pubviaroot - 1] = BoolGetDatum(publish_via_partition_root); + values[Anum_pg_publication_pubowner - 1] = ObjectIdGetDatum(GetUserId()); + nulls[Anum_pg_publication_pubacl - 1] = true; tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 989db0dbece..3b8dffc72ee 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -7720,6 +7720,14 @@ privilege_target: n->objs = $2; $$ = n; } + | PUBLICATION name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->targtype = ACL_TARGET_OBJECT; + n->objtype = OBJECT_PUBLICATION; + n->objs = $2; + $$ = n; + } | SCHEMA name_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 42c06af2391..d15969252da 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -19,12 +19,14 @@ #include "commands/defrem.h" #include "executor/executor.h" #include "fmgr.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" #include "replication/logical.h" #include "replication/logicalproto.h" #include "replication/origin.h" #include "replication/pgoutput.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/inval.h" #include "utils/lsyscache.h" @@ -2082,8 +2084,15 @@ get_rel_sync_entry(PGOutputData *data, Relation relation) * We'll use the top-most relid across all publications. Also * track the ancestor level for this publication. */ - Oid pub_relid = relid; - int ancestor_level = 0; + Oid pub_relid = relid; + int ancestor_level = 0; + + /* Check if we have the usage privilege on the publication. */ + if (pg_publication_aclcheck(pub->oid, GetUserId(), ACL_USAGE) != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for publication %s", + pub->name))); /* * If this is a FOR ALL TABLES publication, pick the partition diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 772c04155c3..fc3fd26a751 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -807,6 +807,10 @@ acldefault(ObjectType objtype, Oid ownerId) world_default = ACL_NO_RIGHTS; owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL; break; + case OBJECT_PUBLICATION: + world_default = ACL_USAGE; + owner_default = ACL_ALL_RIGHTS_PUBLICATION; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ @@ -904,6 +908,9 @@ acldefault_sql(PG_FUNCTION_ARGS) case 'T': objtype = OBJECT_TYPE; break; + case 'P': + objtype = OBJECT_PUBLICATION; + break; default: elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec); } diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 3e68dfc78f9..4c5b6d095ff 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -511,6 +511,8 @@ do { \ CONVERT_PRIV('r', "SELECT"); CONVERT_PRIV('w', "UPDATE"); } + else if (strcmp(type, "PUBLICATION") == 0) + CONVERT_PRIV('U', "USAGE"); else abort(); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 7cc9c72e492..7deae290507 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -3868,6 +3868,8 @@ getPublications(Archive *fout, int *numPublications) int i_pubdelete; int i_pubtruncate; int i_pubviaroot; + int i_pubacl; + int i_acldefault; int i, ntups; @@ -3882,23 +3884,29 @@ getPublications(Archive *fout, int *numPublications) resetPQExpBuffer(query); /* Get the publications. */ - if (fout->remoteVersion >= 130000) + if (fout->remoteVersion >= 150000) appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot, p.pubacl, acldefault('P', p.pubowner) AS acldefault " + "FROM pg_publication p"); + else if (fout->remoteVersion >= 130000) + appendPQExpBuffer(query, + "SELECT p.tableoid, p.oid, p.pubname, " + "p.pubowner, " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot, '{}' AS pubacl, '{}' AS acldefault " "FROM pg_publication p"); else if (fout->remoteVersion >= 110000) appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot, '{}' AS pubacl, '{}' AS acldefault " "FROM pg_publication p"); else appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.pubname, " "p.pubowner, " - "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot " + "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot, '{}' AS pubacl, '{}' AS acldefault " "FROM pg_publication p"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -3915,6 +3923,8 @@ getPublications(Archive *fout, int *numPublications) i_pubdelete = PQfnumber(res, "pubdelete"); i_pubtruncate = PQfnumber(res, "pubtruncate"); i_pubviaroot = PQfnumber(res, "pubviaroot"); + i_pubacl = PQfnumber(res, "pubacl"); + i_acldefault = PQfnumber(res, "acldefault"); pubinfo = pg_malloc(ntups * sizeof(PublicationInfo)); @@ -3939,6 +3949,11 @@ getPublications(Archive *fout, int *numPublications) (strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0); pubinfo[i].pubviaroot = (strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0); + pubinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_pubacl)); + pubinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); + pubinfo[i].dacl.privtype = 0; + pubinfo[i].dacl.initprivs = NULL; + pubinfo[i].dobj.components |= DUMP_COMPONENT_ACL; /* Decide whether we want to dump it */ selectDumpableObject(&(pubinfo[i].dobj), fout); @@ -4042,6 +4057,11 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo) NULL, pubinfo->rolname, pubinfo->dobj.catId, 0, pubinfo->dobj.dumpId); + if (pubinfo->dobj.dump & DUMP_COMPONENT_ACL) + dumpACL(fout, pubinfo->dobj.dumpId, InvalidDumpId, "PUBLICATION", + pg_strdup(fmtId(pubinfo->dobj.name)), NULL, NULL, + pubinfo->rolname, &pubinfo->dacl); + destroyPQExpBuffer(delq); destroyPQExpBuffer(query); free(qpubname); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 1d21c2906f1..15168995baa 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -613,6 +613,7 @@ typedef struct _policyInfo typedef struct _PublicationInfo { DumpableObject dobj; + DumpableAcl dacl; const char *rolname; bool puballtables; bool pubinsert; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 55af9eb04e4..a6a2ec72ece 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3835,6 +3835,7 @@ psql_completion(const char *text, int start, int end) "LARGE OBJECT", "PARAMETER", "PROCEDURE", + "PUBLICATION", "ROUTINE", "SCHEMA", "SEQUENCE", @@ -3872,6 +3873,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_languages); else if (TailMatches("PROCEDURE")) COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures); + else if (TailMatches("PUBLICATION")) + COMPLETE_WITH_VERSIONED_QUERY(Query_for_list_of_publications); else if (TailMatches("ROUTINE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines); else if (TailMatches("SCHEMA")) diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h index 48205ba4293..00287fd3f90 100644 --- a/src/include/catalog/pg_publication.h +++ b/src/include/catalog/pg_publication.h @@ -54,6 +54,12 @@ CATALOG(pg_publication,6104,PublicationRelationId) /* true if partition changes are published using root schema */ bool pubviaroot; + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + /* NOTE: These fields are not present in a relcache entry's rd_rel field. */ + /* access permissions */ + aclitem pubacl[1] BKI_DEFAULT(_null_); +#endif } FormData_pg_publication; /* ---------------- diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 73f635b4553..f49dd92ae6d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -87,8 +87,8 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ #define ACL_REFERENCES (1<<5) #define ACL_TRIGGER (1<<6) #define ACL_EXECUTE (1<<7) /* for functions */ -#define ACL_USAGE (1<<8) /* for languages, namespaces, FDWs, and - * servers */ +#define ACL_USAGE (1<<8) /* for languages, namespaces, FDWs, servers + * and publications */ #define ACL_CREATE (1<<9) /* for namespaces and databases */ #define ACL_CREATE_TEMP (1<<10) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */ diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 48f7d72add5..d8408fe95e1 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -168,6 +168,7 @@ typedef struct ArrayType Acl; #define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) +#define ACL_ALL_RIGHTS_PUBLICATION (ACL_USAGE) /* operation codes for pg_*_aclmask */ typedef enum @@ -252,6 +253,8 @@ extern AclMode pg_parameter_acl_aclmask(Oid acl_oid, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_proc_aclmask(Oid proc_oid, Oid roleid, AclMode mask, AclMaskHow how); +extern AclMode pg_publication_aclmask(Oid pubid, Oid roleid, + AclMode mask, AclMaskHow how); extern AclMode pg_language_aclmask(Oid lang_oid, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_largeobject_aclmask_snapshot(Oid lobj_oid, Oid roleid, @@ -283,6 +286,7 @@ extern AclResult pg_parameter_aclcheck(const char *name, Oid roleid, extern AclResult pg_parameter_acl_aclcheck(Oid acl_oid, Oid roleid, AclMode mode); extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode); +extern AclResult pg_publication_aclcheck(Oid pub_oid, Oid roleid, AclMode mode); extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode); extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid, AclMode mode, Snapshot snapshot); -- 2.31.1 --=-=-=-- ^ permalink raw reply [nested|flat] 3+ messages in thread
* [PATCH v1 5/5] Use anonymous files to back shared memory segments @ 2024-10-15 14:18 Dmitrii Dolgov <9erthalion6@gmail.com> 0 siblings, 0 replies; 3+ messages in thread From: Dmitrii Dolgov @ 2024-10-15 14:18 UTC (permalink / raw) Allow to use anonymous files for shared memory, instead of plain anonymous memory. Such an anonymous file is created via memfd_create, it lives in memory, behaves like a regular file and semantically equivalent to an anonymous memory allocated via mmap with MAP_ANONYMOUS. Advantages of using anon files are following: * We've got a file descriptor, which could be used for regular file operations (modification, truncation, you name it). * The file could be given a name, which improves readability when it comes to process maps. Here is how it looks like 7f5a2bd04000-7f5a32e52000 rw-s 00000000 00:01 1845 /memfd:strategy (deleted) 7f5a39252000-7f5a4030e000 rw-s 00000000 00:01 1842 /memfd:checkpoint (deleted) 7f5a4670e000-7f5a4d7ba000 rw-s 00000000 00:01 1839 /memfd:iocv (deleted) 7f5a53bba000-7f5a5ad26000 rw-s 00000000 00:01 1836 /memfd:descriptors (deleted) 7f5a9ad26000-7f5aa9d94000 rw-s 00000000 00:01 1833 /memfd:buffers (deleted) 7f5d29d94000-7f5d30e00000 rw-s 00000000 00:01 1830 /memfd:main (deleted) * By default, Linux will not add file-backed shared mappings into a core dump, making it more convenient to work with them in PostgreSQL: no more huge dumps to process. The downside is that memfd_create is Linux specific. --- src/backend/port/sysv_shmem.c | 47 +++++++++++++++++++++++++++++------ src/include/portability/mem.h | 2 +- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index 4bdadbb0e2..a01c3e4789 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -103,6 +103,7 @@ typedef struct AnonymousMapping void *shmem; /* Pointer to the start of the mapped memory */ void *seg_addr; /* SysV shared memory for the header */ unsigned long seg_id; /* IPC key */ + int segment_fd; /* fd for the backing anon file */ } AnonymousMapping; static AnonymousMapping Mappings[ANON_MAPPINGS]; @@ -116,7 +117,7 @@ static int next_free_slot = 0; * 00400000-00490000 /path/bin/postgres * ... * 012d9000-0133e000 [heap] - * 7f443a800000-7f470a800000 /dev/zero (deleted) + * 7f443a800000-7f470a800000 /memfd:main (deleted) * 7f470a800000-7f471831d000 /usr/lib/locale/locale-archive * 7f4718400000-7f4718401000 /usr/lib64/libicudata.so.74.2 * ... @@ -143,9 +144,9 @@ static int next_free_slot = 0; * The result would look like this: * * 012d9000-0133e000 [heap] - * 7f4426f54000-7f442e010000 /dev/zero (deleted) + * 7f4426f54000-7f442e010000 /memfd:main (deleted) * [...free space...] - * 7f443a800000-7f444196c000 /dev/zero (deleted) + * 7f443a800000-7f444196c000 /memfd:buffers (deleted) * [...free space...] * 7f470a800000-7f471831d000 /usr/lib/locale/locale-archive * 7f4718400000-7f4718401000 /usr/lib64/libicudata.so.74.2 @@ -708,6 +709,18 @@ CreateAnonymousSegment(AnonymousMapping *mapping) void *ptr = MAP_FAILED; int mmap_errno = 0; + /* + * Prepare an anonymous file backing the segment. Its size will be + * specified later via ftruncate. + * + * The file behaves like a regular file, but lives in memory. Once all + * references to the file are dropped, it is automatically released. + * Anonymous memory is used for all backing pages of the file, thus it has + * the same semantics as anonymous memory allocations using mmap with the + * MAP_ANONYMOUS flag. + */ + mapping->segment_fd = memfd_create(MappingName(mapping->shmem_slot), 0); + #ifndef MAP_HUGETLB /* PGSharedMemoryCreate should have dealt with this case */ Assert(huge_pages != HUGE_PAGES_ON); @@ -725,8 +738,13 @@ CreateAnonymousSegment(AnonymousMapping *mapping) if (allocsize % hugepagesize != 0) allocsize += hugepagesize - (allocsize % hugepagesize); + /* + * Do not use an anonymous file here yet. When adding it, do not forget + * to use ftruncate and flags MFD_HUGETLB & MFD_HUGE_2MB/MFD_HUGE_1GB + * in memfd_create. + */ ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE, - PG_MMAP_FLAGS | mmap_flags, -1, 0); + PG_MMAP_FLAGS | MAP_ANONYMOUS | mmap_flags, -1, 0); mmap_errno = errno; if (huge_pages == HUGE_PAGES_TRY && ptr == MAP_FAILED) { @@ -762,7 +780,8 @@ CreateAnonymousSegment(AnonymousMapping *mapping) * - First create the temporary probe mapping of a fixed size and let * kernel to place it at address of its choice. By the virtue of the * probe mapping size we expect it to be located at the lowest - * possible address, expecting some non mapped space above. + * possible address, expecting some non mapped space above. The probe + * is does not need to be backed by an anonymous file. * * - Unmap the probe mapping, remember the address. * @@ -777,7 +796,7 @@ CreateAnonymousSegment(AnonymousMapping *mapping) * without a restart. */ probe = mmap(NULL, PROBE_MAPPING_SIZE, PROT_READ | PROT_WRITE, - PG_MMAP_FLAGS, -1, 0); + PG_MMAP_FLAGS | MAP_ANONYMOUS, -1, 0); if (probe == MAP_FAILED) { @@ -793,8 +812,14 @@ CreateAnonymousSegment(AnonymousMapping *mapping) munmap(probe, PROBE_MAPPING_SIZE); + /* + * Specify the segment file size using allocsize, which contains + * potentially modified size. + */ + ftruncate(mapping->segment_fd, allocsize); + ptr = mmap(probe - offset, allocsize, PROT_READ | PROT_WRITE, - PG_MMAP_FLAGS | MAP_FIXED_NOREPLACE, -1, 0); + PG_MMAP_FLAGS | MAP_FIXED_NOREPLACE, mapping->segment_fd, 0); mmap_errno = errno; if (ptr == MAP_FAILED) { @@ -813,8 +838,11 @@ CreateAnonymousSegment(AnonymousMapping *mapping) */ allocsize = mapping->shmem_size; + /* Specify the segment file size using allocsize. */ + ftruncate(mapping->segment_fd, allocsize); + ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE, - PG_MMAP_FLAGS, -1, 0); + PG_MMAP_FLAGS, mapping->segment_fd, 0); mmap_errno = errno; } @@ -903,6 +931,9 @@ AnonymousShmemResize(int newval, void *extra) if (m->shmem_size == new_size) continue; + /* Resize the backing anon file. */ + ftruncate(m->segment_fd, new_size); + if (mremap(m->shmem, m->shmem_size, new_size, 0) < 0) elog(LOG, "mremap(%p, %zu) failed: %m", m->shmem, m->shmem_size); diff --git a/src/include/portability/mem.h b/src/include/portability/mem.h index 2cd05313b8..50db0da28d 100644 --- a/src/include/portability/mem.h +++ b/src/include/portability/mem.h @@ -38,7 +38,7 @@ #define MAP_NOSYNC 0 #endif -#define PG_MMAP_FLAGS (MAP_SHARED|MAP_ANONYMOUS|MAP_HASSEMAPHORE) +#define PG_MMAP_FLAGS (MAP_SHARED|MAP_HASSEMAPHORE) /* Some really old systems don't define MAP_FAILED. */ #ifndef MAP_FAILED -- 2.45.1 --ers4zrhjqnhlajas-- ^ permalink raw reply [nested|flat] 3+ messages in thread
end of thread, other threads:[~2024-10-15 14:18 UTC | newest] Thread overview: 3+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2022-05-18 08:58 [PATCH] Draft implementation of USAGE privilege on PUBLICATION. Antonin Houska <ah@cybertec.at> 2022-05-18 08:58 [PATCH] Draft implementation of USAGE privilege on PUBLICATION. Antonin Houska <ah@cybertec.at> 2024-10-15 14:18 [PATCH v1 5/5] Use anonymous files to back shared memory segments Dmitrii Dolgov <9erthalion6@gmail.com>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox