/* * CORE * Copyright (c)2010-2012 the Boeing Company. * See the LICENSE file included in this distribution. * * author: Tom Goff * * vnode_server.c * */ #include #include #include #include #include #include #include #include #include #include "netns.h" #include "myerr.h" #include "vnode_msg.h" #include "vnode_chnl.h" #include "vnode_cmd.h" #include "vnode_io.h" #include "vnode_server.h" extern int verbose; static vnode_cliententry_t *vnode_server_newclient(vnode_server_t *server, int fd); static void vnode_server_delclient(vnode_cliententry_t *client); static void client_ioerror(vnode_msgio_t *msgio) { vnode_cliententry_t *client = msgio->data; if (verbose) INFO("i/o error for client fd %d; deleting client", client->msgio.fd); vnode_server_delclient(client); return; } static vnode_cliententry_t *vnode_server_newclient(vnode_server_t *server, int fd) { vnode_cliententry_t *client; vnode_msghandler_t msghandler[VNODE_MSG_MAX] = { [VNODE_MSG_CMDREQ] = vnode_recv_cmdreq, [VNODE_MSG_CMDSIGNAL] = vnode_recv_cmdsignal, }; #ifdef DEBUG WARNX("new client on fd %d", fd); #endif set_cloexec(fd); if ((client = malloc(sizeof(*client))) == NULL) { WARN("malloc() failed"); return NULL; } client->server = server; client->clientfd = fd; TAILQ_INSERT_TAIL(&server->clientlisthead, client, entries); if (vnode_msgiostart(&client->msgio, server->loop, client->clientfd, client, client_ioerror, msghandler)) { WARNX("vnode_msgiostart() failed"); free(client); return NULL; } return client; } static void vnode_server_delclient(vnode_cliententry_t *client) { #ifdef DEBUG WARNX("deleting client for fds %d %d", client->clientfd, client->msgio.fd); #endif TAILQ_REMOVE(&client->server->clientlisthead, client, entries); vnode_msgiostop(&client->msgio); close(client->clientfd); memset(client, 0, sizeof(*client)); free(client); return; } /* XXX put this in vnode_cmd.c ?? */ static void vnode_child_cb(struct ev_loop *loop, ev_child *w, int revents) { vnode_server_t *server = w->data; vnode_cmdentry_t *cmd; char *how; int status; #ifdef DEBUG WARNX("child process %d exited with status 0x%x", w->rpid, w->rstatus); if (WIFEXITED(w->rstatus)) WARNX("normal terminataion status: %d", WEXITSTATUS(w->rstatus)); else if (WIFSIGNALED(w->rstatus)) WARNX("terminated by signal: %d", WTERMSIG(w->rstatus)); else WARNX("unexpected status: %d", w->rstatus); #endif if (WIFEXITED(w->rstatus)) { how = "normally"; status = WEXITSTATUS(w->rstatus); } else if (WIFSIGNALED(w->rstatus)) { how = "due to signal"; status = WTERMSIG(w->rstatus); } else { how = "for unknown reason"; status = w->rstatus; } TAILQ_FOREACH(cmd, &server->cmdlisthead, entries) { if (cmd->pid == w->rpid) { vnode_cliententry_t *client = cmd->data; #ifdef DEBUG WARNX("pid %d found in cmd list; removing", w->rpid); #endif TAILQ_REMOVE(&server->cmdlisthead, cmd, entries); if (verbose) INFO("cmd completed %s: pid: %d; cmdid: %d; status %d", how, w->rpid, cmd->cmdid, status); if (vnode_send_cmdstatus(client->clientfd, cmd->cmdid, w->rstatus)) WARNX("vnode_send_cmdstatus() failed"); free(cmd); return; } } WARNX("pid %d not found in client command list: " "completed %s with status %d", w->rpid, how, status); return; } static void vnode_server_cb(struct ev_loop *loop, ev_io *w, int revents) { vnode_server_t *server = w->data; int fd; for (;;) { fd = accept(server->serverfd, NULL, NULL); if (fd < 0) { if (errno != EAGAIN) WARN("accept() failed"); break; } if (vnode_server_newclient(server, fd) == NULL) { WARN("vnode_server_newclient() failed"); close(fd); } } return; } static vnode_server_t *vnode_newserver(struct ev_loop *loop, int ctrlfd, const char *ctrlchnlname) { vnode_server_t *server; if ((server = malloc(sizeof(*server))) == NULL) { WARN("malloc() failed"); return NULL; } TAILQ_INIT(&server->clientlisthead); TAILQ_INIT(&server->cmdlisthead); server->loop = loop; strncpy(server->ctrlchnlname, ctrlchnlname, sizeof(server->ctrlchnlname)); server->ctrlchnlname[sizeof(server->ctrlchnlname) -1] = '\0'; memset(server->pidfilename, 0, sizeof(server->pidfilename)); server->serverfd = ctrlfd; #ifdef DEBUG WARNX("adding vnode_child_cb for pid 0"); #endif server->childwatcher.data = server; ev_child_init(&server->childwatcher, vnode_child_cb, 0, 0); ev_child_start(server->loop, &server->childwatcher); #ifdef DEBUG WARNX("adding vnode_server_cb for fd %d", server->serverfd); #endif server->fdwatcher.data = server; ev_io_init(&server->fdwatcher, vnode_server_cb, server->serverfd, EV_READ); ev_io_start(server->loop, &server->fdwatcher); return server; } void vnode_delserver(vnode_server_t *server) { unlink(server->ctrlchnlname); if (server->pidfilename[0] != '\0') { unlink(server->pidfilename); } ev_io_stop(server->loop, &server->fdwatcher); close(server->serverfd); ev_child_stop(server->loop, &server->childwatcher); while (!TAILQ_EMPTY(&server->clientlisthead)) { vnode_cliententry_t *client; client = TAILQ_FIRST(&server->clientlisthead); TAILQ_REMOVE(&server->clientlisthead, client, entries); vnode_server_delclient(client); } while (!TAILQ_EMPTY(&server->cmdlisthead)) { vnode_cmdentry_t *cmd; cmd = TAILQ_FIRST(&server->cmdlisthead); TAILQ_REMOVE(&server->cmdlisthead, cmd, entries); free(cmd); } memset(server, 0, sizeof(*server)); free(server); return; } vnode_server_t *vnoded(int newnetns, const char *ctrlchnlname, const char *logfilename, const char *pidfilename, const char *chdirname) { int ctrlfd; unsigned int i; long openmax; vnode_server_t *server; pid_t pid; setsid(); if ((ctrlfd = vnode_listen(ctrlchnlname)) < 0) { WARNX("vnode_listen() failed for '%s'", ctrlchnlname); return NULL; } set_cloexec(ctrlfd); if (newnetns) { pid = nsfork(0); if (pid == -1) { WARN("nsfork() failed"); close(ctrlfd); unlink(ctrlchnlname); return NULL; } } else { pid = getpid(); } if (pid) { printf("%u\n", pid); fflush(stdout); if (pidfilename) { FILE *pidfile; pidfile = fopen(pidfilename, "w"); if (pidfile != NULL) { fprintf(pidfile, "%u\n", pid); fclose(pidfile); } else { WARN("fopen() failed for '%s'", pidfilename); } } if (newnetns) _exit(0); /* nothing else for the parent to do */ } /* try to close any open files */ if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) openmax = 1024; assert(openmax >= _POSIX_OPEN_MAX); for (i = 3; i < openmax; i++) if (i != ctrlfd) close(i); if (!logfilename) logfilename = "/dev/null"; #define DUPFILE(filename, mode, fileno) \ do { \ int fd; \ if ((fd = open(filename, mode, 0644)) == -1) \ WARN("open() failed for '%s'", filename); \ else \ { \ if (dup2(fd, fileno) == -1) \ WARN("dup2() failed for " #fileno); \ close(fd); \ } \ } while (0); DUPFILE("/dev/null", O_RDONLY, STDIN_FILENO); DUPFILE(logfilename, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, STDOUT_FILENO); DUPFILE(logfilename, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, STDERR_FILENO); #undef DUPFILE setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stderr, NULL, _IOLBF, 0); if (chdirname && chdir(chdirname)) WARN("chdir() failed"); server = vnode_newserver(ev_default_loop(0), ctrlfd, ctrlchnlname); if (!server) { close(ctrlfd); unlink(ctrlchnlname); } if (pidfilename) { strncpy(server->pidfilename, pidfilename, sizeof(server->pidfilename)); server->pidfilename[sizeof(server->pidfilename) -1] = '\0'; } return server; }