* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file lib/eclient.c
+ * @brief Client code for event-driven programs
+ */
#include <config.h>
#include "types.h"
/* 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",
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 ******************************************************/
/* 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),
/* 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);
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;
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;
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;
/* 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" : "",
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,
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;
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 */