All,

The attached patch for review implements a directory permission system that
allows for providing a directory read/write capability to directories for
COPY TO/FROM and Generic File Access Functions to non-superusers.  This is
not a complete solution as it does not currently contain documentation or
regression tests.  Though it is my hopes to get some feedback as I am sure
there are some aspects of it that need to be reworked.  So I thought I'd
put it out there for comments/review.

The approach taken is to create "directory aliases" that have a unique name
and path, as well as an associated ACL list.  A superuser can create a new
alias to any directory on the system and then provide READ or WRITE
permissions to any non-superuser.  When a non-superuser then attempts to
execute a COPY TO/FROM or any one of the generic file access functions, a
permission check is performed against the aliases for the user and target
directory.  Superusers are allowed to bypass all of these checks.  All
alias paths must be an absolute path in order to avoid potential risks.
However, in the generic file access functions superusers are still allowed
to execute the functions with a relative path where non-superusers are
required to provide an absolute path.

- Implementation Details -

System Catalog:
pg_diralias
 - dirname - the name of the directory alias
 - dirpath - the directory path - must be absolute
 - diracl - the ACL for the directory

Syntax:
CREATE DIRALIAS <name> AS '<path>'
ALTER DIRALIAS <name> AS '<path>'
ALTER DIRALIAS <name> RENAME TO <new_name>
DROP DIRALIAS <name>

This is probably the area that I would really appreciate your thoughts and
recommendations. To GRANT permissions to a directory alias, I had to create
a special variant of GRANT since READ and WRITE are not reserved keywords
and causes grammar issues.  Therefore, I chose to start with the following
syntax:

GRANT ON DIRALIAS <name> <permissions> TO <roles>

where <permissions> is either READ, WRITE or ALL.

Any comments, suggestions or feedback would be greatly appreciated.

Thanks,
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/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 c98c27a..1a95229 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
@@ -4622,6 +4631,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 ...
@@ -5497,6 +5534,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; }
@@ -6330,6 +6368,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
  *
  *****************************************************************************/
@@ -7289,6 +7388,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);
@@ -13087,6 +13195,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 00c882d..d1abe61 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 c56a4cb..9b6a6fa 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,
@@ -2983,6 +2984,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,
@@ -8214,6 +8308,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 */
@@ -15617,6 +15714,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 10ae87a..67ef824 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/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..8cc083e 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_directory|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

Reply via email to