diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index cfdb33a1fd6508aac31b0a87b8bb3ed73432c618..679c40a64ec64fbc0623e878954287a5e6081dc4 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 e30c5a0cf4e8f1d31c93907bd6894a8b38189005..7727ea7f89cf76e9fe7dbd0ced57f18dde387278 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -445,17 +445,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> @@ -464,8 +471,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> @@ -482,8 +489,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. @@ -506,7 +513,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 @@ -1852,7 +1859,7 @@ SET ENABLE_SEQSCAN TO OFF; <varname>commit_delay</varname> behaved differently and was much less effective: it affected only commits, rather than all WAL flushes, and waited for the entire configured delay even if the WAL flush - was completed sooner. Beginning in <productname>PostgreSQL</> 9.3, + was completed sooner. Beginning in <productname>PostgreSQL</> 9.3, the first process that becomes ready to flush waits for the configured interval, while subsequent processes wait only until the leader completes the flush. The default <varname>commit_delay</> is zero @@ -6556,7 +6563,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/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml index 4e5cd02589c8ebdcdfb68afdb06a492f24e06de6..a1f36e1836cc040ff2e862da0696429e766d81f5 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 87177989d76f2140ccd34175472c87869b66c320..9cc9d4260a9f69063b14034a6c4a88f740a007d3 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1718,7 +1718,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 5272811cc0af190803666cd32dc7ac7167c12c77..5e86987f221fee01c5049f925492e0f9c441d372 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; /* @@ -140,8 +140,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 */ @@ -234,29 +234,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; @@ -273,6 +287,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 @@ -286,10 +303,14 @@ 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 (Lock_AF_UNIX(unixSocketDir, unixSocketPath) != STATUS_OK) return STATUS_ERROR; - service = sock_path; + service = unixSocketPath; } else #endif /* HAVE_UNIX_SOCKETS */ @@ -432,7 +453,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))); @@ -443,7 +464,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; @@ -490,10 +511,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); - /* * Grab an interlock file associated with the socket file. * @@ -502,13 +521,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; } @@ -518,11 +547,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 @@ -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 - * 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 fee699ea9ef2da504c5ce2b4d9b2284ea37eef06..9e26fb895f3f6c70eef42f69f4f4f283cb15ba65 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -157,7 +157,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; /* @@ -612,7 +614,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': @@ -856,7 +858,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 */ @@ -872,12 +874,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) @@ -896,7 +898,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"))); @@ -943,13 +945,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 /* @@ -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 * 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 37dfa18c1d0b793d096f662597b4b0a2687a1fd6..f1248a851bf90188da8d3a7e8b61ac99bf78ebbd 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3344,7 +3344,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 e1b57ba3fc71fa2cb3898503fa380ff4bc21861b..d9e6bc4338b06dd2d2a1a48ffaaeefe32f4f294d 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -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 * diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index fb376a0d271730f2813bb1d1f2e15562e82ff865..775d71f56c57f4c954054653b454270427fc5395 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; /* ---------------------------------------------------------------- @@ -628,7 +628,7 @@ GetUserNameFromId(Oid roleid) * Interlock-file support * * 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 * more information to a data-directory lockfile after it's created, using * AddToDataDirLockFile(). See miscadmin.h for documentation of the contents @@ -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); errno = 0; if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) @@ -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, * 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 @@ -1011,8 +1023,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) @@ -1020,8 +1034,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) @@ -1032,7 +1048,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, @@ -1042,36 +1058,50 @@ 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, "bogus data in \"%s\"", DIRECTORY_LOCK_FILE); 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 087aaf9b0bb4f09c449c346f4ec7524f7c675472..80e5aa1816f272d526543c9c6267e5353cc6f401 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2895,14 +2895,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 fa75d00f70872bbe7b48192861baf89ec236db98..c24afb043436e46fe0d959ec048aa8b0c7784f0e 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -65,7 +65,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 4292231d0d59f3caa31879e6c0b804587dbfa721..132ad0fa4cdb9b59f052f2d97494910c27b1f219 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -1156,7 +1156,7 @@ static void setup_config(void) { char **conflines; - char repltok[TZ_STRLEN_MAX + 100]; + char repltok[MAXPGPATH]; char path[MAXPGPATH]; const char *default_timezone; @@ -1178,6 +1178,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 72fc4c1abf6ae80b92aafea727c9f07d36d9efb3..af8d8b28e690ce37aa3a8565bcbe4f3e4ffa62a9 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -521,7 +521,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 7083cd866b68ea906ab66b4f59a9e3b6dfd47885..41e52f2a7c68c9b9d4761d1b0e6e925c9cb7637b 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 8df2a28126a1f24e7a72513fb253da5c3f87773f..9f57989f8056344a239f9da6e99f6f83f717062e 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -411,7 +411,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) * @@ -429,8 +429,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 7d01d3d6248610a0aecee9bcdbd85d4cb54ca8fa..0fe7ec26db7646472fca248dee6982dc5c595c6c 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 f706ba705231e05891b0dc3b3dce1f97bf9e7487..c9c665dae09c08a726c04ed58591d63e3b1b3347 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -756,6 +756,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);