Commit c9b0cbe9 authored by Tom Lane's avatar Tom Lane

Support having multiple Unix-domain sockets per postmaster.

Replace unix_socket_directory with unix_socket_directories, which is a list
of socket directories, and adjust postmaster's code to allow zero or more
Unix-domain sockets to be created.

This is mostly a straightforward change, but since the Unix sockets ought
to be created after the TCP/IP sockets for safety reasons (better chance
of detecting a port number conflict), AddToDataDirLockFile needs to be
fixed to support out-of-order updates of data directory lockfile lines.
That's a change that had been foreseen to be necessary someday anyway.

Honza Horak, reviewed and revised by Tom Lane
parent 85642ec0
...@@ -838,7 +838,7 @@ omicron bryanh guest1 ...@@ -838,7 +838,7 @@ omicron bryanh guest1
<varname>unix_socket_permissions</varname> (and possibly <varname>unix_socket_permissions</varname> (and possibly
<varname>unix_socket_group</varname>) configuration parameters as <varname>unix_socket_group</varname>) configuration parameters as
described in <xref linkend="runtime-config-connection">. Or you 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 configuration parameter to place the socket file in a suitably
restricted directory. restricted directory.
</para> </para>
......
...@@ -445,17 +445,24 @@ SET ENABLE_SEQSCAN TO OFF; ...@@ -445,17 +445,24 @@ SET ENABLE_SEQSCAN TO OFF;
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry id="guc-unix-socket-directory" xreflabel="unix_socket_directory"> <varlistentry id="guc-unix-socket-directories" xreflabel="unix_socket_directories">
<term><varname>unix_socket_directory</varname> (<type>string</type>)</term> <term><varname>unix_socket_directories</varname> (<type>string</type>)</term>
<indexterm> <indexterm>
<primary><varname>unix_socket_directory</> configuration parameter</primary> <primary><varname>unix_socket_directories</> configuration parameter</primary>
</indexterm> </indexterm>
<listitem> <listitem>
<para> <para>
Specifies the directory of the Unix-domain socket on which the Specifies the directory of the Unix-domain socket(s) on which the
server is to listen for server is to listen for connections from client applications.
connections from client applications. The default is normally Multiple sockets can be created by listing multiple directories
<filename>/tmp</filename>, but can be changed at build time. 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. This parameter can only be set at server start.
</para> </para>
...@@ -464,8 +471,8 @@ SET ENABLE_SEQSCAN TO OFF; ...@@ -464,8 +471,8 @@ SET ENABLE_SEQSCAN TO OFF;
<literal>.s.PGSQL.<replaceable>nnnn</></literal> where <literal>.s.PGSQL.<replaceable>nnnn</></literal> where
<replaceable>nnnn</> is the server's port number, an ordinary file <replaceable>nnnn</> is the server's port number, an ordinary file
named <literal>.s.PGSQL.<replaceable>nnnn</>.lock</literal> will be named <literal>.s.PGSQL.<replaceable>nnnn</>.lock</literal> will be
created in the <varname>unix_socket_directory</> directory. Neither created in each of the <varname>unix_socket_directories</> directories.
file should ever be removed manually. Neither file should ever be removed manually.
</para> </para>
<para> <para>
...@@ -482,8 +489,8 @@ SET ENABLE_SEQSCAN TO OFF; ...@@ -482,8 +489,8 @@ SET ENABLE_SEQSCAN TO OFF;
</indexterm> </indexterm>
<listitem> <listitem>
<para> <para>
Sets the owning group of the Unix-domain socket. (The owning Sets the owning group of the Unix-domain socket(s). (The owning
user of the socket is always the user that starts the user of the sockets is always the user that starts the
server.) In combination with the parameter server.) In combination with the parameter
<varname>unix_socket_permissions</varname> this can be used as <varname>unix_socket_permissions</varname> this can be used as
an additional access control mechanism for Unix-domain connections. an additional access control mechanism for Unix-domain connections.
...@@ -506,7 +513,7 @@ SET ENABLE_SEQSCAN TO OFF; ...@@ -506,7 +513,7 @@ SET ENABLE_SEQSCAN TO OFF;
</indexterm> </indexterm>
<listitem> <listitem>
<para> <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. sockets use the usual Unix file system permission set.
The parameter value is expected to be a numeric mode The parameter value is expected to be a numeric mode
specified in the format accepted by the specified in the format accepted by the
...@@ -6556,7 +6563,7 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1) ...@@ -6556,7 +6563,7 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1)
</row> </row>
<row> <row>
<entry><option>-k <replaceable>x</replaceable></option></entry> <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>
<row> <row>
<entry><option>-l</option></entry> <entry><option>-l</option></entry>
......
...@@ -254,8 +254,14 @@ PostgreSQL documentation ...@@ -254,8 +254,14 @@ PostgreSQL documentation
<para> <para>
Specifies the directory of the Unix-domain socket on which Specifies the directory of the Unix-domain socket on which
<command>postgres</command> is to listen for <command>postgres</command> is to listen for
connections from client applications. The default is normally connections from client applications. The value can also be a
<filename>/tmp</filename>, but can be changed at build time. 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -1718,7 +1718,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 ...@@ -1718,7 +1718,7 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<para> <para>
The simplest way to prevent spoofing for <literal>local</> The simplest way to prevent spoofing for <literal>local</>
connections is to use a Unix domain socket directory (<xref 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 for a trusted local user. This prevents a malicious user from creating
their own socket file in that directory. If you are concerned that their own socket file in that directory. If you are concerned that
some applications might still reference <filename>/tmp</> for the some applications might still reference <filename>/tmp</> for the
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
* StreamServerPort - Open postmaster's server port * StreamServerPort - Open postmaster's server port
* StreamConnection - Create new connection with client * StreamConnection - Create new connection with client
* StreamClose - Close a client/backend connection * 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_init - initialize libpq at backend startup
* pq_comm_reset - reset libpq during error recovery * pq_comm_reset - reset libpq during error recovery
* pq_close - shutdown libpq at backend exit * pq_close - shutdown libpq at backend exit
...@@ -103,8 +103,8 @@ int Unix_socket_permissions; ...@@ -103,8 +103,8 @@ int Unix_socket_permissions;
char *Unix_socket_group; char *Unix_socket_group;
/* Where the Unix socket file is */ /* Where the Unix socket files are (list of palloc'd strings) */
static char sock_path[MAXPGPATH]; static List *sock_paths = NIL;
/* /*
...@@ -140,8 +140,8 @@ static int internal_flush(void); ...@@ -140,8 +140,8 @@ static int internal_flush(void);
static void pq_set_nonblocking(bool nonblocking); static void pq_set_nonblocking(bool nonblocking);
#ifdef HAVE_UNIX_SOCKETS #ifdef HAVE_UNIX_SOCKETS
static int Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName); static int Lock_AF_UNIX(char *unixSocketDir, char *unixSocketPath);
static int Setup_AF_UNIX(void); static int Setup_AF_UNIX(char *sock_path);
#endif /* HAVE_UNIX_SOCKETS */ #endif /* HAVE_UNIX_SOCKETS */
...@@ -234,29 +234,43 @@ pq_close(int code, Datum arg) ...@@ -234,29 +234,43 @@ pq_close(int code, Datum arg)
/* StreamDoUnlink() /* StreamDoUnlink()
* Shutdown routine for backend connection * 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 #ifdef HAVE_UNIX_SOCKETS
static void static void
StreamDoUnlink(int code, Datum arg) StreamDoUnlink(int code, Datum arg)
{ {
Assert(sock_path[0]); ListCell *l;
/* Loop through all created sockets... */
foreach(l, sock_paths)
{
char *sock_path = (char *) lfirst(l);
unlink(sock_path); unlink(sock_path);
}
/* Since we're about to exit, no need to reclaim storage */
sock_paths = NIL;
} }
#endif /* HAVE_UNIX_SOCKETS */ #endif /* HAVE_UNIX_SOCKETS */
/* /*
* StreamServerPort -- open a "listening" port to accept connections. * StreamServerPort -- open a "listening" port to accept connections.
* *
* Successfully opened sockets are added to the ListenSocket[] array, * family should be AF_UNIX or AF_UNSPEC; portNumber is the port number.
* at the first position that isn't PGINVALID_SOCKET. * 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 * RETURNS: STATUS_OK or STATUS_ERROR
*/ */
int int
StreamServerPort(int family, char *hostName, unsigned short portNumber, StreamServerPort(int family, char *hostName, unsigned short portNumber,
char *unixSocketName, char *unixSocketDir,
pgsocket ListenSocket[], int MaxListen) pgsocket ListenSocket[], int MaxListen)
{ {
pgsocket fd; pgsocket fd;
...@@ -273,6 +287,9 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, ...@@ -273,6 +287,9 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
int listen_index = 0; int listen_index = 0;
int added = 0; int added = 0;
#ifdef HAVE_UNIX_SOCKETS
char unixSocketPath[MAXPGPATH];
#endif
#if !defined(WIN32) || defined(IPV6_V6ONLY) #if !defined(WIN32) || defined(IPV6_V6ONLY)
int one = 1; int one = 1;
#endif #endif
...@@ -286,10 +303,14 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, ...@@ -286,10 +303,14 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
#ifdef HAVE_UNIX_SOCKETS #ifdef HAVE_UNIX_SOCKETS
if (family == AF_UNIX) 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 (Lock_AF_UNIX(unixSocketDir, unixSocketPath) != STATUS_OK)
return STATUS_ERROR; return STATUS_ERROR;
service = sock_path; service = unixSocketPath;
} }
else else
#endif /* HAVE_UNIX_SOCKETS */ #endif /* HAVE_UNIX_SOCKETS */
...@@ -432,7 +453,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, ...@@ -432,7 +453,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
(IS_AF_UNIX(addr->ai_family)) ? (IS_AF_UNIX(addr->ai_family)) ?
errhint("Is another postmaster already running on port %d?" errhint("Is another postmaster already running on port %d?"
" If not, remove socket file \"%s\" and retry.", " If not, remove socket file \"%s\" and retry.",
(int) portNumber, sock_path) : (int) portNumber, service) :
errhint("Is another postmaster already running on port %d?" errhint("Is another postmaster already running on port %d?"
" If not, wait a few seconds and retry.", " If not, wait a few seconds and retry.",
(int) portNumber))); (int) portNumber)));
...@@ -443,7 +464,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, ...@@ -443,7 +464,7 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
#ifdef HAVE_UNIX_SOCKETS #ifdef HAVE_UNIX_SOCKETS
if (addr->ai_family == AF_UNIX) if (addr->ai_family == AF_UNIX)
{ {
if (Setup_AF_UNIX() != STATUS_OK) if (Setup_AF_UNIX(service) != STATUS_OK)
{ {
closesocket(fd); closesocket(fd);
break; break;
...@@ -490,10 +511,8 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber, ...@@ -490,10 +511,8 @@ StreamServerPort(int family, char *hostName, unsigned short portNumber,
* Lock_AF_UNIX -- configure unix socket file path * Lock_AF_UNIX -- configure unix socket file path
*/ */
static int static int
Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName) Lock_AF_UNIX(char *unixSocketDir, char *unixSocketPath)
{ {
UNIXSOCK_PATH(sock_path, portNumber, unixSocketName);
/* /*
* Grab an interlock file associated with the socket file. * Grab an interlock file associated with the socket file.
* *
...@@ -502,13 +521,23 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName) ...@@ -502,13 +521,23 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName)
* more portable, and second, it lets us remove any pre-existing socket * more portable, and second, it lets us remove any pre-existing socket
* file without race conditions. * file without race conditions.
*/ */
CreateSocketLockFile(sock_path, true); CreateSocketLockFile(unixSocketPath, true, unixSocketDir);
/* /*
* Once we have the interlock, we can safely delete any pre-existing * Once we have the interlock, we can safely delete any pre-existing
* socket file to avoid failure at bind() time. * 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; return STATUS_OK;
} }
...@@ -518,11 +547,8 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName) ...@@ -518,11 +547,8 @@ Lock_AF_UNIX(unsigned short portNumber, char *unixSocketName)
* Setup_AF_UNIX -- configure unix socket permissions * Setup_AF_UNIX -- configure unix socket permissions
*/ */
static int 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 * 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
...@@ -704,20 +730,24 @@ StreamClose(pgsocket sock) ...@@ -704,20 +730,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 * 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 * files have a recent mod date (ordinary operations on sockets usually won't
* change the mod date). That saves it from being removed by * change the mod date). That saves them from being removed by
* overenthusiastic /tmp-directory-cleaner daemons. (Another reason we should * overenthusiastic /tmp-directory-cleaner daemons. (Another reason we should
* never have put the socket file in /tmp...) * never have put the socket file in /tmp...)
*/ */
void void
TouchSocketFile(void) TouchSocketFiles(void)
{ {
/* Do nothing if we did not create a socket... */ ListCell *l;
if (sock_path[0] != '\0')
/* 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 * 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 * have neither, there's no way to affect the mod or access time of
......
...@@ -157,7 +157,9 @@ static Backend *ShmemBackendArray; ...@@ -157,7 +157,9 @@ static Backend *ShmemBackendArray;
/* The socket number we are listening for connections on */ /* The socket number we are listening for connections on */
int PostPortNumber; int PostPortNumber;
char *UnixSocketDir; /* The directory names for Unix socket(s) */
char *Unix_socket_directories;
/* The TCP listen address(es) */
char *ListenAddresses; char *ListenAddresses;
/* /*
...@@ -612,7 +614,7 @@ PostmasterMain(int argc, char *argv[]) ...@@ -612,7 +614,7 @@ PostmasterMain(int argc, char *argv[])
break; break;
case 'k': case 'k':
SetConfigOption("unix_socket_directory", optarg, PGC_POSTMASTER, PGC_S_ARGV); SetConfigOption("unix_socket_directories", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break; break;
case 'l': case 'l':
...@@ -856,7 +858,7 @@ PostmasterMain(int argc, char *argv[]) ...@@ -856,7 +858,7 @@ PostmasterMain(int argc, char *argv[])
/* Need a modifiable copy of ListenAddresses */ /* Need a modifiable copy of ListenAddresses */
rawstring = pstrdup(ListenAddresses); rawstring = pstrdup(ListenAddresses);
/* Parse string into list of identifiers */ /* Parse string into list of hostnames */
if (!SplitIdentifierString(rawstring, ',', &elemlist)) if (!SplitIdentifierString(rawstring, ',', &elemlist))
{ {
/* syntax error in list */ /* syntax error in list */
...@@ -872,12 +874,12 @@ PostmasterMain(int argc, char *argv[]) ...@@ -872,12 +874,12 @@ PostmasterMain(int argc, char *argv[])
if (strcmp(curhost, "*") == 0) if (strcmp(curhost, "*") == 0)
status = StreamServerPort(AF_UNSPEC, NULL, status = StreamServerPort(AF_UNSPEC, NULL,
(unsigned short) PostPortNumber, (unsigned short) PostPortNumber,
UnixSocketDir, NULL,
ListenSocket, MAXLISTEN); ListenSocket, MAXLISTEN);
else else
status = StreamServerPort(AF_UNSPEC, curhost, status = StreamServerPort(AF_UNSPEC, curhost,
(unsigned short) PostPortNumber, (unsigned short) PostPortNumber,
UnixSocketDir, NULL,
ListenSocket, MAXLISTEN); ListenSocket, MAXLISTEN);
if (status == STATUS_OK) if (status == STATUS_OK)
...@@ -896,7 +898,7 @@ PostmasterMain(int argc, char *argv[]) ...@@ -896,7 +898,7 @@ PostmasterMain(int argc, char *argv[])
curhost))); curhost)));
} }
if (!success && list_length(elemlist)) if (!success && elemlist != NIL)
ereport(FATAL, ereport(FATAL,
(errmsg("could not create any TCP/IP sockets"))); (errmsg("could not create any TCP/IP sockets")));
...@@ -943,13 +945,54 @@ PostmasterMain(int argc, char *argv[]) ...@@ -943,13 +945,54 @@ PostmasterMain(int argc, char *argv[])
#endif #endif
#ifdef HAVE_UNIX_SOCKETS #ifdef HAVE_UNIX_SOCKETS
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, status = StreamServerPort(AF_UNIX, NULL,
(unsigned short) PostPortNumber, (unsigned short) PostPortNumber,
UnixSocketDir, socketdir,
ListenSocket, MAXLISTEN); ListenSocket, MAXLISTEN);
if (status != STATUS_OK)
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, ereport(WARNING,
(errmsg("could not create Unix-domain socket"))); (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 #endif
/* /*
...@@ -1439,15 +1482,15 @@ ServerLoop(void) ...@@ -1439,15 +1482,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 * they are not removed by overzealous /tmp-cleaning tasks. We assume
* no one runs cleaners with cutoff times of less than an hour ... * no one runs cleaners with cutoff times of less than an hour ...
*/ */
now = time(NULL); now = time(NULL);
if (now - last_touch_time >= 58 * SECS_PER_MINUTE) if (now - last_touch_time >= 58 * SECS_PER_MINUTE)
{ {
TouchSocketFile(); TouchSocketFiles();
TouchSocketLockFile(); TouchSocketLockFiles();
last_touch_time = now; last_touch_time = now;
} }
} }
......
...@@ -3344,7 +3344,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx) ...@@ -3344,7 +3344,7 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx)
break; break;
case 'k': case 'k':
SetConfigOption("unix_socket_directory", optarg, ctx, gucsource); SetConfigOption("unix_socket_directories", optarg, ctx, gucsource);
break; break;
case 'l': case 'l':
......
...@@ -2446,6 +2446,116 @@ SplitIdentifierString(char *rawstring, char separator, ...@@ -2446,6 +2446,116 @@ 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, 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 whitespace */
curname = nextp;
while (*nextp && *nextp != separator &&
!isspace((unsigned char) *nextp))
nextp++;
endp = nextp;
if (curname == nextp)
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 * Comparison Functions used for bytea
* *
......
...@@ -49,8 +49,8 @@ ...@@ -49,8 +49,8 @@
ProcessingMode Mode = InitProcessing; ProcessingMode Mode = InitProcessing;
/* Note: we rely on this to initialize as zeroes */ /* List of lock files to be removed at proc exit */
static char socketLockFile[MAXPGPATH]; static List *lock_files = NIL;
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -628,7 +628,7 @@ GetUserNameFromId(Oid roleid) ...@@ -628,7 +628,7 @@ GetUserNameFromId(Oid roleid)
* Interlock-file support * Interlock-file support
* *
* These routines are used to create both a data-directory lockfile * These routines are used to create both a data-directory lockfile
* ($DATADIR/postmaster.pid) and a Unix-socket-file lockfile ($SOCKFILE.lock). * ($DATADIR/postmaster.pid) and Unix-socket-file lockfiles ($SOCKFILE.lock).
* Both kinds of files contain the same info initially, although we can add * Both kinds of files contain the same info initially, although we can add
* more information to a data-directory lockfile after it's created, using * more information to a data-directory lockfile after it's created, using
* AddToDataDirLockFile(). See miscadmin.h for documentation of the contents * AddToDataDirLockFile(). See miscadmin.h for documentation of the contents
...@@ -640,32 +640,35 @@ GetUserNameFromId(Oid roleid) ...@@ -640,32 +640,35 @@ GetUserNameFromId(Oid roleid)
*/ */
/* /*
* proc_exit callback to remove a lockfile. * proc_exit callback to remove lockfiles.
*/ */
static void 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)
{ {
char *curfile = (char *) lfirst(l);
unlink(curfile);
/* Should we complain if the unlink fails? */ /* Should we complain if the unlink fails? */
} }
free(fname); /* Since we're about to exit, no need to reclaim storage */
} lock_files = NIL;
} }
/* /*
* Create a lockfile. * 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. * 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. * isDDLock and refName are used to determine what error message to produce.
*/ */
static void static void
CreateLockFile(const char *filename, bool amPostmaster, CreateLockFile(const char *filename, bool amPostmaster,
const char *socketDir,
bool isDDLock, const char *refName) bool isDDLock, const char *refName)
{ {
int fd; int fd;
...@@ -891,12 +894,7 @@ CreateLockFile(const char *filename, bool amPostmaster, ...@@ -891,12 +894,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
DataDir, DataDir,
(long) MyStartTime, (long) MyStartTime,
PostPortNumber, PostPortNumber,
#ifdef HAVE_UNIX_SOCKETS socketDir);
(*UnixSocketDir != '\0') ? UnixSocketDir : DEFAULT_PGSOCKET_DIR
#else
""
#endif
);
errno = 0; errno = 0;
if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) if (write(fd, buffer, strlen(buffer)) != strlen(buffer))
...@@ -934,9 +932,14 @@ CreateLockFile(const char *filename, bool amPostmaster, ...@@ -934,9 +932,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));
} }
/* /*
...@@ -945,41 +948,50 @@ CreateLockFile(const char *filename, bool amPostmaster, ...@@ -945,41 +948,50 @@ CreateLockFile(const char *filename, bool amPostmaster,
* When this is called, we must have already switched the working * When this is called, we must have already switched the working
* directory to DataDir, so we can just use a relative path. This * directory to DataDir, so we can just use a relative path. This
* helps ensure that we are locking the directory we should be. * 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 void
CreateDataDirLockFile(bool amPostmaster) 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. * Create a lockfile for the specified Unix socket file.
*/ */
void void
CreateSocketLockFile(const char *socketfile, bool amPostmaster) CreateSocketLockFile(const char *socketfile, bool amPostmaster,
const char *socketDir)
{ {
char lockfile[MAXPGPATH]; char lockfile[MAXPGPATH];
snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile); snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile);
CreateLockFile(lockfile, amPostmaster, false, socketfile); CreateLockFile(lockfile, amPostmaster, socketDir, false, socketfile);
/* Save name of lockfile for TouchSocketLockFile */
strcpy(socketLockFile, lockfile);
} }
/* /*
* 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 * This routine should be called every so often to ensure that the socket
* has a recent mod or access date. That saves it * lock files have a recent mod or access date. That saves them
* from being removed by overenthusiastic /tmp-directory-cleaner daemons. * from being removed by overenthusiastic /tmp-directory-cleaner daemons.
* (Another reason we should never have put the socket file in /tmp...) * (Another reason we should never have put the socket file in /tmp...)
*/ */
void void
TouchSocketLockFile(void) TouchSocketLockFiles(void)
{ {
/* Do nothing if we did not create a socket... */ ListCell *l;
if (socketLockFile[0] != '\0')
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 * utime() is POSIX standard, utimes() is a common alternative; if we
* have neither, fall back to actually reading the file (which only * have neither, fall back to actually reading the file (which only
...@@ -1011,8 +1023,10 @@ TouchSocketLockFile(void) ...@@ -1011,8 +1023,10 @@ TouchSocketLockFile(void)
* Add (or replace) a line in the data directory lock file. * Add (or replace) a line in the data directory lock file.
* The given string should not include a trailing newline. * The given string should not include a trailing newline.
* *
* Caution: this erases all following lines. In current usage that is OK * Note: because we don't truncate the file, if we were to rewrite a line
* because lines are added in order. We could improve it if needed. * 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 void
AddToDataDirLockFile(int target_line, const char *str) AddToDataDirLockFile(int target_line, const char *str)
...@@ -1020,8 +1034,10 @@ AddToDataDirLockFile(int target_line, const char *str) ...@@ -1020,8 +1034,10 @@ AddToDataDirLockFile(int target_line, const char *str)
int fd; int fd;
int len; int len;
int lineno; int lineno;
char *ptr; char *srcptr;
char buffer[BLCKSZ]; char *destptr;
char srcbuffer[BLCKSZ];
char destbuffer[BLCKSZ];
fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0); fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0);
if (fd < 0) if (fd < 0)
...@@ -1032,7 +1048,7 @@ AddToDataDirLockFile(int target_line, const char *str) ...@@ -1032,7 +1048,7 @@ AddToDataDirLockFile(int target_line, const char *str)
DIRECTORY_LOCK_FILE))); DIRECTORY_LOCK_FILE)));
return; return;
} }
len = read(fd, buffer, sizeof(buffer) - 1); len = read(fd, srcbuffer, sizeof(srcbuffer) - 1);
if (len < 0) if (len < 0)
{ {
ereport(LOG, ereport(LOG,
...@@ -1042,36 +1058,50 @@ AddToDataDirLockFile(int target_line, const char *str) ...@@ -1042,36 +1058,50 @@ AddToDataDirLockFile(int target_line, const char *str)
close(fd); close(fd);
return; 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++) for (lineno = 1; lineno < target_line; lineno++)
{ {
if ((ptr = strchr(ptr, '\n')) == NULL) if ((srcptr = strchr(srcptr, '\n')) == NULL)
{ {
elog(LOG, "bogus data in \"%s\"", DIRECTORY_LOCK_FILE); elog(LOG, "bogus data in \"%s\"", DIRECTORY_LOCK_FILE);
close(fd); close(fd);
return; return;
} }
ptr++; srcptr++;
} }
memcpy(destbuffer, srcbuffer, srcptr - srcbuffer);
destptr = destbuffer + (srcptr - srcbuffer);
/* /*
* Write or rewrite the target line. * 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 * And rewrite the data. Since we write in a single kernel call, this
* update should appear atomic to onlookers. * update should appear atomic to onlookers.
*/ */
len = strlen(buffer); len = strlen(destbuffer);
errno = 0; errno = 0;
if (lseek(fd, (off_t) 0, SEEK_SET) != 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 write didn't set errno, assume problem is no disk space */
if (errno == 0) if (errno == 0)
......
...@@ -2895,14 +2895,18 @@ static struct config_string ConfigureNamesString[] = ...@@ -2895,14 +2895,18 @@ static struct config_string ConfigureNamesString[] =
}, },
{ {
{"unix_socket_directory", PGC_POSTMASTER, CONN_AUTH_SETTINGS, {"unix_socket_directories", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
gettext_noop("Sets the directory where the Unix-domain socket will be created."), gettext_noop("Sets the directories where Unix-domain sockets will be created."),
NULL, NULL,
GUC_SUPERUSER_ONLY GUC_SUPERUSER_ONLY
}, },
&UnixSocketDir, &Unix_socket_directories,
#ifdef HAVE_UNIX_SOCKETS
DEFAULT_PGSOCKET_DIR,
#else
"", "",
check_canonical_path, NULL, NULL #endif
NULL, NULL, NULL
}, },
{ {
......
...@@ -65,7 +65,8 @@ ...@@ -65,7 +65,8 @@
# Note: Increasing max_connections costs ~400 bytes of shared memory per # Note: Increasing max_connections costs ~400 bytes of shared memory per
# connection slot, plus lock space (see max_locks_per_transaction). # connection slot, plus lock space (see max_locks_per_transaction).
#superuser_reserved_connections = 3 # (change requires restart) #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_group = '' # (change requires restart)
#unix_socket_permissions = 0777 # begin with 0 to use octal notation #unix_socket_permissions = 0777 # begin with 0 to use octal notation
# (change requires restart) # (change requires restart)
......
...@@ -1156,7 +1156,7 @@ static void ...@@ -1156,7 +1156,7 @@ static void
setup_config(void) setup_config(void)
{ {
char **conflines; char **conflines;
char repltok[TZ_STRLEN_MAX + 100]; char repltok[MAXPGPATH];
char path[MAXPGPATH]; char path[MAXPGPATH];
const char *default_timezone; const char *default_timezone;
...@@ -1178,6 +1178,15 @@ setup_config(void) ...@@ -1178,6 +1178,15 @@ setup_config(void)
n_buffers * (BLCKSZ / 1024)); n_buffers * (BLCKSZ / 1024));
conflines = replace_token(conflines, "#shared_buffers = 32MB", repltok); 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 #if DEF_PGPORT != 5432
snprintf(repltok, sizeof(repltok), "#port = %d", DEF_PGPORT); snprintf(repltok, sizeof(repltok), "#port = %d", DEF_PGPORT);
conflines = replace_token(conflines, "#port = 5432", repltok); conflines = replace_token(conflines, "#port = 5432", repltok);
......
...@@ -521,7 +521,7 @@ test_postmaster_connection(bool do_checkpoint) ...@@ -521,7 +521,7 @@ test_postmaster_connection(bool do_checkpoint)
hostaddr = optlines[LOCK_FILE_LINE_LISTEN_ADDR - 1]; 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 * directories, libpq's host parameter must have a
* leading slash to indicate a socket directory. So, * leading slash to indicate a socket directory. So,
* ignore sockdir if it's relative, and try to use TCP * ignore sockdir if it's relative, and try to use TCP
......
...@@ -45,11 +45,11 @@ typedef struct ...@@ -45,11 +45,11 @@ typedef struct
* prototypes for functions in pqcomm.c * prototypes for functions in pqcomm.c
*/ */
extern int StreamServerPort(int family, char *hostName, extern int StreamServerPort(int family, char *hostName,
unsigned short portNumber, char *unixSocketName, pgsocket ListenSocket[], unsigned short portNumber, char *unixSocketDir,
int MaxListen); pgsocket ListenSocket[], int MaxListen);
extern int StreamConnection(pgsocket server_fd, Port *port); extern int StreamConnection(pgsocket server_fd, Port *port);
extern void StreamClose(pgsocket sock); extern void StreamClose(pgsocket sock);
extern void TouchSocketFile(void); extern void TouchSocketFiles(void);
extern void pq_init(void); extern void pq_init(void);
extern void pq_comm_reset(void); extern void pq_comm_reset(void);
extern int pq_getbytes(char *s, size_t len); extern int pq_getbytes(char *s, size_t len);
......
...@@ -411,7 +411,7 @@ extern char *local_preload_libraries_string; ...@@ -411,7 +411,7 @@ extern char *local_preload_libraries_string;
* 2 data directory path * 2 data directory path
* 3 postmaster start timestamp (time_t representation) * 3 postmaster start timestamp (time_t representation)
* 4 port number * 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) * 6 first listen_address (IP address or "*"; empty if no TCP port)
* 7 shared memory key (not present on Windows) * 7 shared memory key (not present on Windows)
* *
...@@ -429,8 +429,9 @@ extern char *local_preload_libraries_string; ...@@ -429,8 +429,9 @@ extern char *local_preload_libraries_string;
#define LOCK_FILE_LINE_SHMEM_KEY 7 #define LOCK_FILE_LINE_SHMEM_KEY 7
extern void CreateDataDirLockFile(bool amPostmaster); extern void CreateDataDirLockFile(bool amPostmaster);
extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster); extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster,
extern void TouchSocketLockFile(void); const char *socketDir);
extern void TouchSocketLockFiles(void);
extern void AddToDataDirLockFile(int target_line, const char *str); extern void AddToDataDirLockFile(int target_line, const char *str);
extern void ValidatePgVersion(const char *path); extern void ValidatePgVersion(const char *path);
extern void process_shared_preload_libraries(void); extern void process_shared_preload_libraries(void);
......
...@@ -19,7 +19,7 @@ extern int ReservedBackends; ...@@ -19,7 +19,7 @@ extern int ReservedBackends;
extern int PostPortNumber; extern int PostPortNumber;
extern int Unix_socket_permissions; extern int Unix_socket_permissions;
extern char *Unix_socket_group; extern char *Unix_socket_group;
extern char *UnixSocketDir; extern char *Unix_socket_directories;
extern char *ListenAddresses; extern char *ListenAddresses;
extern bool ClientAuthInProgress; extern bool ClientAuthInProgress;
extern int PreAuthDelay; extern int PreAuthDelay;
......
...@@ -756,6 +756,8 @@ extern int varstr_cmp(char *arg1, int len1, char *arg2, int len2, Oid collid); ...@@ -756,6 +756,8 @@ extern int varstr_cmp(char *arg1, int len1, char *arg2, int len2, Oid collid);
extern List *textToQualifiedNameList(text *textval); extern List *textToQualifiedNameList(text *textval);
extern bool SplitIdentifierString(char *rawstring, char separator, extern bool SplitIdentifierString(char *rawstring, char separator,
List **namelist); List **namelist);
extern bool SplitDirectoriesString(char *rawstring, char separator,
List **namelist);
extern Datum replace_text(PG_FUNCTION_ARGS); extern Datum replace_text(PG_FUNCTION_ARGS);
extern text *replace_text_regexp(text *src_text, void *regexp, extern text *replace_text_regexp(text *src_text, void *regexp,
text *replace_text, bool glob); text *replace_text, bool glob);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment