summaryrefslogtreecommitdiffstats
path: root/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'server.c')
-rw-r--r--server.c575
1 files changed, 263 insertions, 312 deletions
diff --git a/server.c b/server.c
index 1113d4dc..236cd201 100644
--- a/server.c
+++ b/server.c
@@ -24,6 +24,7 @@
#include <sys/wait.h>
#include <errno.h>
+#include <event.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
@@ -45,112 +46,28 @@
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;
-
- 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_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;
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
@@ -191,40 +108,14 @@ server_create_socket(void)
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 @@ server_start(char *path)
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,7 +173,20 @@ server_start(char *path)
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. */
@@ -291,127 +195,65 @@ error:
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;
-
- siginit();
- log_debug("server socket is %d", srv_fd);
+ struct timeval tv;
- last = time(NULL);
+ memset(&tv, 0, sizeof tv);
+ tv.tv_usec = POLL_TIMEOUT * 1000;
- pfds = NULL;
- for (;;) {
- /* If sigterm, kill all windows and clients. */
- if (sigterm)
- server_shutdown();
+ while (!server_should_shutdown()) {
+ server_update_socket();
- /* 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);
- /* Call second-based timers. */
- now = time(NULL);
- if (now != last) {
- last = now;
- server_second_timers();
- }
+ event_loopexit(&tv);
+ event_loop(EVLOOP_ONCE);
- /* 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_shutdown(void)
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 (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;
}
- return (1);
}
/* 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 @@ server_child_signal(void)
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 @@ server_second_timers(void)
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. */
@@ -606,32 +586,3 @@ server_lock_sessions(void)
}
}
}
-
-/* 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);
-}