Blob Blame History Raw
Back-port of upstream 9.3 patch to support multiple sockets
(upstream git commits c9b0cbe98bd783e24a8c4d8d8ac472a494b81292 and
d2286a98ef3fb88bafb57381b4c20b8b878827f1, plus updates of derived
documentation files).

Note the patch also touches html-stamp and man-stamp in doc/src/sgml/;
this is to keep the makefiles from trying to rebuild the derived doc
files.  We don't want that to happen because the BuildRequires for the
package don't include the necessary documentation tools.  Those diff
hunks *must be at the end* so that those files are newer than the
master doc files.

diff --git a/contrib/pg_upgrade/server.c b/contrib/pg_upgrade/server.c
index 0940e00..c5ecb84 100644
--- a/contrib/pg_upgrade/server.c
+++ b/contrib/pg_upgrade/server.c
@@ -197,7 +197,8 @@ start_postmaster(ClusterInfo *cluster)
 		snprintf(socket_string + strlen(socket_string),
 				 sizeof(socket_string) - strlen(socket_string),
 				 " -c %s='%s'",
-				 (GET_MAJOR_VERSION(cluster->major_version) < 903) ?
+	/* assume 9.1 build will not have unix_socket_directories patch */
+				 (GET_MAJOR_VERSION(cluster->major_version) < 902) ?
 				 "unix_socket_directory" : "unix_socket_directories",
 				 cluster->sockdir);
 #endif
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index b9664cb..94e166d 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -838,7 +838,7 @@ omicron         bryanh                  guest1
     <varname>unix_socket_permissions</varname> (and possibly
     <varname>unix_socket_group</varname>) configuration parameters as
     described in <xref linkend="runtime-config-connection">.  Or you
-    could set the <varname>unix_socket_directory</varname>
+    could set the <varname>unix_socket_directories</varname>
     configuration parameter to place the socket file in a suitably
     restricted directory.
    </para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 51d7da9..cb5c5d2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -453,17 +453,24 @@ SET ENABLE_SEQSCAN TO OFF;
       </listitem>
      </varlistentry>
 
-     <varlistentry id="guc-unix-socket-directory" xreflabel="unix_socket_directory">
-      <term><varname>unix_socket_directory</varname> (<type>string</type>)</term>
+     <varlistentry id="guc-unix-socket-directories" xreflabel="unix_socket_directories">
+      <term><varname>unix_socket_directories</varname> (<type>string</type>)</term>
       <indexterm>
-       <primary><varname>unix_socket_directory</> configuration parameter</primary>
+       <primary><varname>unix_socket_directories</> configuration parameter</primary>
       </indexterm>
       <listitem>
        <para>
-        Specifies the directory of the Unix-domain socket on which the
-        server is to listen for
-        connections from client applications.  The default is normally
-        <filename>/tmp</filename>, but can be changed at build time.
+        Specifies the directory of the Unix-domain socket(s) on which the
+        server is to listen for connections from client applications.
+        Multiple sockets can be created by listing multiple directories
+        separated by commas.  Whitespace between entries is
+        ignored; surround a directory name with double quotes if you need
+        to include whitespace or commas in the name.
+        An empty value
+        specifies not listening on any Unix-domain sockets, in which case
+        only TCP/IP sockets can be used to connect to the server.
+        The default value is normally
+        <filename>/tmp</filename>, but that can be changed at build time.
         This parameter can only be set at server start.
        </para>
 
@@ -472,8 +479,8 @@ SET ENABLE_SEQSCAN TO OFF;
         <literal>.s.PGSQL.<replaceable>nnnn</></literal> where
         <replaceable>nnnn</> is the server's port number, an ordinary file
         named <literal>.s.PGSQL.<replaceable>nnnn</>.lock</literal> will be
-        created in the <varname>unix_socket_directory</> directory.  Neither
-        file should ever be removed manually.
+        created in each of the <varname>unix_socket_directories</> directories.
+        Neither file should ever be removed manually.
        </para>
 
        <para>
@@ -490,8 +497,8 @@ SET ENABLE_SEQSCAN TO OFF;
       </indexterm>
       <listitem>
        <para>
-        Sets the owning group of the Unix-domain socket.  (The owning
-        user of the socket is always the user that starts the
+        Sets the owning group of the Unix-domain socket(s).  (The owning
+        user of the sockets is always the user that starts the
         server.)  In combination with the parameter
         <varname>unix_socket_permissions</varname> this can be used as
         an additional access control mechanism for Unix-domain connections.
@@ -514,7 +521,7 @@ SET ENABLE_SEQSCAN TO OFF;
       </indexterm>
       <listitem>
        <para>
-        Sets the access permissions of the Unix-domain socket.  Unix-domain
+        Sets the access permissions of the Unix-domain socket(s).  Unix-domain
         sockets use the usual Unix file system permission set.
         The parameter value is expected to be a numeric mode
         specified in the format accepted by the
@@ -6624,7 +6631,7 @@ LOG:  CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1)
        </row>
        <row>
         <entry><option>-k <replaceable>x</replaceable></option></entry>
-        <entry><literal>unix_socket_directory = <replaceable>x</replaceable></></entry>
+        <entry><literal>unix_socket_directories = <replaceable>x</replaceable></></entry>
        </row>
        <row>
         <entry><option>-l</option></entry>
diff --git a/doc/src/sgml/html/app-postgres.html b/doc/src/sgml/html/app-postgres.html
index b1f4e31..b6359a8 100644
--- a/doc/src/sgml/html/app-postgres.html
+++ b/doc/src/sgml/html/app-postgres.html
@@ -574,11 +574,19 @@ CLASS="REPLACEABLE"
 CLASS="COMMAND"
 >postgres</TT
 > is to listen for
-        connections from client applications.  The default is normally
+        connections from client applications.  The value can also be a
+        comma-separated list of directories.  An empty value
+        specifies not listening on any Unix-domain sockets, in which case
+        only TCP/IP sockets can be used to connect to the server.
+        The default value is normally
         <TT
 CLASS="FILENAME"
 >/tmp</TT
->, but can be changed at build time.
+>, but that can be changed at build time.
+        Specifying this option is equivalent to setting the <A
+HREF="runtime-config-connection.html#GUC-UNIX-SOCKET-DIRECTORIES"
+>unix_socket_directories</A
+> configuration parameter.
        </P
 ></DD
 ><DT
diff --git a/doc/src/sgml/html/auth-methods.html b/doc/src/sgml/html/auth-methods.html
index ab544a9..e1225d1 100644
--- a/doc/src/sgml/html/auth-methods.html
+++ b/doc/src/sgml/html/auth-methods.html
@@ -161,7 +161,7 @@ HREF="runtime-config-connection.html"
 >.  Or you
     could set the <TT
 CLASS="VARNAME"
->unix_socket_directory</TT
+>unix_socket_directories</TT
 >
     configuration parameter to place the socket file in a suitably
     restricted directory.
diff --git a/doc/src/sgml/html/bookindex.html b/doc/src/sgml/html/bookindex.html
index 5e25f95..1562ffe 100644
--- a/doc/src/sgml/html/bookindex.html
+++ b/doc/src/sgml/html/bookindex.html
@@ -17250,7 +17250,7 @@ HREF="xfunc-c.html#DFUNC"
 ></DL
 ></DD
 ><DT
->unix_socket_directory configuration parameter,
+>unix_socket_directories configuration parameter,
     <A
 HREF="runtime-config-connection.html#RUNTIME-CONFIG-CONNECTION-SETTINGS"
 >Connection Settings</A
diff --git a/doc/src/sgml/html/preventing-server-spoofing.html b/doc/src/sgml/html/preventing-server-spoofing.html
index 77d11ab..6abc511 100644
--- a/doc/src/sgml/html/preventing-server-spoofing.html
+++ b/doc/src/sgml/html/preventing-server-spoofing.html
@@ -115,8 +115,8 @@ CLASS="LITERAL"
 >local</TT
 >
    connections is to use a Unix domain socket directory (<A
-HREF="runtime-config-connection.html#GUC-UNIX-SOCKET-DIRECTORY"
->unix_socket_directory</A
+HREF="runtime-config-connection.html#GUC-UNIX-SOCKET-DIRECTORIES"
+>unix_socket_directories</A
 >) that has write permission only
    for a trusted local user.  This prevents a malicious user from creating
    their own socket file in that directory.  If you are concerned that
diff --git a/doc/src/sgml/html/runtime-config-connection.html b/doc/src/sgml/html/runtime-config-connection.html
index 74f096e..0ba6987 100644
--- a/doc/src/sgml/html/runtime-config-connection.html
+++ b/doc/src/sgml/html/runtime-config-connection.html
@@ -274,24 +274,31 @@ CLASS="VARNAME"
 ></DD
 ><DT
 ><A
-NAME="GUC-UNIX-SOCKET-DIRECTORY"
+NAME="GUC-UNIX-SOCKET-DIRECTORIES"
 ></A
 ><TT
 CLASS="VARNAME"
->unix_socket_directory</TT
+>unix_socket_directories</TT
 > (<TT
 CLASS="TYPE"
 >string</TT
 >)</DT
 ><DD
 ><P
->        Specifies the directory of the Unix-domain socket on which the
-        server is to listen for
-        connections from client applications.  The default is normally
+>        Specifies the directory of the Unix-domain socket(s) on which the
+        server is to listen for connections from client applications.
+        Multiple sockets can be created by listing multiple directories
+        separated by commas.  Whitespace between entries is
+        ignored; surround a directory name with double quotes if you need
+        to include whitespace or commas in the name.
+        An empty value
+        specifies not listening on any Unix-domain sockets, in which case
+        only TCP/IP sockets can be used to connect to the server.
+        The default value is normally
         <TT
 CLASS="FILENAME"
 >/tmp</TT
->, but can be changed at build time.
+>, but that can be changed at build time.
         This parameter can only be set at server start.
        </P
 ><P
@@ -320,11 +327,11 @@ CLASS="REPLACEABLE"
 ></TT
 >.lock</TT
 > will be
-        created in the <TT
+        created in each of the <TT
 CLASS="VARNAME"
->unix_socket_directory</TT
-> directory.  Neither
-        file should ever be removed manually.
+>unix_socket_directories</TT
+> directories.
+        Neither file should ever be removed manually.
        </P
 ><P
 >        This parameter is irrelevant on Windows, which does not have
@@ -344,8 +351,8 @@ CLASS="TYPE"
 >)</DT
 ><DD
 ><P
->        Sets the owning group of the Unix-domain socket.  (The owning
-        user of the socket is always the user that starts the
+>        Sets the owning group of the Unix-domain socket(s).  (The owning
+        user of the sockets is always the user that starts the
         server.)  In combination with the parameter
         <TT
 CLASS="VARNAME"
@@ -374,7 +381,7 @@ CLASS="TYPE"
 >)</DT
 ><DD
 ><P
->        Sets the access permissions of the Unix-domain socket.  Unix-domain
+>        Sets the access permissions of the Unix-domain socket(s).  Unix-domain
         sockets use the usual Unix file system permission set.
         The parameter value is expected to be a numeric mode
         specified in the format accepted by the
diff --git a/doc/src/sgml/html/runtime-config-short.html b/doc/src/sgml/html/runtime-config-short.html
index 8c39c2f..f0d7080 100644
--- a/doc/src/sgml/html/runtime-config-short.html
+++ b/doc/src/sgml/html/runtime-config-short.html
@@ -330,7 +330,7 @@ CLASS="REPLACEABLE"
 ><TD
 ><TT
 CLASS="LITERAL"
->unix_socket_directory = <TT
+>unix_socket_directories = <TT
 CLASS="REPLACEABLE"
 ><I
 >x</I
diff --git a/doc/src/sgml/man1/postgres.1 b/doc/src/sgml/man1/postgres.1
index 6ec18ee..3378dde 100644
--- a/doc/src/sgml/man1/postgres.1
+++ b/doc/src/sgml/man1/postgres.1
@@ -195,8 +195,10 @@ directly\&.
 .RS 4
 Specifies the directory of the Unix\-domain socket on which
 \fBpostgres\fR
-is to listen for connections from client applications\&. The default is normally
-/tmp, but can be changed at build time\&.
+is to listen for connections from client applications\&. The value can also be a comma\-separated list of directories\&. An empty value specifies not listening on any Unix\-domain sockets, in which case only TCP/IP sockets can be used to connect to the server\&. The default value is normally
+/tmp, but that can be changed at build time\&. Specifying this option is equivalent to setting the
+unix_socket_directories
+configuration parameter\&.
 .RE
 .PP
 \fB\-l\fR
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
index 4e5cd02..a1f36e1 100644
--- a/doc/src/sgml/ref/postgres-ref.sgml
+++ b/doc/src/sgml/ref/postgres-ref.sgml
@@ -254,8 +254,14 @@ PostgreSQL documentation
        <para>
         Specifies the directory of the Unix-domain socket on which
         <command>postgres</command> is to listen for
-        connections from client applications.  The default is normally
-        <filename>/tmp</filename>, but can be changed at build time.
+        connections from client applications.  The value can also be a
+        comma-separated list of directories.  An empty value
+        specifies not listening on any Unix-domain sockets, in which case
+        only TCP/IP sockets can be used to connect to the server.
+        The default value is normally
+        <filename>/tmp</filename>, but that can be changed at build time.
+        Specifying this option is equivalent to setting the <xref
+        linkend="guc-unix-socket-directories"> configuration parameter.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index cfd3532..e6c9eaa 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1800,7 +1800,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   <para>
    The simplest way to prevent spoofing for <literal>local</>
    connections is to use a Unix domain socket directory (<xref
-   linkend="guc-unix-socket-directory">) that has write permission only
+   linkend="guc-unix-socket-directories">) that has write permission only
    for a trusted local user.  This prevents a malicious user from creating
    their own socket file in that directory.  If you are concerned that
    some applications might still reference <filename>/tmp</> for the
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index aeeffd7..b7e0d09 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -42,7 +42,7 @@
  *		StreamServerPort	- Open postmaster's server port
  *		StreamConnection	- Create new connection with client
  *		StreamClose			- Close a client/backend connection
- *		TouchSocketFile		- Protect socket file against /tmp cleaners
+ *		TouchSocketFiles	- Protect socket files against /tmp cleaners
  *		pq_init			- initialize libpq at backend startup
  *		pq_comm_reset	- reset libpq during error recovery
  *		pq_close		- shutdown libpq at backend exit
@@ -103,8 +103,8 @@ int			Unix_socket_permissions;
 char	   *Unix_socket_group;
 
 
-/* Where the Unix socket file is */
-static char sock_path[MAXPGPATH];
+/* Where the Unix socket files are (list of palloc'd strings) */
+static List *sock_paths = NIL;
 
 
 /*
@@ -141,8 +141,8 @@ static int	internal_flush(void);
 static void pq_set_nonblocking(bool nonblocking);
 
 #ifdef HAVE_UNIX_SOCKETS
-static int	Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName);
-static int	Setup_AF_UNIX(void);
+static int	Lock_AF_UNIX(char *unixSocketDir, char *unixSocketPath);
+static int	Setup_AF_UNIX(char *sock_path);
 #endif   /* HAVE_UNIX_SOCKETS */
 
 
@@ -236,29 +236,43 @@ pq_close(int code, Datum arg)
 
 /* StreamDoUnlink()
  * Shutdown routine for backend connection
- * If a Unix socket is used for communication, explicitly close it.
+ * If any Unix sockets are used for communication, explicitly close them.
  */
 #ifdef HAVE_UNIX_SOCKETS
 static void
 StreamDoUnlink(int code, Datum arg)
 {
-	Assert(sock_path[0]);
-	unlink(sock_path);
+	ListCell   *l;
+
+	/* Loop through all created sockets... */
+	foreach(l, sock_paths)
+	{
+		char	   *sock_path = (char *) lfirst(l);
+
+		unlink(sock_path);
+	}
+	/* Since we're about to exit, no need to reclaim storage */
+	sock_paths = NIL;
 }
 #endif   /* HAVE_UNIX_SOCKETS */
 
 /*
  * StreamServerPort -- open a "listening" port to accept connections.
  *
- * Successfully opened sockets are added to the ListenSocket[] array,
- * at the first position that isn't PGINVALID_SOCKET.
+ * family should be AF_UNIX or AF_UNSPEC; portNumber is the port number.
+ * For AF_UNIX ports, hostName should be NULL and unixSocketDir must be
+ * specified.  For TCP ports, hostName is either NULL for all interfaces or
+ * the interface to listen on, and unixSocketDir is ignored (can be NULL).
+ *
+ * Successfully opened sockets are added to the ListenSocket[] array (of
+ * length MaxListen), at the first position that isn't PGINVALID_SOCKET.
  *
  * RETURNS: STATUS_OK or STATUS_ERROR
  */
 
 int
 StreamServerPort(int family, char *hostName, unsigned short portNumber,
-				 char *unixSocketName,
+				 char *unixSocketDir,
 				 pgsocket ListenSocket[], int MaxListen)
 {
 	pgsocket	fd;
@@ -275,6 +289,9 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
 	int			listen_index = 0;
 	int			added = 0;
 
+#ifdef HAVE_UNIX_SOCKETS
+	char		unixSocketPath[MAXPGPATH];
+#endif
 #if !defined(WIN32) || defined(IPV6_V6ONLY)
 	int			one = 1;
 #endif
@@ -288,10 +305,22 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
 #ifdef HAVE_UNIX_SOCKETS
 	if (family == AF_UNIX)
 	{
-		/* Lock_AF_UNIX will also fill in sock_path. */
-		if (Lock_AF_UNIX(portNumber, unixSocketName) != STATUS_OK)
+		/*
+		 * Create unixSocketPath from portNumber and unixSocketDir and lock
+		 * that file path
+		 */
+		UNIXSOCK_PATH(unixSocketPath, portNumber, unixSocketDir);
+		if (strlen(unixSocketPath) >= UNIXSOCK_PATH_BUFLEN)
+		{
+			ereport(LOG,
+					(errmsg("Unix-domain socket path \"%s\" is too long (maximum %d bytes)",
+							unixSocketPath,
+							(int) (UNIXSOCK_PATH_BUFLEN - 1))));
 			return STATUS_ERROR;
-		service = sock_path;
+		}
+		if (Lock_AF_UNIX(unixSocketDir, unixSocketPath) != STATUS_OK)
+			return STATUS_ERROR;
+		service = unixSocketPath;
 	}
 	else
 #endif   /* HAVE_UNIX_SOCKETS */
@@ -434,7 +463,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
 					 (IS_AF_UNIX(addr->ai_family)) ?
 				  errhint("Is another postmaster already running on port %d?"
 						  " If not, remove socket file \"%s\" and retry.",
-						  (int) portNumber, sock_path) :
+						  (int) portNumber, service) :
 				  errhint("Is another postmaster already running on port %d?"
 						  " If not, wait a few seconds and retry.",
 						  (int) portNumber)));
@@ -445,7 +474,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
 #ifdef HAVE_UNIX_SOCKETS
 		if (addr->ai_family == AF_UNIX)
 		{
-			if (Setup_AF_UNIX() != STATUS_OK)
+			if (Setup_AF_UNIX(service) != STATUS_OK)
 			{
 				closesocket(fd);
 				break;
@@ -492,18 +521,8 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
  * Lock_AF_UNIX -- configure unix socket file path
  */
 static int
-Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName)
+Lock_AF_UNIX(char *unixSocketDir, char *unixSocketPath)
 {
-	UNIXSOCK_PATH(sock_path, portNumber, unixSocketName);
-	if (strlen(sock_path) >= UNIXSOCK_PATH_BUFLEN)
-	{
-		ereport(LOG,
-				(errmsg("Unix-domain socket path \"%s\" is too long (maximum %d bytes)",
-						sock_path,
-						(int) (UNIXSOCK_PATH_BUFLEN - 1))));
-		return STATUS_ERROR;
-	}
-
 	/*
 	 * Grab an interlock file associated with the socket file.
 	 *
@@ -512,13 +531,23 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName)
 	 * more portable, and second, it lets us remove any pre-existing socket
 	 * file without race conditions.
 	 */
-	CreateSocketLockFile(sock_path, true);
+	CreateSocketLockFile(unixSocketPath, true, unixSocketDir);
 
 	/*
 	 * Once we have the interlock, we can safely delete any pre-existing
 	 * socket file to avoid failure at bind() time.
 	 */
-	unlink(sock_path);
+	unlink(unixSocketPath);
+
+	/*
+	 * Arrange to unlink the socket file(s) at proc_exit.  If this is the
+	 * first one, set up the on_proc_exit function to do it; then add this
+	 * socket file to the list of files to unlink.
+	 */
+	if (sock_paths == NIL)
+		on_proc_exit(StreamDoUnlink, 0);
+
+	sock_paths = lappend(sock_paths, pstrdup(unixSocketPath));
 
 	return STATUS_OK;
 }
@@ -528,11 +557,8 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName)
  * Setup_AF_UNIX -- configure unix socket permissions
  */
 static int
-Setup_AF_UNIX(void)
+Setup_AF_UNIX(char *sock_path)
 {
-	/* Arrange to unlink the socket file at exit */
-	on_proc_exit(StreamDoUnlink, 0);
-
 	/*
 	 * Fix socket ownership/permission if requested.  Note we must do this
 	 * before we listen() to avoid a window where unwanted connections could
@@ -714,20 +740,24 @@ StreamClose(pgsocket sock)
 }
 
 /*
- * TouchSocketFile -- mark socket file as recently accessed
+ * TouchSocketFiles -- mark socket files as recently accessed
  *
  * This routine should be called every so often to ensure that the socket
- * file has a recent mod date (ordinary operations on sockets usually won't
- * change the mod date).  That saves it from being removed by
+ * files have a recent mod date (ordinary operations on sockets usually won't
+ * change the mod date).  That saves them from being removed by
  * overenthusiastic /tmp-directory-cleaner daemons.  (Another reason we should
  * never have put the socket file in /tmp...)
  */
 void
-TouchSocketFile(void)
+TouchSocketFiles(void)
 {
-	/* Do nothing if we did not create a socket... */
-	if (sock_path[0] != '\0')
+	ListCell   *l;
+
+	/* Loop through all created sockets... */
+	foreach(l, sock_paths)
 	{
+		char	   *sock_path = (char *) lfirst(l);
+
 		/*
 		 * utime() is POSIX standard, utimes() is a common alternative. If we
 		 * have neither, there's no way to affect the mod or access time of
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 07aca31..e84a18c 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -160,7 +160,9 @@ static Backend *ShmemBackendArray;
 
 /* The socket number we are listening for connections on */
 int			PostPortNumber;
-char	   *UnixSocketDir;
+/* The directory names for Unix socket(s) */
+char	   *Unix_socket_directories;
+/* The TCP listen address(es) */
 char	   *ListenAddresses;
 
 /*
@@ -636,7 +638,7 @@ PostmasterMain(int argc, char *argv[])
 				break;
 
 			case 'k':
-				SetConfigOption("unix_socket_directory", optarg, PGC_POSTMASTER, PGC_S_ARGV);
+				SetConfigOption("unix_socket_directories", optarg, PGC_POSTMASTER, PGC_S_ARGV);
 				break;
 
 			case 'l':
@@ -880,7 +882,7 @@ PostmasterMain(int argc, char *argv[])
 		/* Need a modifiable copy of ListenAddresses */
 		rawstring = pstrdup(ListenAddresses);
 
-		/* Parse string into list of identifiers */
+		/* Parse string into list of hostnames */
 		if (!SplitIdentifierString(rawstring, ',', &elemlist))
 		{
 			/* syntax error in list */
@@ -896,12 +898,12 @@ PostmasterMain(int argc, char *argv[])
 			if (strcmp(curhost, "*") == 0)
 				status = StreamServerPort(AF_UNSPEC, NULL,
 										  (unsigned short) PostPortNumber,
-										  UnixSocketDir,
+										  NULL,
 										  ListenSocket, MAXLISTEN);
 			else
 				status = StreamServerPort(AF_UNSPEC, curhost,
 										  (unsigned short) PostPortNumber,
-										  UnixSocketDir,
+										  NULL,
 										  ListenSocket, MAXLISTEN);
 
 			if (status == STATUS_OK)
@@ -920,7 +922,7 @@ PostmasterMain(int argc, char *argv[])
 								curhost)));
 		}
 
-		if (!success && list_length(elemlist))
+		if (!success && elemlist != NIL)
 			ereport(FATAL,
 					(errmsg("could not create any TCP/IP sockets")));
 
@@ -967,13 +969,54 @@ PostmasterMain(int argc, char *argv[])
 #endif
 
 #ifdef HAVE_UNIX_SOCKETS
-	status = StreamServerPort(AF_UNIX, NULL,
-							  (unsigned short) PostPortNumber,
-							  UnixSocketDir,
-							  ListenSocket, MAXLISTEN);
-	if (status != STATUS_OK)
-		ereport(WARNING,
-				(errmsg("could not create Unix-domain socket")));
+	if (Unix_socket_directories)
+	{
+		char	   *rawstring;
+		List	   *elemlist;
+		ListCell   *l;
+		int			success = 0;
+
+		/* Need a modifiable copy of Unix_socket_directories */
+		rawstring = pstrdup(Unix_socket_directories);
+
+		/* Parse string into list of directories */
+		if (!SplitDirectoriesString(rawstring, ',', &elemlist))
+		{
+			/* syntax error in list */
+			ereport(FATAL,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("invalid list syntax for \"unix_socket_directories\"")));
+		}
+
+		foreach(l, elemlist)
+		{
+			char	   *socketdir = (char *) lfirst(l);
+
+			status = StreamServerPort(AF_UNIX, NULL,
+									  (unsigned short) PostPortNumber,
+									  socketdir,
+									  ListenSocket, MAXLISTEN);
+
+			if (status == STATUS_OK)
+			{
+				success++;
+				/* record the first successful Unix socket in lockfile */
+				if (success == 1)
+					AddToDataDirLockFile(LOCK_FILE_LINE_SOCKET_DIR, socketdir);
+			}
+			else
+				ereport(WARNING,
+						(errmsg("could not create Unix-domain socket in directory \"%s\"",
+								socketdir)));
+		}
+
+		if (!success && elemlist != NIL)
+			ereport(FATAL,
+					(errmsg("could not create any Unix-domain sockets")));
+
+		list_free_deep(elemlist);
+		pfree(rawstring);
+	}
 #endif
 
 	/*
@@ -1452,15 +1495,15 @@ ServerLoop(void)
 		}
 
 		/*
-		 * Touch the socket and lock file every 58 minutes, to ensure that
+		 * Touch Unix socket and lock files every 58 minutes, to ensure that
 		 * they are not removed by overzealous /tmp-cleaning tasks.  We assume
 		 * no one runs cleaners with cutoff times of less than an hour ...
 		 */
 		now = time(NULL);
 		if (now - last_touch_time >= 58 * SECS_PER_MINUTE)
 		{
-			TouchSocketFile();
-			TouchSocketLockFile();
+			TouchSocketFiles();
+			TouchSocketLockFiles();
 			last_touch_time = now;
 		}
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 39017ff..318ac38 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3393,7 +3393,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx,
 				break;
 
 			case 'k':
-				SetConfigOption("unix_socket_directory", optarg, ctx, gucsource);
+				SetConfigOption("unix_socket_directories", optarg, ctx, gucsource);
 				break;
 
 			case 'l':
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index d4abfe3..e8217ac 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2446,6 +2446,119 @@ SplitIdentifierString(char *rawstring, char separator,
 }
 
 
+/*
+ * SplitDirectoriesString --- parse a string containing directory names
+ *
+ * This is similar to SplitIdentifierString, except that the parsing
+ * rules are meant to handle pathnames instead of identifiers: there is
+ * no downcasing, embedded spaces are allowed, the max length is MAXPGPATH-1,
+ * and we apply canonicalize_path() to each extracted string.  Because of the
+ * last, the returned strings are separately palloc'd rather than being
+ * pointers into rawstring --- but we still scribble on rawstring.
+ *
+ * Inputs:
+ *	rawstring: the input string; must be modifiable!
+ *	separator: the separator punctuation expected between directories
+ *			   (typically ',' or ';').	Whitespace may also appear around
+ *			   directories.
+ * Outputs:
+ *	namelist: filled with a palloc'd list of directory names.
+ *			  Caller should list_free_deep() this even on error return.
+ *
+ * Returns TRUE if okay, FALSE if there is a syntax error in the string.
+ *
+ * Note that an empty string is considered okay here.
+ */
+bool
+SplitDirectoriesString(char *rawstring, char separator,
+					   List **namelist)
+{
+	char	   *nextp = rawstring;
+	bool		done = false;
+
+	*namelist = NIL;
+
+	while (isspace((unsigned char) *nextp))
+		nextp++;				/* skip leading whitespace */
+
+	if (*nextp == '\0')
+		return true;			/* allow empty string */
+
+	/* At the top of the loop, we are at start of a new directory. */
+	do
+	{
+		char	   *curname;
+		char	   *endp;
+
+		if (*nextp == '\"')
+		{
+			/* Quoted name --- collapse quote-quote pairs */
+			curname = nextp + 1;
+			for (;;)
+			{
+				endp = strchr(nextp + 1, '\"');
+				if (endp == NULL)
+					return false;		/* mismatched quotes */
+				if (endp[1] != '\"')
+					break;		/* found end of quoted name */
+				/* Collapse adjacent quotes into one quote, and look again */
+				memmove(endp, endp + 1, strlen(endp));
+				nextp = endp;
+			}
+			/* endp now points at the terminating quote */
+			nextp = endp + 1;
+		}
+		else
+		{
+			/* Unquoted name --- extends to separator or end of string */
+			curname = endp = nextp;
+			while (*nextp && *nextp != separator)
+			{
+				/* trailing whitespace should not be included in name */
+				if (!isspace((unsigned char) *nextp))
+					endp = nextp + 1;
+				nextp++;
+			}
+			if (curname == endp)
+				return false;	/* empty unquoted name not allowed */
+		}
+
+		while (isspace((unsigned char) *nextp))
+			nextp++;			/* skip trailing whitespace */
+
+		if (*nextp == separator)
+		{
+			nextp++;
+			while (isspace((unsigned char) *nextp))
+				nextp++;		/* skip leading whitespace for next */
+			/* we expect another name, so done remains false */
+		}
+		else if (*nextp == '\0')
+			done = true;
+		else
+			return false;		/* invalid syntax */
+
+		/* Now safe to overwrite separator with a null */
+		*endp = '\0';
+
+		/* Truncate path if it's overlength */
+		if (strlen(curname) >= MAXPGPATH)
+			curname[MAXPGPATH - 1] = '\0';
+
+		/*
+		 * Finished isolating current name --- add it to list
+		 */
+		curname = pstrdup(curname);
+		canonicalize_path(curname);
+		*namelist = lappend(*namelist, curname);
+
+		/* Loop back if we didn't reach end of string */
+	} while (!done);
+
+	return true;
+}
+
+
 /*****************************************************************************
  *	Comparison Functions used for bytea
  *
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index f994af6..db5303c 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -49,8 +49,8 @@
 
 ProcessingMode Mode = InitProcessing;
 
-/* Note: we rely on this to initialize as zeroes */
-static char socketLockFile[MAXPGPATH];
+/* List of lock files to be removed at proc exit */
+static List *lock_files = NIL;
 
 
 /* ----------------------------------------------------------------
@@ -640,32 +640,35 @@ GetUserNameFromId(Oid roleid)
  */
 
 /*
- * proc_exit callback to remove a lockfile.
+ * proc_exit callback to remove lockfiles.
  */
 static void
-UnlinkLockFile(int status, Datum filename)
+UnlinkLockFiles(int status, Datum arg)
 {
-	char	   *fname = (char *) DatumGetPointer(filename);
+	ListCell   *l;
 
-	if (fname != NULL)
+	foreach(l, lock_files)
 	{
-		if (unlink(fname) != 0)
-		{
-			/* Should we complain if the unlink fails? */
-		}
-		free(fname);
+		char	   *curfile = (char *) lfirst(l);
+
+		unlink(curfile);
+		/* Should we complain if the unlink fails? */
 	}
+	/* Since we're about to exit, no need to reclaim storage */
+	lock_files = NIL;
 }
 
 /*
  * Create a lockfile.
  *
- * filename is the name of the lockfile to create.
+ * filename is the path name of the lockfile to create.
  * amPostmaster is used to determine how to encode the output PID.
+ * socketDir is the Unix socket directory path to include (possibly empty).
  * isDDLock and refName are used to determine what error message to produce.
  */
 static void
 CreateLockFile(const char *filename, bool amPostmaster,
+			   const char *socketDir,
 			   bool isDDLock, const char *refName)
 {
 	int			fd;
@@ -891,12 +894,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
 			 DataDir,
 			 (long) MyStartTime,
 			 PostPortNumber,
-#ifdef HAVE_UNIX_SOCKETS
-			 (*UnixSocketDir != '\0') ? UnixSocketDir : DEFAULT_PGSOCKET_DIR
-#else
-			 ""
-#endif
-		);
+			 socketDir);
 
 	/*
 	 * In a standalone backend, the next line (LOCK_FILE_LINE_LISTEN_ADDR)
@@ -941,9 +939,14 @@ CreateLockFile(const char *filename, bool amPostmaster,
 	}
 
 	/*
-	 * Arrange for automatic removal of lockfile at proc_exit.
+	 * Arrange to unlink the lock file(s) at proc_exit.  If this is the
+	 * first one, set up the on_proc_exit function to do it; then add this
+	 * lock file to the list of files to unlink.
 	 */
-	on_proc_exit(UnlinkLockFile, PointerGetDatum(strdup(filename)));
+	if (lock_files == NIL)
+		on_proc_exit(UnlinkLockFiles, 0);
+
+	lock_files = lappend(lock_files, pstrdup(filename));
 }
 
 /*
@@ -952,41 +955,50 @@ CreateLockFile(const char *filename, bool amPostmaster,
  * When this is called, we must have already switched the working
  * directory to DataDir, so we can just use a relative path.  This
  * helps ensure that we are locking the directory we should be.
+ *
+ * Note that the socket directory path line is initially written as empty.
+ * postmaster.c will rewrite it upon creating the first Unix socket.
  */
 void
 CreateDataDirLockFile(bool amPostmaster)
 {
-	CreateLockFile(DIRECTORY_LOCK_FILE, amPostmaster, true, DataDir);
+	CreateLockFile(DIRECTORY_LOCK_FILE, amPostmaster, "", true, DataDir);
 }
 
 /*
  * Create a lockfile for the specified Unix socket file.
  */
 void
-CreateSocketLockFile(const char *socketfile, bool amPostmaster)
+CreateSocketLockFile(const char *socketfile, bool amPostmaster,
+					 const char *socketDir)
 {
 	char		lockfile[MAXPGPATH];
 
 	snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile);
-	CreateLockFile(lockfile, amPostmaster, false, socketfile);
-	/* Save name of lockfile for TouchSocketLockFile */
-	strcpy(socketLockFile, lockfile);
+	CreateLockFile(lockfile, amPostmaster, socketDir, false, socketfile);
 }
 
 /*
- * TouchSocketLockFile -- mark socket lock file as recently accessed
+ * TouchSocketLockFiles -- mark socket lock files as recently accessed
  *
- * This routine should be called every so often to ensure that the lock file
- * has a recent mod or access date.  That saves it
+ * This routine should be called every so often to ensure that the socket
+ * lock files have a recent mod or access date.  That saves them
  * from being removed by overenthusiastic /tmp-directory-cleaner daemons.
  * (Another reason we should never have put the socket file in /tmp...)
  */
 void
-TouchSocketLockFile(void)
+TouchSocketLockFiles(void)
 {
-	/* Do nothing if we did not create a socket... */
-	if (socketLockFile[0] != '\0')
+	ListCell   *l;
+
+	foreach(l, lock_files)
 	{
+		char	   *socketLockFile = (char *) lfirst(l);
+
+		/* No need to touch the data directory lock file, we trust */
+		if (strcmp(socketLockFile, DIRECTORY_LOCK_FILE) == 0)
+			continue;
+
 		/*
 		 * utime() is POSIX standard, utimes() is a common alternative; if we
 		 * have neither, fall back to actually reading the file (which only
@@ -1018,8 +1030,10 @@ TouchSocketLockFile(void)
  * Add (or replace) a line in the data directory lock file.
  * The given string should not include a trailing newline.
  *
- * Caution: this erases all following lines.  In current usage that is OK
- * because lines are added in order.  We could improve it if needed.
+ * Note: because we don't truncate the file, if we were to rewrite a line
+ * with less data than it had before, there would be garbage after the last
+ * line.  We don't ever actually do that, so not worth adding another kernel
+ * call to cover the possibility.
  */
 void
 AddToDataDirLockFile(int target_line, const char *str)
@@ -1027,8 +1041,10 @@ AddToDataDirLockFile(int target_line, const char *str)
 	int			fd;
 	int			len;
 	int			lineno;
-	char	   *ptr;
-	char		buffer[BLCKSZ];
+	char	   *srcptr;
+	char	   *destptr;
+	char		srcbuffer[BLCKSZ];
+	char		destbuffer[BLCKSZ];
 
 	fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0);
 	if (fd < 0)
@@ -1039,7 +1055,7 @@ AddToDataDirLockFile(int target_line, const char *str)
 						DIRECTORY_LOCK_FILE)));
 		return;
 	}
-	len = read(fd, buffer, sizeof(buffer) - 1);
+	len = read(fd, srcbuffer, sizeof(srcbuffer) - 1);
 	if (len < 0)
 	{
 		ereport(LOG,
@@ -1049,37 +1065,51 @@ AddToDataDirLockFile(int target_line, const char *str)
 		close(fd);
 		return;
 	}
-	buffer[len] = '\0';
+	srcbuffer[len] = '\0';
 
 	/*
-	 * Skip over lines we are not supposed to rewrite.
+	 * Advance over lines we are not supposed to rewrite, then copy them
+	 * to destbuffer.
 	 */
-	ptr = buffer;
+	srcptr = srcbuffer;
 	for (lineno = 1; lineno < target_line; lineno++)
 	{
-		if ((ptr = strchr(ptr, '\n')) == NULL)
+		if ((srcptr = strchr(srcptr, '\n')) == NULL)
 		{
 			elog(LOG, "incomplete data in \"%s\": found only %d newlines while trying to add line %d",
 				 DIRECTORY_LOCK_FILE, lineno - 1, target_line);
 			close(fd);
 			return;
 		}
-		ptr++;
+		srcptr++;
 	}
+	memcpy(destbuffer, srcbuffer, srcptr - srcbuffer);
+	destptr = destbuffer + (srcptr - srcbuffer);
 
 	/*
 	 * Write or rewrite the target line.
 	 */
-	snprintf(ptr, buffer + sizeof(buffer) - ptr, "%s\n", str);
+	snprintf(destptr, destbuffer + sizeof(destbuffer) - destptr, "%s\n", str);
+	destptr += strlen(destptr);
+
+	/*
+	 * If there are more lines in the old file, append them to destbuffer.
+	 */
+	if ((srcptr = strchr(srcptr, '\n')) != NULL)
+	{
+		srcptr++;
+		snprintf(destptr, destbuffer + sizeof(destbuffer) - destptr, "%s",
+				 srcptr);
+	}
 
 	/*
 	 * And rewrite the data.  Since we write in a single kernel call, this
 	 * update should appear atomic to onlookers.
 	 */
-	len = strlen(buffer);
+	len = strlen(destbuffer);
 	errno = 0;
 	if (lseek(fd, (off_t) 0, SEEK_SET) != 0 ||
-		(int) write(fd, buffer, len) != len)
+		(int) write(fd, destbuffer, len) != len)
 	{
 		/* if write didn't set errno, assume problem is no disk space */
 		if (errno == 0)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e5ee0f8..1428cb6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2894,14 +2894,18 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"unix_socket_directory", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
-			gettext_noop("Sets the directory where the Unix-domain socket will be created."),
+		{"unix_socket_directories", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
+			gettext_noop("Sets the directories where Unix-domain sockets will be created."),
 			NULL,
 			GUC_SUPERUSER_ONLY
 		},
-		&UnixSocketDir,
+		&Unix_socket_directories,
+#ifdef HAVE_UNIX_SOCKETS
+		DEFAULT_PGSOCKET_DIR,
+#else
 		"",
-		check_canonical_path, NULL, NULL
+#endif
+		NULL, NULL, NULL
 	},
 
 	{
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 013a91a..c0ab61c 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -67,7 +67,8 @@
 # Note:  Increasing max_connections costs ~400 bytes of shared memory per
 # connection slot, plus lock space (see max_locks_per_transaction).
 #superuser_reserved_connections = 3	# (change requires restart)
-#unix_socket_directory = ''		# (change requires restart)
+#unix_socket_directories = '/tmp'	# comma-separated list of directories
+					# (change requires restart)
 #unix_socket_group = ''			# (change requires restart)
 #unix_socket_permissions = 0777		# begin with 0 to use octal notation
 					# (change requires restart)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 23fa468..452ac0b 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -991,7 +991,7 @@ static void
 setup_config(void)
 {
 	char	  **conflines;
-	char		repltok[TZ_STRLEN_MAX + 100];
+	char		repltok[MAXPGPATH];
 	char		path[MAXPGPATH];
 	const char *default_timezone;
 
@@ -1013,6 +1013,15 @@ setup_config(void)
 				 n_buffers * (BLCKSZ / 1024));
 	conflines = replace_token(conflines, "#shared_buffers = 32MB", repltok);
 
+#ifdef HAVE_UNIX_SOCKETS
+	snprintf(repltok, sizeof(repltok), "#unix_socket_directories = '%s'",
+			 DEFAULT_PGSOCKET_DIR);
+#else
+	snprintf(repltok, sizeof(repltok), "#unix_socket_directories = ''");
+#endif
+	conflines = replace_token(conflines, "#unix_socket_directories = '/tmp'",
+							  repltok);
+
 #if DEF_PGPORT != 5432
 	snprintf(repltok, sizeof(repltok), "#port = %d", DEF_PGPORT);
 	conflines = replace_token(conflines, "#port = 5432", repltok);
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 66ef6bd..b815ef2 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -561,7 +561,7 @@ test_postmaster_connection(bool do_checkpoint)
 						hostaddr = optlines[LOCK_FILE_LINE_LISTEN_ADDR - 1];
 
 						/*
-						 * While unix_socket_directory can accept relative
+						 * While unix_socket_directories can accept relative
 						 * directories, libpq's host parameter must have a
 						 * leading slash to indicate a socket directory.  So,
 						 * ignore sockdir if it's relative, and try to use TCP
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 6dd91ba..2bfec2b 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -44,12 +44,12 @@ typedef struct
 /*
  * prototypes for functions in pqcomm.c
  */
-extern int StreamServerPort(int family, char *hostName,
-	unsigned short portNumber, char *unixSocketName, pgsocket ListenSocket[],
-				 int MaxListen);
+extern int	StreamServerPort(int family, char *hostName,
+				 unsigned short portNumber, char *unixSocketDir,
+				 pgsocket ListenSocket[], int MaxListen);
 extern int	StreamConnection(pgsocket server_fd, Port *port);
 extern void StreamClose(pgsocket sock);
-extern void TouchSocketFile(void);
+extern void TouchSocketFiles(void);
 extern void pq_init(void);
 extern void pq_comm_reset(void);
 extern int	pq_getbytes(char *s, size_t len);
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 1051ca4..e6c663c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -424,7 +424,7 @@ extern char *local_preload_libraries_string;
  *		2	data directory path
  *		3	postmaster start timestamp (time_t representation)
  *		4	port number
- *		5	socket directory path (empty on Windows)
+ *		5	first Unix socket directory path (empty if none)
  *		6	first listen_address (IP address or "*"; empty if no TCP port)
  *		7	shared memory key (not present on Windows)
  *
@@ -442,8 +442,9 @@ extern char *local_preload_libraries_string;
 #define LOCK_FILE_LINE_SHMEM_KEY	7
 
 extern void CreateDataDirLockFile(bool amPostmaster);
-extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster);
-extern void TouchSocketLockFile(void);
+extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster,
+					 const char *socketDir);
+extern void TouchSocketLockFiles(void);
 extern void AddToDataDirLockFile(int target_line, const char *str);
 extern void ValidatePgVersion(const char *path);
 extern void process_shared_preload_libraries(void);
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 683ce3c..080a8ca 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -19,7 +19,7 @@ extern int	ReservedBackends;
 extern int	PostPortNumber;
 extern int	Unix_socket_permissions;
 extern char *Unix_socket_group;
-extern char *UnixSocketDir;
+extern char *Unix_socket_directories;
 extern char *ListenAddresses;
 extern bool ClientAuthInProgress;
 extern int	PreAuthDelay;
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 4bf07e4..c830243 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -754,6 +754,8 @@ extern int	varstr_cmp(char *arg1, int len1, char *arg2, int len2, Oid collid);
 extern List *textToQualifiedNameList(text *textval);
 extern bool SplitIdentifierString(char *rawstring, char separator,
 					  List **namelist);
+extern bool SplitDirectoriesString(char *rawstring, char separator,
+					   List **namelist);
 extern Datum replace_text(PG_FUNCTION_ARGS);
 extern text *replace_text_regexp(text *src_text, void *regexp,
 					text *replace_text, bool glob);
diff --git a/doc/src/sgml/html-stamp b/doc/src/sgml/html-stamp
index e69de29..fcf9276 100644
--- a/doc/src/sgml/html-stamp
+++ b/doc/src/sgml/html-stamp
@@ -0,0 +1 @@
+hack
diff --git a/doc/src/sgml/man-stamp b/doc/src/sgml/man-stamp
index e69de29..fcf9276 100644
--- a/doc/src/sgml/man-stamp
+++ b/doc/src/sgml/man-stamp
@@ -0,0 +1 @@
+hack