Extend pg_ctl to handle service management under WIN32. Lacks docs.
authorTom Lane
Thu, 24 Jun 2004 18:23:26 +0000 (18:23 +0000)
committerTom Lane
Thu, 24 Jun 2004 18:23:26 +0000 (18:23 +0000)
Claudio Natoli and Magnus Hagander

src/bin/pg_ctl/pg_ctl.c

index 8eb7777774a46dcdec9589258274669fcd439eee..4b06385c9a6195e4e79fd1027516170c5b6148ff 100644 (file)
@@ -4,7 +4,7 @@
  *
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.16 2004/06/11 16:36:31 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.17 2004/06/24 18:23:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -52,7 +52,10 @@ typedef enum
    RESTART_COMMAND,
    RELOAD_COMMAND,
    STATUS_COMMAND,
-   KILL_COMMAND
+   KILL_COMMAND,
+   REGISTER_COMMAND,
+   UNREGISTER_COMMAND,
+   RUN_AS_SERVICE_COMMAND
 }  CtlCommand;
 
 
@@ -63,14 +66,20 @@ static bool silence_echo = false;
 static ShutdownMode shutdown_mode = SMART_MODE;
 static int sig = SIGTERM;  /* default */
 static CtlCommand ctl_command = NO_COMMAND;
-static char *pg_data_opts = NULL;
 static char *pg_data = NULL;
 static char *post_opts = NULL;
 static const char *progname;
 static char *log_file = NULL;
 static char *postgres_path = NULL;
+static char *register_servicename = "PostgreSQL"; /* FIXME: + version ID? */
+static char *register_username = NULL;
+static char *register_password = NULL;
 static char *argv0 = NULL;
 
+static void write_stderr(const char *fmt,...)
+/* This extension allows gcc to check the format string for consistency with
+   the supplied arguments. */
+__attribute__((format(printf, 1, 2)));
 static void *xmalloc(size_t size);
 static char *xstrdup(const char *s);
 static void do_advice(void);
@@ -83,6 +92,16 @@ static void do_restart(void);
 static void do_reload(void);
 static void do_status(void);
 static void do_kill(pgpid_t pid);
+#ifdef WIN32
+static bool  pgwin32_IsInstalled(SC_HANDLE);
+static char* pgwin32_CommandLine(bool);
+static void pgwin32_doRegister();
+static void pgwin32_doUnregister();
+static void pgwin32_SetServiceStatus(DWORD);
+static void WINAPI pgwin32_ServiceHandler(DWORD);
+static void WINAPI pgwin32_ServiceMain(DWORD, LPTSTR*);
+static void pgwin32_doRunAsService();
+#endif
 static pgpid_t get_pgpid(void);
 static char **readfile(char *path);
 static int start_postmaster(void);
@@ -93,6 +112,63 @@ static char postopts_file[MAXPGPATH];
 static char pid_file[MAXPGPATH];
 static char conf_file[MAXPGPATH];
 
+
+#ifdef WIN32
+static void
+write_eventlog(int level, const char *line)
+{
+   static HANDLE evtHandle = INVALID_HANDLE_VALUE;
+
+   if (evtHandle == INVALID_HANDLE_VALUE) {
+       evtHandle = RegisterEventSource(NULL,"PostgreSQL");
+       if (evtHandle == NULL) {
+           evtHandle = INVALID_HANDLE_VALUE;
+           return;
+       }
+   }
+
+   ReportEvent(evtHandle,
+               level,
+               0,
+               0, /* All events are Id 0 */
+               NULL,
+               1,
+               0,
+               &line,
+               NULL);
+}
+#endif
+
+/*
+ * Write errors to stderr (or by equal means when stderr is
+ * not available).
+ */
+static void
+write_stderr(const char *fmt,...)
+{
+   va_list ap;
+
+   va_start(ap, fmt);
+#ifndef WIN32
+   /* On Unix, we just fprintf to stderr */
+   vfprintf(stderr, fmt, ap);
+#else
+   /* 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 */
+   {
+       char errbuf[2048]; /* Arbitrary size? */
+
+       vsnprintf(errbuf, sizeof(errbuf), fmt, ap);
+
+       write_eventlog(EVENTLOG_ERROR_TYPE, errbuf);
+   }
+   else /* Not running as service, write to stderr */
+       vfprintf(stderr, fmt, ap);
+#endif
+   va_end(ap);
+}
+
 /*
  * routines to check memory allocations and fail noisily.
  */
@@ -105,7 +181,7 @@ xmalloc(size_t size)
    result = malloc(size);
    if (!result)
    {
-       fprintf(stderr, _("%s: out of memory\n"), progname);
+       write_stderr(_("%s: out of memory\n"), progname);
        exit(1);
    }
    return result;
@@ -121,7 +197,7 @@ xstrdup(const char *s)
    result = strdup(s);
    if (!result)
    {
-       fprintf(stderr, _("%s: out of memory\n"), progname);
+       write_stderr(_("%s: out of memory\n"), progname);
        exit(1);
    }
    return result;
@@ -352,10 +428,9 @@ do_start(void)
    {
        old_pid = get_pgpid();
        if (old_pid != 0)
-           fprintf(stderr,
-                   _("%s: Another postmaster may be running. "
-                   "Trying to start postmaster anyway.\n"),
-                   progname);
+           write_stderr(_("%s: Another postmaster may be running. "
+                          "Trying to start postmaster anyway.\n"),
+                        progname);
    }
 
    if (post_opts == NULL)
@@ -371,13 +446,13 @@ do_start(void)
                post_opts = "";
            else
            {
-               fprintf(stderr, _("%s: cannot read %s\n"), progname, postopts_file);
+               write_stderr(_("%s: cannot read %s\n"), progname, postopts_file);
                exit(1);
            }
        }
        else if (optlines[0] == NULL || optlines[1] != NULL)
        {
-           fprintf(stderr, _("%s: option file %s must have exactly 1 line\n"),
+           write_stderr(_("%s: option file %s must have exactly 1 line\n"),
                    progname, ctl_command == RESTART_COMMAND ?
                    postopts_file : def_postopts_file);
            exit(1);
@@ -419,18 +494,16 @@ do_start(void)
                                   postmaster_path)) < 0)
        {
            if (ret == -1)
-               fprintf(stderr,
-                       _("The program \"postmaster\" is needed by %s "
-                         "but was not found in the same directory as "
-                         "\"%s\".\n"
-                         "Check your installation.\n"),
-                       progname, progname);
+               write_stderr(_("The program \"postmaster\" is needed by %s "
+                              "but was not found in the same directory as "
+                              "\"%s\".\n"
+                              "Check your installation.\n"),
+                            progname, progname);
            else
-               fprintf(stderr,
-                       _("The program \"postmaster\" was found by %s "
-                         "but was not the same version as \"%s\".\n"
-                         "Check your installation.\n"),
-                       progname, progname);
+               write_stderr(_("The program \"postmaster\" was found by %s "
+                              "but was not the same version as \"%s\".\n"
+                              "Check your installation.\n"),
+                            progname, progname);
            exit(1);
        }
        postgres_path = postmaster_path;
@@ -438,7 +511,7 @@ do_start(void)
 
    if (start_postmaster() != 0)
    {
-       fprintf(stderr, _("Unable to run the postmaster binary\n"));
+       write_stderr(_("Unable to run the postmaster binary\n"));
        exit(1);
    }
 
@@ -448,10 +521,9 @@ do_start(void)
        pid = get_pgpid();
        if (pid == old_pid)
        {
-           fprintf(stderr,
-                   _("%s: cannot start postmaster\n"
-                   "Examine the log output\n"),
-                   progname);
+           write_stderr(_("%s: cannot start postmaster\n"
+                          "Examine the log output\n"),
+                        progname);
            exit(1);
        }
    }
@@ -485,23 +557,22 @@ do_stop(void)
 
    if (pid == 0)               /* no pid file */
    {
-       fprintf(stderr, _("%s: could not find %s\n"), progname, pid_file);
-       fprintf(stderr, _("Is postmaster running?\n"));
+       write_stderr(_("%s: could not find %s\n"), progname, pid_file);
+       write_stderr(_("Is postmaster running?\n"));
        exit(1);
    }
    else if (pid < 0)           /* standalone backend, not postmaster */
    {
        pid = -pid;
-       fprintf(stderr,
-               _("%s: cannot stop postmaster; "
-               "postgres is running (PID: %ld)\n"),
-               progname, pid);
+       write_stderr(_("%s: cannot stop postmaster; "
+                      "postgres is running (PID: %ld)\n"),
+                    progname, pid);
        exit(1);
    }
 
    if (kill((pid_t) pid, sig) != 0)
    {
-       fprintf(stderr, _("stop signal failed (PID: %ld): %s\n"), pid,
+       write_stderr(_("stop signal failed (PID: %ld): %s\n"), pid,
                strerror(errno));
        exit(1);
    }
@@ -540,7 +611,7 @@ do_stop(void)
            if (!silence_echo)
                printf(_(" failed\n"));
    
-           fprintf(stderr, _("%s: postmaster does not shut down\n"), progname);
+           write_stderr(_("%s: postmaster does not shut down\n"), progname);
            exit(1);
        }
        if (!silence_echo)
@@ -565,25 +636,24 @@ do_restart(void)
 
    if (pid == 0)               /* no pid file */
    {
-       fprintf(stderr, _("%s: could not find %s\n"), progname, pid_file);
-       fprintf(stderr, _("Is postmaster running?\nstarting postmaster anyway\n"));
+       write_stderr(_("%s: could not find %s\n"), progname, pid_file);
+       write_stderr(_("Is postmaster running?\nstarting postmaster anyway\n"));
        do_start();
        return;
    }
    else if (pid < 0)           /* standalone backend, not postmaster */
    {
        pid = -pid;
-       fprintf(stderr,
-               _("%s: cannot restart postmaster; "
-               "postgres is running (PID: %ld)\n"),
-               progname, pid);
-       fprintf(stderr, _("Please terminate postgres and try again.\n"));
+       write_stderr(_("%s: cannot restart postmaster; "
+                      "postgres is running (PID: %ld)\n"),
+                    progname, pid);
+       write_stderr(_("Please terminate postgres and try again.\n"));
        exit(1);
    }
 
    if (kill((pid_t) pid, sig) != 0)
    {
-       fprintf(stderr, _("stop signal failed (PID: %ld): %s\n"), pid,
+       write_stderr(_("stop signal failed (PID: %ld): %s\n"), pid,
                strerror(errno));
        exit(1);
    }
@@ -616,7 +686,7 @@ do_restart(void)
        if (!silence_echo)
            printf(_(" failed\n"));
 
-       fprintf(stderr, _("%s: postmaster does not shut down\n"), progname);
+       write_stderr(_("%s: postmaster does not shut down\n"), progname);
        exit(1);
    }
 
@@ -636,24 +706,23 @@ do_reload(void)
    pid = get_pgpid();
    if (pid == 0)               /* no pid file */
    {
-       fprintf(stderr, _("%s: could not find %s\n"), progname, pid_file);
-       fprintf(stderr, _("Is postmaster running?\n"));
+       write_stderr(_("%s: could not find %s\n"), progname, pid_file);
+       write_stderr(_("Is postmaster running?\n"));
        exit(1);
    }
    else if (pid < 0)           /* standalone backend, not postmaster */
    {
        pid = -pid;
-       fprintf(stderr,
-               _("%s: cannot reload postmaster; "
-               "postgres is running (PID: %ld)\n"),
-               progname, pid);
-       fprintf(stderr, _("Please terminate postgres and try again.\n"));
+       write_stderr(_("%s: cannot reload postmaster; "
+                      "postgres is running (PID: %ld)\n"),
+                    progname, pid);
+       write_stderr(_("Please terminate postgres and try again.\n"));
        exit(1);
    }
 
    if (kill((pid_t) pid, sig) != 0)
    {
-       fprintf(stderr, _("reload signal failed (PID: %ld): %s\n"), pid,
+       write_stderr(_("reload signal failed (PID: %ld): %s\n"), pid,
                strerror(errno));
        exit(1);
    }
@@ -674,7 +743,7 @@ do_status(void)
    pid = get_pgpid();
    if (pid == 0)               /* no pid file */
    {
-       fprintf(stderr, _("%s: postmaster or postgres not running\n"), progname);
+       write_stderr(_("%s: postmaster or postgres not running\n"), progname);
        exit(1);
    }
    else if (pid < 0)           /* standalone backend */
@@ -702,18 +771,244 @@ do_kill(pgpid_t pid)
 {
    if (kill((pid_t) pid, sig) != 0)
    {
-       fprintf(stderr, _("signal %d failed (PID: %ld): %s\n"), sig, pid,
+       write_stderr(_("signal %d failed (PID: %ld): %s\n"), sig, pid,
                strerror(errno));
        exit(1);
    }
 }
 
+#ifdef WIN32
+
+static bool pgwin32_IsInstalled(SC_HANDLE hSCM)
+{
+   SC_HANDLE hService = OpenService(hSCM, register_servicename, SERVICE_QUERY_CONFIG);
+   bool bResult = (hService != NULL);
+   if (bResult)
+       CloseServiceHandle(hService);
+   return bResult;
+}
+
+static char* pgwin32_CommandLine(bool registration)
+{
+   static char cmdLine[MAXPGPATH];
+   int ret;
+   if (registration)
+       ret = find_my_exec(argv0, cmdLine);
+   else
+       ret = find_other_exec(argv0, "postmaster", PM_VERSIONSTR, cmdLine);
+   if (ret != 0)
+   {
+       write_stderr(_("Unable to find exe"));
+       exit(1);
+   }
+
+   if (registration)
+   {
+       if (strcasecmp(cmdLine+strlen(cmdLine)-4,".exe"))
+       {
+           /* If commandline does not end in .exe, append it */
+           strcat(cmdLine,".exe");
+       }
+       strcat(cmdLine," runservice -N \"");
+       strcat(cmdLine,register_servicename);
+       strcat(cmdLine,"\"");
+   }
+
+   if (pg_data)
+   {
+       strcat(cmdLine," -D \"");
+       strcat(cmdLine,pg_data);
+       strcat(cmdLine,"\"");
+   }
+
+   if (post_opts)
+   {
+       strcat(cmdLine," ");
+       if (registration)
+           strcat(cmdLine," -o \"");
+       strcat(cmdLine,post_opts);
+       if (registration)
+           strcat(cmdLine,"\"");
+   }
+
+   return cmdLine;
+}
+
+static void
+pgwin32_doRegister()
+{
+   SC_HANDLE hService;
+   SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+   if (hSCM == NULL)
+   {
+       write_stderr(_("Unable to open service manager\n"));
+       exit(1);
+   }
+   if (pgwin32_IsInstalled(hSCM))
+   {
+       CloseServiceHandle(hSCM);
+       write_stderr(_("Service \"%s\" already registered\n"),register_servicename);
+       exit(1);
+   }
+
+   if ((hService = CreateService(hSCM, register_servicename, register_servicename,
+                                 SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
+                                 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
+                                 pgwin32_CommandLine(true),
+                                 NULL, NULL, "RPCSS\0", register_username, register_password)) == NULL)
+   {
+       CloseServiceHandle(hSCM);
+       write_stderr(_("Unable to register service \"%s\" [%d]\n"), register_servicename, (int)GetLastError());
+       exit(1);
+   }
+   CloseServiceHandle(hService);
+   CloseServiceHandle(hSCM);
+}
+
+static void
+pgwin32_doUnregister()
+{
+   SC_HANDLE hService;
+   SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+   if (hSCM == NULL)
+   {
+       write_stderr(_("Unable to open service manager\n"));
+       exit(1);
+   }
+   if (!pgwin32_IsInstalled(hSCM))
+   {
+       CloseServiceHandle(hSCM);
+       write_stderr(_("Service \"%s\" not registered\n"),register_servicename);
+       exit(1);
+   }
+
+   if ((hService = OpenService(hSCM, register_servicename, DELETE)) == NULL)
+   {
+       CloseServiceHandle(hSCM);
+       write_stderr(_("Unable to open service \"%s\" [%d]\n"), register_servicename, (int)GetLastError());
+       exit(1);
+   }
+   if (!DeleteService(hService)) {
+       CloseServiceHandle(hService);
+       CloseServiceHandle(hSCM);
+       write_stderr(_("Unable to unregister service \"%s\" [%d]\n"), register_servicename, (int)GetLastError());
+       exit(1);
+   }
+   CloseServiceHandle(hService);
+   CloseServiceHandle(hSCM);
+}
 
 
+static SERVICE_STATUS status;
+static SERVICE_STATUS_HANDLE hStatus = (SERVICE_STATUS_HANDLE)0;
+static HANDLE shutdownHandles[2];
+static pid_t postmasterPID = -1;
+#define shutdownEvent     shutdownHandles[0]
+#define postmasterProcess shutdownHandles[1]
+
+static void pgwin32_SetServiceStatus(DWORD currentState)
+{
+   status.dwCurrentState = currentState;
+   SetServiceStatus(hStatus, (LPSERVICE_STATUS)&status);
+}
+
+static void WINAPI pgwin32_ServiceHandler(DWORD request)
+{
+   switch (request)
+   {
+       case SERVICE_CONTROL_STOP:
+       case SERVICE_CONTROL_SHUTDOWN:
+           pgwin32_SetServiceStatus(SERVICE_STOP_PENDING);
+           SetEvent(shutdownEvent);
+           return;
+
+       case SERVICE_CONTROL_PAUSE:
+           /* Win32 config reloading */
+           kill(postmasterPID,SIGHUP);
+           return;
+
+       /* FIXME: These could be used to replace other signals etc */
+       case SERVICE_CONTROL_CONTINUE:
+       case SERVICE_CONTROL_INTERROGATE:
+       default:
+           break;
+   }
+}
+
+static void WINAPI pgwin32_ServiceMain(DWORD argc, LPTSTR *argv)
+{
+   STARTUPINFO si;
+   PROCESS_INFORMATION pi;
+   DWORD ret;
+
+   /* Initialize variables */
+   status.dwWin32ExitCode  = S_OK;
+   status.dwCheckPoint     = 0;
+   status.dwWaitHint       = 0;
+   status.dwServiceType    = SERVICE_WIN32_OWN_PROCESS;
+   status.dwControlsAccepted           = SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_PAUSE_CONTINUE;
+   status.dwServiceSpecificExitCode    = 0;
+   status.dwCurrentState = SERVICE_START_PENDING;
+
+   memset(&pi,0,sizeof(pi));
+   memset(&si,0,sizeof(si));
+   si.cb = sizeof(si);
+
+   /* Register the control request handler */
+   if ((hStatus = RegisterServiceCtrlHandler(register_servicename, pgwin32_ServiceHandler)) == (SERVICE_STATUS_HANDLE)0)
+       return;
+
+   if ((shutdownEvent = CreateEvent(NULL,true,false,NULL)) == NULL)
+       return;
+
+   /* Start the postmaster */
+   pgwin32_SetServiceStatus(SERVICE_START_PENDING);
+   if (!CreateProcess(NULL,pgwin32_CommandLine(false),NULL,NULL,TRUE,0,NULL,NULL,&si,&pi))
+   {
+       pgwin32_SetServiceStatus(SERVICE_STOPPED);
+       return;
+   }
+   postmasterPID       = pi.dwProcessId;
+   postmasterProcess   = pi.hProcess;
+   CloseHandle(pi.hThread);
+   pgwin32_SetServiceStatus(SERVICE_RUNNING);
+
+   /* Wait for quit... */
+   ret = WaitForMultipleObjects(2,shutdownHandles,FALSE,INFINITE);
+   pgwin32_SetServiceStatus(SERVICE_STOP_PENDING);
+   switch (ret)
+   {
+       case WAIT_OBJECT_0: /* shutdown event */
+           kill(postmasterPID,SIGINT);
+           WaitForSingleObject(postmasterProcess,INFINITE);
+           break;
+
+       case (WAIT_OBJECT_0+1): /* postmaster went down */
+           break;
+
+       default:
+           /* assert(false); */
+   }
+
+   CloseHandle(shutdownEvent);
+   CloseHandle(postmasterProcess);
+
+   pgwin32_SetServiceStatus(SERVICE_STOPPED);
+}
+
+static void pgwin32_doRunAsService()
+{
+   SERVICE_TABLE_ENTRY st[] = {{ register_servicename, pgwin32_ServiceMain },
+                               { NULL, NULL }};
+   StartServiceCtrlDispatcher(st);
+}
+
+#endif
+
 static void
 do_advice(void)
 {
-   fprintf(stderr, _("\nTry \"%s --help\" for more information.\n"), progname);
+   write_stderr(_("\nTry \"%s --help\" for more information.\n"), progname);
 }
 
 
@@ -730,9 +1025,18 @@ do_help(void)
    printf(_("  %s reload  [-D DATADIR] [-s]\n"), progname);
    printf(_("  %s status  [-D DATADIR]\n"), progname);
    printf(_("  %s kill    SIGNALNAME PROCESSID\n"), progname);
+#ifdef WIN32
+   printf(_("  %s register   [-N servicename] [-U username] [-P password] [-D DATADIR] [-o \"OPTIONS\"]\n"), progname);
+   printf(_("  %s unregister [-N servicename]\n"), progname);
+#endif
    printf(_("Common options:\n"));
    printf(_("  -D, --pgdata DATADIR   location of the database storage area\n"));
    printf(_("  -s, --silent only print errors, no informational messages\n"));
+#ifdef WIN32
+   printf(_("  -N       service name with which to register PostgreSQL server\n"));
+   printf(_("  -P       user name of account to register PostgreSQL server\n"));
+   printf(_("  -U       password  of account to register PostgreSQL server\n"));
+#endif
    printf(_("  -w           wait until operation completes\n"));
    printf(_("  -W           do not wait until operation completes\n"));
    printf(_("  --help       show this help, then exit\n"));
@@ -778,7 +1082,7 @@ set_mode(char *modeopt)
    }
    else
    {
-       fprintf(stderr, _("%s: invalid shutdown mode %s\n"), progname, modeopt);
+       write_stderr(_("%s: invalid shutdown mode %s\n"), progname, modeopt);
        do_advice();
        exit(1);
    }
@@ -811,7 +1115,7 @@ set_sig(char *signame)
        sig = SIGUSR2;
    else
    {
-       fprintf(stderr, _("%s: invalid signal \"%s\"\n"), progname, signame);
+       write_stderr(_("%s: invalid signal \"%s\"\n"), progname, signame);
        do_advice();
        exit(1);
    }
@@ -879,19 +1183,17 @@ main(int argc, char **argv)
    /* process command-line options */
    while (optind < argc)
    {
-       while ((c = getopt_long(argc, argv, "D:l:m:o:p:swW", long_options, &option_index)) != -1)
+       while ((c = getopt_long(argc, argv, "D:l:m:N:o:p:P:sU:wW", long_options, &option_index)) != -1)
        {
            switch (c)
            {
                case 'D':
                {
-                   int         len = strlen(optarg) + 4;
+                   int         len = strlen(optarg);
                    char       *env_var;
        
-                   pg_data_opts = xmalloc(len);
-                   snprintf(pg_data_opts, len, "-D %s", optarg);
-                   env_var = xmalloc(len + sizeof("PGDATA="));
-                   snprintf(env_var, len + sizeof("PGDATA="), "PGDATA=%s", optarg);
+                   env_var = xmalloc(len + 8);
+                   snprintf(env_var, len + 8, "PGDATA=%s", optarg);
                    putenv(env_var);
                    break;
                }
@@ -901,15 +1203,36 @@ main(int argc, char **argv)
                case 'm':
                    set_mode(optarg);
                    break;
+               case 'N':
+                   register_servicename = xstrdup(optarg);
+                   break;
                case 'o':
                    post_opts = xstrdup(optarg);
                    break;
                case 'p':
                    postgres_path = xstrdup(optarg);
                    break;
+               case 'P':
+                   register_password  = xstrdup(optarg);
+                   break;
                case 's':
                    silence_echo = true;
                    break;
+               case 'U':
+                   if (strchr(optarg,'\\'))
+                       register_username  = xstrdup(optarg);
+                   else /* Prepend .\ for local accounts */
+                   {
+                       register_username = malloc(strlen(optarg)+3);
+                       if (!register_username)
+                       {
+                           write_stderr(_("%s: out of memory\n"), progname);
+                           exit(1);
+                       }
+                       strcpy(register_username,".\\");
+                       strcat(register_username,optarg);
+                   }
+                   break;
                case 'w':
                    do_wait = true;
                    wait_set = true;
@@ -919,7 +1242,7 @@ main(int argc, char **argv)
                    wait_set = true;
                    break;
                default:
-                   fprintf(stderr, _("%s: invalid option %s\n"), progname, optarg);
+                   write_stderr(_("%s: invalid option %s\n"), progname, optarg);
                    do_advice();
                    exit(1);
            }
@@ -930,7 +1253,7 @@ main(int argc, char **argv)
        {
            if (ctl_command != NO_COMMAND)
            {
-               fprintf(stderr, _("%s: extra operation mode %s\n"), progname, argv[optind]);
+               write_stderr(_("%s: extra operation mode %s\n"), progname, argv[optind]);
                do_advice();
                exit(1);
            }
@@ -949,7 +1272,7 @@ main(int argc, char **argv)
            {
                if (argc - optind < 3)
                {
-                   fprintf(stderr, _("%s: invalid kill syntax\n"), progname);
+                   write_stderr(_("%s: invalid kill syntax\n"), progname);
                    do_advice();
                    exit(1);
                }
@@ -957,32 +1280,45 @@ main(int argc, char **argv)
                set_sig(argv[++optind]);
                killproc = atol(argv[++optind]);
            }
+#ifdef WIN32
+           else if (strcmp(argv[optind], "register") == 0)
+               ctl_command = REGISTER_COMMAND;
+           else if (strcmp(argv[optind], "unregister") == 0)
+               ctl_command = UNREGISTER_COMMAND;
+           else if (strcmp(argv[optind], "runservice") == 0)
+               ctl_command = RUN_AS_SERVICE_COMMAND;
+#endif
            else
            {
-               fprintf(stderr, _("%s: invalid operation mode %s\n"), progname, argv[optind]);
+               write_stderr(_("%s: invalid operation mode %s\n"), progname, argv[optind]);
                do_advice();
                exit(1);
            }
            optind++;
        }
    }
-   
+
    if (ctl_command == NO_COMMAND)
    {
-       fprintf(stderr, _("%s: no operation specified\n"), progname);
+       write_stderr(_("%s: no operation specified\n"), progname);
        do_advice();
        exit(1);
    }
 
+   /* Note we put any -D switch into the env var above */
    pg_data = getenv("PGDATA");
-   canonicalize_path(pg_data);
+   if (pg_data)
+   {
+       /* XXX modifies environment var in-place ... ugly ... */
+       canonicalize_path(pg_data);
+   }
 
-   if (pg_data == NULL && ctl_command != KILL_COMMAND)
+   if (pg_data == NULL &&
+       ctl_command != KILL_COMMAND && ctl_command != UNREGISTER_COMMAND)
    {
-       fprintf(stderr,
-               _("%s: no database directory specified "
-               "and environment variable PGDATA unset\n"),
-               progname);
+       write_stderr(_("%s: no database directory specified "
+                      "and environment variable PGDATA unset\n"),
+                    progname);
        do_advice();
        exit(1);
    }
@@ -1034,6 +1370,17 @@ main(int argc, char **argv)
        case KILL_COMMAND:
            do_kill(killproc);
            break;
+#ifdef WIN32
+       case REGISTER_COMMAND:
+           pgwin32_doRegister();
+           break;
+       case UNREGISTER_COMMAND:
+           pgwin32_doUnregister();
+           break;
+       case RUN_AS_SERVICE_COMMAND:
+           pgwin32_doRunAsService();
+           break;
+#endif
        default:
            break;
    }