701 lines
16 KiB
C
701 lines
16 KiB
C
/* $NetBSD: ndc.c,v 1.1.1.1 1999/11/20 18:54:02 veego Exp $ */
|
|
|
|
#if !defined(lint) && !defined(SABER)
|
|
static const char rcsid[] = "Id: ndc.c,v 1.13 1999/10/13 16:39:16 vixie 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 <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/file.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/nameser.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
|
|
#include <isc/eventlib.h>
|
|
#include <isc/ctl.h>
|
|
|
|
#include "port_after.h"
|
|
#include "pathnames.h"
|
|
|
|
typedef union {
|
|
struct sockaddr_in in;
|
|
struct sockaddr_un un;
|
|
} 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, ...);
|
|
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);
|
|
static void trace(const char *, ...);
|
|
static void result(const char *, ...);
|
|
static void fatal(const char *, ...);
|
|
static void verror(const char *, va_list);
|
|
static void error(const char *, ...);
|
|
|
|
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], '/')) != 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) {
|
|
printf(helpfmt, "start", "start the server");
|
|
printf(helpfmt, "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;
|
|
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;
|
|
int sig;
|
|
|
|
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;
|
|
|
|
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 if ((slash = strrchr(name, '/')) != NULL) {
|
|
*slash = '\0';
|
|
memset(&addr->in, '\0', sizeof addr->in);
|
|
if (!inet_pton(AF_INET, name, &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;
|
|
*slash = ':';
|
|
} 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));
|
|
case AF_UNIX:
|
|
return (sizeof(struct sockaddr_un));
|
|
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);
|
|
}
|