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

Reply via email to