diff --git a/src/backend/main/main.c b/src/backend/main/main.c index 33c5a0a..968959b 100644 --- a/src/backend/main/main.c +++ b/src/backend/main/main.c @@ -191,6 +191,8 @@ main(int argc, char *argv[]) AuxiliaryProcessMain(argc, argv); /* does not return */ else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0) GucInfoMain(); /* does not return */ + else if (argc > 1 && strncmp(argv[1], "--child=", 8) == 0) + ChildPostgresMain(argc, argv, get_current_username(progname)); /* does not return */ else if (argc > 1 && strcmp(argv[1], "--single") == 0) PostgresMain(argc, argv, get_current_username(progname)); /* does not return */ else diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 73520a6..c5730bd 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -463,6 +463,7 @@ typedef struct static void read_backend_variables(char *id, Port *port); static void restore_backend_variables(BackendParameters *param, Port *port); +static void read_standalone_child_variables(char *id, int *psock); #ifndef WIN32 static bool save_backend_variables(BackendParameters *param, Port *port); @@ -4268,6 +4269,97 @@ ExitPostmaster(int status) proc_exit(status); } + +/* + * ChildPostgresMain - start a new-style standalone postgres process + * + * This may not belong here, but it does share a lot of code with ConnCreate + * and BackendInitialize. Basically what it has to do is set up a + * MyProcPort structure and then hand off control to PostgresMain. + * Beware that not very much stuff is initialized yet. + * + * In the future it might be interesting to support a "standalone + * multiprocess" mode in which we have a postmaster process that doesn't + * listen for connections, but does supervise autovacuum, bgwriter, etc + * auxiliary processes. So that's another reason why postmaster.c might be + * the right place for this. + */ +void +ChildPostgresMain(int argc, char *argv[], const char *username) +{ + Port *port; +#ifdef WIN32 + char paramHandleStr[32]; +#endif + + /* + * Fire up essential subsystems: error and memory management + */ + MemoryContextInit(); + + /* + * Build a Port structure for the client connection + */ + if (!(port = (Port *) calloc(1, sizeof(Port)))) + ereport(FATAL, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + /* + * GSSAPI specific state struct must exist even though we won't use it + */ +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) + port->gss = (pg_gssinfo *) calloc(1, sizeof(pg_gssinfo)); + if (!port->gss) + ereport(FATAL, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); +#endif + +#ifndef WIN32 + /* The file descriptor of the client socket is the argument of --child */ + if (sscanf(argv[1], "--child=%d", &port->sock) != 1) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid argument for --child: \"%s\"", argv[1]))); +#else +/* The file descriptor of the client socket is the argument of --child */ + if (sscanf(argv[1], "--child=%s", paramHandleStr) != 1) + ereport(FATAL, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid argument for --child: \"%s\"", argv[1]))); + + read_standalone_child_variables(paramHandleStr, &port->sock); + +#endif + + /* Default assumption about protocol to use */ + FrontendProtocol = port->proto = PG_PROTOCOL_LATEST; + + /* save process start time */ + port->SessionStartTime = GetCurrentTimestamp(); + MyStartTime = timestamptz_to_time_t(port->SessionStartTime); + + /* set these to empty in case they are needed */ + port->remote_host = ""; + port->remote_port = ""; + + MyProcPort = port; + + /* + * We can now initialize libpq and then enable reporting of ereport errors + * to the client. + */ + pq_init(); /* initialize libpq to talk to client */ + whereToSendOutput = DestRemote; /* now safe to ereport to client */ + + /* And pass off control to PostgresMain */ + PostgresMain(argc, argv, username); + + abort(); /* not reached */ +} + + /* * sigusr1_handler - handle signal conditions from child processes */ @@ -5073,6 +5165,46 @@ restore_backend_variables(BackendParameters *param, Port *port) } +#ifdef WIN32 +static void read_standalone_child_variables(char *id, int *psock) +{ + HANDLE paramHandle; + InheritableSocket param; + InheritableSocket *paramp; + +/* Win32 version uses mapped file */ +#ifdef _WIN64 + paramHandle = (HANDLE) _atoi64(id); +#else + paramHandle = (HANDLE) atol(id); +#endif + paramp = MapViewOfFile(paramHandle, FILE_MAP_READ, 0, 0, 0); + if (!paramp) + { + write_stderr("could not map view of backend variables: error code %lu\n", + GetLastError()); + exit(1); + } + + memcpy(¶m, paramp, sizeof(InheritableSocket)); + + if (!UnmapViewOfFile(paramp)) + { + write_stderr("could not unmap view of backend variables: error code %lu\n", + GetLastError()); + exit(1); + } + + if (!CloseHandle(paramHandle)) + { + write_stderr("could not close handle to backend parameter variables: error code %lu\n", + GetLastError()); + exit(1); + } + + read_inheritable_socket(psock, ¶m); +} +#endif Size ShmemBackendArraySize(void) { diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index f1248a8..c6a7de6 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3257,8 +3257,10 @@ process_postgres_switches(int argc, char *argv[], GucContext ctx) { gucsource = PGC_S_ARGV; /* switches came from command line */ - /* Ignore the initial --single argument, if present */ - if (argc > 1 && strcmp(argv[1], "--single") == 0) + /* Ignore the initial --single or --child argument, if present */ + if (argc > 1 && + (strcmp(argv[1], "--single") == 0 || + strncmp(argv[1], "--child=", 8) == 0)) { argv++; argc--; @@ -3522,18 +3524,18 @@ PostgresMain(int argc, char *argv[], const char *username) * standalone). */ if (!IsUnderPostmaster) - { MyProcPid = getpid(); + if (MyProcPort == NULL) MyStartTime = time(NULL); - } /* * Fire up essential subsystems: error and memory management * - * If we are running under the postmaster, this is done already. + * If we are running under the postmaster, this is done already; likewise + * in child-postgres mode. */ - if (!IsUnderPostmaster) + if (MyProcPort == NULL) MemoryContextInit(); SetProcessingMode(InitProcessing); diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 0fe7ec2..6d4245c 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -47,6 +47,9 @@ extern int postmaster_alive_fds[2]; extern const char *progname; extern void PostmasterMain(int argc, char *argv[]) __attribute__((noreturn)); + +extern void ChildPostgresMain(int argc, char *argv[], const char *username) __attribute__((noreturn)); + extern void ClosePostmasterPorts(bool am_syslogger); extern int MaxLivePostmasterChildren(void); diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 9eaf410..4d83ce5 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -259,6 +260,12 @@ static const PQconninfoOption PQconninfoOptions[] = { {"replication", NULL, NULL, NULL, "Replication", "D", 5}, + {"standalone_datadir", NULL, NULL, NULL, + "Standalone-Data-Directory", "D", 64}, + + {"standalone_backend", NULL, NULL, NULL, + "Standalone-Backend", "D", 64}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} @@ -360,6 +367,27 @@ pqDropConnection(PGconn *conn) if (conn->sock >= 0) closesocket(conn->sock); conn->sock = -1; + /* If we forked a child postgres process, wait for it to exit */ + if (conn->postgres_pid > 0) + { +#ifdef WIN32 + while (WaitForSingleObject(conn->proc_handle, INFINITE) != WAIT_OBJECT_0) + { + _dosmaperr(GetLastError()); + /* If interrupted by signal, keep waiting */ + if (errno != EINTR) + break; + } +#else + while (waitpid(conn->postgres_pid, NULL, 0) < 0) + { + /* If interrupted by signal, keep waiting */ + if (errno != EINTR) + break; + } +#endif + } + conn->postgres_pid = -1; /* Discard any unread/unsent data */ conn->inStart = conn->inCursor = conn->inEnd = 0; conn->outCount = 0; @@ -643,6 +671,10 @@ fillPGconn(PGconn *conn, PQconninfoOption *connOptions) conn->pghost = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "port"); conn->pgport = tmp ? strdup(tmp) : NULL; + tmp = conninfo_getval(connOptions, "standalone_datadir"); + conn->standalone_datadir = tmp ? strdup(tmp) : NULL; + tmp = conninfo_getval(connOptions, "standalone_backend"); + conn->standalone_backend = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "tty"); conn->pgtty = tmp ? strdup(tmp) : NULL; tmp = conninfo_getval(connOptions, "options"); @@ -1311,6 +1343,251 @@ setKeepalivesWin32(PGconn *conn) #endif /* SIO_KEEPALIVE_VALS */ #endif /* WIN32 */ +#ifdef WIN32 +int +pgsockpair(int handles[2]) +{ + SOCKET s; + struct sockaddr_in serv_addr; + int len = sizeof(serv_addr); + + handles[0] = handles[1] = INVALID_SOCKET; + + if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + { + return -1; + } + + memset((void *) &serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(0); + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (bind(s, (SOCKADDR *) &serv_addr, len) == SOCKET_ERROR) + { + closesocket(s); + return -1; + } + if (listen(s, 1) == SOCKET_ERROR) + { + closesocket(s); + return -1; + } + if (getsockname(s, (SOCKADDR *) &serv_addr, &len) == SOCKET_ERROR) + { + closesocket(s); + return -1; + } + if ((handles[1] = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + { + closesocket(s); + return -1; + } + + if (connect(handles[1], (SOCKADDR *) &serv_addr, len) == SOCKET_ERROR) + { + closesocket(s); + return -1; + } + if ((handles[0] = accept(s, (SOCKADDR *) &serv_addr, &len)) == INVALID_SOCKET) + { + closesocket(handles[1]); + handles[1] = INVALID_SOCKET; + closesocket(s); + return -1; + } + closesocket(s); + return 0; +} + +/* + * Fork and exec a command, connecting up a pair of anonymous sockets for + * communication. argv[fdarg] is modified by appending the child-side + * socket's file descriptor number. The parent-side socket's FD is returned + * in *psock, and the function result is the child's PID. + */ +static pid_t +fork_backend_child(char **argv, int fdarg, int *psock,HANDLE *phHandle) +{ + int socks[2]; + char newfdarg[32]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + int i; + int j; + char cmdLine[MAXPGPATH * 2]; + typedef struct + { + SOCKET origsocket; /* Original socket value, or PGINVALID_SOCKET + * if not a socket */ + WSAPROTOCOL_INFO wsainfo; + }InheritableSocket; + InheritableSocket *param; + HANDLE paramHandle; + SECURITY_ATTRIBUTES sa; + char paramHandleStr[32]; + + + if(pgsockpair(socks) < 0) + return -1; + + /* Set up shared memory for parameter passing */ + ZeroMemory(&sa, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + paramHandle = CreateFileMapping(INVALID_HANDLE_VALUE, + &sa, + PAGE_READWRITE, + 0, + sizeof(InheritableSocket), + NULL); + if (paramHandle == INVALID_HANDLE_VALUE) + { + return -1; + } + + param = MapViewOfFile(paramHandle, FILE_MAP_WRITE, 0, 0, sizeof(InheritableSocket)); + if (!param) + { + CloseHandle(paramHandle); + return -1; + } + + /* Insert temp file name instead of sockid incase of windows */ +#ifdef _WIN64 + sprintf(paramHandleStr, "%llu", (LONG_PTR) paramHandle); +#else + sprintf(paramHandleStr, "%lu", (DWORD) paramHandle); +#endif + + snprintf(newfdarg, sizeof(newfdarg), "%s%s", argv[fdarg], paramHandleStr); + argv[fdarg] = newfdarg; + + /* Format the cmd line */ + cmdLine[sizeof(cmdLine) - 1] = '\0'; + cmdLine[sizeof(cmdLine) - 2] = '\0'; + snprintf(cmdLine, sizeof(cmdLine) - 1, "\"%s\"", argv[0]); + + i = 0; + while (argv[++i] != NULL) + { + j = strlen(cmdLine); + snprintf(cmdLine + j, sizeof(cmdLine) - 1 - j, " \"%s\"", argv[i]); + } + if (cmdLine[sizeof(cmdLine) - 2] != '\0') + { + return -1; + } + + memset(&pi, 0, sizeof(pi)); + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, CREATE_SUSPENDED, + NULL, NULL, &si, &pi)) + { + return -1; + } + + + param->origsocket = socks[0]; + if (socks[0]!= 0 && socks[0] != PGINVALID_SOCKET) + { + /* Actual socket */ + if (WSADuplicateSocket(socks[0], pi.dwProcessId, ¶m->wsainfo) != 0) + { + if (!TerminateProcess(pi.hProcess, 255)) + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return -1; + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return -1; + } + } + + /* Drop the parameter shared memory that is now inherited to the standalone chile */ + if (!UnmapViewOfFile(param)) + /*here not terminating process as here just an error needs to be logged.*/ + return -1; + if (!CloseHandle(paramHandle)) + /*here not terminating process as here just an error needs to be logged.*/ + return -1; + + /* + * Now that the socket information is shared, we start the child + * thread */ + if (ResumeThread(pi.hThread) == -1) + { + if (!TerminateProcess(pi.hProcess, 255)) + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return -1; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return -1; + } + /* Don't close pi.hProcess here - the wait thread needs access to it */ + CloseHandle(pi.hThread); + + *psock = socks[1]; + *phHandle = pi.hProcess; + return pi.dwProcessId; +} + + +#else + +/* + * Fork and exec a command, connecting up a pair of anonymous sockets for + * communication. argv[fdarg] is modified by appending the child-side + * socket's file descriptor number. The parent-side socket's FD is returned + * in *psock, and the function result is the child's PID. + */ +static pid_t +fork_backend_child(char **argv, int fdarg, int *psock) +{ + pid_t pid; + int socks[2]; + char newfdarg[32]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) + return -1; + + pid = fork(); + + if (pid < 0) + { + /* fork failed */ + close(socks[0]); + close(socks[1]); + return pid; + } + else if (pid == 0) + { + /* successful, in child process */ + close(socks[1]); + snprintf(newfdarg, sizeof(newfdarg), "%s%d", argv[fdarg], socks[0]); + argv[fdarg] = newfdarg; + execv(argv[0], argv); + perror(argv[0]); + exit(1); + } + else + { + /* successful, in parent process */ + close(socks[0]); + *psock = socks[1]; + } + + return pid; +} +#endif + /* ---------- * connectDBStart - * Begin the process of making a connection to the backend. @@ -1339,6 +1616,59 @@ connectDBStart(PGconn *conn) conn->outCount = 0; /* + * If the standalone_datadir option was specified, ignore any host or + * port specifications and just try to fork a standalone backend. + */ + if (conn->standalone_datadir && conn->standalone_datadir[0]) + { + char *be_argv[10]; + + /* + * We set up the backend's command line in execv(3) style, so that + * we don't need to cope with shell quoting rules. + */ + if (conn->standalone_backend && conn->standalone_backend[0]) + be_argv[0] = conn->standalone_backend; + else /* assume we should use hard-wired path */ + be_argv[0] = PGBINDIR "/postgres"; + + be_argv[1] = "--child="; + be_argv[2] = "-D"; + be_argv[3] = conn->standalone_datadir; + be_argv[4] = (conn->dbName && conn->dbName[0]) ? conn->dbName : NULL; + be_argv[5] = NULL; + +#ifdef WIN32 + conn->postgres_pid = fork_backend_child(be_argv, 1, &conn->sock, &conn->proc_handle); +#else + conn->postgres_pid = fork_backend_child(be_argv, 1, &conn->sock); +#endif + if (conn->postgres_pid < 0) + { + char sebuf[256]; + + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not fork standalone backend: %s\n"), + pqStrerror(errno, sebuf, sizeof(sebuf))); + goto connect_errReturn; + } + + /* + * Go directly to CONNECTION_AUTH_OK state, since the standalone + * backend is not going to issue any authentication challenge to us. + * We're just waiting for startup to conclude. + */ +#ifdef USE_SSL + conn->allow_ssl_try = false; +#endif + conn->pversion = PG_PROTOCOL(3, 0); + conn->status = CONNECTION_AUTH_OK; + conn->asyncStatus = PGASYNC_BUSY; + + return 1; + } + + /* * Determine the parameters to pass to pg_getaddrinfo_all. */ @@ -2708,6 +3038,7 @@ makeEmptyPGconn(void) conn->auth_req_received = false; conn->password_needed = false; conn->dot_pgpass_used = false; + conn->postgres_pid = -1; #ifdef USE_SSL conn->allow_ssl_try = true; conn->wait_ssl_try = false; @@ -2784,6 +3115,10 @@ freePGconn(PGconn *conn) free(conn->pgport); if (conn->pgunixsocket) free(conn->pgunixsocket); + if (conn->standalone_datadir) + free(conn->standalone_datadir); + if (conn->standalone_backend) + free(conn->standalone_backend); if (conn->pgtty) free(conn->pgtty); if (conn->connect_timeout) diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4a6c8fe..43c2092 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -303,6 +303,8 @@ struct pg_conn char *pgunixsocket; /* the Unix-domain socket that the server is * listening on; if NULL, uses a default * constructed from pgport */ + char *standalone_datadir; /* data directory for standalone backend */ + char *standalone_backend; /* executable for standalone backend */ char *pgtty; /* tty on which the backend messages is * displayed (OBSOLETE, NOT USED) */ char *connect_timeout; /* connection timeout (numeric string) */ @@ -373,6 +375,9 @@ struct pg_conn bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */ bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */ + /* If we forked a child postgres process, its PID is kept here */ + pid_t postgres_pid; /* PID, or -1 if none */ + /* Transient state needed while establishing connection */ struct addrinfo *addrlist; /* list of possible backend addresses */ struct addrinfo *addr_cur; /* the one currently being tried */ @@ -415,6 +420,10 @@ struct pg_conn /* Status for asynchronous result construction */ PGresult *result; /* result being constructed */ PGresult *next_result; /* next result (used in single-row mode) */ +#ifdef WIN32 + /* If we forked a child postgres process, process handle */ + HANDLE proc_handle; +#endif /* Assorted state for SSL, GSS, etc */