]> granicus.if.org Git - postgresql/commitdiff
Windows: Make pg_ctl reliably detect service status
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 7 Jan 2016 14:59:08 +0000 (11:59 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 7 Jan 2016 14:59:08 +0000 (11:59 -0300)
pg_ctl is using isatty() to verify whether the process is running in a
terminal, and if not it sends its output to Windows' Event Log ... which
does the wrong thing when the output has been redirected to a pipe, as
reported in bug #13592.

To fix, make pg_ctl use the code we already have to detect service-ness:
in the master branch, move src/backend/port/win32/security.c to src/port
(with suitable tweaks so that it runs properly in backend and frontend
environments); pg_ctl already has access to pgport so it Just Works.  In
older branches, that's likely to cause trouble, so instead duplicate the
required code in pg_ctl.c.

Author: Michael Paquier
Bug report and diagnosis: Egon Kocjan
Backpatch: all supported branches

src/bin/pg_ctl/pg_ctl.c

index dd8276063097c8b132872c855f85513f372c02ad..dded781b85a0766c7af6ac8b423e2f7c79cbb75a 100644 (file)
@@ -148,6 +148,10 @@ static void WINAPI pgwin32_ServiceHandler(DWORD);
 static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR *);
 static void pgwin32_doRunAsService(void);
 static int     CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo, bool as_service);
+static bool pgwin32_get_dynamic_tokeninfo(HANDLE token,
+                                                         TOKEN_INFORMATION_CLASS class,
+                                                         char **InfoBuffer, char *errbuf, int errsize);
+static int pgwin32_is_service(void);
 #endif
 
 static pgpid_t get_pgpid(void);
@@ -213,7 +217,7 @@ write_stderr(const char *fmt,...)
         * On Win32, we print to stderr if running on a console, or write to
         * eventlog if running as a service
         */
-       if (!isatty(fileno(stderr)))    /* Running as a service */
+       if (!pgwin32_is_service())      /* Running as a service */
        {
                char            errbuf[2048];           /* Arbitrary size? */
 
@@ -1601,6 +1605,160 @@ pgwin32_doRunAsService(void)
        }
 }
 
+/*
+ * Call GetTokenInformation() on a token and return a dynamically sized
+ * buffer with the information in it. This buffer must be free():d by
+ * the calling function!
+ */
+static bool
+pgwin32_get_dynamic_tokeninfo(HANDLE token, TOKEN_INFORMATION_CLASS class,
+                                                         char **InfoBuffer, char *errbuf, int errsize)
+{
+       DWORD           InfoBufferSize;
+
+       if (GetTokenInformation(token, class, NULL, 0, &InfoBufferSize))
+       {
+               snprintf(errbuf, errsize, "could not get token information: got zero size\n");
+               return false;
+       }
+
+       if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+       {
+               snprintf(errbuf, errsize, "could not get token information: error code %lu\n",
+                                GetLastError());
+               return false;
+       }
+
+       *InfoBuffer = malloc(InfoBufferSize);
+       if (*InfoBuffer == NULL)
+       {
+               snprintf(errbuf, errsize, "could not allocate %d bytes for token information\n",
+                                (int) InfoBufferSize);
+               return false;
+       }
+
+       if (!GetTokenInformation(token, class, *InfoBuffer,
+                                                        InfoBufferSize, &InfoBufferSize))
+       {
+               snprintf(errbuf, errsize, "could not get token information: error code %lu\n",
+                                GetLastError());
+               return false;
+       }
+
+       return true;
+}
+
+/*
+ * We consider ourselves running as a service if one of the following is
+ * true:
+ *
+ * 1) We are running as Local System (only used by services)
+ * 2) Our token contains SECURITY_SERVICE_RID (automatically added to the
+ *       process token by the SCM when starting a service)
+ *
+ * Return values:
+ *      0 = Not service
+ *      1 = Service
+ *     -1 = Error
+ *
+ * Note: we can't report errors via write_stderr (because that calls this)
+ * We are therefore reduced to writing directly on stderr, which sucks, but
+ * we have few alternatives.
+ */
+int
+pgwin32_is_service(void)
+{
+       static int      _is_service = -1;
+       HANDLE          AccessToken;
+       char       *InfoBuffer = NULL;
+       char            errbuf[256];
+       PTOKEN_GROUPS Groups;
+       PTOKEN_USER User;
+       PSID            ServiceSid;
+       PSID            LocalSystemSid;
+       SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
+       UINT            x;
+
+       /* Only check the first time */
+       if (_is_service != -1)
+               return _is_service;
+
+       if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &AccessToken))
+       {
+               fprintf(stderr, "could not open process token: error code %lu\n",
+                               GetLastError());
+               return -1;
+       }
+
+       /* First check for local system */
+       if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenUser, &InfoBuffer,
+                                                                          errbuf, sizeof(errbuf)))
+       {
+               fprintf(stderr, "%s", errbuf);
+               return -1;
+       }
+
+       User = (PTOKEN_USER) InfoBuffer;
+
+       if (!AllocateAndInitializeSid(&NtAuthority, 1,
+                                                         SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0,
+                                                                 &LocalSystemSid))
+       {
+               fprintf(stderr, "could not get SID for local system account\n");
+               CloseHandle(AccessToken);
+               return -1;
+       }
+
+       if (EqualSid(LocalSystemSid, User->User.Sid))
+       {
+               FreeSid(LocalSystemSid);
+               free(InfoBuffer);
+               CloseHandle(AccessToken);
+               _is_service = 1;
+               return _is_service;
+       }
+
+       FreeSid(LocalSystemSid);
+       free(InfoBuffer);
+
+       /* Now check for group SID */
+       if (!pgwin32_get_dynamic_tokeninfo(AccessToken, TokenGroups, &InfoBuffer,
+                                                                          errbuf, sizeof(errbuf)))
+       {
+               fprintf(stderr, "%s", errbuf);
+               return -1;
+       }
+
+       Groups = (PTOKEN_GROUPS) InfoBuffer;
+
+       if (!AllocateAndInitializeSid(&NtAuthority, 1,
+                                                                 SECURITY_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0,
+                                                                 &ServiceSid))
+       {
+               fprintf(stderr, "could not get SID for service group\n");
+               free(InfoBuffer);
+               CloseHandle(AccessToken);
+               return -1;
+       }
+
+       _is_service = 0;
+       for (x = 0; x < Groups->GroupCount; x++)
+       {
+               if (EqualSid(ServiceSid, Groups->Groups[x].Sid))
+               {
+                       _is_service = 1;
+                       break;
+               }
+       }
+
+       free(InfoBuffer);
+       FreeSid(ServiceSid);
+
+       CloseHandle(AccessToken);
+
+       return _is_service;
+}
+
 
 /*
  * Mingw headers are incomplete, and so are the libraries. So we have to load