Add support for abstract Unix-domain sockets

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 <michael@paquier.xyz>
Discussion: https://www.postgresql.org/message-id/flat/6dee8574-b0ad-fc49-9c8c-2edc796f0033@2ndquadrant.com
This commit is contained in:
Peter Eisentraut 2020-11-25 08:14:23 +01:00
parent a7e65dc88b
commit c9f0624bc2
8 changed files with 76 additions and 16 deletions

View File

@ -749,6 +749,21 @@ include_dir 'conf.d'
An empty value An empty value
specifies not listening on any Unix-domain sockets, in which case specifies not listening on any Unix-domain sockets, in which case
only TCP/IP sockets can be used to connect to the server. 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 The default value is normally
<filename>/tmp</filename>, but that can be changed at build time. <filename>/tmp</filename>, but that can be changed at build time.
On Windows, the default is empty, which means no Unix-domain socket is 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 named <literal>.s.PGSQL.<replaceable>nnnn</replaceable>.lock</literal> will be
created in each of the <varname>unix_socket_directories</varname> directories. created in each of the <varname>unix_socket_directories</varname> directories.
Neither file should ever be removed manually. Neither file should ever be removed manually.
For sockets in the abstract namespace, no lock file is created.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -787,7 +803,8 @@ include_dir 'conf.d'
<para> <para>
This parameter is not supported on Windows. Any setting will be 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -834,6 +851,11 @@ include_dir 'conf.d'
similar effect by pointing <varname>unix_socket_directories</varname> to a similar effect by pointing <varname>unix_socket_directories</varname> to a
directory having search permission limited to the desired audience. directory having search permission limited to the desired audience.
</para> </para>
<para>
Sockets in the abstract namespace have no file permissions, so this
setting is also ignored in that case.
</para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -1031,7 +1031,10 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
communication; the value is the name of the directory in which the 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 socket file is stored. (On Unix, an absolute path name begins with a
slash. On Windows, paths starting with drive letters are also 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 specified, or is empty, is to connect to a Unix-domain
socket<indexterm><primary>Unix domain socket</primary></indexterm> in socket<indexterm><primary>Unix domain socket</primary></indexterm> in
<filename>/tmp</filename> (or whatever socket directory was specified <filename>/tmp</filename> (or whatever socket directory was specified

View File

@ -611,6 +611,10 @@ StreamServerPort(int family, const char *hostName, unsigned short portNumber,
static int static int
Lock_AF_UNIX(const char *unixSocketDir, const char *unixSocketPath) 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. * Grab an interlock file associated with the socket file.
* *
@ -642,6 +646,10 @@ Lock_AF_UNIX(const char *unixSocketDir, const char *unixSocketPath)
static int static int
Setup_AF_UNIX(const char *sock_path) 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 * Fix socket ownership/permission if requested. Note we must do this
* before we listen() to avoid a window where unwanted connections could * before we listen() to avoid a window where unwanted connections could

View File

@ -37,6 +37,7 @@
#include "input.h" #include "input.h"
#include "large_obj.h" #include "large_obj.h"
#include "libpq-fe.h" #include "libpq-fe.h"
#include "libpq/pqcomm.h"
#include "mainloop.h" #include "mainloop.h"
#include "portability/instr_time.h" #include "portability/instr_time.h"
#include "pqexpbuffer.h" #include "pqexpbuffer.h"
@ -604,12 +605,9 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
char *host = PQhost(pset.db); char *host = PQhost(pset.db);
char *hostaddr = PQhostaddr(pset.db); char *hostaddr = PQhostaddr(pset.db);
/* if (is_unixsock_path(host))
* If the host is an absolute path, the connection is via socket
* unless overridden by hostaddr
*/
if (is_absolute_path(host))
{ {
/* hostaddr overrides host */
if (hostaddr && *hostaddr) if (hostaddr && *hostaddr)
printf(_("You are connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"), 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)); 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 *host = PQhost(pset.db);
char *hostaddr = PQhostaddr(pset.db); char *hostaddr = PQhostaddr(pset.db);
/* if (is_unixsock_path(host))
* If the host is an absolute path, the connection is via socket
* unless overridden by hostaddr
*/
if (is_absolute_path(host))
{ {
/* hostaddr overrides host */
if (hostaddr && *hostaddr) if (hostaddr && *hostaddr)
printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"), 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)); PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db));

View File

@ -15,6 +15,7 @@
#include "common.h" #include "common.h"
#include "common/string.h" #include "common/string.h"
#include "input.h" #include "input.h"
#include "libpq/pqcomm.h"
#include "prompt.h" #include "prompt.h"
#include "settings.h" #include "settings.h"
@ -136,7 +137,7 @@ get_prompt(promptStatus_t status, ConditionalStack cstack)
const char *host = PQhost(pset.db); const char *host = PQhost(pset.db);
/* INET socket */ /* INET socket */
if (host && host[0] && !is_absolute_path(host)) if (host && host[0] && !is_unixsock_path(host))
{ {
strlcpy(buf, host, sizeof(buf)); strlcpy(buf, host, sizeof(buf));
if (*p == 'm') if (*p == 'm')

View File

@ -217,6 +217,21 @@ getaddrinfo_unix(const char *path, const struct addrinfo *hintsp,
strcpy(unp->sun_path, path); 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 #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
unp->sun_len = sizeof(struct sockaddr_un); unp->sun_len = sizeof(struct sockaddr_un);
#endif #endif
@ -249,7 +264,14 @@ getnameinfo_unix(const struct sockaddr_un *sa, int salen,
if (service) 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) if (ret < 0 || ret >= servicelen)
return EAI_MEMORY; return EAI_MEMORY;
} }

View File

@ -85,6 +85,15 @@ typedef struct
*/ */
#define UNIXSOCK_PATH_BUFLEN sizeof(((struct sockaddr_un *) NULL)->sun_path) #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. * These manipulate the frontend/backend protocol version number.

View File

@ -1093,7 +1093,7 @@ connectOptions2(PGconn *conn)
{ {
ch->type = CHT_HOST_NAME; ch->type = CHT_HOST_NAME;
#ifdef HAVE_UNIX_SOCKETS #ifdef HAVE_UNIX_SOCKETS
if (is_absolute_path(ch->host)) if (is_unixsock_path(ch->host))
ch->type = CHT_UNIX_SOCKET; ch->type = CHT_UNIX_SOCKET;
#endif #endif
} }
@ -6945,7 +6945,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
/* 'localhost' matches pghost of '' or the default socket directory */ /* 'localhost' matches pghost of '' or the default socket directory */
if (hostname == NULL || hostname[0] == '\0') if (hostname == NULL || hostname[0] == '\0')
hostname = DefaultHost; 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 * We should probably use canonicalize_path(), but then we have to