/* $OpenBSD: server.c,v 1.61 2009/10/26 21:38:18 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tmux.h" /* * Main server functions. */ /* Client list. */ 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); void server_poll_add(int, int, void (*)(int, int, void *), void *); 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); int server_should_shutdown(void); void server_child_signal(void); void server_fill_windows(void); void server_fill_clients(void); void server_fill_jobs(void); void server_clean_dead(void); void server_second_timers(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) { struct sockaddr_un sa; size_t size; mode_t mask; int fd, mode; memset(&sa, 0, sizeof sa); sa.sun_family = AF_UNIX; size = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path); if (size >= sizeof sa.sun_path) { errno = ENAMETOOLONG; fatal("socket failed"); } unlink(sa.sun_path); if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) fatal("socket failed"); mask = umask(S_IXUSR|S_IRWXG|S_IRWXO); if (bind(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1) fatal("bind failed"); umask(mask); if (listen(fd, 16) == -1) fatal("listen failed"); if ((mode = fcntl(fd, F_GETFL)) == -1) fatal("fcntl failed"); if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) fatal("fcntl failed"); if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) fatal("fcntl failed"); 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]; /* The first client is special and gets a socketpair; create it. */ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) fatal("socketpair failed"); switch (fork()) { case -1: fatal("fork failed"); case 0: break; default: close(pair[1]); return (pair[0]); } close(pair[0]); /* * Must daemonise before loading configuration as the PID changes so * $TMUX would be wrong for sessions created in the config file. */ if (daemon(1, 0) != 0) fatal("daemon failed"); logfile("server"); log_debug("server started, pid %ld", (long) getpid()); ARRAY_INIT(&windows); ARRAY_INIT(&clients); ARRAY_INIT(&dead_clients); ARRAY_INIT(&sessions); ARRAY_INIT(&dead_sessions); TAILQ_INIT(&session_groups); mode_key_init_trees(); key_bindings_init(); utf8_build(); start_time = time(NULL); socket_path = path; if (realpath(socket_path, rpathbuf) == NULL) strlcpy(rpathbuf, socket_path, sizeof rpathbuf); log_debug("socket path %s", socket_path); setproctitle("server (%s)", rpathbuf); srv_fd = server_create_socket(); server_client_create(pair[1]); if (access(SYSTEM_CFG, R_OK) != 0) { if (errno != ENOENT) { xasprintf( &cause, "%s: %s", strerror(errno), SYSTEM_CFG); goto error; } } else if (load_cfg(SYSTEM_CFG, NULL, &cause) != 0) goto error; if (cfg_file != NULL && load_cfg(cfg_file, NULL, &cause) != 0) goto error; exit(server_main(srv_fd)); error: /* Write the error and shutdown the server. */ c = ARRAY_FIRST(&clients); server_write_error(c, cause); xfree(cause); sigterm = 1; server_shutdown(); exit(server_main(srv_fd)); } /* Main server loop. */ int server_main(int srv_fd) { struct pollfd *pfds; int nfds, xtimeout; u_int i; time_t now, last; siginit(); log_debug("server socket is %d", srv_fd); last = time(NULL); 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_fill_jobs(); server_fill_windows(); server_fill_clients(); /* 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(); } /* 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(); for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { if (ARRAY_ITEM(&sessions, i) != NULL) session_destroy(ARRAY_ITEM(&sessions, i)); } ARRAY_FREE(&sessions); for (i = 0; i < ARRAY_LENGTH(&clients); i++) { if (ARRAY_ITEM(&clients, i) != NULL) server_client_lost(ARRAY_ITEM(&clients, i)); } 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); } /* Kill all clients. */ void server_shutdown(void) { struct session *s; struct client *c; u_int i, j; for (i = 0; i < ARRAY_LENGTH(&clients); i++) { c = ARRAY_ITEM(&clients, i); if (c != NULL) { if (c->flags & (CLIENT_BAD|CLIENT_SUSPENDED)) server_client_lost(c); else server_write_client(c, MSG_SHUTDOWN, NULL, 0); } } 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) session_destroy(s); } } /* 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) return (0); } for (i = 0; i < ARRAY_LENGTH(&clients); i++) { if (ARRAY_ITEM(&clients, i) != NULL) return (0); } 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; for (;;) { switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) { case -1: if (errno == ECHILD) return; fatal("waitpid failed"); case 0: return; } if (!WIFSTOPPED(status)) { SLIST_FOREACH(job, &all_jobs, lentry) { if (pid == job->pid) { job->pid = -1; job->status = status; } } continue; } 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); } } } } } /* Fill window pollfds. */ void server_fill_windows(void) { struct window *w; struct window_pane *wp; u_int i; int events; 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->fd == -1) continue; events = POLLIN; if (BUFFER_USED(wp->out) > 0) events |= POLLOUT; server_poll_add( wp->fd, events, server_window_callback, wp); if (wp->pipe_fd == -1) continue; events = 0; if (BUFFER_USED(wp->pipe_buf) > 0) events |= POLLOUT; server_poll_add( wp->pipe_fd, events, server_window_callback, wp); } } } /* Fill client pollfds. */ void server_fill_clients(void) { struct client *c; u_int i; int events; for (i = 0; i < ARRAY_LENGTH(&clients); i++) { c = ARRAY_ITEM(&clients, i); if (c != NULL) { events = 0; if (!(c->flags & CLIENT_BAD)) events |= POLLIN; if (c->ibuf.w.queued > 0) events |= POLLOUT; server_poll_add( c->ibuf.fd, events, server_client_callback, c); } if (c != NULL && !(c->flags & CLIENT_SUSPENDED) && c->tty.fd != -1 && c->session != NULL) { events = POLLIN; if (BUFFER_USED(c->tty.out) > 0) events |= POLLOUT; server_poll_add( c->tty.fd, events, server_client_callback, c); } } } /* Fill in job fds. */ void server_fill_jobs(void) { struct job *job; SLIST_FOREACH(job, &all_jobs, lentry) { if (job->fd == -1) continue; server_poll_add(job->fd, POLLIN, server_job_callback, job); } } /* 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); } } /* Call any once-per-second timers. */ void server_second_timers(void) { struct window *w; struct window_pane *wp; u_int i; if (options_get_number(&global_s_options, "lock-server")) server_lock_server(); else server_lock_sessions(); 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->mode != NULL && wp->mode->timer != NULL) wp->mode->timer(wp); } } } /* Lock the server if ALL sessions have hit the time limit. */ void server_lock_server(void) { struct session *s; u_int i; int timeout; time_t t; t = time(NULL); for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { if ((s = ARRAY_ITEM(&sessions, i)) == NULL) continue; if (s->flags & SESSION_UNATTACHED) { s->activity = time(NULL); continue; } timeout = options_get_number(&s->options, "lock-after-time"); if (timeout <= 0 || t <= s->activity + timeout) return; /* not timed out */ } server_lock(); recalculate_sizes(); } /* Lock any sessions which have timed out. */ void server_lock_sessions(void) { struct session *s; u_int i; int timeout; time_t t; t = time(NULL); for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { if ((s = ARRAY_ITEM(&sessions, i)) == NULL) continue; if (s->flags & SESSION_UNATTACHED) { s->activity = time(NULL); continue; } timeout = options_get_number(&s->options, "lock-after-time"); if (timeout > 0 && t > s->activity + timeout) { server_lock_session(s); 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); }