/* -*-c-*-
*
- * $Id: client.c,v 1.1 2001/02/03 20:26:37 mdw Exp $
+ * $Id$
*
* Client for TrIPE
*
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/*----- Revision history --------------------------------------------------*
- *
- * $Log: client.c,v $
- * Revision 1.1 2001/02/03 20:26:37 mdw
- * Initial checkin.
- *
- */
-
/*----- Header files ------------------------------------------------------*/
+#include "config.h"
+
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <mLib/alloc.h>
#include <mLib/darray.h>
#include <mLib/dstr.h>
-#include <mLib/lbuf.h>
#include <mLib/mdwopt.h>
#include <mLib/quis.h>
#include <mLib/report.h>
#include <mLib/sel.h>
#include <mLib/selbuf.h>
+#include <mLib/sig.h>
#include <mLib/str.h>
#include "util.h"
/*----- Static variables --------------------------------------------------*/
+static const char *pidfile = 0;
+static const char *logname = 0;
static FILE *logfp = 0;
static unsigned f = 0;
static int fd;
+static const char *bgtag = 0;
#define f_bogus 1u
#define f_spawn 2u
static void reap(int sig)
{
- int s;
int e = errno;
- while (waitpid(-1, &s, WNOHANG) > 0)
+ while (waitpid(-1, 0, WNOHANG) > 0)
;
errno = e;
}
fprintf(logfp, "%s %s: %s\n", buf, cat, msg);
}
-static void cline(char *p, void *b)
+static void checkbg(char **p)
+{
+ char *q = str_getword(p);
+ if (!q)
+ die(EXIT_FAILURE, "missing background tag");
+ if (!bgtag || strcmp(bgtag, q) != 0)
+ die(EXIT_FAILURE, "unexpected background tag `%s'", q);
+}
+
+static void checkfg(void)
+{
+ if (bgtag)
+ die(EXIT_FAILURE, "unexpected foreground response");
+}
+
+static void cline(char *p, size_t len, void *b)
{
char *q;
if (!p) {
writelog("error", d.buf);
dstr_destroy(&d);
}
- } else if (strcmp(q, "ERR") == 0)
+ } else if (strcmp(q, "FAIL") == 0) {
+ checkfg();
die(EXIT_FAILURE, "%s", p);
- else if (strcmp(q, "INFO") == 0)
+ } else if (strcmp(q, "INFO") == 0) {
+ checkfg();
puts(p);
- else if (strcmp(q, "OK") == 0)
+ } else if (strcmp(q, "OK") == 0) {
+ checkfg();
exit(0);
- else
+ } else if (strcmp(q, "BGDETACH") == 0) {
+ if (bgtag)
+ die(EXIT_FAILURE, "repeat detach");
+ bgtag = xstrdup(p);
+ } else if (strcmp(q, "BGOK") == 0) {
+ checkbg(&p);
+ exit(0);
+ } else if (strcmp(q, "BGINFO") == 0) {
+ checkbg(&p);
+ puts(p);
+ } else if (strcmp(q, "BGFAIL") == 0) {
+ checkbg(&p);
+ die(EXIT_FAILURE, "%s", p);
+ } else
die(EXIT_FAILURE, "unexpected output `%s %s'", q, p);
}
-static void sline(char *p, void *b)
+static void sline(char *p, size_t len, void *b)
{
if (!p) {
if (!(f & f_uclose))
puts(p);
}
-static void uline(char *p, void *b)
+static void uline(char *p, size_t len, void *b)
{
- size_t sz;
if (!p) {
selbuf_destroy(b);
shutdown(fd, 1);
f |= f_uclose;
} else {
- sz = strlen(p);
- p[sz] = '\n';
- write(fd, p, sz + 1);
+ p[len] = '\n';
+ errno = EIO;
+ if (write(fd, p, len + 1) != len + 1)
+ moan("write failed: %s", strerror(errno));
}
}
+static void setup(const char *cmd)
+{
+ dstr d = DSTR_INIT;
+ char ch;
+ char *p, *q;
+ int n;
+
+ dstr_puts(&d, cmd);
+ dstr_putc(&d, '\n');
+ errno = EIO; /* Relax: be vague */
+ if (write(fd, d.buf, d.len) != d.len) {
+ die(EXIT_FAILURE, "error sending setup command `%s': %s",
+ cmd, strerror(errno));
+ }
+ dstr_reset(&d);
+ for (;;) {
+ n = read(fd, &ch, 1);
+ if (!n)
+ die(EXIT_FAILURE, "unexpected EOF during setup");
+ if (n < 0) {
+ die(EXIT_FAILURE, "error receiving reply to `%s': %s",
+ cmd, strerror(errno));
+ }
+ if (d.len < 256)
+ dstr_putc(&d, ch);
+ if (ch == '\n') {
+ p = d.buf;
+ q = str_getword(&p);
+ if (!q)
+ ;
+ else if (strcmp(q, "OK") == 0)
+ return;
+ else if (strcmp(q, "FAIL") == 0)
+ die(EXIT_FAILURE, "setup command `%s' failed: %s", cmd, p);
+ dstr_reset(&d);
+ }
+ }
+}
+
+static void logfile(const char *name)
+{
+ FILE *fp;
+
+ if ((fp = fopen(name, "a")) != 0) {
+ if (logfp)
+ fclose(logfp);
+ logfp = fp;
+ setvbuf(logfp, 0, _IOLBF, BUFSIZ);
+ } else {
+ dstr d = DSTR_INIT;
+ dstr_putf(&d, "error opening logfile `%s': %s", name, strerror(errno));
+ if (logfp)
+ writelog("error", d.buf);
+ else if (logname)
+ die(EXIT_FAILURE, d.buf);
+ if (f & f_syslog)
+ syslog(LOG_ERR, d.buf);
+ dstr_destroy(&d);
+ }
+}
+
+static void sighup(int sig, void *v)
+{
+ logfile(logname);
+}
+
+static void cleanup(void)
+{
+ if (pidfile)
+ unlink(pidfile);
+}
+
+static void sigdie(int sig)
+{
+ cleanup();
+ signal(sig, SIG_DFL);
+ raise(sig);
+}
+
static void version(FILE *fp)
{
pquis(fp, "$, TrIPE version " VERSION "\n");
{
pquis(fp, "\
Usage:\n\
- $ [-w] [-options] [command [args]...]\n\
- $ [-Dl] [-f file] [-options]\n\
+ $ [-w] [-OPTIONS] [COMMAND [ARGS]...]\n\
+ $ [-Dl] [-f FILE] [-OPTIONS]\n\
Options:\n\
- [-s] [-d directory] [-a socket] [-p program] [-S arg,arg,...]\n\
+ [-s] [-d DIRECTORY] [-a SOCKET] [-P PIDFILE]\n\
+ [-p PROGRAM] [-S ARG,ARG,...]\n\
");
}
-u, --usage Show brief usage message.\n\
\n\
-D, --daemon Become a background task after connecting.\n\
--d, --directory=DIR Select current directory [default /var/lib/tripe]\n\
--a, --admin-socket=FILE Select socket to connect to.\n\
+-d, --directory=DIR Select current directory [default " CONFIGDIR "].\n\
+-a, --admin-socket=FILE Select socket to connect to\n\
+ [default " SOCKETDIR "/tripesock].\n\
+-P, --pidfile=FILE Write process-id to FILE.\n\
\n\
-s, --spawn Start server rather than connecting.\n\
-p, --spawn-path=PATH Specify path to executable.\n\
int main(int argc, char *argv[])
{
- const char *dir = "/var/lib/tripe";
- const char *sock = "tripesock";
+ const char *dir = CONFIGDIR;
+ const char *sock = SOCKETDIR "/tripesock";
const char *spawnpath = "tripe";
string_v spawnopts = DA_INIT;
char *p;
+ FILE *pidfp = 0;
ego(argv[0]);
{ "syslog", 0, 0, 'l' },
{ "logfile", OPTF_ARGREQ, 0, 'f' },
{ "warnings", 0, 0, 'w' },
+ { "pidfile", OPTF_ARGREQ, 0, 'P' },
{ 0, 0, 0, 0 }
};
- int i = mdwopt(argc, argv, "hvuDd:a:sp:S:lwf:n", opts, 0, 0, 0);
+ int i = mdwopt(argc, argv, "+hvuDd:a:sp:S:lwf:nP:", opts, 0, 0, 0);
if (i < 0)
break;
switch (i) {
f |= f_warn;
break;
case 'f':
- if (logfp)
- fclose(logfp);
- if ((logfp = fopen(optarg, "a")) == 0) {
- die(EXIT_FAILURE, "error opening logfile `%s': %s",
- optarg, strerror(errno));
- }
+ logname = optarg;
f |= f_noinput;
break;
+ case 'P':
+ pidfile = optarg;
+ break;
default:
f |= f_bogus;
break;
exit(EXIT_FAILURE);
}
- /* --- Set the world up --- */
+ /* --- Set various things up --- */
if (chdir(dir)) {
- die(EXIT_FAILURE, "couldn't set current directory to `%s': %s",
+ die(EXIT_FAILURE, "couldn't set `%s' as current directory: %s",
dir, strerror(errno));
}
+ if (logname)
+ logfile(logname);
+ if (!pidfile && (f & f_daemon) && ((f & f_syslog) || logname))
+ pidfile = "tripectl.pid";
+ if (pidfile && (pidfp = fopen(pidfile, "w")) == 0) {
+ die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
+ pidfile, strerror(errno));
+ }
+ signal(SIGINT, sigdie);
+ signal(SIGQUIT, sigdie);
+ signal(SIGTERM, sigdie);
+ atexit(cleanup);
/* --- Connect to the server --- */
#endif
sigaction(SIGCHLD, &sa, 0);
+ DA_UNSHIFT(&spawnopts, (char *)sock);
+ DA_UNSHIFT(&spawnopts, "-a");
+ DA_UNSHIFT(&spawnopts, "-d.");
DA_UNSHIFT(&spawnopts, (char *)spawnpath);
- if (!(f & f_spawnopts)) {
- DA_PUSH(&spawnopts, "-d.");
- DA_PUSH(&spawnopts, "-a");
- DA_PUSH(&spawnopts, (char *)sock);
- }
DA_PUSH(&spawnopts, 0);
if (socketpair(PF_UNIX, SOCK_STREAM, 0, pfd))
die(EXIT_FAILURE, "error from socketpair: %s", strerror(errno));
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
close(pfd[0]);
+ if (logfp)
+ fclose(logfp);
+ if (pidfp)
+ fclose(pidfp);
+ closelog();
+ if (f & f_daemon)
+ u_detach();
execvp(DA(&spawnopts)[0], DA(&spawnopts));
die(127, "couldn't exec `%s': %s", spawnpath, strerror(errno));
}
memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
memcpy(sun.sun_path, sock, sz);
- sz += offsetof(struct sockaddr_un, sun_path);
+ sz = sz + offsetof(struct sockaddr_un, sun_path);
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
die(EXIT_FAILURE, "error making socket: %s", strerror(errno));
if (connect(fd, (struct sockaddr *)&sun, sz)) {
die(EXIT_FAILURE, "error connecting to `%s': %s",
- sock, strerror(errno));
+ sun.sun_path, strerror(errno));
}
}
if (u_daemon())
die(EXIT_FAILURE, "error becoming daemon: %s", strerror(errno));
}
+ if (pidfp) {
+ fprintf(pidfp, "%li\n", (long)getpid());
+ fclose(pidfp);
+ }
+ signal(SIGPIPE, SIG_IGN);
/* --- If we're meant to be interactive, do that --- */
+ if (optind == argc)
+ setup("WATCH -A+tw");
if (!(f & f_noinput) && optind == argc) {
sel_state sel;
selbuf bu, bs;
selbuf_init(&bu, &sel, STDIN_FILENO, uline, &bu);
selbuf_init(&bs, &sel, fd, sline, &bs);
for (;;) {
- if (sel_select(&sel))
+ if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
die(EXIT_FAILURE, "select failed: %s", strerror(errno));
}
}
if (optind < argc) {
dstr d = DSTR_INIT;
+ setup((f & f_warn) ? "WATCH -A+w" : "WATCH -A");
dstr_puts(&d, argv[optind++]);
while (optind < argc) {
dstr_putc(&d, ' ');
dstr_puts(&d, argv[optind++]);
}
dstr_putc(&d, '\n');
- write(fd, d.buf, d.len);
- shutdown(fd, 1);
+ errno = EIO;
+ if (write(fd, d.buf, d.len) != d.len || shutdown(fd, 1))
+ die(EXIT_FAILURE, "write failed: %s", strerror(errno));
dstr_destroy(&d);
f |= f_command;
}
/* --- Pull everything else out of the box --- */
{
- lbuf b;
- lbuf_init(&b, cline, 0);
+ sel_state sel;
+ selbuf b;
+ sig hup;
+
+ sel_init(&sel);
+ selbuf_init(&b, &sel, fd, cline, 0);
+
if (f & f_syslog)
openlog(QUIS, 0, LOG_DAEMON);
+ if (logfp) {
+ sig_init(&sel);
+ sig_add(&hup, SIGHUP, sighup, 0);
+ }
for (;;) {
- size_t sz = lbuf_free(&b, &p);
- ssize_t n = read(fd, p, sz);
- if (n < 0)
- die(EXIT_FAILURE, "read failed: %s", strerror(errno));
- if (n == 0)
- break;
- lbuf_flush(&b, p, n);
+ if (sel_select(&sel) && errno != EINTR && errno != EAGAIN)
+ die(EXIT_FAILURE, "select failed: %s", strerror(errno));
}
- lbuf_close(&b);
}
return (0);