X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/763d5e6ad88ef3ba1cd1d7742d060e4f1e54c6b8..ad58ebcc8a950f4624cfb1a836e18a703c3b6610:/lib/eclient.c diff --git a/lib/eclient.c b/lib/eclient.c index 971dc4c..43fafac 100644 --- a/lib/eclient.c +++ b/lib/eclient.c @@ -17,6 +17,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file lib/eclient.c + * @brief Client code for event-driven programs + */ #include #include "types.h" @@ -57,16 +60,18 @@ /* Types *********************************************************************/ +/** @brief Client state */ enum client_state { - state_disconnected, /* not connected */ - state_connecting, /* waiting for connect() */ - state_connected, /* connected but not authenticated */ - state_idle, /* not doing anything */ - state_cmdresponse, /* waiting for command resonse */ - state_body, /* accumulating body */ - state_log, /* monitoring log */ + state_disconnected, /**< @brief not connected */ + state_connecting, /**< @brief waiting for connect() */ + state_connected, /**< @brief connected but not authenticated */ + state_idle, /**< @brief not doing anything */ + state_cmdresponse, /**< @brief waiting for command resonse */ + state_body, /**< @brief accumulating body */ + state_log, /**< @brief monitoring log */ }; +/** @brief Names for @ref client_state */ static const char *const states[] = { "disconnected", "connecting", @@ -79,42 +84,50 @@ static const char *const states[] = { struct operation; /* forward decl */ +/** @brief Type of an operation callback */ typedef void operation_callback(disorder_eclient *c, struct operation *op); -/* A pending operation. This can be either a command or part of the - * authentication protocol. In the former case new commands are appended to - * the list, in the latter case they are inserted at the front. */ +/** @brief A pending operation. + * + * This can be either a command or part of the authentication protocol. In the + * former case new commands are appended to the list, in the latter case they + * are inserted at the front. */ struct operation { - struct operation *next; /* next operation */ - char *cmd; /* command to send or 0 */ - operation_callback *opcallback; /* internal completion callback */ - void (*completed)(); /* user completion callback or 0 */ - void *v; /* data for COMPLETED */ - disorder_eclient *client; /* owning client */ - int sent; /* true if sent to server */ + struct operation *next; /**< @brief next operation */ + char *cmd; /**< @brief command to send or 0 */ + operation_callback *opcallback; /**< @brief internal completion callback */ + void (*completed)(); /**< @brief user completion callback or 0 */ + void *v; /**< @brief data for COMPLETED */ + disorder_eclient *client; /**< @brief owning client */ + + /** @brief true if sent to server + * + * This is cleared by disorder_eclient_close(), forcing all queued + * commands to be transparently resent. + */ + int sent; }; +/** @brief Client structure */ struct disorder_eclient { const char *ident; - int fd; - enum client_state state; /* current state */ - int authenticated; /* true when authenicated */ - struct dynstr output; /* output buffer */ - struct dynstr input; /* input buffer */ - int eof; /* input buffer is at EOF */ - /* error reporting callbacks */ - const disorder_eclient_callbacks *callbacks; - void *u; - /* operation queuue */ - struct operation *ops, **opstail; + int fd; /**< @brief connection to server */ + enum client_state state; /**< @brief current state */ + int authenticated; /**< @brief true when authenicated */ + struct dynstr output; /**< @brief output buffer */ + struct dynstr input; /**< @brief input buffer */ + int eof; /**< @brief input buffer is at EOF */ + const disorder_eclient_callbacks *callbacks; /**< @brief error callbacks */ + void *u; /**< @brief user data */ + struct operation *ops; /**< @brief queue of operations */ + struct operation **opstail; /**< @brief queue tail */ /* accumulated response */ - int rc; /* response code */ - char *line; /* complete line */ - struct vector vec; /* body */ - /* log client callback */ - const disorder_eclient_log_callbacks *log_callbacks; - void *log_v; - unsigned long statebits; /* current state */ + int rc; /**< @brief response code */ + char *line; /**< @brief complete line */ + struct vector vec; /**< @brief body */ + const disorder_eclient_log_callbacks *log_callbacks; /**< @brief log callbacks */ + void *log_v; /**< @brief user data */ + unsigned long statebits; /**< @brief current state */ }; /* Forward declarations ******************************************************/ @@ -160,13 +173,18 @@ static void logentry_volume(disorder_eclient *c, int nvec, char **vec); /* Tables ********************************************************************/ -static const struct logentry_handler { - const char *name; - int min, max; +/** @brief One possible log entry */ +struct logentry_handler { + const char *name; /**< @brief Entry name */ + int min; /**< @brief Minimum arguments */ + int max; /**< @brief Maximum arguments */ void (*handler)(disorder_eclient *c, int nvec, - char **vec); -} logentry_handlers[] = { + char **vec); /**< @brief Handler function */ +}; + +/** @brief Table for parsing log entries */ +static const struct logentry_handler logentry_handlers[] = { #define LE(X, MIN, MAX) { #X, MIN, MAX, logentry_##X } LE(completed, 1, 1), LE(failed, 2, 2), @@ -183,6 +201,10 @@ static const struct logentry_handler { /* Setup and teardown ********************************************************/ +/** @brief Create a new client + * + * Does NOT connect the client - connections are made (and re-made) on demand. + */ disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb, void *u) { disorder_eclient *c = xmalloc(sizeof *c); @@ -194,9 +216,20 @@ disorder_eclient *disorder_eclient_new(const disorder_eclient_callbacks *cb, vector_init(&c->vec); dynstr_init(&c->input); dynstr_init(&c->output); + if(!config->password) { + error(0, "no password set"); + return 0; + } return c; } +/** @brief Disconnect a client + * @param c Client to disconnect + * + * NB that this routine just disconnnects the TCP connection. It does not + * destroy the client! If you continue to use it then it will attempt to + * reconnect. + */ void disorder_eclient_close(disorder_eclient *c) { struct operation *op; @@ -217,9 +250,34 @@ void disorder_eclient_close(disorder_eclient *c) { op->sent = 0; } +/** @brief Return true if @c c is connected + * + * By connected it is meant that commands have a reasonable chance of being + * processed soon, not merely that a TCP connection exists - for instance if + * the client is still authenticating then that does not count as connected. + */ +int disorder_eclient_connected(const disorder_eclient *c) { + switch(c->state) { + case state_disconnected: + case state_connecting: + case state_connected: + return 0; + case state_idle: + case state_cmdresponse: + case state_body: + case state_log: + return 1; + } + assert(!"reached"); +} + /* Error reporting ***********************************************************/ -/* called when a connection error occurs */ +/** @brief called when a connection error occurs + * + * After this called we will be disconnected (by disorder_eclient_close()), + * so there will be a reconnection before any commands can be sent. + */ static int comms_error(disorder_eclient *c, const char *fmt, ...) { va_list ap; char *s; @@ -233,7 +291,7 @@ static int comms_error(disorder_eclient *c, const char *fmt, ...) { return -1; } -/* called when the server reports an error */ +/** @brief called when the server reports an error */ static int protocol_error(disorder_eclient *c, struct operation *op, int code, const char *fmt, ...) { va_list ap; @@ -249,9 +307,18 @@ static int protocol_error(disorder_eclient *c, struct operation *op, /* State machine *************************************************************/ +/** @brief Called when there's something to do + * @param c Client + * @param mode bitmap of @ref DISORDER_POLL_READ and/or @ref DISORDER_POLL_WRITE. + * + * This should be called from by your code when the file descriptor is readable + * or writable (as requested by the @c poll callback, see @ref + * disorder_eclient_callbacks) and in any case from time to time (with @p mode + * = 0) to allow for retries to work. + */ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { struct operation *op; - + D(("disorder_eclient_polled fd=%d state=%s mode=[%s %s]", c->fd, states[c->state], mode & DISORDER_POLL_READ ? "READ" : "", @@ -381,7 +448,7 @@ void disorder_eclient_polled(disorder_eclient *c, unsigned mode) { if(c->fd != -1) c->callbacks->poll(c->u, c, c->fd, mode); } -/* Called to start connecting */ +/** @brief Called to start connecting */ static int start_connect(void *cc, const struct sockaddr *sa, socklen_t len, @@ -415,7 +482,7 @@ static int start_connect(void *cc, return 0; } -/* Called when maybe connected */ +/** @brief Called when poll triggers while waiting for a connection */ static void maybe_connected(disorder_eclient *c) { /* We either connected, or got an error. */ int err; @@ -546,8 +613,10 @@ static void read_input(disorder_eclient *c) { if(c->state > state_connecting) consume(&c->input, (nl - c->input.vec) + 1); } - if(c->eof) + if(c->eof) { comms_error(c, "reading from %s: server disconnected", c->ident); + c->authenticated = 0; + } } /* called with a line that has just been read */