Peter, You patch is missing the files src/include/catalog/pg_diralias.h, > src/include/commands/diralias.h, and src/backend/commands/diralias.c. > > (Hint: git add -N) >
Yikes, sorry about that, not sure how that happened. Attached is an updated patch. -Adam -- Adam Brightwell - adam.brightw...@crunchydatasolutions.com Database Engineer - www.crunchydatasolutions.com
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index b257b02..8cdc5cb 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h pg_rowsecurity.h \ pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ - toasting.h indexing.h \ + pg_diralias.h toasting.h indexing.h \ ) # location of Catalog.pm diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index d30612c..3717bf5 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -30,6 +30,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" +#include "catalog/pg_diralias.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_extension.h" @@ -48,6 +49,7 @@ #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_dict.h" #include "commands/dbcommands.h" +#include "commands/diralias.h" #include "commands/proclang.h" #include "commands/tablespace.h" #include "foreign/foreign.h" @@ -3183,6 +3185,190 @@ ExecGrant_Type(InternalGrant *istmt) heap_close(relation, RowExclusiveLock); } +/* + * ExecuteGrantDirAliasStmt + * handles the execution of the GRANT/REVOKE ON DIRALIAS command. + * + * stmt - the GrantDirAliasStmt that describes the directory aliases and + * permissions to be granted/revoked. + */ +void +ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt) +{ + Relation pg_diralias_rel; + Oid grantor; + List *grantee_ids = NIL; + AclMode permissions; + ListCell *item; + + /* Must be superuser to grant directory alias permissions */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to grant directory alias permissions"))); + + /* + * Grantor is optional. If it is not provided then set it to the current + * user. + */ + if (stmt->grantor) + grantor = get_role_oid(stmt->grantor, false); + else + grantor = GetUserId(); + + /* Convert grantee names to oids */ + foreach(item, stmt->grantees) + { + PrivGrantee *grantee = (PrivGrantee *) lfirst(item); + + if (grantee->rolname == NULL) + grantee_ids = lappend_oid(grantee_ids, ACL_ID_PUBLIC); + else + { + Oid roleid = get_role_oid(grantee->rolname, false); + grantee_ids = lappend_oid(grantee_ids, roleid); + } + } + + permissions = ACL_NO_RIGHTS; + + /* If ALL was provided then set permissions to ACL_ALL_RIGHTS_DIRALIAS */ + if (stmt->permissions == NIL) + permissions = ACL_ALL_RIGHTS_DIRALIAS; + else + { + /* Condense all permissions */ + foreach(item, stmt->permissions) + { + AccessPriv *priv = (AccessPriv *) lfirst(item); + permissions |= string_to_privilege(priv->priv_name); + } + } + + + /* + * Though it shouldn't be possible to provide permissions other than READ + * and WRITE, check to make sure no others have been set. If they have, + * then warn the user and correct the permissions. + */ + if (permissions & !((AclMode) ACL_ALL_RIGHTS_DIRALIAS)) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_GRANT_OPERATION), + errmsg("directory aliases only support READ and WRITE permissions"))); + + permissions &= ACL_ALL_RIGHTS_DIRALIAS; + } + + pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock); + + /* Grant/Revoke permissions on directory aliases */ + foreach(item, stmt->directories) + { + Datum values[Natts_pg_diralias]; + bool replaces[Natts_pg_diralias]; + bool nulls[Natts_pg_diralias]; + ScanKeyData skey[1]; + HeapScanDesc scandesc; + HeapTuple tuple; + HeapTuple new_tuple; + Datum datum; + Oid owner_id; + Acl *dir_acl; + Acl *new_acl; + bool is_null; + int num_old_members; + int num_new_members; + Oid *old_members; + Oid *new_members; + Oid diralias_id; + char *name; + + name = strVal(lfirst(item)); + + ScanKeyInit(&skey[0], + Anum_pg_diralias_dirname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(name)); + + scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey); + + tuple = heap_getnext(scandesc, ForwardScanDirection); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for directory alias \"%s\"", name); + + /* + * Get directory alias owner id. Since all superusers are considered + * to be owners of a directory alias, it is safe to assume that the + * current user is an owner, given the superuser check above. + */ + owner_id = GetUserId(); + + /* Get directory alias ACL */ + datum = heap_getattr(tuple, Anum_pg_diralias_diracl, + RelationGetDescr(pg_diralias_rel), &is_null); + + /* Get the directory alias oid */ + diralias_id = HeapTupleGetOid(tuple); + + /* + * If there are currently no permissions granted on the directory alias, + * then add default permissions, which should include the permssions + * granted to the owner of the table. Directory aliases are owned by + * all superusers. + */ + if (is_null) + { + dir_acl = acldefault(ACL_OBJECT_DIRALIAS, owner_id); + num_old_members = 0; + old_members = NULL; + } + else + { + dir_acl = DatumGetAclPCopy(datum); + + /* Get the roles in the current ACL */ + num_old_members = aclmembers(dir_acl, &old_members); + } + + /* Merge new ACL with current ACL */ + new_acl = merge_acl_with_grant(dir_acl, stmt->is_grant, false, + DROP_CASCADE, grantee_ids, permissions, + grantor, owner_id); + + num_new_members = aclmembers(new_acl, &new_members); + + /* Insert new ACL value */ + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + memset(replaces, 0, sizeof(replaces)); + + values[Anum_pg_diralias_diracl - 1] = PointerGetDatum(new_acl); + replaces[Anum_pg_diralias_diracl - 1] = true; + + new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel), + values, nulls, replaces); + + simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple); + + /* Update Indexes */ + CatalogUpdateIndexes(pg_diralias_rel, new_tuple); + + /* Update shared dependency ACL information */ + updateAclDependencies(DirAliasRelationId, diralias_id, 0, + owner_id, + num_old_members, old_members, + num_new_members, new_members); + + /* Clean Up */ + pfree(new_acl); + heap_endscan(scandesc); + } + + heap_close(pg_diralias_rel, RowExclusiveLock); +} + static AclMode string_to_privilege(const char *privname) @@ -3307,6 +3493,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] = gettext_noop("permission denied for event trigger %s"), /* ACL_KIND_EXTENSION */ gettext_noop("permission denied for extension %s"), + /* ACL_KIND_DIRALIAS */ + gettext_noop("permission denied for directory alias %s"), }; static const char *const not_owner_msg[MAX_ACL_KIND] = @@ -3353,6 +3541,8 @@ static const char *const not_owner_msg[MAX_ACL_KIND] = gettext_noop("must be owner of event trigger %s"), /* ACL_KIND_EXTENSION */ gettext_noop("must be owner of extension %s"), + /* ACL_KIND_DIRALIAS */ + gettext_noop("must be owner of directory alias %s"), }; @@ -4194,6 +4384,62 @@ pg_foreign_server_aclmask(Oid srv_oid, Oid roleid, } /* + * Exported routine for examining a user's permissions for a directory alias. + */ +AclMode +pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask, AclMaskHow how) +{ + AclMode result; + HeapTuple tuple; + Datum aclDatum; + bool isNull; + Acl *acl; + + /* Bypass permission checks for superusers */ + if (superuser_arg(roleid)) + return mask; + + /* Must get the directory alias's tuple from pg_diralias */ + tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(dir_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("directory alias with OID %u does not exist", dir_oid))); + + aclDatum = SysCacheGetAttr(DIRALIASOID, tuple, + Anum_pg_diralias_diracl, &isNull); + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(ACL_OBJECT_DIRALIAS, roleid); + aclDatum = (Datum) 0; + } + else + { + /* detoast rel's ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + /* + * We use InvalidOid as the ownerid for determining the aclmask. This is + * because directory aliases belong to all superusers. aclmask() uses the + * ownerid to determine grant options by implying that owners always have + * all grant options. If roleid, is not a superuser and therefore an owner + * (which it couldn't be at this point), then this check in aclmask() must + * be false. Therefore, by using InvalidOid we are guaranteed this behavior. + */ + result = aclmask(acl, roleid, InvalidOid, 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 type. */ AclMode @@ -4513,6 +4759,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode) } /* + * Exported routine for checking a user's access permissions to a directory alias + */ +AclResult +pg_diralias_aclcheck(Oid dir_oid, Oid roleid, AclMode mode) +{ + if (pg_diralias_aclmask(dir_oid, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* * Ownership check for a relation (specified by OID). */ bool diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 256486c..b056559 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -33,6 +33,7 @@ #include "catalog/pg_database.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" +#include "catalog/pg_diralias.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_extension.h" #include "catalog/pg_foreign_data_wrapper.h" @@ -56,6 +57,7 @@ #include "catalog/pg_user_mapping.h" #include "commands/comment.h" #include "commands/defrem.h" +#include "commands/diralias.h" #include "commands/event_trigger.h" #include "commands/extension.h" #include "commands/policy.h" @@ -1255,6 +1257,10 @@ doDeletion(const ObjectAddress *object, int flags) RemovePolicyById(object->objectId); break; + case OCLASS_DIRALIAS: + RemoveDirAliasById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); @@ -2325,6 +2331,9 @@ getObjectClass(const ObjectAddress *object) case RowSecurityRelationId: return OCLASS_ROWSECURITY; + + case DirAliasRelationId: + return OCLASS_DIRALIAS; } /* shouldn't get here */ diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index b69b75b..872d233 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -26,6 +26,7 @@ #include "catalog/pg_authid.h" #include "catalog/pg_cast.h" #include "catalog/pg_default_acl.h" +#include "catalog/pg_diralias.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -54,6 +55,7 @@ #include "catalog/pg_user_mapping.h" #include "commands/dbcommands.h" #include "commands/defrem.h" +#include "commands/diralias.h" #include "commands/event_trigger.h" #include "commands/extension.h" #include "commands/policy.h" @@ -358,6 +360,18 @@ static const ObjectPropertyType ObjectProperty[] = false }, { + DirAliasRelationId, + DirAliasOidIndexId, + DIRALIASOID, + -1, + Anum_pg_diralias_dirname, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { EventTriggerRelationId, EventTriggerOidIndexId, EVENTTRIGGEROID, @@ -536,6 +550,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, &relation, missing_ok); break; case OBJECT_DATABASE: + case OBJECT_DIRALIAS: case OBJECT_EXTENSION: case OBJECT_TABLESPACE: case OBJECT_ROLE: @@ -746,6 +761,9 @@ get_object_address_unqualified(ObjectType objtype, case OBJECT_DATABASE: msg = gettext_noop("database name cannot be qualified"); break; + case OBJECT_DIRALIAS: + msg = gettext_noop("directory alias cannot be qualified"); + break; case OBJECT_EXTENSION: msg = gettext_noop("extension name cannot be qualified"); break; @@ -790,6 +808,11 @@ get_object_address_unqualified(ObjectType objtype, address.objectId = get_database_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_DIRALIAS: + address.classId = DirAliasRelationId; + address.objectId = get_diralias_oid(name, missing_ok); + address.objectSubId = 0; + break; case OBJECT_EXTENSION: address.classId = ExtensionRelationId; address.objectId = get_extension_oid(name, missing_ok); @@ -1318,6 +1341,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, break; case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: + case OBJECT_DIRALIAS: /* We treat these object types as being owned by superusers */ if (!superuser_arg(roleid)) ereport(ERROR, @@ -2224,6 +2248,18 @@ getObjectDescription(const ObjectAddress *object) break; } + case OCLASS_DIRALIAS: + { + char *diralias_name; + + diralias_name = get_diralias_name(object->objectId); + if (!diralias_name) + elog(ERROR, "cache lookup failed for directory alias %u", + object->objectId); + appendStringInfo(&buffer, _("directory alias %s"), diralias_name); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index b1ac704..36a897c 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ - dbcommands.o define.o discard.o dropcmds.o \ + dbcommands.o define.o diralias.o discard.o dropcmds.o \ event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \ policy.o portalcmds.o prepare.o proclang.o \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index c9a9baf..47f8d49 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -41,6 +41,7 @@ #include "commands/conversioncmds.h" #include "commands/dbcommands.h" #include "commands/defrem.h" +#include "commands/diralias.h" #include "commands/event_trigger.h" #include "commands/extension.h" #include "commands/policy.h" @@ -349,6 +350,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_AGGREGATE: case OBJECT_COLLATION: case OBJECT_CONVERSION: + case OBJECT_DIRALIAS: case OBJECT_EVENT_TRIGGER: case OBJECT_FDW: case OBJECT_FOREIGN_SERVER: diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 6b83576..3a9562b 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -28,6 +28,7 @@ #include "catalog/pg_type.h" #include "commands/copy.h" #include "commands/defrem.h" +#include "commands/diralias.h" #include "commands/trigger.h" #include "executor/executor.h" #include "libpq/libpq.h" @@ -788,9 +789,13 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed) Oid relid; Node *query = NULL; - /* Disallow COPY to/from file or program except to superusers. */ if (!pipe && !superuser()) { + /* + * Disallow COPY to/from program except to superusers. If COPY is to/from + * a file then diallow unless the current user is either superuser or has + * been granted the appropriate permissions on the target parent directory. + */ if (stmt->is_program) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -798,11 +803,43 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed) errhint("Anyone can COPY to stdout or from stdin. " "psql's \\copy command also works for anyone."))); else - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to COPY to or from a file"), - errhint("Anyone can COPY to stdout or from stdin. " - "psql's \\copy command also works for anyone."))); + { + char *path; + Oid diralias_id; + AclResult aclresult; + + /* Get the parent directory */ + path = pstrdup(stmt->filename); + canonicalize_path(path); + get_parent_directory(path); + + /* Search for directory in pg_diralias */ + diralias_id = get_diralias_oid_by_path(path); + + /* + * If an entry does not exist for the path in pg_diralias then raise + * an error. + */ + if (!OidIsValid(diralias_id)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("a directory alias entry for \"%s\" does not exist.", + path))); + + /* Check directory alias entry permissions */ + if (stmt->is_from) + aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT); + else + aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_UPDATE); + + /* If the current user has insufficient privileges then raise an error. */ + if (aclresult != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must have permissions to COPY to or from \"%s\"", path), + errhint("Anyone can COPY to stdout or from stdin. " + "psql's \\copy command also works for anyone."))); + } } if (stmt->relation) diff --git a/src/backend/commands/diralias.c b/src/backend/commands/diralias.c new file mode 100644 index 0000000..f269624 --- /dev/null +++ b/src/backend/commands/diralias.c @@ -0,0 +1,375 @@ +/*------------------------------------------------------------------------- + * + * directory.c + * Commands for manipulating directories. + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/commands/directory.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_diralias.h" +#include "commands/diralias.h" +#include "commands/user.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/fmgroids.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * RemoveDirAliasById + * remove a directory alias by its OID. If a directory does not exist with + * the provided oid, then an error is raised. + * + * diralias_id - the oid of the directory alias. + */ +void +RemoveDirAliasById(Oid diralias_id) +{ + Relation pg_diralias_rel; + HeapTuple tuple; + + pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock); + + /* + * Find the directory alias to delete. + */ + tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(diralias_id)); + + /* If the directory alias exists, then remove it, otherwise raise an error. */ + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for directory alias %u", diralias_id); + + simple_heap_delete(pg_diralias_rel, &tuple->t_self); + + ReleaseSysCache(tuple); + heap_close(pg_diralias_rel, RowExclusiveLock); +} + +/* + * CreateDirAlias + * handles the execution of the CREATE DIRALIAS command. + * + * stmt - the CreateDirAliasStmt that describes the directory alias entry to + * create. + */ +void +CreateDirAlias(CreateDirAliasStmt *stmt) +{ + Relation pg_diralias_rel; + Datum values[Natts_pg_diralias]; + bool nulls[Natts_pg_diralias]; + ScanKeyData skey[1]; + HeapScanDesc scandesc; + HeapTuple tuple; + Oid diralias_id; + char *path; + + /* Must be superuser to create a directory alias entry. */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to create directory alias"))); + + /* Unix-ify the path, and strip any trailing slashes */ + path = pstrdup(stmt->path); + canonicalize_path(path); + + /* Disallow quotes */ + if (strchr(path, '\'')) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("directory path cannot contain single quotes"))); + + /* + * Allowing relative paths seems risky and really a bad idea. Therefore, + * if a relative path is provided then an error is raised. + * + * This also helps us ensure that directory path is not empty or whitespace. + */ + if (!is_absolute_path(path)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("directory path must be an absolute path"))); + + /* Open pg_diralias catalog */ + pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock); + + /* + * Make sure a duplicate does not already exist. Need to check both the name + * and the path. If either exists, then raise an error. + */ + + /* Check alias name does not already exist */ + ScanKeyInit(&skey[0], + Anum_pg_diralias_dirname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->name)); + + /* + * We use a heapscan here even though there is an index on alias and path. + * We do this on the theory that pg_diralias will usually have a + * relatively small number of entries and therefore it is safe to assume + * an index scan would be wasted effort. + */ + scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey); + + if (HeapTupleIsValid(heap_getnext(scandesc, ForwardScanDirection))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("directory alias \"%s\" already exists", stmt->name))); + + heap_endscan(scandesc); + + ScanKeyInit(&skey[0], + Anum_pg_diralias_dirpath, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(path)); + + scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey); + + /* Check that path does not already exist. */ + if (HeapTupleIsValid(heap_getnext(scandesc, ForwardScanDirection))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("directory alias with path \"%s\" already exists", path))); + + heap_endscan(scandesc); + + /* + * All is well and safe to insert. + */ + + /* zero-clear */ + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_diralias_dirname - 1] = CStringGetDatum(stmt->name); + values[Anum_pg_diralias_dirpath - 1] = CStringGetTextDatum(path); + + /* No ACL items are set on the directory by default */ + nulls[Anum_pg_diralias_diracl - 1] = true; + + tuple = heap_form_tuple(RelationGetDescr(pg_diralias_rel), values, nulls); + + diralias_id = simple_heap_insert(pg_diralias_rel, tuple); + + /* Update Indexes */ + CatalogUpdateIndexes(pg_diralias_rel, tuple); + + /* Post creation hook for new directory alias */ + InvokeObjectPostCreateHook(DirAliasRelationId, diralias_id, 0); + + /* Clean up */ + heap_close(pg_diralias_rel, RowExclusiveLock); +} + +/* + * AlterDirAlias + * handles the execution of the ALTER DIRALIAS command. + * + * stmt - the AlterDirAliasStmt that describes the directory alias entry to alter. + */ +void +AlterDirAlias(AlterDirAliasStmt *stmt) +{ + Relation pg_diralias_rel; + ScanKeyData skey[1]; + HeapScanDesc scandesc; + HeapTuple tuple; + Datum values[Natts_pg_diralias]; + bool nulls[Natts_pg_diralias]; + bool replaces[Natts_pg_diralias]; + HeapTuple new_tuple; + char *path; + + /* Must be superuser to alter directory alias */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to alter directory alias"))); + + /* Unix-ify the new path, and strip any trailing slashes */ + path = pstrdup(stmt->path); + canonicalize_path(path); + + /* Disallow quotes */ + if (strchr(path, '\'')) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("directory path cannot contain single quotes"))); + + /* Open pg_diralias catalog */ + pg_diralias_rel = heap_open(DirAliasRelationId, RowExclusiveLock); + + /* Search for directory alias by name */ + ScanKeyInit(&skey[0], + Anum_pg_diralias_dirname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->name)); + + /* + * We use a heapscan here even though there is an index on alias and path. + * We do this on the theory that pg_diralias will usually have a + * relatively small number of entries and therefore it is safe to assume + * an index scan would be wasted effort. + */ + scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey); + + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* If directory alias does not exist then raise an error */ + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("directory alias \"%s\" does not exist", stmt->name))); + + /* Build new tuple and update pg_diralias */ + memset(nulls, 0, sizeof(nulls)); + memset(replaces, 0, sizeof(replaces)); + memset(values, 0, sizeof(values)); + + values[Anum_pg_diralias_dirpath - 1] = CStringGetTextDatum(path); + replaces[Anum_pg_diralias_dirpath - 1] = true; + + new_tuple = heap_modify_tuple(tuple, RelationGetDescr(pg_diralias_rel), + values, nulls, replaces); + + simple_heap_update(pg_diralias_rel, &new_tuple->t_self, new_tuple); + + /* Update Indexes */ + CatalogUpdateIndexes(pg_diralias_rel, new_tuple); + + /* Post alter hook for directory alias */ + InvokeObjectPostAlterHook(DirAliasRelationId, HeapTupleGetOid(tuple), 0); + + /* Clean Up */ + heap_freetuple(new_tuple); + heap_endscan(scandesc); + heap_close(pg_diralias_rel, RowExclusiveLock); +} + +/* + * get_diralias_name + * given a directory alias OID, look up the name. If the directory does not + * exist then NULL is returned. + * + * diralias_id - the OID of the directory alias entry in pg_diralias. + */ +char * +get_diralias_name(Oid diralias_id) +{ + char *name = NULL; + HeapTuple tuple; + + tuple = SearchSysCache1(DIRALIASOID, ObjectIdGetDatum(diralias_id)); + if (HeapTupleIsValid(tuple)) + { + name = pstrdup(NameStr(((Form_pg_diralias) GETSTRUCT(tuple))->dirname)); + ReleaseSysCache(tuple); + } + + return name; +} + +/* + * get_directory_oid_by_path + * given a directory path, look up the OID. If the directory does not exist + * this InvalidOid is returned. + * + * path - the path of the directory + */ +Oid +get_diralias_oid_by_path(const char *path) +{ + Oid dir_id = InvalidOid; + Relation pg_diralias_rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData skey[1]; + + /* + * Search pg_diralias. We use a heapscan here even though there is an index + * on alias. We do this on the theory that pg_diralias will usually have a + * relatively small number of entries and therefore it is safe to assume + * an index scan would be wasted effort. + */ + pg_diralias_rel = heap_open(DirAliasRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_diralias_dirpath, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(path)); + + scandesc = heap_beginscan_catalog(pg_diralias_rel, 1, skey); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + if (HeapTupleIsValid(tuple)) + dir_id = HeapTupleGetOid(tuple); + + heap_endscan(scandesc); + heap_close(pg_diralias_rel, AccessShareLock); + + return dir_id; +} + +/* + * get_directory_oid + * given a directory alias name, look up the OID. If a directory alias does + * not exist for the given name then raise an error. However, if missing_ok + * is true, then return InvalidOid. + * + * name - the name of the directory alias + * missing_ok - false if an error should be raised if the directory alias does + * not exist. + */ +Oid +get_diralias_oid(const char *name, bool missing_ok) +{ + Oid dir_id; + Relation pg_diralias_rel; + ScanKeyData skey[1]; + SysScanDesc sscan; + HeapTuple tuple; + + /* Search pg_diralias for a directory alias entry with provided name */ + pg_diralias_rel = heap_open(DirAliasRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_diralias_dirname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(name)); + + sscan = systable_beginscan(pg_diralias_rel, DirAliasNameIndexId, + true, NULL, 1, skey); + + tuple = systable_getnext(sscan); + + if (HeapTupleIsValid(tuple)) + dir_id = HeapTupleGetOid(tuple); + else + dir_id = InvalidOid; + + systable_endscan(sscan); + heap_close(pg_diralias_rel, AccessShareLock); + + if (!OidIsValid(dir_id) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("directory alias \"%s\" does not exist", name))); + + return dir_id; +} diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index 8583581..8cae12e 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -380,6 +380,10 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs) list_length(objname) - 1)); } break; + case OBJECT_DIRALIAS: + msg = gettext_noop("directory alias \"%s\" does not exist, skipping"); + name = NameListToString(objname); + break; case OBJECT_EVENT_TRIGGER: msg = gettext_noop("event trigger \"%s\" does not exist, skipping"); name = NameListToString(objname); diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 1b8c94b..b70c322 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -73,6 +73,7 @@ static event_trigger_support_data event_trigger_support[] = { {"COLLATION", true}, {"CONVERSION", true}, {"DATABASE", false}, + {"DIRALIAS", true}, {"DOMAIN", true}, {"EXTENSION", true}, {"EVENT TRIGGER", false}, @@ -924,6 +925,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_CONSTRAINT: case OBJECT_COLLATION: case OBJECT_CONVERSION: + case OBJECT_DIRALIAS: case OBJECT_DOMAIN: case OBJECT_EXTENSION: case OBJECT_FDW: @@ -998,6 +1000,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_DEFACL: case OCLASS_EXTENSION: case OCLASS_ROWSECURITY: + case OCLASS_DIRALIAS: return true; case MAX_OCLASS: diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 21b070a..8941fa2 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2702,6 +2702,20 @@ _copyGrantRoleStmt(const GrantRoleStmt *from) return newnode; } +static GrantDirAliasStmt * +_copyGrantDirAliasStmt(const GrantDirAliasStmt *from) +{ + GrantDirAliasStmt *newnode = makeNode(GrantDirAliasStmt); + + COPY_NODE_FIELD(directories); + COPY_NODE_FIELD(permissions); + COPY_NODE_FIELD(grantees); + COPY_SCALAR_FIELD(is_grant); + COPY_STRING_FIELD(grantor); + + return newnode; +} + static AlterDefaultPrivilegesStmt * _copyAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *from) { @@ -3879,6 +3893,28 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from) return newnode; } +static CreateDirAliasStmt * +_copyCreateDirAliasStmt(const CreateDirAliasStmt *from) +{ + CreateDirAliasStmt *newnode = makeNode(CreateDirAliasStmt); + + COPY_STRING_FIELD(name); + COPY_STRING_FIELD(path); + + return newnode; +} + +static AlterDirAliasStmt * +_copyAlterDirAliasStmt(const AlterDirAliasStmt *from) +{ + AlterDirAliasStmt *newnode = makeNode(AlterDirAliasStmt); + + COPY_STRING_FIELD(name); + COPY_STRING_FIELD(path); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -4318,6 +4354,9 @@ copyObject(const void *from) case T_GrantStmt: retval = _copyGrantStmt(from); break; + case T_GrantDirAliasStmt: + retval = _copyGrantDirAliasStmt(from); + break; case T_GrantRoleStmt: retval = _copyGrantRoleStmt(from); break; @@ -4597,6 +4636,12 @@ copyObject(const void *from) case T_AlterPolicyStmt: retval = _copyAlterPolicyStmt(from); break; + case T_CreateDirAliasStmt: + retval = _copyCreateDirAliasStmt(from); + break; + case T_AlterDirAliasStmt: + retval = _copyAlterDirAliasStmt(from); + break; case T_A_Expr: retval = _copyAExpr(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 358395f..e266ad8 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1046,6 +1046,18 @@ _equalGrantRoleStmt(const GrantRoleStmt *a, const GrantRoleStmt *b) } static bool +_equalGrantDirAliasStmt(const GrantDirAliasStmt *a, const GrantDirAliasStmt *b) +{ + COMPARE_NODE_FIELD(directories); + COMPARE_NODE_FIELD(permissions); + COMPARE_NODE_FIELD(grantees); + COMPARE_SCALAR_FIELD(is_grant); + COMPARE_STRING_FIELD(grantor); + + return true; +} + +static bool _equalAlterDefaultPrivilegesStmt(const AlterDefaultPrivilegesStmt *a, const AlterDefaultPrivilegesStmt *b) { COMPARE_NODE_FIELD(options); @@ -2034,6 +2046,24 @@ _equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b) } static bool +_equalCreateDirAliasStmt(const CreateDirAliasStmt *a, const CreateDirAliasStmt *b) +{ + COMPARE_STRING_FIELD(name); + COMPARE_STRING_FIELD(path); + + return true; +} + +static bool +_equalAlterDirAliasStmt(const AlterDirAliasStmt *a, const AlterDirAliasStmt *b) +{ + COMPARE_STRING_FIELD(name); + COMPARE_STRING_FIELD(path); + + return true; +} + +static bool _equalAExpr(const A_Expr *a, const A_Expr *b) { COMPARE_SCALAR_FIELD(kind); @@ -2778,6 +2808,9 @@ equal(const void *a, const void *b) case T_GrantStmt: retval = _equalGrantStmt(a, b); break; + case T_GrantDirAliasStmt: + retval = _equalGrantDirAliasStmt(a, b); + break; case T_GrantRoleStmt: retval = _equalGrantRoleStmt(a, b); break; @@ -3057,6 +3090,12 @@ equal(const void *a, const void *b) case T_AlterPolicyStmt: retval = _equalAlterPolicyStmt(a, b); break; + case T_CreateDirAliasStmt: + retval = _equalCreateDirAliasStmt(a, b); + break; + case T_AlterDirAliasStmt: + retval = _equalAlterDirAliasStmt(a, b); + break; case T_A_Expr: retval = _equalAExpr(a, b); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0de9584..24d5eb5 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -258,7 +258,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DeallocateStmt PrepareStmt ExecuteStmt DropOwnedStmt ReassignOwnedStmt AlterTSConfigurationStmt AlterTSDictionaryStmt - CreateMatViewStmt RefreshMatViewStmt + CreateMatViewStmt RefreshMatViewStmt GrantDirStmt RevokeDirStmt + CreateDirAliasStmt AlterDirAliasStmt %type <node> select_no_parens select_with_parens select_clause simple_select values_clause @@ -324,6 +325,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <node> RowSecurityOptionalWithCheck RowSecurityOptionalExpr %type <list> RowSecurityDefaultToRole RowSecurityOptionalToRole +%type <node> dir_perm_opts +%type <list> dir_permissions dir_perm_list + %type <str> iso_level opt_encoding %type <node> grantee %type <list> grantee_list @@ -559,7 +563,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC - DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP + DICTIONARY DIRALIAS DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P + DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN @@ -734,6 +739,7 @@ stmt : | AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt + | AlterDirAliasStmt | AlterDomainStmt | AlterEnumStmt | AlterExtensionStmt @@ -769,6 +775,7 @@ stmt : | CreateAssertStmt | CreateCastStmt | CreateConversionStmt + | CreateDirAliasStmt | CreateDomainStmt | CreateExtensionStmt | CreateFdwStmt @@ -820,6 +827,7 @@ stmt : | ExplainStmt | FetchStmt | GrantStmt + | GrantDirStmt | GrantRoleStmt | ImportForeignSchemaStmt | IndexStmt @@ -837,6 +845,7 @@ stmt : | RemoveOperStmt | RenameStmt | RevokeStmt + | RevokeDirStmt | RevokeRoleStmt | RuleStmt | SecLabelStmt @@ -4606,6 +4615,34 @@ row_security_cmd: /***************************************************************************** * + * QUERIES: + * CREATE DIRALIAS <name> AS <path> + * ALTER DIRALIAS <name> AS <path> + * + *****************************************************************************/ + +CreateDirAliasStmt: + CREATE DIRALIAS name AS Sconst + { + CreateDirAliasStmt *n = makeNode(CreateDirAliasStmt); + n->name = $3; + n->path = $5; + $$ = (Node *) n; + } + ; + +AlterDirAliasStmt: + ALTER DIRALIAS name AS Sconst + { + AlterDirAliasStmt *n = makeNode(AlterDirAliasStmt); + n->name = $3; + n->path = $5; + $$ = (Node *) n; + } + ; + +/***************************************************************************** + * * QUERIES : * CREATE TRIGGER ... * DROP TRIGGER ... @@ -5481,6 +5518,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; } | CONVERSION_P { $$ = OBJECT_CONVERSION; } | SCHEMA { $$ = OBJECT_SCHEMA; } | EXTENSION { $$ = OBJECT_EXTENSION; } + | DIRALIAS { $$ = OBJECT_DIRALIAS; } | TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; } | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } @@ -6314,6 +6352,67 @@ opt_granted_by: GRANTED BY RoleId { $$ = $3; } /***************************************************************************** * + * QUERIES: + * GRANT ON DIRALIAS <alias> <permissions> TO <roles> + * REVOKE ON DIRALIAS <alias> <permsissions> FROM <roles> + * + *****************************************************************************/ +GrantDirStmt: + GRANT ON DIRALIAS name_list dir_permissions TO grantee_list + opt_granted_by + { + GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt); + n->is_grant = true; + n->directories = $4; + n->permissions = $5; + n->grantees = $7; + n->grantor = $8; + $$ = (Node*)n; + } + ; + +RevokeDirStmt: + REVOKE ON DIRALIAS name_list dir_permissions FROM grantee_list + { + GrantDirAliasStmt *n = makeNode(GrantDirAliasStmt); + n->is_grant = false; + n->directories = $4; + n->permissions = $5; + n->grantees = $7; + $$ = (Node*)n; + } + ; + +/* either ALL or a list of individual permissions */ +dir_permissions: dir_perm_list + { $$ = $1; } + | ALL { $$ = NIL; } + ; + + +dir_perm_list: dir_perm_opts { $$ = list_make1($1); } + | dir_perm_list ',' dir_perm_opts { $$ = lappend($1, $3); } + ; + +dir_perm_opts: + READ + { + AccessPriv *n = makeNode(AccessPriv); + n->priv_name = pstrdup("select"); + n->cols = NIL; + $$ = (Node*)n; + } + | WRITE + { + AccessPriv *n = makeNode(AccessPriv); + n->priv_name = pstrdup("update"); + n->cols = NIL; + $$ = (Node*)n; + } + ; + +/***************************************************************************** + * * ALTER DEFAULT PRIVILEGES statement * *****************************************************************************/ @@ -7273,6 +7372,15 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name n->missing_ok = false; $$ = (Node *)n; } + | ALTER DIRALIAS name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_DIRALIAS; + n->object = list_make1(makeString($3)); + n->newname = $6; + n->missing_ok = false; + $$ = (Node *)n; + } | ALTER DOMAIN_P any_name RENAME TO name { RenameStmt *n = makeNode(RenameStmt); @@ -13051,6 +13159,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DICTIONARY + | DIRALIAS | DISABLE_P | DISCARD | DOCUMENT_P diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4a2a339..4980016 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -33,6 +33,7 @@ #include "commands/createas.h" #include "commands/dbcommands.h" #include "commands/defrem.h" +#include "commands/diralias.h" #include "commands/discard.h" #include "commands/event_trigger.h" #include "commands/explain.h" @@ -185,6 +186,7 @@ check_xact_readonly(Node *parsetree) case T_DropRoleStmt: case T_GrantStmt: case T_GrantRoleStmt: + case T_GrantDirAliasStmt: case T_AlterDefaultPrivilegesStmt: case T_TruncateStmt: case T_DropOwnedStmt: @@ -557,6 +559,10 @@ standard_ProcessUtility(Node *parsetree, GrantRole((GrantRoleStmt *) parsetree); break; + case T_GrantDirAliasStmt: + ExecuteGrantDirAliasStmt((GrantDirAliasStmt *) parsetree); + break; + case T_CreatedbStmt: /* no event triggers for global objects */ PreventTransactionChain(isTopLevel, "CREATE DATABASE"); @@ -1329,6 +1335,14 @@ ProcessUtilitySlow(Node *parsetree, AlterPolicy((AlterPolicyStmt *) parsetree); break; + case T_CreateDirAliasStmt: /* CREATE DIRALIAS */ + CreateDirAlias((CreateDirAliasStmt *) parsetree); + break; + + case T_AlterDirAliasStmt: /* ALTER DIRALIAS */ + AlterDirAlias((AlterDirAliasStmt *) parsetree); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -1596,6 +1610,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_DATABASE: tag = "ALTER DATABASE"; break; + case OBJECT_DIRALIAS: + tag = "ALTER DIRALIAS"; + break; case OBJECT_DOMAIN: tag = "ALTER DOMAIN"; break; @@ -1959,6 +1976,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_POLICY: tag = "DROP POLICY"; break; + case OBJECT_DIRALIAS: + tag = "DROP DIRALIAS"; + break; default: tag = "???"; } @@ -2016,6 +2036,14 @@ CreateCommandTag(Node *parsetree) } break; + case T_GrantDirAliasStmt: + { + GrantDirAliasStmt *stmt = (GrantDirAliasStmt *) parsetree; + + tag = (stmt->is_grant ? "GRANT ON DIRALIAS" : "REVOKE ON DIRALIAS"); + } + break; + case T_GrantRoleStmt: { GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree; @@ -2310,6 +2338,14 @@ CreateCommandTag(Node *parsetree) tag = "ALTER POLICY"; break; + case T_CreateDirAliasStmt: + tag = "CREATE DIRALIAS"; + break; + + case T_AlterDirAliasStmt: + tag = "ALTER DIRALIAS"; + break; + case T_PrepareStmt: tag = "PREPARE"; break; @@ -2862,6 +2898,14 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateDirAliasStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterDirAliasStmt: + lev = LOGSTMT_DDL; + break; + case T_AlterTSDictionaryStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index dc6eb2c..7cc00ef 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -790,6 +790,10 @@ acldefault(GrantObjectType objtype, Oid ownerId) world_default = ACL_USAGE; owner_default = ACL_ALL_RIGHTS_TYPE; break; + case ACL_OBJECT_DIRALIAS: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_DIRALIAS; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index 3a0957f..58dd1dc 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -22,11 +22,13 @@ #include "access/htup_details.h" #include "catalog/pg_type.h" +#include "commands/diralias.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/syslogger.h" #include "storage/fd.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -37,46 +39,44 @@ typedef struct DIR *dirdesc; } directory_fctx; - /* - * Convert a "text" filename argument to C string, and check it's allowable. + * Check the directory permissions for the provided filename/path. * - * Filename may be absolute or relative to the DataDir, but we only allow - * absolute paths that match DataDir or Log_directory. + * The filename must be an absolute path to the file. */ -static char * -convert_and_check_filename(text *arg) +static void +check_directory_permissions(char *directory) { - char *filename; + Oid diralias_id; + AclResult aclresult; - filename = text_to_cstring(arg); - canonicalize_path(filename); /* filename can change length here */ + /* Do not allow relative paths */ + if (!is_absolute_path(directory)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("relative path not allowed"))); - if (is_absolute_path(filename)) - { - /* Disallow '/a/b/data/..' */ - if (path_contains_parent_reference(filename)) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("reference to parent directory (\"..\") not allowed")))); - - /* - * Allow absolute paths if within DataDir or Log_directory, even - * though Log_directory might be outside DataDir. - */ - if (!path_is_prefix_of_path(DataDir, filename) && - (!is_absolute_path(Log_directory) || - !path_is_prefix_of_path(Log_directory, filename))) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("absolute path not allowed")))); - } - else if (!path_is_relative_and_below_cwd(filename)) + /* Search for directory in pg_diralias */ + diralias_id = get_diralias_oid_by_path(directory); + + /* + * If an entry does not exist for the path in pg_diralias then raise + * an error. + */ + if (!OidIsValid(diralias_id)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("path must be in or below the current directory")))); + errmsg("directory alias entry for \"%s\" does not exist", + directory))); + + /* Check directory alias entry permissions */ + aclresult = pg_diralias_aclcheck(diralias_id, GetUserId(), ACL_SELECT); - return filename; + /* If the current user has insufficient privileges then raise an error */ + if (aclresult != ACLCHECK_OK) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must have read permissions on directory"))); } @@ -173,13 +173,19 @@ pg_read_file(PG_FUNCTION_ARGS) int64 seek_offset = PG_GETARG_INT64(1); int64 bytes_to_read = PG_GETARG_INT64(2); char *filename; + char *directory; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); + /* Convert and cleanup the filename */ + filename = text_to_cstring(filename_t); + canonicalize_path(filename); - filename = convert_and_check_filename(filename_t); + /* Superuser is always allowed to bypass directory permissions */ + if (!superuser()) + { + directory = pstrdup(filename); + get_parent_directory(directory); + check_directory_permissions(directory); + } if (bytes_to_read < 0) ereport(ERROR, @@ -197,13 +203,19 @@ pg_read_file_all(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); char *filename; + char *directory; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); + /* Convert and cleanup the filename */ + filename = text_to_cstring(filename_t); + canonicalize_path(filename); - filename = convert_and_check_filename(filename_t); + /* Superuser is always allowed to bypass directory permissions */ + if (!superuser()) + { + directory = pstrdup(filename); + get_parent_directory(directory); + check_directory_permissions(directory); + } PG_RETURN_TEXT_P(read_text_file(filename, 0, -1)); } @@ -218,13 +230,19 @@ pg_read_binary_file(PG_FUNCTION_ARGS) int64 seek_offset = PG_GETARG_INT64(1); int64 bytes_to_read = PG_GETARG_INT64(2); char *filename; + char *directory; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); + /* Convert and cleanup the filename */ + filename = text_to_cstring(filename_t); + canonicalize_path(filename); - filename = convert_and_check_filename(filename_t); + /* Superuser is always allowed to bypass directory permissions */ + if (!superuser()) + { + directory = pstrdup(filename); + get_parent_directory(directory); + check_directory_permissions(directory); + } if (bytes_to_read < 0) ereport(ERROR, @@ -242,13 +260,19 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); char *filename; + char *directory; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to read files")))); + /* Convert and cleanup the filename */ + filename = text_to_cstring(filename_t); + canonicalize_path(filename); - filename = convert_and_check_filename(filename_t); + /* Superuser is always allowed to bypass directory permissions */ + if (!superuser()) + { + directory = pstrdup(filename); + get_parent_directory(directory); + check_directory_permissions(directory); + } PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1)); } @@ -261,18 +285,23 @@ pg_stat_file(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); char *filename; + char *directory; struct stat fst; Datum values[6]; bool isnull[6]; HeapTuple tuple; TupleDesc tupdesc; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to get file information")))); + filename = text_to_cstring(filename_t); + canonicalize_path(filename); - filename = convert_and_check_filename(filename_t); + /* Superuser is always allowed to bypass directory permissions */ + if (!superuser()) + { + directory = pstrdup(filename); + get_parent_directory(directory); + check_directory_permissions(directory); + } if (stat(filename, &fst) < 0) ereport(ERROR, @@ -331,11 +360,6 @@ pg_ls_dir(PG_FUNCTION_ARGS) struct dirent *de; directory_fctx *fctx; - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be superuser to get directory listings")))); - if (SRF_IS_FIRSTCALL()) { MemoryContext oldcontext; @@ -344,7 +368,12 @@ pg_ls_dir(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); fctx = palloc(sizeof(directory_fctx)); - fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0)); + fctx->location = text_to_cstring(PG_GETARG_TEXT_P(0)); + canonicalize_path(fctx->location); + + /* Superuser is always allowed to bypass directory permissions */ + if (!superuser()) + check_directory_permissions(fctx->location); fctx->dirdesc = AllocateDir(fctx->location); diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 94d951c..383e1c9 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -37,6 +37,7 @@ #include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" #include "catalog/pg_description.h" +#include "catalog/pg_diralias.h" #include "catalog/pg_enum.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_foreign_data_wrapper.h" @@ -367,6 +368,17 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + {DirAliasRelationId, /* DIRALIASOID */ + DirAliasOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, {EnumRelationId, /* ENUMOID */ EnumOidIndexId, 1, diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 8bfc604..da46c9f 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -103,6 +103,7 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr) int numForeignServers; int numDefaultACLs; int numEventTriggers; + int numDirectoryAliases; if (g_verbose) write_msg(NULL, "reading schemas\n"); @@ -251,6 +252,10 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr) write_msg(NULL, "reading row-security policies\n"); getRowSecurity(fout, tblinfo, numTables); + if (g_verbose) + write_msg(NULL, "reading directory aliases\n"); + getDirectoryAliases(fout, &numDirectoryAliases); + *numTablesPtr = numTables; return tblinfo; } diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 259c472..08761e3 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -19,6 +19,7 @@ #include "dumputils.h" #include "parser/keywords.h" +#include "pg_backup_utils.h" /* Globals from keywords.c */ @@ -545,10 +546,16 @@ buildACLCommands(const char *name, const char *subname, * wire-in knowledge about the default public privileges for different * kinds of objects. */ - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name); + if (strcmp(type, "DIRALIAS") == 0) + appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM PUBLIC;\n", + name); + else + { + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); + if (subname) + appendPQExpBuffer(firstsql, "(%s)", subname); + appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name); + } /* * We still need some hacking though to cover the case where new default @@ -593,16 +600,34 @@ buildACLCommands(const char *name, const char *subname, ? strcmp(privswgo->data, "ALL") != 0 : strcmp(privs->data, "ALL") != 0) { - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", - type, name, fmtId(grantee->data)); + /* Handle special GRANT syntax for DIRALIAS */ + if (strcmp(type, "DIRALIAS") == 0) + appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s;\n", + name, fmtId(grantee->data)); + else + { + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); + if (subname) + appendPQExpBuffer(firstsql, "(%s)", subname); + appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", + type, name, fmtId(grantee->data)); + } + if (privs->len > 0) - appendPQExpBuffer(firstsql, - "%sGRANT %s ON %s %s TO %s;\n", - prefix, privs->data, type, name, - fmtId(grantee->data)); + { + /* Handle special GRANT syntax for DIRALIAS */ + if (strcmp(type, "DIRALIAS") == 0) + appendPQExpBuffer(firstsql, + "%sGRANT ON DIRALIAS %s %s TO %s;\n", + prefix, name, privs->data, + fmtId(grantee->data)); + else + appendPQExpBuffer(firstsql, + "%sGRANT %s ON %s %s TO %s;\n", + prefix, privs->data, type, name, + fmtId(grantee->data)); + } + if (privswgo->len > 0) appendPQExpBuffer(firstsql, "%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n", @@ -622,8 +647,14 @@ buildACLCommands(const char *name, const char *subname, if (privs->len > 0) { - appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ", - prefix, privs->data, type, name); + /* Handle special GRANT syntax for DIRALIAS */ + if (strcmp(type, "DIRALIAS") == 0) + appendPQExpBuffer(secondsql, "%sGRANT ON DIRALIAS %s %s TO ", + prefix, name, privs->data); + else + appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ", + prefix, privs->data, type, name); + if (grantee->len == 0) appendPQExpBufferStr(secondsql, "PUBLIC;\n"); else if (strncmp(grantee->data, "group ", @@ -660,11 +691,18 @@ buildACLCommands(const char *name, const char *subname, */ if (!found_owner_privs && owner) { - appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); - if (subname) - appendPQExpBuffer(firstsql, "(%s)", subname); - appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", - type, name, fmtId(owner)); + /* Handle special GRANT syntax for DIRALIAS */ + if (strcmp(type, "DIRALIAS") == 0) + appendPQExpBuffer(firstsql, "REVOKE ON DIRALIAS %s ALL FROM %s", + name, fmtId(owner)); + else + { + appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); + if (subname) + appendPQExpBuffer(firstsql, "(%s)", subname); + appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", + type, name, fmtId(owner)); + } } destroyPQExpBuffer(grantee); @@ -873,6 +911,11 @@ do { \ CONVERT_PRIV('r', "SELECT"); CONVERT_PRIV('w', "UPDATE"); } + else if (strcmp(type, "DIRALIAS") == 0) + { + CONVERT_PRIV('r', "READ"); + CONVERT_PRIV('w', "WRITE"); + } else abort(); diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index ed28d36..0449b87 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3106,7 +3106,8 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH) strcmp(type, "SCHEMA") == 0 || strcmp(type, "FOREIGN DATA WRAPPER") == 0 || strcmp(type, "SERVER") == 0 || - strcmp(type, "USER MAPPING") == 0) + strcmp(type, "USER MAPPING") == 0 || + strcmp(type, "DIRALIAS") == 0) { /* We already know that search_path was set properly */ appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag)); @@ -3307,7 +3308,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 || strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 || strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 || - strcmp(te->desc, "SERVER") == 0) + strcmp(te->desc, "SERVER") == 0 || + strcmp(te->desc, "DIRALIAS") == 0) { PQExpBuffer temp = createPQExpBuffer(); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 1e8f089..c1c8c01 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -191,6 +191,7 @@ static void dumpUserMappings(Archive *fout, const char *servername, const char *namespace, const char *owner, CatalogId catalogId, DumpId dumpId); static void dumpDefaultACL(Archive *fout, DumpOptions *dopt, DefaultACLInfo *daclinfo); +static void dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo); static void dumpACL(Archive *fout, DumpOptions *dopt, CatalogId objCatId, DumpId objDumpId, const char *type, const char *name, const char *subname, @@ -2987,6 +2988,99 @@ dumpRowSecurity(Archive *fout, DumpOptions *dopt, RowSecurityInfo *rsinfo) destroyPQExpBuffer(delqry); } +void +getDirectoryAliases(Archive *fout, int *numDirectoryAliases) +{ + PQExpBuffer query; + PGresult *res; + DirectoryAliasInfo *dirinfo; + int i_tableoid; + int i_oid; + int i_diralias; + int i_dirpath; + int i_rolname; + int i_diracl; + int i, ntups; + + if (fout->remoteVersion < 90500) + return; + + query = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + appendPQExpBuffer(query, "SELECT tableoid, oid, diralias, dirpath, " + "(%s dirowner) AS rolname, diracl " + "FROM pg_catalog.pg_directory", + username_subquery); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + dirinfo = (DirectoryAliasInfo *) pg_malloc(ntups * sizeof(DirectoryAliasInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_diralias = PQfnumber(res, "diralias"); + i_dirpath = PQfnumber(res, "dirpath"); + i_rolname = PQfnumber(res, "rolname"); + i_diracl = PQfnumber(res, "diracl"); + + for (i = 0; i < ntups; i++) + { + dirinfo[i].dobj.objType = DO_DIRALIAS; + dirinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + dirinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&dirinfo[i].dobj); + dirinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_diralias)); + dirinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); + dirinfo[i].dirpath = pg_strdup(PQgetvalue(res, i, i_dirpath)); + dirinfo[i].diracl = pg_strdup(PQgetvalue(res, i, i_diracl)); + } +} + +static void +dumpDirectoryAlias(Archive *fout, DumpOptions *dopt, DirectoryAliasInfo *dirinfo) +{ + PQExpBuffer create_query; + PQExpBuffer delete_query; + char *diralias; + + if (!dirinfo->dobj.dump || dopt->dataOnly) + return; + + create_query = createPQExpBuffer(); + delete_query = createPQExpBuffer(); + + diralias = pg_strdup(fmtId(dirinfo->dobj.name)); + + appendPQExpBuffer(delete_query, "DROP DIRALIAS %s;\n", diralias); + + appendPQExpBuffer(create_query, "CREATE DIRALIAS %s AS \'%s\';\n", + diralias, dirinfo->dirpath); + + ArchiveEntry(fout, dirinfo->dobj.catId, dirinfo->dobj.dumpId, + dirinfo->dobj.name, + NULL, NULL, + dirinfo->rolname, false, + "DIRALIAS", SECTION_POST_DATA, + create_query->data, delete_query->data, NULL, + NULL, 0, + NULL, NULL); + + /* Dump ACL - because of the special GRANT syntax, we cannot use dumpACL */ + if (dirinfo->diracl) + dumpACL(fout, dopt, dirinfo->dobj.catId, dirinfo->dobj.dumpId, + "DIRALIAS", diralias, NULL, dirinfo->dobj.name, + NULL, dirinfo->rolname, + dirinfo->diracl); + + destroyPQExpBuffer(create_query); + destroyPQExpBuffer(delete_query); +} + static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout, PQExpBuffer upgrade_buffer, @@ -8218,6 +8312,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj) case DO_ROW_SECURITY: dumpRowSecurity(fout, dopt, (RowSecurityInfo *) dobj); break; + case DO_DIRALIAS: + dumpDirectoryAlias(fout, dopt, (DirectoryAliasInfo *) dobj); + break; case DO_PRE_DATA_BOUNDARY: case DO_POST_DATA_BOUNDARY: /* never dumped, nothing to do */ @@ -15615,6 +15712,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, case DO_EVENT_TRIGGER: case DO_DEFAULT_ACL: case DO_ROW_SECURITY: + case DO_DIRALIAS: /* Post-data objects: must come after the post-data boundary */ addObjectDependency(dobj, postDataBound->dumpId); break; diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index a7eb2fd..14a712c 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -76,7 +76,8 @@ typedef enum DO_POST_DATA_BOUNDARY, DO_EVENT_TRIGGER, DO_REFRESH_MATVIEW, - DO_ROW_SECURITY + DO_ROW_SECURITY, + DO_DIRALIAS } DumpableObjectType; typedef struct _dumpableObject @@ -469,6 +470,15 @@ typedef struct _rowSecurityInfo char *rsecwithcheck; } RowSecurityInfo; +typedef struct _directoryAliasInfo +{ + DumpableObject dobj; + char *diralias; + char *diracl; + char *dirpath; + char *rolname; /* name of owner */ +} DirectoryAliasInfo; + /* global decls */ extern bool force_quotes; /* double-quotes for identifiers flag */ extern bool g_verbose; /* verbose flag */ @@ -550,5 +560,6 @@ extern void getExtensionMembership(Archive *fout, DumpOptions *dopt, ExtensionIn int numExtensions); extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers); extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables); +extern void getDirectoryAliases(Archive *fout, int *numDirectoryAliases); #endif /* PG_DUMP_H */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 030bccc..5cece21 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -73,7 +73,8 @@ static const int oldObjectTypePriority[] = 13, /* DO_POST_DATA_BOUNDARY */ 20, /* DO_EVENT_TRIGGER */ 15, /* DO_REFRESH_MATVIEW */ - 21 /* DO_ROW_SECURITY */ + 21, /* DO_ROW_SECURITY */ + 22, /* DO_DIRALIAS */ }; /* @@ -122,7 +123,8 @@ static const int newObjectTypePriority[] = 25, /* DO_POST_DATA_BOUNDARY */ 32, /* DO_EVENT_TRIGGER */ 33, /* DO_REFRESH_MATVIEW */ - 34 /* DO_ROW_SECURITY */ + 34, /* DO_ROW_SECURITY */ + 35, /* DO_DIRALIAS */ }; static DumpId preDataBoundId; @@ -1443,6 +1445,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "ROW-SECURITY POLICY (ID %d OID %u)", obj->dumpId, obj->catId.oid); return; + case DO_DIRALIAS: + snprintf(buf, bufsize, + "DIRECTORY ALIAS (ID %d OID %u)", + obj->dumpId, obj->catId.oid); + return; case DO_PRE_DATA_BOUNDARY: snprintf(buf, bufsize, "PRE-DATA BOUNDARY (ID %d)", diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 6a4913a..1779bc0 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -148,6 +148,7 @@ typedef enum ObjectClass OCLASS_EXTENSION, /* pg_extension */ OCLASS_EVENT_TRIGGER, /* pg_event_trigger */ OCLASS_ROWSECURITY, /* pg_rowsecurity */ + OCLASS_DIRALIAS, /* pg_diralias */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 870692c..e8a195d 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree( DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops)); #define RangeTypidIndexId 3542 +DECLARE_UNIQUE_INDEX(pg_directory_oid_index, 6101, on pg_diralias using btree(oid oid_ops)); +#define DirAliasOidIndexId 6101 + +DECLARE_UNIQUE_INDEX(pg_directory_name_index, 6102, on pg_diralias using btree(dirname name_ops)); +#define DirAliasNameIndexId 6102 + DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 3257, on pg_rowsecurity using btree(oid oid_ops)); #define RowSecurityOidIndexId 3257 diff --git a/src/include/catalog/pg_diralias.h b/src/include/catalog/pg_diralias.h new file mode 100644 index 0000000..e58f047 --- /dev/null +++ b/src/include/catalog/pg_diralias.h @@ -0,0 +1,46 @@ +/* + * pg_diralias.h + * definition of the system catalog for directory permissions (pg_diralias) + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + */ +#ifndef PG_DIRALIAS_H +#define PG_DIRALIAS_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_diralias definition. cpp turns this into + * typedef struct FormData_pg_diralias + * ---------------- + */ +#define DirAliasRelationId 6100 + +CATALOG(pg_diralias,6100) +{ + NameData dirname; /* directory alias name */ + text dirpath; /* directory path */ +#ifdef CATALOG_VARLEN + aclitem diracl[1]; /* directory permissions */ +#endif +} FormData_pg_diralias; + +/* ---------------- + * Form_pg_diralias corresponds to a pointer to a row with + * the format of pg_diralias relation. + * ---------------- + */ +typedef FormData_pg_diralias *Form_pg_diralias; + +/* ---------------- + * compiler constants for pg_diralias + * ---------------- + */ +#define Natts_pg_diralias 3 +#define Anum_pg_diralias_dirname 1 +#define Anum_pg_diralias_dirpath 2 +#define Anum_pg_diralias_diracl 3 + +#endif /* PG_DIRALIAS_H */ \ No newline at end of file diff --git a/src/include/commands/diralias.h b/src/include/commands/diralias.h new file mode 100644 index 0000000..851d321 --- /dev/null +++ b/src/include/commands/diralias.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * directory.h + * prototypes for directory.c. + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/directory.h + * + *------------------------------------------------------------------------- + */ + +#ifndef DIRECTORY_H +#define DIRECTORY_H + +#include "nodes/parsenodes.h" + +extern void RemoveDirAliasById(Oid dir_id); +extern void CreateDirAlias(CreateDirAliasStmt *stmt); +extern void AlterDirAlias(AlterDirAliasStmt *stmt); + +extern char *get_diralias_name(Oid dir_id); +extern Oid get_diralias_oid(const char *name, bool missing_ok); +extern Oid get_diralias_owner(Oid dir_id); +extern Oid get_diralias_oid_by_path(const char *path); + +#endif /* DIRECTORY_H */ \ No newline at end of file diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 154d943..6b293aa 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -278,6 +278,7 @@ typedef enum NodeTag T_AlterDomainStmt, T_SetOperationStmt, T_GrantStmt, + T_GrantDirAliasStmt, T_GrantRoleStmt, T_AlterDefaultPrivilegesStmt, T_ClosePortalStmt, @@ -368,6 +369,8 @@ typedef enum NodeTag T_AlterSystemStmt, T_CreatePolicyStmt, T_AlterPolicyStmt, + T_CreateDirAliasStmt, + T_AlterDirAliasStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index cef9544..4b7197c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1212,6 +1212,7 @@ typedef enum ObjectType OBJECT_COLLATION, OBJECT_CONVERSION, OBJECT_DATABASE, + OBJECT_DIRALIAS, OBJECT_DOMAIN, OBJECT_EVENT_TRIGGER, OBJECT_EXTENSION, @@ -1412,6 +1413,7 @@ typedef enum GrantObjectType ACL_OBJECT_LARGEOBJECT, /* largeobject */ ACL_OBJECT_NAMESPACE, /* namespace */ ACL_OBJECT_TABLESPACE, /* tablespace */ + ACL_OBJECT_DIRALIAS, /* directory alias */ ACL_OBJECT_TYPE /* type */ } GrantObjectType; @@ -1483,6 +1485,20 @@ typedef struct GrantRoleStmt } GrantRoleStmt; /* ---------------------- + * Grant/Revoke Directory Permission Statement + * ---------------------- + */ +typedef struct GrantDirAliasStmt +{ + NodeTag type; + List *directories; /* the directory alias/name */ + List *permissions; /* list of permission to be granted/revoked */ + List *grantees; /* list of roles to be granted/revoked permission */ + bool is_grant; /* true = GRANT, false = REVOKE */ + char *grantor; /* set grantor to other than current role */ +} GrantDirAliasStmt; + +/* ---------------------- * Alter Default Privileges Statement * ---------------------- */ @@ -1890,6 +1906,28 @@ typedef struct AlterPolicyStmt } AlterPolicyStmt; /* ---------------------- + * Create DIRALIAS Statement + * ---------------------- + */ +typedef struct CreateDirAliasStmt +{ + NodeTag type; + char *name; + char *path; +} CreateDirAliasStmt; + +/* ---------------------- + * Alter DIRALIAS Statement + * ---------------------- + */ +typedef struct AlterDirAliasStmt +{ + NodeTag type; + char *name; + char *path; +} AlterDirAliasStmt; + +/* ---------------------- * Create TRIGGER Statement * ---------------------- */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index e14dc9a..eb31cf7 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -125,6 +125,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD) PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD) PG_KEYWORD("desc", DESC, RESERVED_KEYWORD) PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD) +PG_KEYWORD("diralias", DIRALIAS, UNRESERVED_KEYWORD) PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD) PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD) diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index a8e3164..cb75b1a 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -156,6 +156,7 @@ typedef ArrayType Acl; #define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) +#define ACL_ALL_RIGHTS_DIRALIAS (ACL_SELECT|ACL_UPDATE) /* operation codes for pg_*_aclmask */ typedef enum @@ -197,6 +198,7 @@ typedef enum AclObjectKind ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */ ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */ ACL_KIND_EXTENSION, /* pg_extension */ + ACL_KIND_DIRALIAS, /* pg_diralias */ MAX_ACL_KIND /* MUST BE LAST */ } AclObjectKind; @@ -255,6 +257,7 @@ extern Datum aclexplode(PG_FUNCTION_ARGS); */ extern void ExecuteGrantStmt(GrantStmt *stmt); extern void ExecAlterDefaultPrivilegesStmt(AlterDefaultPrivilegesStmt *stmt); +extern void ExecuteGrantDirAliasStmt(GrantDirAliasStmt *stmt); extern void RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid); extern void RemoveDefaultACLById(Oid defaclOid); @@ -281,6 +284,8 @@ extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid, AclMode mask, AclMaskHow how); extern AclMode pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how); +extern AclMode pg_diralias_aclmask(Oid dir_oid, Oid roleid, AclMode mask, + AclMaskHow how); extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum, Oid roleid, AclMode mode); @@ -297,6 +302,7 @@ extern AclResult pg_tablespace_aclcheck(Oid spc_oid, Oid roleid, AclMode mode); extern AclResult pg_foreign_data_wrapper_aclcheck(Oid fdw_oid, Oid roleid, AclMode mode); extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mode); extern AclResult pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode); +extern AclResult pg_diralias_aclcheck(Oid diroid, Oid roleid, AclMode mode); extern void aclcheck_error(AclResult aclerr, AclObjectKind objectkind, const char *objectname); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index f97229f..c4b822e 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -54,6 +54,7 @@ enum SysCacheIdentifier CONVOID, DATABASEOID, DEFACLROLENSPOBJ, + DIRALIASOID, ENUMOID, ENUMTYPOIDNAME, EVENTTRIGGERNAME, diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 2c8ec11..334f648 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -102,6 +102,7 @@ pg_db_role_setting|t pg_default_acl|t pg_depend|t pg_description|t +pg_diralias|t pg_enum|t pg_event_trigger|t pg_extension|t
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers