Initial changes to move tmux to libevent.

This moves the client-side loops are pretty much fully over to event-based only
(tmux.c and client.c) but server-side (server.c and friends) treats libevent as
a sort of clever poll, waking up after every event to run various things.

Moving the server stuff over to bufferevents and timers and so on will come
later.
pull/1/head
Nicholas Marriott 2009-11-04 20:50:11 +00:00
parent f575e39b0a
commit abf3a5d50e
13 changed files with 601 additions and 632 deletions

View File

@ -45,7 +45,7 @@ CDIAGFLAGS+= -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations
CDIAGFLAGS+= -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare
CDIAGFLAGS+= -Wundef -Wbad-function-cast -Winline -Wcast-align
LDADD= -lutil -lcurses
LDADD= -lutil -lcurses -levent
DPADD= ${LIBUTIL}
.include <bsd.prog.mk>

View File

@ -19,6 +19,7 @@
#include <sys/types.h>
#include <errno.h>
#include <event.h>
#include <unistd.h>
#include "tmux.h"
@ -29,9 +30,7 @@ buffer_poll(int fd, int events, struct buffer *in, struct buffer *out)
{
ssize_t n;
if (events & (POLLERR|POLLNVAL))
return (-1);
if (in != NULL && events & POLLIN) {
if (in != NULL && events & EV_READ) {
buffer_ensure(in, BUFSIZ);
n = read(fd, BUFFER_IN(in), BUFFER_FREE(in));
if (n == 0)
@ -41,9 +40,8 @@ buffer_poll(int fd, int events, struct buffer *in, struct buffer *out)
return (-1);
} else
buffer_add(in, n);
} else if (events & POLLHUP)
return (-1);
if (out != NULL && BUFFER_USED(out) > 0 && events & POLLOUT) {
}
if (out != NULL && BUFFER_USED(out) > 0 && events & EV_WRITE) {
n = write(fd, BUFFER_OUT(out), BUFFER_USED(out));
if (n == -1) {
if (errno != EINTR && errno != EAGAIN)

214
client.c
View File

@ -24,6 +24,7 @@
#include <sys/wait.h>
#include <errno.h>
#include <event.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdlib.h>
@ -34,13 +35,15 @@
#include "tmux.h"
struct imsgbuf client_ibuf;
struct event client_event;
const char *client_exitmsg;
void client_send_identify(int);
void client_send_environ(void);
void client_write_server(enum msgtype, void *, size_t);
int client_dispatch(void);
void client_suspend(void);
void client_send_identify(int);
void client_send_environ(void);
void client_write_server(enum msgtype, void *, size_t);
void client_signal(int, short, void *);
void client_callback(int, short, void *);
int client_dispatch(void);
struct imsgbuf *
client_init(char *path, int cmdflags, int flags)
@ -154,76 +157,54 @@ client_write_server(enum msgtype type, void *buf, size_t len)
__dead void
client_main(void)
{
struct pollfd pfd;
int n, nfds;
siginit();
struct event ev_sigcont, ev_sigterm, ev_sigwinch;
struct sigaction sigact;
short events;
logfile("client");
/* Note: event_init() has already been called. */
/* Set up signals. */
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(SIGUSR1, &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(&ev_sigcont, SIGCONT, client_signal, NULL);
signal_add(&ev_sigcont, NULL);
signal_set(&ev_sigterm, SIGTERM, client_signal, NULL);
signal_add(&ev_sigterm, NULL);
signal_set(&ev_sigwinch, SIGWINCH, client_signal, NULL);
signal_add(&ev_sigwinch, NULL);
/*
* imsg_read in the first client poll loop (before the terminal has
* been initialiased) may have read messages into the buffer after the
* MSG_READY switched to here. Process anything outstanding now so poll
* doesn't hang waiting for messages that have already arrived.
* been initialised) may have read messages into the buffer after the
* MSG_READY switched to here. Process anything outstanding now to
* avoid hanging waiting for messages that have already arrived.
*/
if (client_dispatch() != 0)
goto out;
for (;;) {
if (sigterm) {
client_exitmsg = "terminated";
client_write_server(MSG_EXITING, NULL, 0);
}
if (sigchld) {
sigchld = 0;
waitpid(WAIT_ANY, NULL, WNOHANG);
continue;
}
if (sigwinch) {
sigwinch = 0;
client_write_server(MSG_RESIZE, NULL, 0);
continue;
}
if (sigcont) {
sigcont = 0;
siginit();
client_write_server(MSG_WAKEUP, NULL, 0);
continue;
}
pfd.fd = client_ibuf.fd;
pfd.events = POLLIN;
if (client_ibuf.w.queued > 0)
pfd.events |= POLLOUT;
if ((nfds = poll(&pfd, 1, INFTIM)) == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
fatal("poll failed");
}
if (nfds == 0)
continue;
if (pfd.revents & (POLLERR|POLLHUP|POLLNVAL))
fatalx("socket error");
if (pfd.revents & POLLIN) {
if ((n = imsg_read(&client_ibuf)) == -1 || n == 0) {
client_exitmsg = "lost server";
break;
}
if (client_dispatch() != 0)
break;
}
if (pfd.revents & POLLOUT) {
if (msgbuf_write(&client_ibuf.w) < 0) {
client_exitmsg = "lost server";
break;
}
}
}
/* Set up the client-server socket event. */
events = EV_READ;
if (client_ibuf.w.queued > 0)
events |= EV_WRITE;
event_set(&client_event, client_ibuf.fd, events, client_callback, NULL);
event_add(&client_event, NULL);
event_dispatch();
out:
/* Print the exit message, if any, and exit. */
@ -235,12 +216,78 @@ out:
exit(0);
}
void
client_signal(int sig, short events, unused void *data)
{
struct sigaction sigact;
switch (sig) {
case SIGTERM:
client_exitmsg = "terminated";
client_write_server(MSG_EXITING, NULL, 0);
break;
case SIGWINCH:
client_write_server(MSG_RESIZE, NULL, 0);
break;
case SIGCONT:
memset(&sigact, 0, sizeof sigact);
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_RESTART;
sigact.sa_handler = SIG_IGN;
if (sigaction(SIGTSTP, &sigact, NULL) != 0)
fatal("sigaction failed");
client_write_server(MSG_WAKEUP, NULL, 0);
break;
}
event_del(&client_event);
events = EV_READ;
if (client_ibuf.w.queued > 0)
events |= EV_WRITE;
event_set(&client_event, client_ibuf.fd, events, client_callback, NULL);
event_add(&client_event, NULL);
}
void
client_callback(unused int fd, short events, unused void *data)
{
int n;
if (events & EV_READ) {
if ((n = imsg_read(&client_ibuf)) == -1 || n == 0)
goto lost_server;
if (client_dispatch() != 0) {
event_loopexit(NULL);
return;
}
}
if (events & EV_WRITE) {
if (msgbuf_write(&client_ibuf.w) < 0)
goto lost_server;
}
event_del(&client_event);
events = EV_READ;
if (client_ibuf.w.queued > 0)
events |= EV_WRITE;
event_set(&client_event, client_ibuf.fd, events, client_callback, NULL);
event_add(&client_event, NULL);
return;
lost_server:
client_exitmsg = "lost server";
event_loopexit(NULL);
}
int
client_dispatch(void)
{
struct imsg imsg;
struct msg_lock_data lockdata;
ssize_t n, datalen;
struct imsg imsg;
struct msg_lock_data lockdata;
struct sigaction sigact;
ssize_t n, datalen;
for (;;) {
if ((n = imsg_get(&client_ibuf, &imsg)) == -1)
@ -249,6 +296,7 @@ client_dispatch(void)
return (0);
datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
log_debug("client got %d", imsg.hdr.type);
switch (imsg.hdr.type) {
case MSG_DETACH:
if (datalen != 0)
@ -281,7 +329,13 @@ client_dispatch(void)
if (datalen != 0)
fatalx("bad MSG_SUSPEND size");
client_suspend();
memset(&sigact, 0, sizeof sigact);
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_RESTART;
sigact.sa_handler = SIG_DFL;
if (sigaction(SIGTSTP, &sigact, NULL) != 0)
fatal("sigaction failed");
kill(getpid(), SIGTSTP);
break;
case MSG_LOCK:
if (datalen != sizeof lockdata)
@ -299,23 +353,3 @@ client_dispatch(void)
imsg_free(&imsg);
}
}
void
client_suspend(void)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART;
act.sa_handler = SIG_DFL;
if (sigaction(SIGTSTP, &act, NULL) != 0)
fatal("sigaction failed");
act.sa_handler = sighandler;
if (sigaction(SIGCONT, &act, NULL) != 0)
fatal("sigaction failed");
kill(getpid(), SIGTSTP);
}

View File

@ -43,7 +43,7 @@ const struct cmd_entry cmd_kill_server_entry = {
int
cmd_kill_server_exec(unused struct cmd *self, unused struct cmd_ctx *ctx)
{
sigterm = 1;
kill(getpid(), SIGTERM);
return (0);
}

View File

@ -88,7 +88,7 @@ cmd_pipe_pane_exec(struct cmd *self, struct cmd_ctx *ctx)
case 0:
/* Child process. */
close(pipe_fd[0]);
sigreset();
server_signal_clear();
if (dup2(pipe_fd[1], STDIN_FILENO) == -1)
_exit(1);

4
job.c
View File

@ -87,6 +87,7 @@ job_add(struct jobs *jobs, int flags, struct client *c, const char *cmd,
job->fd = -1;
job->out = buffer_create(BUFSIZ);
memset(&job->event, 0, sizeof job->event);
job->callbackfn = callbackfn;
job->freefn = freefn;
@ -126,6 +127,7 @@ job_free(struct job *job)
close(job->fd);
if (job->out != NULL)
buffer_destroy(job->out);
event_del(&job->event);
xfree(job);
}
@ -147,7 +149,7 @@ job_run(struct job *job)
case -1:
return (-1);
case 0: /* child */
sigreset();
server_signal_clear();
/* XXX environ? */
if (dup2(out[1], STDOUT_FILENO) == -1)

View File

@ -18,6 +18,7 @@
#include <sys/types.h>
#include <event.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
@ -134,7 +135,9 @@ server_client_lost(struct client *c)
close(c->ibuf.fd);
imsg_clear(&c->ibuf);
event_del(&c->event);
event_del(&c->tty.event);
for (i = 0; i < ARRAY_LENGTH(&dead_clients); i++) {
if (ARRAY_ITEM(&dead_clients, i) == NULL) {
ARRAY_SET(&dead_clients, i, c);
@ -162,25 +165,31 @@ server_client_prepare(void)
events = 0;
if (!(c->flags & CLIENT_BAD))
events |= POLLIN;
events |= EV_READ;
if (c->ibuf.w.queued > 0)
events |= POLLOUT;
server_poll_add(c->ibuf.fd, events, server_client_callback, c);
events |= EV_WRITE;
event_del(&c->event);
event_set(&c->event,
c->ibuf.fd, events, server_client_callback, c);
event_add(&c->event, NULL);
if (c->tty.fd == -1)
continue;
if (c->flags & CLIENT_SUSPENDED || c->session == NULL)
continue;
events = POLLIN;
events = EV_READ;
if (BUFFER_USED(c->tty.out) > 0)
events |= POLLOUT;
server_poll_add(c->tty.fd, events, server_client_callback, c);
events |= EV_WRITE;
event_del(&c->tty.event);
event_set(&c->tty.event,
c->tty.fd, events, server_client_callback, c);
event_add(&c->tty.event, NULL);
}
}
/* Process a single client event. */
void
server_client_callback(int fd, int events, void *data)
server_client_callback(int fd, short events, void *data)
{
struct client *c = data;
@ -188,10 +197,7 @@ server_client_callback(int fd, int events, void *data)
return;
if (fd == c->ibuf.fd) {
if (events & (POLLERR|POLLNVAL|POLLHUP))
goto client_lost;
if (events & POLLOUT && msgbuf_write(&c->ibuf.w) < 0)
if (events & EV_WRITE && msgbuf_write(&c->ibuf.w) < 0)
goto client_lost;
if (c->flags & CLIENT_BAD) {
@ -200,7 +206,7 @@ server_client_callback(int fd, int events, void *data)
return;
}
if (events & POLLIN && server_client_msg_dispatch(c) != 0)
if (events & EV_READ && server_client_msg_dispatch(c) != 0)
goto client_lost;
}
@ -211,7 +217,7 @@ server_client_callback(int fd, int events, void *data)
if (buffer_poll(fd, events, c->tty.in, c->tty.out) != 0)
goto client_lost;
}
return;
client_lost:
@ -424,7 +430,7 @@ server_client_check_redraw(struct client *c)
if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) {
if (options_get_number(&s->options, "set-titles"))
server_client_set_title(c);
if (c->message_string != NULL)
redraw = status_message_redraw(c);
else if (c->prompt_string != NULL)

View File

@ -18,6 +18,7 @@
#include <sys/types.h>
#include <event.h>
#include <unistd.h>
#include "tmux.h"
@ -31,13 +32,16 @@ server_job_prepare(void)
SLIST_FOREACH(job, &all_jobs, lentry) {
if (job->fd == -1)
continue;
server_poll_add(job->fd, POLLIN, server_job_callback, job);
event_del(&job->event);
event_set(
&job->event, job->fd, EV_READ, server_job_callback, job);
event_add(&job->event, NULL);
}
}
/* Process a single job event. */
void
server_job_callback(int fd, int events, void *data)
server_job_callback(int fd, short events, void *data)
{
struct job *job = data;
@ -55,7 +59,7 @@ void
server_job_loop(void)
{
struct job *job;
restart:
SLIST_FOREACH(job, &all_jobs, lentry) {
if (job->flags & JOB_DONE || job->fd != -1 || job->pid != -1)

View File

@ -18,6 +18,7 @@
#include <sys/types.h>
#include <event.h>
#include <unistd.h>
#include "tmux.h"
@ -47,19 +48,23 @@ server_window_prepare(void)
continue;
events = 0;
if (!server_window_backoff(wp))
events |= POLLIN;
events = EV_READ;
if (BUFFER_USED(wp->out) > 0)
events |= POLLOUT;
server_poll_add(
events |= EV_WRITE;
event_del(&wp->event);
event_set(&wp->event,
wp->fd, events, server_window_callback, wp);
event_add(&wp->event, NULL);
if (wp->pipe_fd == -1)
continue;
events = 0;
if (BUFFER_USED(wp->pipe_buf) > 0)
events |= POLLOUT;
server_poll_add(
events |= EV_WRITE;
event_del(&wp->pipe_event);
event_set(&wp->pipe_event,
wp->pipe_fd, events, server_window_callback, wp);
event_add(&wp->pipe_event, NULL);
}
}
}
@ -90,7 +95,7 @@ server_window_backoff(struct window_pane *wp)
/* Process a single window pane event. */
void
server_window_callback(int fd, int events, void *data)
server_window_callback(int fd, short events, void *data)
{
struct window_pane *wp = data;

601
server.c
View File

@ -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,150 +195,39 @@ 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;
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();
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). */
@ -454,49 +247,28 @@ server_should_shutdown(void)
return (1);
}
/* Handle SIGCHLD. */
/* Shutdown the server by killing all clients and windows. */
void
server_child_signal(void)
server_send_shutdown(void)
{
struct window *w;
struct window_pane *wp;
struct job *job;
int status;
pid_t pid;
u_int i;
struct client *c;
struct session *s;
u_int i;
for (;;) {
switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
case -1:
if (errno == ECHILD)
return;
fatal("waitpid failed");
case 0:
return;
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);
c->session = NULL;
}
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);
}
}
}
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
if ((s = ARRAY_ITEM(&sessions, i)) != NULL)
session_destroy(s);
}
}
@ -525,12 +297,215 @@ server_clean_dead(void)
}
}
/* Call any once-per-second timers. */
/* 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);
}
/* Callback for server socket. */
void
server_second_timers(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)
{
int status;
pid_t pid;
for (;;) {
switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
case -1:
if (errno == ECHILD)
return;
fatal("waitpid failed");
case 0:
return;
}
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;
}
}
}
SLIST_FOREACH(job, &all_jobs, lentry) {
if (pid == job->pid) {
job->pid = -1;
job->status = status;
}
}
}
/* Handle stopped children. */
void
server_child_stopped(pid_t pid, int status)
{
struct window *w;
struct window_pane *wp;
u_int i;
if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
return;
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) {
if (killpg(pid, SIGCONT) != 0)
kill(pid, SIGCONT);
}
}
}
}
/* Handle once-per-second timer events. */
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);
}

303
tmux.c
View File

@ -20,6 +20,7 @@
#include <sys/stat.h>
#include <errno.h>
#include <event.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
@ -34,13 +35,6 @@
extern char *malloc_options;
#endif
volatile sig_atomic_t sigwinch;
volatile sig_atomic_t sigterm;
volatile sig_atomic_t sigcont;
volatile sig_atomic_t sigchld;
volatile sig_atomic_t sigusr1;
volatile sig_atomic_t sigusr2;
char *cfg_file;
struct options global_s_options; /* session options */
struct options global_w_options; /* window options */
@ -55,9 +49,18 @@ int login_shell;
__dead void usage(void);
void fill_session(struct msg_command_data *);
char *makesockpath(const char *);
int dispatch_imsg(struct imsgbuf *, const char *, int *);
__dead void shell_exec(const char *, const char *);
struct imsgbuf *main_ibuf;
struct event main_ev_sigterm;
int main_exitval;
void main_set_signals(void);
void main_clear_signals(void);
void main_signal(int, short, unused void *);
void main_callback(int, short, void *);
void main_dispatch(const char *);
__dead void
usage(void)
{
@ -81,96 +84,6 @@ logfile(const char *name)
}
}
void
sighandler(int sig)
{
int saved_errno;
saved_errno = errno;
switch (sig) {
case SIGWINCH:
sigwinch = 1;
break;
case SIGTERM:
sigterm = 1;
break;
case SIGCHLD:
sigchld = 1;
break;
case SIGCONT:
sigcont = 1;
break;
case SIGUSR1:
sigusr1 = 1;
break;
case SIGUSR2:
sigusr2 = 1;
break;
}
errno = saved_errno;
}
void
siginit(void)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART;
act.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGINT, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTSTP, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGQUIT, &act, NULL) != 0)
fatal("sigaction failed");
act.sa_handler = sighandler;
if (sigaction(SIGWINCH, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTERM, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGCHLD, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR1, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR2, &act, NULL) != 0)
fatal("sigaction failed");
}
void
sigreset(void)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = SIG_DFL;
if (sigaction(SIGPIPE, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR1, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR2, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGINT, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTSTP, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGQUIT, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGWINCH, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTERM, &act, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGCHLD, &act, NULL) != 0)
fatal("sigaction failed");
}
const char *
getshell(void)
{
@ -281,23 +194,43 @@ makesockpath(const char *label)
return (path);
}
__dead void
shell_exec(const char *shell, const char *shellcmd)
{
const char *shellname, *ptr;
char *argv0;
ptr = strrchr(shell, '/');
if (ptr != NULL && *(ptr + 1) != '\0')
shellname = ptr + 1;
else
shellname = shell;
if (login_shell)
xasprintf(&argv0, "-%s", shellname);
else
xasprintf(&argv0, "%s", shellname);
setenv("SHELL", shell, 1);
execl(shell, argv0, "-c", shellcmd, (char *) NULL);
fatal("execl failed");
}
int
main(int argc, char **argv)
{
struct cmd_list *cmdlist;
struct cmd *cmd;
struct pollfd pfd;
enum msgtype msg;
struct passwd *pw;
struct options *so, *wo;
struct keylist *keylist;
struct imsgbuf *ibuf;
struct msg_command_data cmddata;
char *s, *shellcmd, *path, *label, *home, *cause;
char cwd[MAXPATHLEN], **var;
void *buf;
size_t len;
int nfds, retcode, opt, flags, cmdflags = 0;
int opt, flags, cmdflags = 0;
short events;
#ifdef DEBUG
malloc_options = (char *) "AFGJPX";
@ -359,7 +292,6 @@ main(int argc, char **argv)
usage();
log_open_tty(debug_level);
siginit();
if (!(flags & IDENTIFY_UTF8)) {
/*
@ -549,63 +481,121 @@ main(int argc, char **argv)
cmd_list_free(cmdlist);
}
if ((ibuf = client_init(path, cmdflags, flags)) == NULL)
if ((main_ibuf = client_init(path, cmdflags, flags)) == NULL)
exit(1);
xfree(path);
imsg_compose(ibuf, msg, PROTOCOL_VERSION, -1, -1, buf, len);
event_init();
retcode = 0;
for (;;) {
pfd.fd = ibuf->fd;
pfd.events = POLLIN;
if (ibuf->w.queued != 0)
pfd.events |= POLLOUT;
imsg_compose(main_ibuf, msg, PROTOCOL_VERSION, -1, -1, buf, len);
if ((nfds = poll(&pfd, 1, INFTIM)) == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
fatal("poll failed");
}
if (nfds == 0)
continue;
main_set_signals();
if (pfd.revents & (POLLERR|POLLHUP|POLLNVAL))
fatalx("socket error");
events = EV_READ;
if (main_ibuf->w.queued > 0)
events |= EV_WRITE;
event_once(main_ibuf->fd, events, main_callback, shellcmd, NULL);
if (pfd.revents & POLLIN) {
if (dispatch_imsg(ibuf, shellcmd, &retcode) != 0)
break;
}
main_exitval = 0;
event_dispatch();
if (pfd.revents & POLLOUT) {
if (msgbuf_write(&ibuf->w) < 0)
fatalx("msgbuf_write failed");
}
}
main_clear_signals();
options_free(&global_s_options);
options_free(&global_w_options);
return (retcode);
client_main(); /* doesn't return */
}
int
dispatch_imsg(struct imsgbuf *ibuf, const char *shellcmd, int *retcode)
void
main_set_signals(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(SIGUSR1, &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(&main_ev_sigterm, SIGTERM, main_signal, NULL);
signal_add(&main_ev_sigterm, NULL);
}
void
main_clear_signals(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(SIGUSR1, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGUSR2, &sigact, NULL) != 0)
fatal("sigaction failed");
if (sigaction(SIGTSTP, &sigact, NULL) != 0)
fatal("sigaction failed");
event_del(&main_ev_sigterm);
}
void
main_signal(int sig, unused short events, unused void *data)
{
switch (sig) {
case SIGTERM:
exit(1);
}
}
void
main_callback(unused int fd, short events, void *data)
{
char *shellcmd = data;
if (events & EV_READ)
main_dispatch(shellcmd);
if (events & EV_WRITE) {
if (msgbuf_write(&main_ibuf->w) < 0)
fatalx("msgbuf_write failed");
}
events = EV_READ;
if (main_ibuf->w.queued > 0)
events |= EV_WRITE;
event_once(main_ibuf->fd, events, main_callback, shellcmd, NULL);
}
void
main_dispatch(const char *shellcmd)
{
struct imsg imsg;
ssize_t n, datalen;
struct msg_print_data printdata;
struct msg_shell_data shelldata;
if ((n = imsg_read(ibuf)) == -1 || n == 0)
if ((n = imsg_read(main_ibuf)) == -1 || n == 0)
fatalx("imsg_read failed");
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
if ((n = imsg_get(main_ibuf, &imsg)) == -1)
fatalx("imsg_get failed");
if (n == 0)
return (0);
return;
datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
switch (imsg.hdr.type) {
@ -614,10 +604,8 @@ dispatch_imsg(struct imsgbuf *ibuf, const char *shellcmd, int *retcode)
if (datalen != 0)
fatalx("bad MSG_EXIT size");
return (-1);
exit(main_exitval);
case MSG_ERROR:
*retcode = 1;
/* FALLTHROUGH */
case MSG_PRINT:
if (datalen != sizeof printdata)
fatalx("bad MSG_PRINT size");
@ -625,26 +613,30 @@ dispatch_imsg(struct imsgbuf *ibuf, const char *shellcmd, int *retcode)
printdata.msg[(sizeof printdata.msg) - 1] = '\0';
log_info("%s", printdata.msg);
if (imsg.hdr.type == MSG_ERROR)
main_exitval = 1;
break;
case MSG_READY:
if (datalen != 0)
fatalx("bad MSG_READY size");
client_main(); /* doesn't return */
event_loopexit(NULL); /* move to client_main() */
break;
case MSG_VERSION:
if (datalen != 0)
fatalx("bad MSG_VERSION size");
log_warnx("protocol version mismatch (client %u, "
"server %u)", PROTOCOL_VERSION, imsg.hdr.peerid);
*retcode = 1;
return (-1);
exit(1);
case MSG_SHELL:
if (datalen != sizeof shelldata)
fatalx("bad MSG_SHELL size");
memcpy(&shelldata, imsg.data, sizeof shelldata);
shelldata.shell[(sizeof shelldata.shell) - 1] = '\0';
main_clear_signals();
shell_exec(shelldata.shell, shellcmd);
default:
fatalx("unexpected message");
@ -653,26 +645,3 @@ dispatch_imsg(struct imsgbuf *ibuf, const char *shellcmd, int *retcode)
imsg_free(&imsg);
}
}
__dead void
shell_exec(const char *shell, const char *shellcmd)
{
const char *shellname, *ptr;
char *argv0;
sigreset();
ptr = strrchr(shell, '/');
if (ptr != NULL && *(ptr + 1) != '\0')
shellname = ptr + 1;
else
shellname = shell;
if (login_shell)
xasprintf(&argv0, "-%s", shellname);
else
xasprintf(&argv0, "%s", shellname);
setenv("SHELL", shell, 1);
execl(shell, argv0, "-c", shellcmd, (char *) NULL);
fatal("execl failed");
}

28
tmux.h
View File

@ -28,9 +28,9 @@
#include <sys/uio.h>
#include <bitstring.h>
#include <event.h>
#include <getopt.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
@ -661,6 +661,7 @@ struct job {
struct client *client;
int fd;
struct event event;
struct buffer *out;
void (*callbackfn)(struct job *);
@ -796,14 +797,17 @@ struct window_pane {
char *cwd;
pid_t pid;
int fd;
char tty[TTY_NAME_MAX];
int fd;
struct event event;
struct buffer *in;
struct buffer *out;
struct input_ctx ictx;
int pipe_fd;
struct event pipe_event;
struct buffer *pipe_buf;
size_t pipe_off;
@ -998,6 +1002,7 @@ struct tty {
struct tty_term *term;
int fd;
struct event event;
struct buffer *in;
struct buffer *out;
@ -1062,6 +1067,7 @@ struct mouse_event {
/* Client connection. */
struct client {
struct imsgbuf ibuf;
struct event event;
struct timeval creation_time;
struct timeval activity_time;
@ -1235,12 +1241,6 @@ extern const struct set_option_entry set_option_table[];
extern const struct set_option_entry set_window_option_table[];
/* tmux.c */
extern volatile sig_atomic_t sigwinch;
extern volatile sig_atomic_t sigterm;
extern volatile sig_atomic_t sigcont;
extern volatile sig_atomic_t sigchld;
extern volatile sig_atomic_t sigusr1;
extern volatile sig_atomic_t sigusr2;
extern struct options global_s_options;
extern struct options global_w_options;
extern struct environ global_environ;
@ -1251,9 +1251,6 @@ extern time_t start_time;
extern char *socket_path;
extern int login_shell;
void logfile(const char *);
void siginit(void);
void sigreset(void);
void sighandler(int);
const char *getshell(void);
int checkshell(const char *);
int areshell(const char *);
@ -1582,23 +1579,24 @@ const char *key_string_lookup_key(int);
extern struct clients clients;
extern struct clients dead_clients;
int server_start(char *);
void server_poll_add(int, int, void (*)(int, int, void *), void *);
void server_signal_set(void);
void server_signal_clear(void);
/* server-client.c */
void server_client_create(int);
void server_client_lost(struct client *);
void server_client_prepare(void);
void server_client_callback(int, int, void *);
void server_client_callback(int, short, void *);
void server_client_loop(void);
/* server-job.c */
void server_job_prepare(void);
void server_job_callback(int, int, void *);
void server_job_callback(int, short, void *);
void server_job_loop(void);
/* server-window.c */
void server_window_prepare(void);
void server_window_callback(int, int, void *);
void server_window_callback(int, short, void *);
void server_window_loop(void);
/* server-fn.c */

View File

@ -455,10 +455,12 @@ window_pane_destroy(struct window_pane *wp)
if (wp->pipe_fd != -1) {
buffer_destroy(wp->pipe_buf);
close(wp->pipe_fd);
event_del(&wp->pipe_event);
}
buffer_destroy(wp->in);
buffer_destroy(wp->out);
event_del(&wp->event);
if (wp->cwd != NULL)
xfree(wp->cwd);
@ -543,7 +545,7 @@ window_pane_spawn(struct window_pane *wp, const char *cmd, const char *shell,
setenv(envent->name, envent->value, 1);
}
sigreset();
server_signal_clear();
log_close();
if (*wp->cmd != '\0') {