Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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 8a5c8cf2a86b4a63d6991ea9aa78f2e3312a235c..a8a8564599df4891cfe24e6bfab3bf37595a4860 100644 (file)
@@ -151,6 +151,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);
@@ -216,7 +220,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? */
 
@@ -1604,6 +1608,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