Add support for abstract Unix-domain sockets
authorPeter Eisentraut <[email protected]>
Wed, 25 Nov 2020 07:14:23 +0000 (08:14 +0100)
committerPeter Eisentraut <[email protected]>
Wed, 25 Nov 2020 07:33:57 +0000 (08:33 +0100)
This is a variant of the normal Unix-domain sockets that don't use the
file system but a separate "abstract" namespace.  At the user
interface, such sockets are represented by names starting with "@".
Supported on Linux and Windows right now.

Reviewed-by: Michael Paquier <[email protected]>
Discussion: https://www.postgresql.org/message-id/flat/6dee8574-b0ad-fc49-9c8c-2edc796f0033@2ndquadrant.com

doc/src/sgml/config.sgml
doc/src/sgml/libpq.sgml
src/backend/libpq/pqcomm.c
src/bin/psql/command.c
src/bin/psql/prompt.c
src/common/ip.c
src/include/libpq/pqcomm.h
src/interfaces/libpq/fe-connect.c

index 3795c570048c7a0a8945770080b06c651ff273a5..f810789ea82bb40cdab53e24b6b99aede52460bf 100644 (file)
@@ -749,6 +749,21 @@ include_dir 'conf.d'
         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.
+       </para>
+
+       <para>
+        A value that starts with <literal>@</literal> specifies that a
+        Unix-domain socket in the abstract namespace should be created
+        (currently supported on Linux and Windows).  In that case, this value
+        does not specify a <quote>directory</quote> but a prefix from which
+        the actual socket name is computed in the same manner as for the
+        file-system namespace.  While the abstract socket name prefix can be
+        chosen freely, since it is not a file-system location, the convention
+        is to nonetheless use file-system-like values such as
+        <literal>@/tmp</literal>.
+       </para>
+
+       <para>
         The default value is normally
         <filename>/tmp</filename>, but that can be changed at build time.
         On Windows, the default is empty, which means no Unix-domain socket is
@@ -763,6 +778,7 @@ include_dir 'conf.d'
         named <literal>.s.PGSQL.<replaceable>nnnn</replaceable>.lock</literal> will be
         created in each of the <varname>unix_socket_directories</varname> directories.
         Neither file should ever be removed manually.
+        For sockets in the abstract namespace, no lock file is created.
        </para>
       </listitem>
      </varlistentry>
@@ -787,7 +803,8 @@ include_dir 'conf.d'
 
        <para>
         This parameter is not supported on Windows.  Any setting will be
-        ignored.
+        ignored.  Also, sockets in the abstract namespace have no file owner,
+        so this setting is also ignored in that case.
        </para>
       </listitem>
      </varlistentry>
@@ -834,6 +851,11 @@ include_dir 'conf.d'
         similar effect by pointing <varname>unix_socket_directories</varname> to a
         directory having search permission limited to the desired audience.
        </para>
+
+       <para>
+        Sockets in the abstract namespace have no file permissions, so this
+        setting is also ignored in that case.
+       </para>
       </listitem>
      </varlistentry>
 
index 9d4b6ab4a89dfc99d7ba63b8c0fb3fbadff03922..06bd412044c649bd7b93084f264cb2147e66160b 100644 (file)
@@ -1031,7 +1031,10 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         communication; the value is the name of the directory in which the
         socket file is stored.  (On Unix, an absolute path name begins with a
         slash.  On Windows, paths starting with drive letters are also
-        recognized.)  The default behavior when <literal>host</literal> is not
+        recognized.)  If the host name starts with <literal>@</literal>, it is
+        taken as a Unix-domain socket in the abstract namespace (currently
+        supported on Linux and Windows).
+        The default behavior when <literal>host</literal> is not
         specified, or is empty, is to connect to a Unix-domain
         socket<indexterm><primary>Unix domain socket</primary></indexterm> in
         <filename>/tmp</filename> (or whatever socket directory was specified
index d7de962a04ce69a394381cdff03ceb82dec8e262..a9a52d48f9127d70a528981f8942bd2a0bcbaa2a 100644 (file)
@@ -611,6 +611,10 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
 static int
 Lock_AF_UNIX(const char *unixSocketDir, const char *unixSocketPath)
 {
+   /* no lock file for abstract sockets */
+   if (unixSocketPath[0] == '@')
+       return STATUS_OK;
+
    /*
     * Grab an interlock file associated with the socket file.
     *
@@ -642,6 +646,10 @@ Lock_AF_UNIX(const char *unixSocketDir, const char *unixSocketPath)
 static int
 Setup_AF_UNIX(const char *sock_path)
 {
+   /* no file system permissions for abstract sockets */
+   if (sock_path[0] == '@')
+       return STATUS_OK;
+
    /*
     * Fix socket ownership/permission if requested.  Note we must do this
     * before we listen() to avoid a window where unwanted connections could
index c7a83d5dfc59292524e853032393ef538b4a2062..55b349d55a35bfa94d7df079455cc1006f471303 100644 (file)
@@ -37,6 +37,7 @@
 #include "input.h"
 #include "large_obj.h"
 #include "libpq-fe.h"
+#include "libpq/pqcomm.h"
 #include "mainloop.h"
 #include "portability/instr_time.h"
 #include "pqexpbuffer.h"
@@ -604,12 +605,9 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
            char       *host = PQhost(pset.db);
            char       *hostaddr = PQhostaddr(pset.db);
 
-           /*
-            * If the host is an absolute path, the connection is via socket
-            * unless overridden by hostaddr
-            */
-           if (is_absolute_path(host))
+           if (is_unixsock_path(host))
            {
+               /* hostaddr overrides host */
                if (hostaddr && *hostaddr)
                    printf(_("You are connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
                           db, PQuser(pset.db), hostaddr, PQport(pset.db));
@@ -3407,12 +3405,9 @@ do_connect(enum trivalue reuse_previous_specification,
            char       *host = PQhost(pset.db);
            char       *hostaddr = PQhostaddr(pset.db);
 
-           /*
-            * If the host is an absolute path, the connection is via socket
-            * unless overridden by hostaddr
-            */
-           if (is_absolute_path(host))
+           if (is_unixsock_path(host))
            {
+               /* hostaddr overrides host */
                if (hostaddr && *hostaddr)
                    printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
                           PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db));
index ef503ec41bb48bfba1a8d91567b15721a219d337..f42c3dfc7488d15645c25fb4d2c1d18bafd2117f 100644 (file)
@@ -15,6 +15,7 @@
 #include "common.h"
 #include "common/string.h"
 #include "input.h"
+#include "libpq/pqcomm.h"
 #include "prompt.h"
 #include "settings.h"
 
@@ -136,7 +137,7 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
                        const char *host = PQhost(pset.db);
 
                        /* INET socket */
-                       if (host && host[0] && !is_absolute_path(host))
+                       if (host && host[0] && !is_unixsock_path(host))
                        {
                            strlcpy(buf, host, sizeof(buf));
                            if (*p == 'm')
index 69fcca8479df15d30b6fc332370179a10e98f961..bcc779e00c313920a4d24c476e4d6b05bc24f51f 100644 (file)
@@ -217,6 +217,21 @@ getaddrinfo_unix(const char *path, const struct addrinfo *hintsp,
 
    strcpy(unp->sun_path, path);
 
+   /*
+    * If the supplied path starts with @, replace that with a zero byte for
+    * the internal representation.  In that mode, the entire sun_path is the
+    * address, including trailing zero bytes.  But we set the address length
+    * to only include the length of the original string.  That way the
+    * trailing zero bytes won't show up in any network or socket lists of the
+    * operating system.  This is just a convention, also followed by other
+    * packages.
+    */
+   if (path[0] == '@')
+   {
+       unp->sun_path[0] = '\0';
+       aip->ai_addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(path);
+   }
+
 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
    unp->sun_len = sizeof(struct sockaddr_un);
 #endif
@@ -249,7 +264,14 @@ getnameinfo_unix(const struct sockaddr_un *sa, int salen,
 
    if (service)
    {
-       ret = snprintf(service, servicelen, "%s", sa->sun_path);
+       /*
+        * Check whether it looks like an abstract socket, but it could also
+        * just be an empty string.
+        */
+       if (sa->sun_path[0] == '\0' && sa->sun_path[1] != '\0')
+           ret = snprintf(service, servicelen, "@%s", sa->sun_path + 1);
+       else
+           ret = snprintf(service, servicelen, "%s", sa->sun_path);
        if (ret < 0 || ret >= servicelen)
            return EAI_MEMORY;
    }
index 781d86c8efa6d7120a10997578b501302fda5e09..cf967c39871cde8387e34924103edbad938459c2 100644 (file)
@@ -85,6 +85,15 @@ typedef struct
  */
 #define UNIXSOCK_PATH_BUFLEN sizeof(((struct sockaddr_un *) NULL)->sun_path)
 
+/*
+ * A host that looks either like an absolute path or starts with @ is
+ * interpreted as a Unix-domain socket address.
+ */
+static inline bool
+is_unixsock_path(const char *path)
+{
+   return is_absolute_path(path) || path[0] == '@';
+}
 
 /*
  * These manipulate the frontend/backend protocol version number.
index e7781d010f0d73a675d1d4fd6b2ff78a2a077ec5..7d04d3664e044e151ac45a11efaaaf374e71229a 100644 (file)
@@ -1093,7 +1093,7 @@ connectOptions2(PGconn *conn)
        {
            ch->type = CHT_HOST_NAME;
 #ifdef HAVE_UNIX_SOCKETS
-           if (is_absolute_path(ch->host))
+           if (is_unixsock_path(ch->host))
                ch->type = CHT_UNIX_SOCKET;
 #endif
        }
@@ -6945,7 +6945,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
    /* 'localhost' matches pghost of '' or the default socket directory */
    if (hostname == NULL || hostname[0] == '\0')
        hostname = DefaultHost;
-   else if (is_absolute_path(hostname))
+   else if (is_unixsock_path(hostname))
 
        /*
         * We should probably use canonicalize_path(), but then we have to