/*
* This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2006 Richard Kettlewell
+ * Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
#include "eventlog.h"
#include "defs.h"
#include "cache.h"
+#include "unicode.h"
#ifndef NONCE_SIZE
# define NONCE_SIZE 16
int pf;
};
+/** @brief One client connection */
struct conn {
+ /** @brief Read commands from here */
ev_reader *r;
+ /** @brief Send responses to here */
ev_writer *w;
+ /** @brief Underlying file descriptor */
int fd;
+ /** @brief Unique identifier for connection used in log messages */
unsigned tag;
+ /** @brief Login name or NULL */
char *who;
+ /** @brief Event loop */
ev_source *ev;
+ /** @brief Nonce chosen for this connection */
unsigned char nonce[NONCE_SIZE];
+ /** @brief Current reader callback
+ *
+ * We change this depending on whether we're servicing the @b log command
+ */
ev_reader_callback *reader;
+ /** @brief Event log output sending to this connection */
struct eventlog_output *lo;
+ /** @brief Parent listener */
const struct listener *l;
};
static int reader_callback(ev_source *ev,
ev_reader *reader,
- int fd,
void *ptr,
size_t bytes,
int eof,
static const char *noyes[] = { "no", "yes" };
+/** @brief Called when a connection's writer fails or is shut down
+ *
+ * If the connection still has a raeder that is cancelled.
+ */
static int writer_error(ev_source attribute((unused)) *ev,
- int fd,
int errno_value,
void *u) {
struct conn *c = u;
- D(("server writer_error %d %d", fd, errno_value));
+ D(("server writer_error S%x %d", c->tag, errno_value));
if(errno_value == 0) {
/* writer is done */
- c->w = 0;
- if(c->r == 0) {
- D(("server writer_error closes %d", fd));
- xclose(fd); /* reader is done too, close */
- } else {
- D(("server writer_error shutdown %d SHUT_WR", fd));
- xshutdown(fd, SHUT_WR); /* reader is not done yet */
- }
+ D(("S%x writer completed", c->tag));
} else {
if(errno_value != EPIPE)
error(errno_value, "S%x write error on socket", c->tag);
- if(c->r)
+ if(c->r) {
+ D(("cancel reader"));
ev_reader_cancel(c->r);
- xclose(fd);
+ c->r = 0;
+ }
+ D(("done cancel reader"));
}
+ c->w = 0;
+ ev_report(ev);
return 0;
}
+/** @brief Called when a conncetion's reader fails or is shut down
+ *
+ * If connection still has a writer then it is closed.
+ */
static int reader_error(ev_source attribute((unused)) *ev,
- int fd,
int errno_value,
void *u) {
struct conn *c = u;
- D(("server reader_error %d %d", fd, errno_value));
- error(errno, "S%x read error on socket", c->tag);
- ev_writer_cancel(c->w);
- xclose(fd);
+ D(("server reader_error S%x %d", c->tag, errno_value));
+ error(errno_value, "S%x read error on socket", c->tag);
+ if(c->w)
+ ev_writer_close(c->w);
+ c->w = 0;
+ c->r = 0;
+ ev_report(ev);
return 0;
}
-/* return true if we are talking to a trusted user */
+/** @brief Return true if we are talking to a trusted user */
static int trusted(struct conn *c) {
int n;
return 1; /* completed */
}
+static void got_stats(char *stats, void *u) {
+ struct conn *const c = u;
+
+ sink_printf(ev_writer_sink(c->w), "253 stats\n%s\n.\n", stats);
+ /* Now we can start processing commands again */
+ ev_reader_enable(c->r);
+}
+
static int c_stats(struct conn *c,
char attribute((unused)) **vec,
int attribute((unused)) nvec) {
- char **v;
- int nv, n;
-
- v = trackdb_stats(&nv);
- sink_printf(ev_writer_sink(c->w), "253 stats\n");
- for(n = 0; n < nv; ++n) {
- if(v[n][0] == '.')
- sink_writes(ev_writer_sink(c->w), ".");
- sink_printf(ev_writer_sink(c->w), "%s\n", v[n]);
- }
- sink_writes(ev_writer_sink(c->w), ".\n");
- return 1;
+ trackdb_stats_subprocess(c->ev, got_stats, c);
+ return 0; /* not yet complete */
}
static int c_volume(struct conn *c,
return 1;
}
-/* we are logging, and some data is available to read */
-static int logging_reader_callback(ev_source *ev,
+/** @brief Called when data arrives on a log connection
+ *
+ * We just discard all such data. The client may occasionally send data as a
+ * keepalive.
+ */
+static int logging_reader_callback(ev_source attribute((unused)) *ev,
ev_reader *reader,
- int fd,
- void *ptr,
+ void attribute((unused)) *ptr,
size_t bytes,
- int eof,
- void *u) {
+ int attribute((unused)) eof,
+ void attribute((unused)) *u) {
struct conn *c = u;
- /* don't log to this conn any more */
- eventlog_remove(c->lo);
- /* terminate the log output */
- sink_writes(ev_writer_sink(c->w), ".\n");
- /* restore the reader callback */
- c->reader = reader_callback;
- /* ...and exit via it */
- return c->reader(ev, reader, fd, ptr, bytes, eof, u);
+ ev_reader_consume(reader, bytes);
+ if(eof) {
+ /* Oops, that's all for now */
+ D(("logging reader eof"));
+ if(c->w) {
+ D(("close writer"));
+ ev_writer_close(c->w);
+ c->w = 0;
+ }
+ c->r = 0;
+ }
+ return 0;
}
static void logclient(const char *msg, void *user) {
struct conn *c = user;
+ if(!c->w || !c->r) {
+ /* This connection has gone up in smoke for some reason */
+ eventlog_remove(c->lo);
+ return;
+ }
sink_printf(ev_writer_sink(c->w), "%"PRIxMAX" %s\n",
(uintmax_t)time(0), msg);
}
return 0;
}
-static void post_move_cleanup(void) {
- struct queue_entry *q;
-
- /* If we have caused the random track to not be at the end then we make it no
- * longer be random. */
- for(q = qhead.next; q != &qhead; q = q->next)
- if(q->state == playing_random && q->next != &qhead)
- q->state = playing_unplayed;
- /* That might mean we need to add a new random track. */
- add_random_track();
- queue_write();
-}
-
static int c_move(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
return 1;
}
n = queue_move(q, atoi(vec[1]), c->who);
- post_move_cleanup();
sink_printf(ev_writer_sink(c->w), "252 %d\n", n);
/* If we've moved to the head of the queue then prepare the track. */
if(q == qhead.next)
return 1;
}
queue_moveafter(q, nvec, qs, c->who);
- post_move_cleanup();
sink_printf(ev_writer_sink(c->w), "250 Moved tracks\n");
/* If we've moved to the head of the queue then prepare the track. */
if(q == qhead.next)
static int c_set_global(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
+ if(vec[0][0] == '_') {
+ sink_writes(ev_writer_sink(c->w), "550 cannot set internal global preferences\n");
+ return 1;
+ }
trackdb_set_global(vec[0], vec[1], c->who);
sink_printf(ev_writer_sink(c->w), "250 OK\n");
return 1;
int nvec, n;
D(("server command %s", line));
+ /* We force everything into NFC as early as possible */
+ if(!(line = utf8_compose_canon(line, strlen(line), 0))) {
+ sink_writes(ev_writer_sink(c->w), "500 cannot normalize command\n");
+ return 1;
+ }
if(!(vec = split(line, &nvec, SPLIT_QUOTES, command_error, c))) {
sink_writes(ev_writer_sink(c->w), "500 cannot parse command\n");
return 1;
/* redirect to the right reader callback for our current state */
static int redirect_reader_callback(ev_source *ev,
ev_reader *reader,
- int fd,
void *ptr,
size_t bytes,
int eof,
void *u) {
struct conn *c = u;
- return c->reader(ev, reader, fd, ptr, bytes, eof, u);
+ return c->reader(ev, reader, ptr, bytes, eof, u);
}
/* the main command reader */
static int reader_callback(ev_source attribute((unused)) *ev,
ev_reader *reader,
- int attribute((unused)) fd,
void *ptr,
size_t bytes,
int eof,
if(eof) {
if(bytes)
error(0, "S%x unterminated line", c->tag);
+ D(("normal reader close"));
c->r = 0;
- return ev_writer_close(c->w);
+ if(c->w) {
+ D(("close associated writer"));
+ ev_writer_close(c->w);
+ c->w = 0;
+ }
}
return 0;
}
cloexec(fd);
c->tag = tags++;
c->ev = ev;
- c->w = ev_writer_new(ev, fd, writer_error, c);
- c->r = ev_reader_new(ev, fd, redirect_reader_callback, reader_error, c);
+ c->w = ev_writer_new(ev, fd, writer_error, c,
+ "client writer");
+ c->r = ev_reader_new(ev, fd, redirect_reader_callback, reader_error, c,
+ "client reader");
+ ev_tie(c->r, c->w);
c->fd = fd;
c->reader = reader_callback;
c->l = l;
cloexec(fd);
l->name = name;
l->pf = pf;
- if(ev_listen(ev, fd, listen_callback, l)) exit(EXIT_FAILURE);
+ if(ev_listen(ev, fd, listen_callback, l, "server listener"))
+ exit(EXIT_FAILURE);
return fd;
}