/* -*-c-*-
*
- * $Id: admin.c,v 1.5 2001/02/16 21:22:51 mdw Exp $
+ * $Id: admin.c,v 1.8 2003/04/06 10:25:17 mdw Exp $
*
* Admin interface for configuration
*
/*----- Revision history --------------------------------------------------*
*
* $Log: admin.c,v $
+ * Revision 1.8 2003/04/06 10:25:17 mdw
+ * Support Linux TUN/TAP device. Fix some bugs.
+ *
+ * Revision 1.7 2002/01/13 14:57:33 mdw
+ * Track @lbuf@ and @dstr_vputf@ changes in mLib.
+ *
+ * Revision 1.6 2001/02/19 19:11:09 mdw
+ * Output buffering on admin connections.
+ *
* Revision 1.5 2001/02/16 21:22:51 mdw
* Support for displaying statistics. Make client connections blocking, so
* that things don't get dropped. (This might change again if I add
#define T_RESOLVE SEC(30)
+static void a_destroy(admin */*a*/);
+static void a_lock(admin */*a*/);
+static void a_unlock(admin */*a*/);
+
+/*----- Output functions --------------------------------------------------*/
+
+/* --- @trywrite@ --- *
+ *
+ * Arguments: @admin *a@ = pointer to an admin block
+ * @const char *p@ = pointer to buffer to write
+ * @size_t sz@ = size of data to write
+ *
+ * Returns: The number of bytes written, or less than zero on error.
+ *
+ * Use: Attempts to write data to a client.
+ */
+
+static ssize_t trywrite(admin *a, const char *p, size_t sz)
+{
+ ssize_t n, done = 0;
+
+again:
+ if (!sz)
+ return (done);
+ n = write(a->w.fd, p, sz);
+ if (n > 0) {
+ done += n;
+ p += n;
+ sz -= n;
+ goto again;
+ }
+ if (n < 0) {
+ if (errno == EINTR)
+ goto again;
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ a_destroy(a);
+ a_warn("disconnecting admin client due to write errors: %s",
+ strerror(errno));
+ return (-1);
+ }
+ }
+ return (done);
+}
+
+/* --- @dosend@ --- *
+ *
+ * Arguemnts: @admin *a@ = pointer to an admin block
+ * @const char *p@ = pointer to buffer to write
+ * @size_t sz@ = size of data to write
+ *
+ * Returns: ---
+ *
+ * Use: Sends data to an admin client.
+ */
+
+static void dosend(admin *a, const char *p, size_t sz)
+{
+ ssize_t n;
+ obuf *o;
+
+ if (a->f & AF_DEAD)
+ return;
+
+ /* --- Try to send the data immediately --- */
+
+ if (!a->o_head) {
+ if ((n = trywrite(a, p, sz)) < 0)
+ return;
+ p += n;
+ sz -= n;
+ if (!sz)
+ return;
+ }
+
+ /* --- Fill buffers with the data until it's all gone --- */
+
+ o = a->o_tail;
+ if (!o)
+ sel_addfile(&a->w);
+ else if (o->p_in < o->buf + OBUFSZ)
+ goto noalloc;
+
+ do {
+ o = xmalloc(sizeof(obuf));
+ o->next = 0;
+ o->p_in = o->p_out = o->buf;
+ if (a->o_tail)
+ a->o_tail->next = o;
+ else
+ a->o_head = o;
+ a->o_tail = o;
+
+ noalloc:
+ n = o->buf + OBUFSZ - o->p_in;
+ if (n > sz)
+ n = sz;
+ memcpy(o->p_in, p, n);
+ o->p_in += n;
+ p += n;
+ sz -= n;
+ } while (sz);
+}
+
+/* --- @a_flush@ --- *
+ *
+ * Arguments: @int fd@ = file descriptor
+ * @unsigned mode@ = what's happening
+ * @void *v@ = pointer to my admin block
+ *
+ * Returns: ---
+ *
+ * Use: Flushes buffers when a client is ready to read again.
+ */
+
+static void a_flush(int fd, unsigned mode, void *v)
+{
+ admin *a = v;
+ obuf *o, *oo;
+ ssize_t n;
+
+ o = a->o_head;
+ while (o) {
+ if ((n = trywrite(a, o->p_out, o->p_in - o->p_out)) < 0)
+ return;
+ o->p_out += n;
+ if (o->p_in < o->p_out)
+ break;
+ oo = o;
+ o = o->next;
+ xfree(oo);
+ }
+ a->o_head = o;
+ if (!o) {
+ a->o_tail = 0;
+ sel_rmfile(&a->w);
+ }
+}
+
/*----- Utility functions -------------------------------------------------*/
/* --- @a_write@ --- *
va_list ap;
dstr d = DSTR_INIT;
va_start(ap, fmt);
- dstr_vputf(&d, fmt, ap);
+ dstr_vputf(&d, fmt, &ap);
va_end(ap);
- write(a->fd, d.buf, d.len);
+ dosend(a, d.buf, d.len);
dstr_destroy(&d);
}
void a_warn(const char *fmt, ...)
{
va_list ap;
- admin *a;
+ admin *a, *aa;
dstr d = DSTR_INIT;
if (flags & F_INIT)
dstr_puts(&d, "WARN ");
va_start(ap, fmt);
- dstr_vputf(&d, fmt, ap);
+ dstr_vputf(&d, fmt, &ap);
va_end(ap);
if (!(flags & F_INIT))
moan("%s", d.buf);
else {
dstr_putc(&d, '\n');
- for (a = admins; a; a = a->next)
- write(a->fd, d.buf, d.len);
+ for (a = admins; a; a = aa) {
+ aa = a->next;
+ dosend(a, d.buf, d.len);
+ }
}
dstr_destroy(&d);
}
static void a_trace(const char *p, size_t sz, void *v)
{
dstr d = DSTR_INIT;
- admin *a;
+ admin *a, *aa;
dstr_puts(&d, "TRACE ");
dstr_putm(&d, p, sz);
dstr_putc(&d, '\n');
- for (a = admins; a; a = a->next)
- write(a->fd, d.buf, d.len);
+ for (a = admins; a; a = aa) {
+ aa = a->next;
+ dosend(a, d.buf, d.len);
+ }
dstr_destroy(&d);
}
#endif
static void a_resolve(struct hostent *h, void *v)
{
admin *a = v;
+
+ a_lock(a);
T( trace(T_ADMIN, "admin: %u resolved", a->seq); )
TIMER;
sel_rmtimer(&a->t);
xfree(a->paddr);
a->pname = 0;
selbuf_enable(&a->b);
+ a_unlock(a);
}
/* --- @a_timer@ --- *
static void a_timer(struct timeval *tv, void *v)
{
admin *a = v;
+
+ a_lock(a);
T( trace(T_ADMIN, "admin: %u resolver timeout", a->seq); )
bres_abort(&a->r);
a_write(a, "FAIL timeout resolving `%s'\n", a->paddr);
xfree(a->paddr);
a->pname = 0;
selbuf_enable(&a->b);
+ a_unlock(a);
}
/* --- @acmd_add@ --- *
a_write(a, "INFO %u\nOK\n", p_port());
}
-static void a_destroy(admin */*a*/);
-
static void acmd_daemon(admin *a, unsigned ac, char *av[])
{
if (flags & F_DAEMON)
/*----- Connection handling -----------------------------------------------*/
-/* --- @a_destroy@ --- *
+/* --- @a_lock@ --- *
*
* Arguments: @admin *a@ = pointer to an admin block
*
* Returns: ---
*
- * Use: Destroys an admin block.
+ * Use: Locks an admin block so that it won't be destroyed
+ * immediately.
*/
-static void a_destroy(admin *a)
+static void a_lock(admin *a) { assert(!(a->f & AF_LOCK)); a->f |= AF_LOCK; }
+
+/* --- @a_unlock@ --- *
+ *
+ * Arguments: @admin *a@ = pointer to an admin block
+ *
+ * Returns: ---
+ *
+ * Use: Unlocks an admin block, allowing its destruction. This is
+ * also the second half of @a_destroy@.
+ */
+
+static void a_unlock(admin *a)
{
- T( trace(T_ADMIN, "admin: destroying connection %u", a->seq); )
+ assert(a->f & AF_LOCK);
+ if (!(a->f & AF_DEAD)) {
+ a->f &= ~AF_LOCK;
+ return;
+ }
+
+ T( trace(T_ADMIN, "admin: completing destruction of connection %u",
+ a->seq); )
+
selbuf_destroy(&a->b);
- if (a->b.reader.fd != a->fd)
- close(a->b.reader.fd);
- close(a->fd);
if (a->pname) {
xfree(a->pname);
xfree(a->paddr);
bres_abort(&a->r);
sel_rmtimer(&a->t);
}
+ if (a->b.reader.fd != a->w.fd)
+ close(a->b.reader.fd);
+ close(a->w.fd);
+
+ if (a_stdin == a)
+ a_stdin = 0;
if (a->next)
a->next->prev = a->prev;
if (a->prev)
a->prev->next = a->next;
else
admins = a->next;
- if (a_stdin == a)
- a_stdin = 0;
DESTROY(a);
}
+/* --- @a_destroy@ --- *
+ *
+ * Arguments: @admin *a@ = pointer to an admin block
+ *
+ * Returns: ---
+ *
+ * Use: Destroys an admin block. This requires a certain amount of
+ * care.
+ */
+
+static void a_destroy(admin *a)
+{
+ /* --- Don't multiply destroy admin blocks --- */
+
+ if (a->f & AF_DEAD)
+ return;
+
+ /* --- Make sure nobody expects it to work --- */
+
+ a->f |= AF_DEAD;
+ T( trace(T_ADMIN, "admin: destroying connection %u", a->seq); )
+
+ /* --- Free the output buffers --- */
+
+ if (a->o_head) {
+ obuf *o, *oo;
+ sel_rmfile(&a->w);
+ for (o = a->o_head; o; o = oo) {
+ oo = o->next;
+ xfree(o);
+ }
+ a->o_head = 0;
+ }
+
+ /* --- If the block is locked, that's all we can manage --- */
+
+ if (a->f & AF_LOCK) {
+ T( trace(T_ADMIN, "admin: deferring destruction..."); )
+ return;
+ }
+ a->f |= AF_LOCK;
+ a_unlock(a);
+}
+
/* --- @a_line@ --- *
*
* Arguments: @char *p@ = pointer to the line read
+ * @size_t len@ = length of the line
* @void *vp@ = pointer to my admin block
*
* Returns: ---
* Use: Handles a line of input.
*/
-static void a_line(char *p, void *vp)
+static void a_line(char *p, size_t len, void *vp)
{
admin *a = vp;
const acmd *c;
size_t ac;
TIMER;
+ if (a->f & AF_DEAD)
+ return;
if (!p) {
a_destroy(a);
return;
ac--;
if (c->argmin > ac || ac > c->argmax)
a_write(a, "FAIL syntax: %s\n", c->help);
- else
+ else {
+ a_lock(a);
c->func(a, ac, av + 1);
+ a_unlock(a);
+ }
return;
}
}
a->seq = seq++; )
T( trace(T_ADMIN, "admin: accepted connection %u", a->seq); )
a->pname = 0;
+ a->f = 0;
if (fd_in == STDIN_FILENO)
a_stdin = a;
- fdflags(fd_in, O_NONBLOCK, 0, FD_CLOEXEC, FD_CLOEXEC);
+ fdflags(fd_in, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
if (fd_out != fd_in)
- fdflags(fd_out, O_NONBLOCK, 0, FD_CLOEXEC, FD_CLOEXEC);
- a->fd = fd_out;
+ fdflags(fd_out, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
selbuf_init(&a->b, &sel, fd_in, a_line, a);
+ sel_initfile(&sel, &a->w, fd_out, SEL_WRITE, a_flush, a);
+ a->o_head = 0;
+ a->o_tail = 0;
a->next = admins;
a->prev = 0;
if (admins)