/*------------------------------------------------------------------------- * * pg_ctl --- start/stops/restarts the PostgreSQL server * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * * $PostgreSQL: pgsql-server/src/bin/initdb/initdb.c,v 1.32 2004/05/18 03:36:36 momjian Exp $ * *------------------------------------------------------------------------- */ #include "libpq-fe.h" #include "postgres_fe.h" #include #include #include "libpq/pqsignal.h" #include #include #include #define _(x) gettext((x)) #define WHITESPACE "\f\n\r\t\v" /* as defined by isspace() */ /* postmaster version ident string */ #define PM_VERSIONSTR "postmaster (PostgreSQL) " PG_VERSION "\n" typedef enum { SMART_MODE, FAST_MODE, IMMEDIATE_MODE } ShutdownMode; typedef enum { NO_COMMAND = 0, START_COMMAND, STOP_COMMAND, RESTART_COMMAND, RELOAD_COMMAND, STATUS_COMMAND, KILL_COMMAND } CtlCommand; static bool do_wait = false; static bool wait_set = false; static int wait_seconds = 60; static bool silence_echo = false; static ShutdownMode shutdown_mode = SMART_MODE; static int sig = SIGTERM; /* default */ static int killproc; 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 *argv0 = NULL; static void *xmalloc(size_t size); static char *xstrdup(const char *s); static void do_advice(void); static void do_help(void); static void set_mode(char *modeopt); static void set_sig(char *signame); static void do_start(); static void do_stop(void); static void do_restart(void); static void do_reload(void); static void do_status(void); static void do_kill(void); static long get_pgpid(void); static char **readfile(char *path); static void start_postmaster(void); static bool test_postmaster_connection(void); static char def_postopts_file[MAXPGPATH]; static char postopts_file[MAXPGPATH]; static char pid_file[MAXPGPATH]; static char conf_file[MAXPGPATH]; /* * routines to check memory allocations and fail noisily. */ static void * xmalloc(size_t size) { void *result; result = malloc(size); if (!result) { fprintf(stderr, _("%s: out of memory\n"), progname); exit(1); } return result; } static char * xstrdup(const char *s) { char *result; result = strdup(s); if (!result) { fprintf(stderr, _("%s: out of memory\n"), progname); exit(1); } return result; } static long get_pgpid(void) { FILE *pidf; long pid; pidf = fopen(pid_file, "r"); if (pidf == NULL) { /* No pid file, not an error on startup */ if (errno == ENOENT) return 0; else { perror("openning pid file"); exit(1); } } fscanf(pidf, "%ld", &pid); fclose(pidf); return pid; } /* * get the lines from a text file - return NULL if file can't be opened */ static char ** readfile(char *path) { FILE *infile; int maxlength = 0, linelen = 0; int nlines = 0; char **result; char *buffer; int c; if ((infile = fopen(path, "r")) == NULL) return NULL; /* pass over the file twice - the first time to size the result */ while ((c = fgetc(infile)) != EOF) { linelen++; if (c == '\n') { nlines++; if (linelen > maxlength) maxlength = linelen; linelen = 0; } } /* handle last line without a terminating newline (yuck) */ if (linelen) nlines++; if (linelen > maxlength) maxlength = linelen; /* set up the result and the line buffer */ result = (char **) xmalloc((nlines + 1) * sizeof(char *)); buffer = (char *) xmalloc(maxlength + 1); /* now reprocess the file and store the lines */ rewind(infile); nlines = 0; while (fgets(buffer, maxlength + 1, infile) != NULL) result[nlines++] = xstrdup(buffer); fclose(infile); result[nlines] = NULL; return result; } /* * start/test/stop routines */ static void start_postmaster(void) { /* * Since there might be quotes to handle here, it is easier simply * to pass everything to a shell to process them. */ char cmd[MAXPGPATH]; if (log_file != NULL) snprintf(cmd, MAXPGPATH, "\"%s\" %s < %s >>\"%s\" 2>&1 &", postgres_path, post_opts, DEVNULL, log_file); else snprintf(cmd, MAXPGPATH, "\"%s\" %s < %s 2>&1 &", postgres_path, post_opts, DEVNULL); system(cmd); } /* Find the pgport and try a connection */ static bool test_postmaster_connection(void) { PGconn *conn; bool success = false; int i; char portstr[32]; *portstr = '\0'; if (getenv("PGPORT") != NULL) /* environment */ snprintf(portstr, sizeof(portstr), "%d", getenv("PGPORT")); else /* post_opts */ { char *p; for (p = post_opts; *p; p++) { /* advance past whitespace/quoting */ while (isspace(*p) || *p == '\'' || *p == '"') p++; if (strncmp(p, "-p", strlen("-p")) == 0) { /* advance past whitespace/quoting */ while (isspace(*p) || *p == '\'' || *p == '"') p++; StrNCpy(portstr, p, Min(strcspn(p, "\"'"WHITESPACE) + 1, sizeof(portstr))); break; } /* Advance to next whitespace */ while (!isspace(*p)) p++; } } /* config file */ if (!*portstr) { char **optlines; optlines = readfile(conf_file); if (optlines != NULL) { for (;*optlines != NULL; optlines++) { char *p = *optlines; while (isspace(*p)) p++; if (strncmp(p, "port", strlen("port")) != 0) continue; while (isspace(*p)) p++; if (*p != '=') continue; p++; while (isspace(*p)) p++; StrNCpy(portstr, p, Min(strcspn(p, "#"WHITESPACE) + 1, sizeof(portstr))); break; } } } /* default */ if (!*portstr) snprintf(portstr, sizeof(portstr), "%d", DEF_PGPORT); for (i = 0; i < wait_seconds; i++) { if ((conn = PQsetdbLogin(NULL, portstr, NULL, NULL, "template1", NULL, NULL)) != NULL) { PQfinish(conn); success = true; break; } } return success; } static void do_start(void) { long pid; long old_pid = 0; char *optline = NULL; if (ctl_command != RESTART_COMMAND) { old_pid = get_pgpid(); if (old_pid != 0) fprintf(stderr, "%s: Another postmaster may be running. " "Trying to start postmaster anyway.\n", progname); } if (post_opts == NULL) { char **optlines; int len; optlines = readfile(ctl_command == RESTART_COMMAND ? postopts_file : def_postopts_file); if (optlines == NULL) { if (ctl_command == START_COMMAND) post_opts = ""; else { fprintf(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", progname, ctl_command == RESTART_COMMAND ? postopts_file : def_postopts_file); exit(1); } else { optline = optlines[0]; len = strcspn(optline, "\r\n"); optline[len] = '\0'; if (ctl_command == RESTART_COMMAND) { char *arg1; arg1 = strchr(optline, '\''); if (arg1 == NULL || arg1 == optline) post_opts = ""; else { *(arg1 - 1) = '\0'; /* this should be a space */ post_opts = arg1; } if (postgres_path != NULL) postgres_path = optline; } else post_opts = optline; } } if (postgres_path == NULL) { char *postmaster_path; int ret; postmaster_path = xmalloc(MAXPGPATH); if ((ret = find_other_exec(argv0, "postmaster", PM_VERSIONSTR, 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); 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); exit(1); } postgres_path = postmaster_path; } start_postmaster(); if (old_pid != 0) { pg_usleep(1000000); pid = get_pgpid(); if (pid == old_pid) { fprintf(stderr, "%s: cannot start postmaster\n" "Examine the log output\n", progname); exit(1); } } if (do_wait) { if (!silence_echo) { printf("waiting for postmaster to start..."); fflush(stdout); } if (test_postmaster_connection() == false) printf("could not start postmaster\n"); else if (!silence_echo) printf("done\npostmaster successfully started\n"); } else if (!silence_echo) printf("postmaster starting\n"); } static void do_stop(void) { int cnt; long pid; 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"); 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); exit(1); } if (kill((pid_t) pid, sig) != 0) fprintf(stderr, "failed\n"); if (!do_wait) { if (!silence_echo) printf("postmaster shutting down\n"); return; } else { if (!silence_echo) { printf("waiting for postmaster to shut down..."); fflush(stdout); } for (cnt = 0; cnt < wait_seconds; cnt++) { if ((pid = get_pgpid()) != 0) { if (!silence_echo) { printf("."); fflush(stdout); } pg_usleep(1000000); /* 1 sec */ } else break; } if (pid != 0) /* pid file still exists */ { if (!silence_echo) printf(" failed\n"); fprintf(stderr, "%s: postmaster does not shut down\n", progname); exit(1); } } } /* * restart/reload routines */ static void do_restart(void) { int cnt; long pid; 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?\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"); exit(1); } if (kill((pid_t) pid, sig) != 0) fprintf(stderr, "failed\n"); if (!silence_echo) { printf("waiting for postmaster to shut down..."); fflush(stdout); } /* always wait for restart */ for (cnt = 0; cnt < wait_seconds; cnt++) { if ((pid = get_pgpid()) != 0) { if (!silence_echo) { printf("."); fflush(stdout); } pg_usleep(1000000); /* 1 sec */ } else break; } if (pid != 0) /* pid file still exists */ { if (!silence_echo) printf(" failed\n"); fprintf(stderr, "%s: postmaster does not shut down\n", progname); exit(1); } do_start(); } static void do_reload(void) { long pid; 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"); 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"); exit(1); } if (kill((pid_t) pid, sig) != 0) fprintf(stderr, "failed\n"); if (!silence_echo) fprintf(stdout, "postmaster signaled successfully\n"); } /* * utility routines */ static void do_status(void) { long pid; pid = get_pgpid(); if (pid == 0) /* no pid file */ { fprintf(stderr, "%s: postmaster or postgres not running", progname); exit(1); } else if (pid < 0) /* standalone backend */ { pid = -pid; fprintf(stdout, "%s: a standalone backend \"postgres\" is running (PID: %ld)", progname, pid); } else /* postmaster */ { char **optlines; fprintf(stdout, "%s: postmaster is running (PID: %ld)", progname, pid); optlines = readfile(postopts_file); if (optlines != NULL) for (; *optlines != NULL; optlines++) fputs(*optlines, stdout); } } static void do_kill(void) { if (kill(killproc, sig) != 0) fprintf(stderr, "failed\n"); } static void do_advice(void) { fprintf(stderr, "\nTry \"%s --help\" for more information.\n", progname); } static char *helplines[] = { "%s is a utility to start, stop, restart, reload configuration files,\n", "report the status of a PostgreSQL server, or kill a PostgreSQL process\n\n", "Usage:\n", " %s start [-w] [-D DATADIR] [-s] [-l FILENAME] [-o \"OPTIONS\"]\n", " %s stop [-W] [-D DATADIR] [-s] [-m SHUTDOWN-MODE]\n", " %s restart [-w] [-D DATADIR] [-s] [-m SHUTDOWN-MODE] [-o \"OPTIONS\"]\n", " %s reload [-D DATADIR] [-s]\n", " %s status [-D DATADIR]\n", " %s kill SIGNALNAME PROCESSID\n", "Common options:\n", " -D DATADIR location of the database storage area\n", " -s only print errors, no informational messages\n", " -w wait until operation completes\n", " -W do not wait until operation completes\n", " --help show this help, then exit\n", " --version output version information, then exit\n", "(The default is to wait for shutdown, but not for start or restart.)\n\n", "If the -D option is omitted, the environment variable PGDATA is used.\n\n", "Options for start or restart:\n", " -l FILENAME write (or append) server log to FILENAME. The\n", " use of this option is highly recommended.\n", " -o OPTIONS command line options to pass to the postmaster\n", " (PostgreSQL server executable)\n", " -p PATH-TO-POSTMASTER normally not necessary\n\n", "Options for stop or restart:\n", " -m SHUTDOWN-MODE may be 'smart', 'fast', or 'immediate'\n\n", "Allowed signal names for kill:\n", " -HUP -INT -QUIT -ABRT -TERM -USR1 -USR2\n\n", "Shutdown modes are:\n", " smart quit after all clients have disconnected\n", " fast quit directly, with proper shutdown\n", " immediate quit without complete shutdown; will lead to recovery on restart\n\n", "Report bugs to .\n", NULL }; static void do_help(void) { char **line; for (line = helplines; *line; line++) /* assuming we can use gettext this way */ printf(_(*line), progname); } static void set_mode(char *modeopt) { if (strcmp(modeopt, "s") == 0 || strcmp(modeopt, "smart") == 0) { shutdown_mode = SMART_MODE; sig = SIGTERM; } else if (strcmp(modeopt, "f") == 0 || strcmp(modeopt, "fast") == 0) { shutdown_mode = FAST_MODE; sig = SIGINT; } else if (strcmp(modeopt, "i") == 0 || strcmp(modeopt, "immediate") == 0) { shutdown_mode = IMMEDIATE_MODE; sig = SIGQUIT; } else { fprintf(stderr, "%s: invalid shutdown mode %s\n", progname, modeopt); do_advice(); exit(1); } } static void set_sig(char *signame) { if (!strcmp(signame, "-HUP")) sig = SIGHUP; else if (!strcmp(signame, "-INT")) sig = SIGINT; else if (!strcmp(signame, "-QUIT")) sig = SIGQUIT; else if (!strcmp(signame, "-ABRT")) sig = SIGABRT; /* * probably should NOT provide SIGKILL * * else if (!strcmp(signame,"-KILL")) sig = SIGKILL; */ else if (!strcmp(signame, "-TERM")) sig = SIGTERM; else if (!strcmp(signame, "-USR1")) sig = SIGUSR1; else if (!strcmp(signame, "-USR2")) sig = SIGUSR2; else { fprintf(stderr, "%s: invalid signal \"%s\"\n", progname, signame); do_advice(); exit(1); } } int main(int argc, char **argv) { int i; #ifdef WIN32 setvbuf(stderr, NULL, _IONBF, 0); #endif progname = get_progname(argv[0]); /* * save argv[0] so do_start() can look for the postmaster if * necessary. we don't look for postmaster here because in many cases * we won't need it. */ argv0 = argv[0]; umask(077); for (i = 0; i < argc; i++) { char *arg = argv[i]; if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0 || strcmp(arg, "-?") == 0) { do_help(); exit(0); } else if (strcmp(arg, "-V") == 0 || strcmp(arg, "--version") == 0) { printf("pg_ctl (PostgreSQL) %s\n", PG_VERSION); exit(0); } else if (strcmp(arg, "-D") == 0 && i < argc - 1) { int len = strlen(arg) + 4; char *env_var; i++; arg = argv[i]; pg_data_opts = xmalloc(len); snprintf(pg_data_opts, len, "-D %s", arg); env_var = xmalloc(len + sizeof("PGDATA=")); snprintf(env_var, len + sizeof("PGDATA="), "PGDATA=%s", arg); putenv(env_var); } else if (strcmp(arg, "-l") == 0 && i < argc - 1) { i++; arg = argv[i]; log_file = xstrdup(arg); } else if (strncmp(arg, "-l", 2) == 0 && strlen(arg) > 2) log_file = xstrdup(arg + 2); else if (strcmp(arg, "-m") == 0 && i < argc - 1) { i++; arg = argv[i]; set_mode(arg); } else if (strncmp(arg, "-m", 2) == 0 && strlen(arg) > 2) set_mode(arg + 2); else if (strcmp(arg, "-o") == 0 && i < argc - 1) { i++; arg = argv[i]; post_opts = xstrdup(arg); } else if (strcmp(arg, "-p") == 0 && i < argc - 1) { i++; arg = argv[i]; postgres_path = xstrdup(arg); } else if (strcmp(arg, "-s") == 0) silence_echo = true; else if (strcmp(arg, "-w") == 0) { do_wait = true; wait_set = true; } else if (strcmp(arg, "-W") == 0) { do_wait = false; wait_set = true; } else if (arg[0] == '-') { fprintf(stderr, "%s: invalid option %s\n", progname, arg); do_advice(); exit(1); } else if (strcmp(arg, "start") == 0 && ctl_command == NO_COMMAND) ctl_command = START_COMMAND; else if (strcmp(arg, "stop") == 0 && ctl_command == NO_COMMAND) ctl_command = STOP_COMMAND; else if (strcmp(arg, "restart") == 0 && ctl_command == NO_COMMAND) ctl_command = RESTART_COMMAND; else if (strcmp(arg, "reload") == 0 && ctl_command == NO_COMMAND) ctl_command = RELOAD_COMMAND; else if (strcmp(arg, "status") == 0 && ctl_command == NO_COMMAND) ctl_command = STATUS_COMMAND; else if (i == 0 && argc >= 3 && strcmp(arg, "kill") == 0) { /* stricter syntax for kill command */ ctl_command = KILL_COMMAND; set_sig(argv[1]); killproc = atol(argv[2]); i += 2; } else if (arg[0] == '-') { fprintf(stderr, "%s: invalid operation mode %s\n", progname, arg); do_advice(); exit(1); } } if (ctl_command == NO_COMMAND) { fprintf(stderr, "%s: no operation specified\n", progname); do_advice(); exit(1); } pg_data = getenv("PGDATA"); canonicalize_path(pg_data); if (pg_data == NULL && ctl_command != KILL_COMMAND) { fprintf(stderr, "%s: no database directory specified " "and environment variable PGDATA unset\n", progname); do_advice(); exit(1); } if (!wait_set) { switch (ctl_command) { case RESTART_COMMAND: case START_COMMAND: do_wait = false; break; case STOP_COMMAND: do_wait = true; break; default: break; } } if (ctl_command == RELOAD_COMMAND) { sig = SIGHUP; do_wait = false; } snprintf(def_postopts_file, MAXPGPATH, "%s/postmaster.opts.default", pg_data); snprintf(postopts_file, MAXPGPATH, "%s/postmaster.opts", pg_data); snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data); snprintf(conf_file, MAXPGPATH, "%s/postgresql.conf", pg_data); switch (ctl_command) { case STATUS_COMMAND: do_status(); break; case START_COMMAND: do_start(); break; case STOP_COMMAND: do_stop(); break; case RESTART_COMMAND: do_restart(); break; case RELOAD_COMMAND: do_reload(); break; case KILL_COMMAND: do_kill(); break; default: break; } exit(0); }