[BACK]Return to server.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / tmux

File: [local] / src / usr.bin / tmux / server.c (download)

Revision 1.60, Thu Oct 22 19:41:51 2009 UTC (14 years, 7 months ago) by nicm
Branch: MAIN
Changes since 1.59: +122 -878 lines

Split the server code handling clients, jobs and windows off into separate
files from server.c (merging server-msg.c into the client file) and rather than
iterating over each set after poll(), allow a callback to be specified when the
fd is added and just walk once over the returned pollfds calling each callback
where needed.

More to come, getting this in so it is tested.

/* $OpenBSD: server.c,v 1.60 2009/10/22 19:41:51 nicm Exp $ */

/*
 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
 *
 * 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 <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>

#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#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) {
			server_child_signal();
			sigchld = 0;
		}

		/* Recreate socket on SIGUSR1. */
		if (sigusr1) {
			close(srv_fd);
			srv_fd = server_create_socket();
			sigusr1 = 0;
		}

		/* 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);
}