/* $NetBSD: ndc.c,v 1.4 2001/05/17 22:59:41 itojun Exp $ */ #if !defined(lint) && !defined(SABER) static const char rcsid[] = "Id: ndc.c,v 1.16.2.1 2001/04/26 02:56:10 marka Exp"; #endif /* not lint */ /* * Portions Copyright (c) 1999 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ #include "port_before.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "port_after.h" #include "pathnames.h" #ifndef PATH_SEP #define PATH_SEP '/' #endif typedef union { struct sockaddr_in in; #ifndef NO_SOCKADDR_UN struct sockaddr_un un; #endif } sockaddr_t; typedef void (*closure)(void *, const char *, int); static const char * program = "amnesia"; static enum { e_channel, e_signals } mode = e_channel; static char * channel = _PATH_NDCSOCK; static const char helpfmt[] = "\t%-16s\t%s\n"; static const char * pidfile = _PATH_PIDFILE; static sockaddr_t client, server; static int quiet = 0, tracing = 0, silent = 0, client_set = 0; static int debug = 0, errors = 0, doneflag, exitflag; static int logger_show = 1; static evContext ev; static char cmd[1000]; static const char * named_path = _PATH_NAMED; static int slashcmd(void); static void slashhelp(void); static int builtincmd(void); static void command(void); static int running(int, pid_t *); static void command_channel(void); static void channel_loop(char *, int, closure, void *); static void getpid_closure(void *, const char *, int); static void banner(struct ctl_cctx *, void *, const char *, u_int); static void done(struct ctl_cctx *, void *, const char *, u_int); static void logger(enum ctl_severity, const char *fmt, ...) __attribute__((__format__(__printf__, 2, 3))); static void command_signals(void); static void stop_named(pid_t); static void start_named(const char *, int); static int fgetpid(const char *, pid_t *); static int get_sockaddr(char *, sockaddr_t *); static size_t impute_addrlen(const struct sockaddr *); static void vtrace(const char *, va_list) __attribute__((__format__(__printf__, 1, 0))); static void trace(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); static void result(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); static void fatal(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); static void verror(const char *, va_list) __attribute__((__format__(__printf__, 1, 0))); static void error(const char *, ...) __attribute__((__format__(__printf__, 1, 2))); static void usage(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))); static void usage(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "%s: usage error: ", program); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); fatal("usage: %s \ [-l localsock] [-c channel] [-p pidfile] [-n namedpath] \ [-dqst] [command [args]]\n\ ", program); } /* Public. */ int main(int argc, char *argv[], char *envp[]) { char *p; int ch; if ((program = strrchr(argv[0], PATH_SEP)) != NULL) program++; else program = argv[0]; while ((ch = getopt(argc, argv, "c:p:l:n:dqst")) != -1) { switch (ch) { case 'c': channel = optarg; mode = e_channel; break; case 'p': pidfile = optarg; mode = e_signals; break; case 'l': if (!get_sockaddr(optarg, &client)) usage("bad local socket (%s)", optarg); client_set++; break; case 'n': named_path = optarg; break; case 'd': tracing++; debug++; break; case 'q': quiet++; break; case 's': silent++; break; case 't': tracing++; break; default: usage("unrecognized command option (%c)", ch); /* NOTREACHED */ } } if (mode != e_channel && client_set) usage("the -l flag is only valid for control channels"); if (mode == e_channel) { if (!get_sockaddr(channel, &server)) usage("bad channel name (%s)", channel); if (evCreate(&ev) < 0) fatal("evCreate - %s", strerror(errno)); } *(p = cmd) = '\0'; for (argc -= optind, argv += optind; argc > 0; argc--, argv++) { size_t t = strlen(*argv); if ((p - cmd) + t + 2 > sizeof cmd) usage("command too long"); strcpy(p, *argv); p += t; if (argv[1] != NULL) *p++ = ' '; *p = '\0'; } if (cmd[0] != '\0') { command(); } else { if (!quiet) result("Type help -or- /h if you need help."); for (exitflag = 0; !exitflag; (void)NULL) { if (!quiet) { printf("%s> ", program); fflush(stdout); } if (!fgets(cmd, sizeof cmd, stdin)) { if (!quiet) result("EOF"); exitflag++; continue; } if (cmd[strlen(cmd) - 1] == '\n') cmd[strlen(cmd) - 1] = '\0'; if (cmd[0] == '\0') continue; if (slashcmd()) continue; command(); } } if (mode == e_channel) evDestroy(ev); exit(errors != 0); } /* Private. */ static int slashcmd(void) { if (strncasecmp(cmd, "/help", strlen(cmd)) == 0) slashhelp(); else if (strncasecmp(cmd, "/exit", strlen(cmd)) == 0) exitflag++; else if (strncasecmp(cmd, "/trace", strlen(cmd)) == 0) result("tracing now %s", (tracing = !tracing) ? "on" : "off"); else if (strncasecmp(cmd, "/debug", strlen(cmd)) == 0) result("debugging now %s", (debug = !debug) ? "on" : "off"); else if (strncasecmp(cmd, "/quiet", strlen(cmd)) == 0) result("%s is now %s", program, (quiet = !quiet) ? "quiet" : "noisy"); else if (strncasecmp(cmd, "/silent", strlen(cmd)) == 0) result("%s is now %s", program, (silent = !silent) ? "silent" : "gregarious"); else return (0); return (1); } static void slashhelp(void) { printf(helpfmt, "/h(elp)", "this text"); printf(helpfmt, "/e(xit)", "leave this program"); printf(helpfmt, "/t(race)", "toggle tracing (protocol and system events)"); printf(helpfmt, "/d(ebug)", "toggle debugging (internal program events)"); printf(helpfmt, "/q(uiet)", "toggle quietude (prompts and results)"); printf(helpfmt, "/s(ilent)", "toggle silence (suppresses nonfatal errors)"); } static int builtincmd(void) { static const char spaces[] = " \t"; char *rest, *syscmd; pid_t pid; int save_quiet = quiet; int len; quiet = 1; len = strcspn(cmd, spaces); rest = cmd + len; if (*rest != '\0') { rest++; rest += strspn(rest, spaces); } syscmd = malloc(strlen(named_path) + sizeof " " + strlen(rest)); if (syscmd == NULL) fatal("malloc() failed - %s", strerror(errno)); strcpy(syscmd, named_path); if (*rest != '\0') { strcat(syscmd, " "); strcat(syscmd, rest); } if (strncasecmp(cmd, "start", len) == 0) { if (running(debug, &pid)) error("name server already running? (pid %ld)", (long)pid); else start_named(syscmd, save_quiet); quiet = save_quiet; free(syscmd); return (1); } else if (strncasecmp(cmd, "restart", len) == 0) { if (!running(debug, &pid)) error("name server was not running (warning only)"); else stop_named(pid); start_named(syscmd, save_quiet); quiet = save_quiet; free(syscmd); return (1); } quiet = save_quiet; free(syscmd); return (0); } static void builtinhelp(void) { const char *fmt; switch (mode) { case e_channel: fmt = "(builtin) %s - %s\n"; break; case e_signals: fmt = helpfmt; break; default: abort(); } printf(fmt, "start", "start the server"); printf(fmt, "restart", "stop server if any, start a new one"); } static void command(void) { if (builtincmd()) return; switch (mode) { case e_channel: command_channel(); break; case e_signals: command_signals(); break; default: abort(); } } static int running(int show, pid_t *pidp) { pid_t pid; switch (mode) { case e_channel: pid = 0; channel_loop("getpid", show, getpid_closure, &pid); if (pid != 0) { if (tracing) result("pid %ld is running", (long)pid); *pidp = pid; return (1); } break; case e_signals: if (fgetpid(pidfile, pidp)) { if (tracing) result("pid %ld is running", (long)pid); return (1); } break; default: abort(); } if (show) error("pid not valid or server not running"); return (0); } static void getpid_closure(void *uap, const char *text, int flags) { pid_t *pidp = uap; const char *cp; flags = flags; if ((cp = strchr(text, '<')) != NULL) { long l = 0; char ch; while ((ch = *++cp) != '\0' && ch != '>' && isdigit(ch)) l *= 10, l += (ch - '0'); if (ch == '>') { *pidp = (pid_t)l; return; } } error("response does not contain pid (%s)", text); } static void command_channel(void) { int helping = (strcasecmp(cmd, "help") == 0); int save_quiet = quiet; if (helping) { quiet = 0; builtinhelp(); } channel_loop(cmd, !quiet, NULL, NULL); quiet = save_quiet; } struct args { const char *cmd; closure cl; void *ua; }; static void channel_loop(char *cmdtext, int show, closure cl, void *ua) { struct ctl_cctx *ctl; struct sockaddr *client_addr; struct args a; evEvent e; int save_logger_show = logger_show; if (!client_set) client_addr = NULL; else client_addr = (struct sockaddr *)&client; a.cmd = cmdtext; a.cl = cl; a.ua = ua; logger_show = show; ctl = ctl_client(ev, client_addr, impute_addrlen(client_addr), (struct sockaddr *)&server, impute_addrlen((struct sockaddr *)&server), banner, &a, 15, logger); if (ctl == NULL) { if (show) error("cannot connect to command channel (%s)", channel); } else { doneflag = 0; while (evGetNext(ev, &e, EV_WAIT) == 0) if (evDispatch(ev, e) < 0 || doneflag) break; ctl_endclient(ctl); } logger_show = save_logger_show; } static void banner(struct ctl_cctx *ctl, void *uap, const char *msg, u_int flags) { struct args *a = uap; if (msg == NULL) { trace("EOF"); doneflag = 1; return; } trace("%s", msg); if ((flags & CTL_MORE) != 0) return; if (ctl_command(ctl, a->cmd, strlen(a->cmd), done, a) < 0) { error("ctl_command failed - %s", strerror(errno)); doneflag = 1; } } static void done(struct ctl_cctx *ctl, void *uap, const char *msg, u_int flags) { struct args *a = uap; if (msg == NULL) { trace("EOF"); doneflag = 1; return; } if (!tracing && !quiet && strlen(msg) > 4) result("%s", msg + 4); trace("%s", msg); if (a->cl) (a->cl)(a->ua, msg, flags); if ((flags & CTL_MORE) == 0) doneflag = 1; } static void logger(enum ctl_severity ctlsev, const char *format, ...) { va_list args; va_start(args, format); switch (ctlsev) { case ctl_debug: /* FALLTHROUGH */ case ctl_warning: if (debug) vtrace(format, args); break; case ctl_error: if (logger_show) verror(format, args); break; default: abort(); } va_end(args); } static struct cmdsig { const char * cmd; int sig; const char * help; } cmdsigs[] = { { "dumpdb", SIGINT, "dump cache database to a file" }, { "reload", SIGHUP, "reload configuration file" }, { "stats", SIGILL, "dump statistics to a file" }, { "trace", SIGUSR1, "increment trace level" }, { "notrace", SIGUSR2, "turn off tracing" }, #ifdef SIGWINCH { "querylog", SIGWINCH, "toggle query logging" }, { "qrylog", SIGWINCH, "alias for querylog" }, #endif { NULL, 0 } }; static void command_signals(void) { struct cmdsig *cmdsig; pid_t pid; if (strcasecmp(cmd, "help") == 0) { printf(helpfmt, "help", "this output"); printf(helpfmt, "status", "check for running server"); printf(helpfmt, "stop", "stop the server"); builtinhelp(); for (cmdsig = cmdsigs; cmdsig->cmd != NULL; cmdsig++) printf(helpfmt, cmdsig->cmd, cmdsig->help); } else if (strcasecmp(cmd, "status") == 0) { if (!fgetpid(pidfile, &pid)) error("pid not valid or server not running"); else result("pid %ld is running", (long)pid); } else if (strcasecmp(cmd, "stop") == 0) { if (!fgetpid(pidfile, &pid)) error("name server not running"); else stop_named(pid); } else { for (cmdsig = cmdsigs; cmdsig->cmd != NULL; cmdsig++) if (strcasecmp(cmd, cmdsig->cmd) == 0) break; if (cmdsig->cmd == NULL) error("unrecognized command (%s)", cmd); else if (!fgetpid(pidfile, &pid)) error("can't get pid (%s)", pidfile); else if (kill(pid, cmdsig->sig) < 0) error("kill() failed - %s", strerror(errno)); else trace("pid %ld sig %d OK", (long)pid, cmdsig->sig); } } static void stop_named(pid_t pid) { int n; trace("stopping named (pid %ld)", (long)pid); switch (mode) { case e_signals: if (kill(pid, SIGTERM) < 0) { error("kill(%ld, SIGTERM) failed - %s", (long)pid, strerror(errno)); return; } trace("SIGTERM ok, waiting for death"); break; case e_channel: channel_loop("stop", tracing, NULL, NULL); break; default: abort(); } for (n = 0; n < 10; n++) { if (kill(pid, 0) != 0) { trace("named (pid %ld) is dead", (long)pid); return; } sleep(1); } error("named (pid %ld) didn't die", (long)pid); } static void start_named(const char *syscmd, int local_quiet) { pid_t pid; if (system(syscmd) != 0) error("could not start new name server (%s)", syscmd); else { sleep(3); if (!running(0, &pid)) error("name server has not started (yet?)"); else if (!local_quiet) result("new pid is %ld", (long)pid); } } static int fgetpid(const char *f, pid_t *pid) { FILE *fp; int try; long t; for (try = 0; try < 5; try++) { trace("pidfile is \"%s\" (try #%d)", pidfile, try + 1); if ((fp = fopen(pidfile, "r")) == NULL) trace("pid file (%s) unavailable - %s", pidfile, strerror(errno)); else if (fscanf(fp, "%ld\n", &t) != 1) trace("pid file (%s) format is bad", pidfile); else if (*pid = (pid_t)t, fclose(fp), kill(*pid, 0) < 0) trace("pid file (%s) contains unusable pid (%d) - %s", pidfile, *pid, strerror(errno)); else { trace("pid is %ld", (long)*pid); return (1); } sleep(1); } trace("pid not found"); return (0); } static int get_sockaddr(char *name, sockaddr_t *addr) { char *slash; #ifndef NO_SOCKADDR_UN if (name[0] == '/') { memset(&addr->un, '\0', sizeof addr->un); addr->un.sun_family = AF_UNIX; strncpy(addr->un.sun_path, name, sizeof addr->un.sun_path - 1); addr->un.sun_path[sizeof addr->un.sun_path - 1] = '\0'; } else #endif if ((slash = strrchr(name, '/')) != NULL) { char *ibuf = malloc(slash - name + 1); if (!ibuf) usage("no memory for IP address (%s)", name); memcpy(ibuf, name, slash - name); ibuf[slash - name] = '\0'; memset(&addr->in, '\0', sizeof addr->in); if (!inet_pton(AF_INET, ibuf, &addr->in.sin_addr)) usage("bad ip address (%s)", name); if ((addr->in.sin_port = htons(atoi(slash+1))) == 0) usage("bad ip port (%s)", slash+1); addr->in.sin_family = AF_INET; free (ibuf); } else { return (0); } return (1); } static size_t impute_addrlen(const struct sockaddr *sa) { if (sa == 0) return (0); switch (sa->sa_family) { case AF_INET: return (sizeof(struct sockaddr_in)); #ifndef NO_SOCKADDR_UN case AF_UNIX: return (sizeof(struct sockaddr_un)); #endif default: abort(); } } static void vtrace(const char *fmt, va_list ap) { if (tracing) { fprintf(stdout, "%s: [", program); vfprintf(stdout, fmt, ap); fputs("]\n", stdout); } } static void trace(const char *fmt, ...) { va_list args; va_start(args, fmt); vtrace(fmt, args); va_end(args); } static void result(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stdout, fmt, args); fputc('\n', stdout); va_end(args); } static void fatal(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "%s: fatal error: ", program); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); exit(1); } static void verror(const char *fmt, va_list ap) { fprintf(stderr, "%s: error: ", program); vfprintf(stderr, fmt, ap); fputc('\n', stderr); errors++; } static void error(const char *fmt, ...) { va_list args; va_start(args, fmt); if (silent) vtrace(fmt, args); else verror(fmt, args); va_end(args); }