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
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>

View 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

View 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

View 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));

View 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')

View 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;
}

View 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.

View 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