=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/tmux/server.c,v retrieving revision 1.65 retrieving revision 1.66 diff -u -r1.65 -r1.66 --- src/usr.bin/tmux/server.c 2009/11/03 20:29:47 1.65 +++ src/usr.bin/tmux/server.c 2009/11/04 20:50:11 1.66 @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.65 2009/11/03 20:29:47 nicm Exp $ */ +/* $OpenBSD: server.c,v 1.66 2009/11/04 20:50:11 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -45,113 +46,29 @@ struct clients clients; struct clients dead_clients; -/* Mapping of a pollfd to an fd independent of its position in the array. */ -struct poll_item { - int fd; - int events; +int server_fd; +int server_shutdown; +struct event server_ev_accept; +struct event server_ev_sigterm; +struct event server_ev_sigusr1; +struct event server_ev_sigchld; +struct event server_ev_second; - void (*fn)(int, int, void *); - void *data; - - RB_ENTRY(poll_item) entry; -}; -RB_HEAD(poll_items, poll_item) poll_items; - -int server_poll_cmp(struct poll_item *, struct poll_item *); -struct poll_item*server_poll_lookup(int); -struct pollfd *server_poll_flatten(int *); -void server_poll_dispatch(struct pollfd *, int); -void server_poll_reset(void); -RB_PROTOTYPE(poll_items, poll_item, entry, server_poll_cmp); -RB_GENERATE(poll_items, poll_item, entry, server_poll_cmp); - int server_create_socket(void); -void server_callback(int, int, void *); -int server_main(int); -void server_shutdown(void); +void server_loop(void); int server_should_shutdown(void); -void server_child_signal(void); +void server_send_shutdown(void); void server_clean_dead(void); -void server_second_timers(void); +int server_update_socket(void); +void server_accept_callback(int, short, void *); +void server_signal_callback(int, short, void *); +void server_child_signal(void); +void server_child_exited(pid_t, int); +void server_child_stopped(pid_t, int); +void server_second_callback(int, short, void *); void server_lock_server(void); void server_lock_sessions(void); -int server_update_socket(void); -int -server_poll_cmp(struct poll_item *pitem1, struct poll_item *pitem2) -{ - return (pitem1->fd - pitem2->fd); -} - -void -server_poll_add(int fd, int events, void (*fn)(int, int, void *), void *data) -{ - struct poll_item *pitem; - - pitem = xmalloc(sizeof *pitem); - pitem->fd = fd; - pitem->events = events; - - pitem->fn = fn; - pitem->data = data; - - RB_INSERT(poll_items, &poll_items, pitem); -} - -struct poll_item * -server_poll_lookup(int fd) -{ - struct poll_item pitem; - - pitem.fd = fd; - return (RB_FIND(poll_items, &poll_items, &pitem)); -} - -struct pollfd * -server_poll_flatten(int *nfds) -{ - struct poll_item *pitem; - struct pollfd *pfds; - - pfds = NULL; - *nfds = 0; - RB_FOREACH(pitem, poll_items, &poll_items) { - pfds = xrealloc(pfds, (*nfds) + 1, sizeof *pfds); - pfds[*nfds].fd = pitem->fd; - pfds[*nfds].events = pitem->events; - (*nfds)++; - } - return (pfds); -} - -void -server_poll_dispatch(struct pollfd *pfds, int nfds) -{ - struct poll_item *pitem; - struct pollfd *pfd; - - while (nfds > 0) { - pfd = &pfds[--nfds]; - if (pfd->revents != 0) { - pitem = server_poll_lookup(pfd->fd); - pitem->fn(pitem->fd, pfd->revents, pitem->data); - } - } - xfree(pfds); -} - -void -server_poll_reset(void) -{ - struct poll_item *pitem; - - while (!RB_EMPTY(&poll_items)) { - pitem = RB_ROOT(&poll_items); - RB_REMOVE(poll_items, &poll_items, pitem); - xfree(pitem); - } -} - /* Create server socket. */ int server_create_socket(void) @@ -191,40 +108,14 @@ return (fd); } -/* Callback for server socket. */ -void -server_callback(int fd, int events, unused void *data) -{ - struct sockaddr_storage sa; - socklen_t slen = sizeof sa; - int newfd; - - if (events & (POLLERR|POLLNVAL|POLLHUP)) - fatalx("lost server socket"); - if (!(events & POLLIN)) - return; - - newfd = accept(fd, (struct sockaddr *) &sa, &slen); - if (newfd == -1) { - if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED) - return; - fatal("accept failed"); - } - if (sigterm) { - close(newfd); - return; - } - server_client_create(newfd); -} - /* Fork new server. */ int server_start(char *path) { struct client *c; - int pair[2], srv_fd; - char *cause; - char rpathbuf[MAXPATHLEN]; + int pair[2]; + char *cause, rpathbuf[MAXPATHLEN]; + struct timeval tv; /* The first client is special and gets a socketpair; create it. */ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) @@ -269,7 +160,7 @@ log_debug("socket path %s", socket_path); setproctitle("server (%s)", rpathbuf); - srv_fd = server_create_socket(); + server_fd = server_create_socket(); server_client_create(pair[1]); if (access(SYSTEM_CFG, R_OK) == 0) { @@ -282,8 +173,21 @@ if (cfg_file != NULL && load_cfg(cfg_file, NULL, &cause) != 0) goto error; - exit(server_main(srv_fd)); + event_init(); + event_set(&server_ev_accept, + server_fd, EV_READ|EV_PERSIST, server_accept_callback, NULL); + event_add(&server_ev_accept, NULL); + + memset(&tv, 0, sizeof tv); + tv.tv_sec = 1; + evtimer_set(&server_ev_second, server_second_callback, NULL); + evtimer_add(&server_ev_second, &tv); + + server_signal_set(); + server_loop(); + exit(0); + error: /* Write the error and shutdown the server. */ c = ARRAY_FIRST(&clients); @@ -291,127 +195,65 @@ server_write_error(c, cause); xfree(cause); - sigterm = 1; - server_shutdown(); + server_shutdown = 1; - exit(server_main(srv_fd)); + server_signal_set(); + server_loop(); + exit(1); } /* Main server loop. */ -int -server_main(int srv_fd) +void +server_loop(void) { - struct pollfd *pfds; - int nfds, xtimeout; - u_int i; - time_t now, last; + struct timeval tv; - siginit(); - log_debug("server socket is %d", srv_fd); + memset(&tv, 0, sizeof tv); + tv.tv_usec = POLL_TIMEOUT * 1000; - last = time(NULL); + while (!server_should_shutdown()) { + server_update_socket(); - pfds = NULL; - for (;;) { - /* If sigterm, kill all windows and clients. */ - if (sigterm) - server_shutdown(); - - /* Stop if no sessions or clients left. */ - if (server_should_shutdown()) - break; - - /* Handle child exit. */ - if (sigchld) { - sigchld = 0; - server_child_signal(); - continue; - } - - /* Recreate socket on SIGUSR1. */ - if (sigusr1) { - sigusr1 = 0; - close(srv_fd); - srv_fd = server_create_socket(); - continue; - } - - /* Initialise pollfd array and add server socket. */ - server_poll_reset(); - server_poll_add(srv_fd, POLLIN, server_callback, NULL); - - /* Fill window and client sockets. */ server_job_prepare(); server_window_prepare(); server_client_prepare(); - - /* Update socket permissions. */ - xtimeout = INFTIM; - if (server_update_socket() != 0) - xtimeout = POLL_TIMEOUT; - /* Do the poll. */ - pfds = server_poll_flatten(&nfds); - if (poll(pfds, nfds, xtimeout) == -1) { - if (errno == EAGAIN || errno == EINTR) - continue; - fatal("poll failed"); - } - server_poll_dispatch(pfds, nfds); + event_loopexit(&tv); + event_loop(EVLOOP_ONCE); - /* Call second-based timers. */ - now = time(NULL); - if (now != last) { - last = now; - server_second_timers(); - } - - /* Run once-per-loop events. */ server_job_loop(); server_window_loop(); server_client_loop(); - /* Collect any unset key bindings. */ key_bindings_clean(); - - /* Collect dead clients and sessions. */ server_clean_dead(); - } - server_poll_reset(); + } +} +/* Check if the server should be shutting down (no more clients or windows). */ +int +server_should_shutdown(void) +{ + u_int i; + for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { if (ARRAY_ITEM(&sessions, i) != NULL) - session_destroy(ARRAY_ITEM(&sessions, i)); + return (0); } - ARRAY_FREE(&sessions); - for (i = 0; i < ARRAY_LENGTH(&clients); i++) { if (ARRAY_ITEM(&clients, i) != NULL) - server_client_lost(ARRAY_ITEM(&clients, i)); + return (0); } - ARRAY_FREE(&clients); - - mode_key_free_trees(); - key_bindings_free(); - - close(srv_fd); - - unlink(socket_path); - xfree(socket_path); - - options_free(&global_s_options); - options_free(&global_w_options); - - return (0); + return (1); } -/* Kill all clients. */ +/* Shutdown the server by killing all clients and windows. */ void -server_shutdown(void) +server_send_shutdown(void) { - struct session *s; struct client *c; - u_int i, j; + struct session *s; + u_int i; for (i = 0; i < ARRAY_LENGTH(&clients); i++) { c = ARRAY_ITEM(&clients, i); @@ -420,50 +262,175 @@ server_client_lost(c); else server_write_client(c, MSG_SHUTDOWN, NULL, 0); + c->session = NULL; } } for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { - s = ARRAY_ITEM(&sessions, i); - for (j = 0; j < ARRAY_LENGTH(&clients); j++) { - c = ARRAY_ITEM(&clients, j); - if (c != NULL && c->session == s) { - s = NULL; - break; - } - } - if (s != NULL) + if ((s = ARRAY_ITEM(&sessions, i)) != NULL) session_destroy(s); } } -/* Check if the server should be shutting down (no more clients or windows). */ +/* Free dead, unreferenced clients and sessions. */ +void +server_clean_dead(void) +{ + struct session *s; + struct client *c; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&dead_sessions); i++) { + s = ARRAY_ITEM(&dead_sessions, i); + if (s == NULL || s->references != 0) + continue; + ARRAY_SET(&dead_sessions, i, NULL); + xfree(s); + } + + for (i = 0; i < ARRAY_LENGTH(&dead_clients); i++) { + c = ARRAY_ITEM(&dead_clients, i); + if (c == NULL || c->references != 0) + continue; + ARRAY_SET(&dead_clients, i, NULL); + xfree(c); + } +} + +/* Update socket execute permissions based on whether sessions are attached. */ int -server_should_shutdown(void) +server_update_socket(void) { - u_int i; + struct session *s; + u_int i; + static int last = -1; + int n; + n = 0; for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { - if (ARRAY_ITEM(&sessions, i) != NULL) - return (0); + s = ARRAY_ITEM(&sessions, i); + if (s != NULL && !(s->flags & SESSION_UNATTACHED)) { + n++; + break; + } } - for (i = 0; i < ARRAY_LENGTH(&clients); i++) { - if (ARRAY_ITEM(&clients, i) != NULL) - return (0); + + if (n != last) { + last = n; + if (n != 0) + chmod(socket_path, S_IRWXU); + else + chmod(socket_path, S_IRUSR|S_IWUSR); } - return (1); + + return (n); } +/* Callback for server socket. */ +void +server_accept_callback(int fd, short events, unused void *data) +{ + struct sockaddr_storage sa; + socklen_t slen = sizeof sa; + int newfd; + + if (!(events & EV_READ)) + return; + + newfd = accept(fd, (struct sockaddr *) &sa, &slen); + if (newfd == -1) { + if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED) + return; + fatal("accept failed"); + } + if (server_shutdown) { + close(newfd); + return; + } + server_client_create(newfd); + +} + +/* Set up server signal handling. */ +void +server_signal_set(void) +{ + struct sigaction sigact; + + memset(&sigact, 0, sizeof sigact); + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_RESTART; + sigact.sa_handler = SIG_IGN; + if (sigaction(SIGINT, &sigact, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGPIPE, &sigact, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGUSR2, &sigact, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGTSTP, &sigact, NULL) != 0) + fatal("sigaction failed"); + + signal_set(&server_ev_sigchld, SIGCHLD, server_signal_callback, NULL); + signal_add(&server_ev_sigchld, NULL); + signal_set(&server_ev_sigterm, SIGTERM, server_signal_callback, NULL); + signal_add(&server_ev_sigterm, NULL); + signal_set(&server_ev_sigusr1, SIGUSR1, server_signal_callback, NULL); + signal_add(&server_ev_sigusr1, NULL); +} + +/* Destroy server signal events. */ +void +server_signal_clear(void) +{ + struct sigaction sigact; + + memset(&sigact, 0, sizeof sigact); + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_RESTART; + sigact.sa_handler = SIG_DFL; + if (sigaction(SIGINT, &sigact, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGPIPE, &sigact, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGUSR2, &sigact, NULL) != 0) + fatal("sigaction failed"); + if (sigaction(SIGTSTP, &sigact, NULL) != 0) + fatal("sigaction failed"); + + signal_del(&server_ev_sigchld); + signal_del(&server_ev_sigterm); + signal_del(&server_ev_sigusr1); +} + +/* Signal handler. */ +void +server_signal_callback(int sig, unused short events, unused void *data) +{ + switch (sig) { + case SIGTERM: + server_shutdown = 1; + server_send_shutdown(); + break; + case SIGCHLD: + server_child_signal(); + break; + case SIGUSR1: + event_del(&server_ev_accept); + close(server_fd); + server_fd = server_create_socket(); + event_set(&server_ev_accept, server_fd, + EV_READ|EV_PERSIST, server_accept_callback, NULL); + event_add(&server_ev_accept, NULL); + break; + } +} + /* Handle SIGCHLD. */ void server_child_signal(void) { - struct window *w; - struct window_pane *wp; - struct job *job; - int status; - pid_t pid; - u_int i; + int status; + pid_t pid; for (;;) { switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) { @@ -474,63 +441,71 @@ case 0: return; } - if (!WIFSTOPPED(status)) { - SLIST_FOREACH(job, &all_jobs, lentry) { - if (pid == job->pid) { - job->pid = -1; - job->status = status; - } - } + if (WIFSTOPPED(status)) + server_child_stopped(pid, status); + else if (WIFEXITED(status)) + server_child_exited(pid, status); + } +} + +/* Handle exited children. */ +void +server_child_exited(pid_t pid, int status) +{ + struct window *w; + struct window_pane *wp; + struct job *job; + u_int i; + + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + if ((w = ARRAY_ITEM(&windows, i)) == NULL) continue; + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->pid == pid) { + close(wp->fd); + wp->fd = -1; + } } - if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) - continue; + } - for (i = 0; i < ARRAY_LENGTH(&windows); i++) { - w = ARRAY_ITEM(&windows, i); - if (w == NULL) - continue; - TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp->pid == pid) { - if (killpg(pid, SIGCONT) != 0) - kill(pid, SIGCONT); - } - } + SLIST_FOREACH(job, &all_jobs, lentry) { + if (pid == job->pid) { + job->pid = -1; + job->status = status; } } } -/* Free dead, unreferenced clients and sessions. */ +/* Handle stopped children. */ void -server_clean_dead(void) +server_child_stopped(pid_t pid, int status) { - struct session *s; - struct client *c; - u_int i; + struct window *w; + struct window_pane *wp; + u_int i; - for (i = 0; i < ARRAY_LENGTH(&dead_sessions); i++) { - s = ARRAY_ITEM(&dead_sessions, i); - if (s == NULL || s->references != 0) - continue; - ARRAY_SET(&dead_sessions, i, NULL); - xfree(s); - } + if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU) + return; - for (i = 0; i < ARRAY_LENGTH(&dead_clients); i++) { - c = ARRAY_ITEM(&dead_clients, i); - if (c == NULL || c->references != 0) + for (i = 0; i < ARRAY_LENGTH(&windows); i++) { + if ((w = ARRAY_ITEM(&windows, i)) == NULL) continue; - ARRAY_SET(&dead_clients, i, NULL); - xfree(c); + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->pid == pid) { + if (killpg(pid, SIGCONT) != 0) + kill(pid, SIGCONT); + } + } } } -/* Call any once-per-second timers. */ +/* Handle once-per-second timer events. */ void -server_second_timers(void) +server_second_callback(unused int fd, unused short events, unused void *arg) { struct window *w; struct window_pane *wp; + struct timeval tv; u_int i; if (options_get_number(&global_s_options, "lock-server")) @@ -548,6 +523,11 @@ wp->mode->timer(wp); } } + + evtimer_del(&server_ev_second); + memset(&tv, 0, sizeof tv); + tv.tv_sec = 1; + evtimer_add(&server_ev_second, &tv); } /* Lock the server if ALL sessions have hit the time limit. */ @@ -605,33 +585,4 @@ recalculate_sizes(); } } -} - -/* Update socket execute permissions based on whether sessions are attached. */ -int -server_update_socket(void) -{ - struct session *s; - u_int i; - static int last = -1; - int n; - - n = 0; - for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { - s = ARRAY_ITEM(&sessions, i); - if (s != NULL && !(s->flags & SESSION_UNATTACHED)) { - n++; - break; - } - } - - if (n != last) { - last = n; - if (n != 0) - chmod(socket_path, S_IRWXU); - else - chmod(socket_path, S_IRUSR|S_IWUSR); - } - - return (n); }