X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=ccan%2Foserver%2Foserver.c;h=d4266eda1ef5ead9329ecd5e4136e307a8a162d4;hb=92f7f79eb9535a281cab710eedb9878c74b950b8;hp=4503aa49610f1a13d55649d37dbfa89c0d9f24ac;hpb=5807ed8dff7520582ee88f16c98b3965389bf8ff;p=ccan-lca-2011.git diff --git a/ccan/oserver/oserver.c b/ccan/oserver/oserver.c index 4503aa4..d4266ed 100644 --- a/ccan/oserver/oserver.c +++ b/ccan/oserver/oserver.c @@ -1,9 +1,11 @@ #include +#include +#include #include #include #include #include -#include +#include #include #include #include @@ -16,36 +18,22 @@ #include #include #include - -enum state { - RECEIVING_USER_QUESTION, - SENDING_ANSWER, - FINISHED -}; +#include +#include +#include +#include static uint16_t state_flag_map[] = { + [SENDING_GREETING] = TEVENT_FD_WRITE, [RECEIVING_USER_QUESTION] = TEVENT_FD_READ, + [SENDING_OTHER_QUESTION_PREFIX] = TEVENT_FD_WRITE, + [SENDING_OTHER_QUESTION] = TEVENT_FD_WRITE, + [RECEIVING_OTHER_ANSWER] = TEVENT_FD_READ, + [SENDING_ANSWER_PREFIX] = TEVENT_FD_WRITE, [SENDING_ANSWER] = TEVENT_FD_WRITE, [FINISHED] = 0 }; -struct client { - /* What are we doing today, brain? */ - enum state state; - /* Our event info, and the file descriptor. */ - struct tevent_fd *fde; - int fd; - /* The question we read from client. */ - char *question; - /* How many bytes of the reply we sent so far. */ - size_t bytes_sent; -}; - -/* 5 clients should be enough for anybody! */ -static struct client *clients[5]; -static int sfd; -static struct tevent_fd *sfde; - static ssize_t write_string(int fd, const char *str) { return write(fd, str, strlen(str)); @@ -95,6 +83,45 @@ static bool send_string(struct client *c, const char *str) return true; } +static bool get_subclient(struct client *me) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(me->oserver->clients); i++) { + struct client *c = me->oserver->clients[i]; + if (!c || c == me) + continue; + if (c->oracle == -1 && input_finished(c->question)) { + me->subclient = c->id; + c->oracle = me->id; + return true; + } + } + return false; +} + +static bool get_oracle(struct client *me) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(me->oserver->clients); i++) { + struct client *c = me->oserver->clients[i]; + if (!c || c == me) + continue; + if (c->subclient == -1 && input_finished(c->question)) { + me->oracle = c->id; + c->subclient = me->id; + return true; + } + } + return false; +} + +static void wakeup(struct client *c) +{ + tevent_fd_set_flags(c->fde, state_flag_map[c->state]); +} + static void service_client(struct tevent_context *ev, struct tevent_fd *fde, uint16_t flags, void *_c) { @@ -102,20 +129,51 @@ static void service_client(struct tevent_context *ev, ssize_t len; switch (c->state) { + case SENDING_GREETING: + if (!send_string(c, "Welcome. Please ask your question.\n")) + goto fail; + break; case RECEIVING_USER_QUESTION: len = read_string(c->fd, &c->question); if (len <= 0) goto fail; - if (input_finished(c->question)) { - unsigned int i; - - for (i = 0; c->question[i]; i++) - c->question[i] = toupper(c->question[i]); - set_state(c, SENDING_ANSWER); + if (input_finished(c->question)) + set_state(c, SENDING_OTHER_QUESTION_PREFIX); + break; + case SENDING_OTHER_QUESTION_PREFIX: + if (c->subclient == -1) + goto need_subclient; + if (!send_string(c, "While the Oracle ponders," + " please answer the following question:\n")) + goto fail; + break; + case SENDING_OTHER_QUESTION: + if (c->subclient == -1) + goto need_subclient; + if (!send_string(c, + c->oserver->clients[c->subclient]->question)) + goto fail; + break; + case RECEIVING_OTHER_ANSWER: + if (c->subclient == -1) + goto need_subclient; + len = read_string(c->fd, + &c->oserver->clients[c->subclient]->answer); + if (len <= 0) + goto fail; + if (input_finished(c->oserver->clients[c->subclient]->answer)) { + set_state(c, SENDING_ANSWER_PREFIX); + wakeup(c->oserver->clients[c->subclient]); } break; + case SENDING_ANSWER_PREFIX: + if (!input_finished(c->answer)) + goto need_answer; + if (!send_string(c, "The Oracle spake thus:\n")) + goto fail; + break; case SENDING_ANSWER: - if (!send_string(c, c->question)) + if (!send_string(c, c->answer)) goto fail; break; default: @@ -124,87 +182,259 @@ static void service_client(struct tevent_context *ev, if (c->state != FINISHED) return; - fail: talloc_free(c); + return; + +need_subclient: + if (!get_subclient(c)) { + /* We can't get one: go to sleep until someone find_oracle() */ + tevent_fd_set_flags(c->fde, 0); + } else + /* In case they are waiting... */ + wakeup(c->oserver->clients[c->subclient]); + return; + +need_answer: + /* If we don't have an oracle and find one, that's OK. */ + if (c->oracle == -1 && get_oracle(c)) { + /* In case they are waiting... */ + wakeup(c->oserver->clients[c->oracle]); + return; + } + + /* Either our oracle is not finished, or we don't have one: sleep. */ + tevent_fd_set_flags(c->fde, 0); } static int cleanup_client(struct client *client) { - unsigned int i; - for (i = 0; i < ARRAY_SIZE(clients); i++) { - if (clients[i] == client) { - clients[i] = NULL; - tevent_fd_set_flags(sfde, TEVENT_FD_READ); - return 0; - } - } - abort(); + /* We were an oracle? */ + if (client->subclient >= 0) + client->oserver->clients[client->subclient]->oracle = -1; + + /* We had an oracle? */ + if (client->oracle >= 0) + client->oserver->clients[client->oracle]->subclient = -1; + + assert(client->oserver->clients[client->id] == client); + client->oserver->clients[client->id] = NULL; + return 0; } static void add_client(struct tevent_context *ev, - struct tevent_fd *fde, uint16_t flags, void *unused) + struct tevent_fd *fde, uint16_t flags, void *_oserver) { + struct oserver *oserver = _oserver; struct client *client; - unsigned int i; - client = talloc(sfde, struct client); - client->fd = accept(sfd, NULL, 0); + client = talloc(oserver, struct client); + client->fd = accept(oserver->fd, NULL, 0); if (client->fd < 0) err(1, "Accepting client connection"); - client->state = RECEIVING_USER_QUESTION; + client->state = SENDING_GREETING; client->bytes_sent = 0; client->question = talloc_strdup(client, ""); + client->oserver = oserver; + client->oracle = -1; + client->subclient = -1; + client->answer = talloc_strdup(client, ""); client->fde = tevent_add_fd(ev, client, client->fd, state_flag_map[client->state], service_client, client); tevent_fd_set_auto_close(client->fde); /* Find empty slot in array for this client. */ - for (i = 0; clients[i]; i++); - clients[i] = client; + for (client->id = 0; oserver->clients[client->id]; client->id++); + oserver->clients[client->id] = client; talloc_set_destructor(client, cleanup_client); /* Full? Stop listening... */ - if (i == ARRAY_SIZE(clients)-1) - tevent_fd_set_flags(sfde, 0); + if (client->id == ARRAY_SIZE(oserver->clients)-1) + tevent_fd_set_flags(oserver->fde, 0); +} + +static void clear_clients(struct oserver *oserver) +{ + memset(oserver->clients, 0, + ARRAY_SIZE(oserver->clients) * sizeof(oserver->clients[0])); +} + +static int destroy_oserver(struct oserver *oserver) +{ + close(oserver->fd); + return 0; } -void *oserver_setup(struct tevent_context *ev, unsigned short port) +static void talloc_dump(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *_oserver) { + struct oserver *oserver = _oserver; + FILE *f; + + /* Fork off a child for the report, so we aren't stopped. */ + if (fork() == 0) { + f = fopen("/var/run/oserver/talloc.dump", "w"); + if (f) { + talloc_report_full(oserver, f); + fclose(f); + } + _exit(0); + } +} + +static void dump(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *_oserver) +{ + struct oserver *oserver = _oserver; + char *str; + int fd; + + str = cdump_bundle(ev, cdump_struct_oserver, oserver); + fd = open(oserver->dumpfile, O_CREAT|O_TRUNC|O_WRONLY, 0600); + write(fd, str, strlen(str)); + close(fd); + talloc_free(str); + if (oserver->argv) + execvp(oserver->argv[0], oserver->argv); +} + +static bool load_file(struct oserver *oserver, const char *file) +{ + char *str; + + if (!file) + return false; + + str = grab_file(oserver, file, NULL); + if (!str) + return false; + + if (!cdump_unbundle(oserver, cdump_struct_oserver, oserver, str)) { + talloc_free(str); + return false; + } + talloc_free(str); + return true; +} + +static bool complete_server(struct tevent_context *ev, + struct oserver *oserver, const char *dumpfile) +{ + /* Re-set this even if restored from file, in case it changed. */ + oserver->dumpfile = dumpfile; + if (oserver->dumpfile) + tevent_add_signal(ev, oserver, SIGHUP, SA_RESTART, + dump, oserver); + + /* Don't kill us if client dies. */ + signal(SIGPIPE, SIG_IGN); + + /* Show talloc tree on SIGUSR1. */ + tevent_add_signal(ev, oserver, SIGUSR1, SA_RESTART, + talloc_dump, oserver); + + oserver->fde = tevent_add_fd(ev, oserver, oserver->fd, + TEVENT_FD_READ, add_client, oserver); + if (!oserver->fde) + return false; + return true; +} + +struct oserver *oserver_restore(struct tevent_context *ev, const char *dumpfile) +{ + unsigned int i; + struct oserver *oserver = talloc(ev, struct oserver); + if (!load_file(oserver, dumpfile)) { + talloc_free(oserver); + return NULL; + } + + /* Restore ignored fields in clients, and talloc hierarchy. */ + for (i = 0; i < ARRAY_SIZE(oserver->clients); i++) { + struct client *client = oserver->clients[i]; + if (!client) + continue; + /* These two were marked CDUMP_IGNORE. */ + client->oserver = oserver; + client->fde = tevent_add_fd(ev, client, client->fd, + state_flag_map[client->state], + service_client, client); + tevent_fd_set_auto_close(client->fde); + /* cdump knows nothing of talloc. */ + talloc_steal(oserver, client); + talloc_steal(client, client->question); + talloc_steal(client, client->answer); + talloc_set_destructor(client, cleanup_client); + } + + talloc_set_destructor(oserver, destroy_oserver); + if (!complete_server(ev, oserver, dumpfile)) { + talloc_free(oserver); + return NULL; + } + return oserver; +} + +struct oserver *oserver_setup(struct tevent_context *ev, unsigned short port, + const char *dumpfile, char *argv[]) +{ + struct oserver *oserver; int one = 1; union { struct sockaddr addr; struct sockaddr_in in; } u; - sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sfd < 0) - return false; + oserver = talloc(ev, struct oserver); + oserver->argv = argv; + if (argv) { + /* Count the terminal NULL in argv_len. */ + for (oserver->argv_len = 1; + argv[oserver->argv_len - 1]; + oserver->argv_len++); + } else + oserver->argv_len = 0; + clear_clients(oserver); + oserver->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (oserver->fd < 0) { + talloc_free(oserver); + return NULL; + } - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) + talloc_set_destructor(oserver, destroy_oserver); + + if (setsockopt(oserver->fd, SOL_SOCKET, SO_REUSEADDR, + &one, sizeof(one))) warn("Setting socket reuse"); u.in.sin_family = AF_INET; u.in.sin_port = htons(port); u.in.sin_addr.s_addr = INADDR_ANY; - if (bind(sfd, &u.addr, sizeof(u.in)) == -1) { - close_noerr(sfd); + if (bind(oserver->fd, &u.addr, sizeof(u.in)) == -1) { + talloc_free(oserver); return NULL; } - if (listen(sfd, 0) != 0) { - close_noerr(sfd); + if (listen(oserver->fd, 0) != 0) { + talloc_free(oserver); return NULL; } - sfde = tevent_add_fd(ev, ev, sfd, TEVENT_FD_READ, add_client, NULL); - tevent_fd_set_auto_close(sfde); - - /* Don't kill us if client dies. */ - signal(SIGPIPE, SIG_IGN); + if (!complete_server(ev, oserver, dumpfile)) { + talloc_free(oserver); + return NULL; + } - return sfde; + return oserver; }