version 1.59, 2009/10/13 06:14:08 |
version 1.60, 2009/10/22 19:41:51 |
|
|
|
|
/* Mapping of a pollfd to an fd independent of its position in the array. */ |
/* Mapping of a pollfd to an fd independent of its position in the array. */ |
struct poll_item { |
struct poll_item { |
struct pollfd pfd; |
int fd; |
|
int events; |
|
|
|
void (*fn)(int, int, void *); |
|
void *data; |
|
|
RB_ENTRY(poll_item) entry; |
RB_ENTRY(poll_item) entry; |
}; |
}; |
RB_HEAD(poll_items, poll_item) poll_items; |
RB_HEAD(poll_items, poll_item) poll_items; |
|
|
int server_poll_cmp(struct poll_item *, struct poll_item *); |
int server_poll_cmp(struct poll_item *, struct poll_item *); |
struct pollfd *server_poll_lookup(int); |
struct poll_item*server_poll_lookup(int); |
void server_poll_add(int, int); |
void server_poll_add(int, int, void (*)(int, int, void *), void *); |
struct pollfd *server_poll_flatten(int *); |
struct pollfd *server_poll_flatten(int *); |
void server_poll_parse(struct pollfd *); |
void server_poll_dispatch(struct pollfd *, int); |
void server_poll_reset(void); |
void server_poll_reset(void); |
RB_PROTOTYPE(poll_items, poll_item, entry, server_poll_cmp); |
RB_PROTOTYPE(poll_items, poll_item, entry, server_poll_cmp); |
RB_GENERATE(poll_items, poll_item, entry, server_poll_cmp); |
RB_GENERATE(poll_items, poll_item, entry, server_poll_cmp); |
|
|
void server_create_client(int); |
|
int server_create_socket(void); |
int server_create_socket(void); |
|
void server_callback(int, int, void *); |
int server_main(int); |
int server_main(int); |
void server_shutdown(void); |
void server_shutdown(void); |
int server_should_shutdown(void); |
int server_should_shutdown(void); |
void server_child_signal(void); |
void server_child_signal(void); |
void server_fill_windows(void); |
void server_fill_windows(void); |
void server_handle_windows(void); |
|
void server_fill_clients(void); |
void server_fill_clients(void); |
void server_handle_clients(void); |
|
void server_fill_jobs(void); |
void server_fill_jobs(void); |
void server_handle_jobs(void); |
|
void server_accept_client(int); |
|
void server_handle_client(struct client *); |
|
void server_handle_window(struct window *, struct window_pane *); |
|
int server_check_window_bell(struct session *, struct window *); |
|
int server_check_window_activity(struct session *, |
|
struct window *); |
|
int server_check_window_content(struct session *, struct window *, |
|
struct window_pane *); |
|
void server_clean_dead(void); |
void server_clean_dead(void); |
void server_lost_client(struct client *); |
void server_second_timers(void); |
void server_check_window(struct window *); |
|
void server_check_redraw(struct client *); |
|
void server_set_title(struct client *); |
|
void server_check_timers(struct client *); |
|
void server_check_jobs(void); |
|
void server_lock_server(void); |
void server_lock_server(void); |
void server_lock_sessions(void); |
void server_lock_sessions(void); |
void server_check_clients(void); |
|
void server_second_timers(void); |
|
int server_update_socket(void); |
int server_update_socket(void); |
|
|
int |
int |
server_poll_cmp(struct poll_item *pitem1, struct poll_item *pitem2) |
server_poll_cmp(struct poll_item *pitem1, struct poll_item *pitem2) |
{ |
{ |
return (pitem1->pfd.fd - pitem2->pfd.fd); |
return (pitem1->fd - pitem2->fd); |
} |
} |
|
|
struct pollfd * |
|
server_poll_lookup(int fd) |
|
{ |
|
struct poll_item pitem; |
|
|
|
pitem.pfd.fd = fd; |
|
return (&RB_FIND(poll_items, &poll_items, &pitem)->pfd); |
|
} |
|
|
|
void |
void |
server_poll_add(int fd, int events) |
server_poll_add(int fd, int events, void (*fn)(int, int, void *), void *data) |
{ |
{ |
struct poll_item *pitem; |
struct poll_item *pitem; |
|
|
pitem = xmalloc(sizeof *pitem); |
pitem = xmalloc(sizeof *pitem); |
pitem->pfd.fd = fd; |
pitem->fd = fd; |
pitem->pfd.events = events; |
pitem->events = events; |
|
|
|
pitem->fn = fn; |
|
pitem->data = data; |
|
|
RB_INSERT(poll_items, &poll_items, pitem); |
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 * |
struct pollfd * |
server_poll_flatten(int *nfds) |
server_poll_flatten(int *nfds) |
{ |
{ |
|
|
*nfds = 0; |
*nfds = 0; |
RB_FOREACH(pitem, poll_items, &poll_items) { |
RB_FOREACH(pitem, poll_items, &poll_items) { |
pfds = xrealloc(pfds, (*nfds) + 1, sizeof *pfds); |
pfds = xrealloc(pfds, (*nfds) + 1, sizeof *pfds); |
pfds[*nfds].fd = pitem->pfd.fd; |
pfds[*nfds].fd = pitem->fd; |
pfds[*nfds].events = pitem->pfd.events; |
pfds[*nfds].events = pitem->events; |
(*nfds)++; |
(*nfds)++; |
} |
} |
return (pfds); |
return (pfds); |
} |
} |
|
|
void |
void |
server_poll_parse(struct pollfd *pfds) |
server_poll_dispatch(struct pollfd *pfds, int nfds) |
{ |
{ |
struct poll_item *pitem; |
struct poll_item *pitem; |
int nfds; |
struct pollfd *pfd; |
|
|
nfds = 0; |
while (nfds > 0) { |
RB_FOREACH(pitem, poll_items, &poll_items) { |
pfd = &pfds[--nfds]; |
pitem->pfd.revents = pfds[nfds].revents; |
if (pfd->revents != 0) { |
nfds++; |
pitem = server_poll_lookup(pfd->fd); |
|
pitem->fn(pitem->fd, pfd->revents, pitem->data); |
|
} |
} |
} |
xfree(pfds); |
xfree(pfds); |
} |
} |
|
|
} |
} |
} |
} |
|
|
/* Create a new client. */ |
/* Create server socket. */ |
void |
int |
server_create_client(int fd) |
server_create_socket(void) |
{ |
{ |
struct client *c; |
struct sockaddr_un sa; |
int mode; |
size_t size; |
u_int i; |
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) |
if ((mode = fcntl(fd, F_GETFL)) == -1) |
fatal("fcntl failed"); |
fatal("fcntl failed"); |
if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) |
if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) |
|
|
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) |
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) |
fatal("fcntl failed"); |
fatal("fcntl failed"); |
|
|
c = xcalloc(1, sizeof *c); |
return (fd); |
c->references = 0; |
} |
imsg_init(&c->ibuf, fd); |
|
|
|
if (gettimeofday(&c->tv, NULL) != 0) |
|
fatal("gettimeofday failed"); |
|
|
|
ARRAY_INIT(&c->prompt_hdata); |
/* 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; |
|
|
c->tty.fd = -1; |
if (events & (POLLERR|POLLNVAL|POLLHUP)) |
c->title = NULL; |
fatalx("lost server socket"); |
|
if (!(events & POLLIN)) |
|
return; |
|
|
c->session = NULL; |
newfd = accept(fd, (struct sockaddr *) &sa, &slen); |
c->tty.sx = 80; |
if (newfd == -1) { |
c->tty.sy = 24; |
if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED) |
|
|
screen_init(&c->status, c->tty.sx, 1, 0); |
|
job_tree_init(&c->status_jobs); |
|
|
|
c->message_string = NULL; |
|
|
|
c->prompt_string = NULL; |
|
c->prompt_buffer = NULL; |
|
c->prompt_index = 0; |
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
if (ARRAY_ITEM(&clients, i) == NULL) { |
|
ARRAY_SET(&clients, i, c); |
|
return; |
return; |
} |
fatal("accept failed"); |
} |
} |
ARRAY_ADD(&clients, c); |
if (sigterm) { |
log_debug("new client %d", fd); |
close(newfd); |
|
return; |
|
} |
|
server_client_create(newfd); |
} |
} |
|
|
/* Fork new server. */ |
/* Fork new server. */ |
|
|
setproctitle("server (%s)", rpathbuf); |
setproctitle("server (%s)", rpathbuf); |
|
|
srv_fd = server_create_socket(); |
srv_fd = server_create_socket(); |
server_create_client(pair[1]); |
server_client_create(pair[1]); |
|
|
if (access(SYSTEM_CFG, R_OK) != 0) { |
if (access(SYSTEM_CFG, R_OK) != 0) { |
if (errno != ENOENT) { |
if (errno != ENOENT) { |
|
|
exit(server_main(srv_fd)); |
exit(server_main(srv_fd)); |
} |
} |
|
|
/* 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); |
|
} |
|
|
|
/* Main server loop. */ |
/* Main server loop. */ |
int |
int |
server_main(int srv_fd) |
server_main(int srv_fd) |
{ |
{ |
struct pollfd *pfds, *pfd; |
struct pollfd *pfds; |
int nfds, xtimeout; |
int nfds, xtimeout; |
u_int i; |
u_int i; |
time_t now, last; |
time_t now, last; |
|
|
sigusr1 = 0; |
sigusr1 = 0; |
} |
} |
|
|
/* Collect any jobs that have died and process clients. */ |
|
server_check_jobs(); |
|
server_check_clients(); |
|
|
|
/* Initialise pollfd array and add server socket. */ |
/* Initialise pollfd array and add server socket. */ |
server_poll_reset(); |
server_poll_reset(); |
server_poll_add(srv_fd, POLLIN); |
server_poll_add(srv_fd, POLLIN, server_callback, NULL); |
|
|
/* Fill window and client sockets. */ |
/* Fill window and client sockets. */ |
server_fill_jobs(); |
server_fill_jobs(); |
|
|
continue; |
continue; |
fatal("poll failed"); |
fatal("poll failed"); |
} |
} |
server_poll_parse(pfds); |
server_poll_dispatch(pfds, nfds); |
|
|
/* Handle server socket. */ |
|
pfd = server_poll_lookup(srv_fd); |
|
if (pfd->revents & (POLLERR|POLLNVAL|POLLHUP)) |
|
fatalx("lost server socket"); |
|
if (pfd->revents & POLLIN) { |
|
server_accept_client(srv_fd); |
|
continue; |
|
} |
|
|
|
/* Call second-based timers. */ |
/* Call second-based timers. */ |
now = time(NULL); |
now = time(NULL); |
if (now != last) { |
if (now != last) { |
|
|
server_second_timers(); |
server_second_timers(); |
} |
} |
|
|
/* Set window names. */ |
/* Run once-per-loop events. */ |
set_window_names(); |
server_job_loop(); |
|
server_window_loop(); |
|
server_client_loop(); |
|
|
/* Handle window and client sockets. */ |
|
server_handle_jobs(); |
|
server_handle_windows(); |
|
server_handle_clients(); |
|
|
|
/* Collect any unset key bindings. */ |
/* Collect any unset key bindings. */ |
key_bindings_clean(); |
key_bindings_clean(); |
|
|
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
if (ARRAY_ITEM(&clients, i) != NULL) |
if (ARRAY_ITEM(&clients, i) != NULL) |
server_lost_client(ARRAY_ITEM(&clients, i)); |
server_client_lost(ARRAY_ITEM(&clients, i)); |
} |
} |
ARRAY_FREE(&clients); |
ARRAY_FREE(&clients); |
|
|
|
|
c = ARRAY_ITEM(&clients, i); |
c = ARRAY_ITEM(&clients, i); |
if (c != NULL) { |
if (c != NULL) { |
if (c->flags & (CLIENT_BAD|CLIENT_SUSPENDED)) |
if (c->flags & (CLIENT_BAD|CLIENT_SUSPENDED)) |
server_lost_client(c); |
server_client_lost(c); |
else |
else |
server_write_client(c, MSG_SHUTDOWN, NULL, 0); |
server_write_client(c, MSG_SHUTDOWN, NULL, 0); |
} |
} |
|
|
events = POLLIN; |
events = POLLIN; |
if (BUFFER_USED(wp->out) > 0) |
if (BUFFER_USED(wp->out) > 0) |
events |= POLLOUT; |
events |= POLLOUT; |
server_poll_add(wp->fd, events); |
server_poll_add( |
|
wp->fd, events, server_window_callback, wp); |
|
|
if (wp->pipe_fd == -1) |
if (wp->pipe_fd == -1) |
continue; |
continue; |
events = 0; |
events = 0; |
if (BUFFER_USED(wp->pipe_buf) > 0) |
if (BUFFER_USED(wp->pipe_buf) > 0) |
events |= POLLOUT; |
events |= POLLOUT; |
server_poll_add(wp->pipe_fd, events); |
server_poll_add( |
|
wp->pipe_fd, events, server_window_callback, wp); |
} |
} |
} |
} |
} |
} |
|
|
/* Handle window pollfds. */ |
|
void |
|
server_handle_windows(void) |
|
{ |
|
struct window *w; |
|
struct window_pane *wp; |
|
struct pollfd *pfd; |
|
u_int i; |
|
|
|
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; |
|
if ((pfd = server_poll_lookup(wp->fd)) == NULL) |
|
continue; |
|
if (buffer_poll(pfd, wp->in, wp->out) != 0) { |
|
close(wp->fd); |
|
wp->fd = -1; |
|
} else |
|
server_handle_window(w, wp); |
|
|
|
if (wp->pipe_fd == -1) |
|
continue; |
|
if ((pfd = server_poll_lookup(wp->pipe_fd)) == NULL) |
|
continue; |
|
if (buffer_poll(pfd, NULL, wp->pipe_buf) != 0) { |
|
buffer_destroy(wp->pipe_buf); |
|
close(wp->pipe_fd); |
|
wp->pipe_fd = -1; |
|
} |
|
} |
|
|
|
server_check_window(w); |
|
} |
|
} |
|
|
|
/* Check clients for redraw and timers. */ |
|
void |
|
server_check_clients(void) |
|
{ |
|
struct client *c; |
|
struct window *w; |
|
struct window_pane *wp; |
|
u_int i; |
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
c = ARRAY_ITEM(&clients, i); |
|
if (c == NULL || c->session == NULL) |
|
continue; |
|
|
|
server_check_timers(c); |
|
server_check_redraw(c); |
|
} |
|
|
|
/* |
|
* Clear any window redraw flags (will have been redrawn as part of |
|
* client). |
|
*/ |
|
for (i = 0; i < ARRAY_LENGTH(&windows); i++) { |
|
w = ARRAY_ITEM(&windows, i); |
|
if (w == NULL) |
|
continue; |
|
|
|
w->flags &= ~WINDOW_REDRAW; |
|
TAILQ_FOREACH(wp, &w->panes, entry) |
|
wp->flags &= ~PANE_REDRAW; |
|
} |
|
} |
|
|
|
/* Check for general redraw on client. */ |
|
void |
|
server_check_redraw(struct client *c) |
|
{ |
|
struct session *s = c->session; |
|
struct window_pane *wp; |
|
int flags, redraw; |
|
|
|
flags = c->tty.flags & TTY_FREEZE; |
|
c->tty.flags &= ~TTY_FREEZE; |
|
|
|
if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) { |
|
if (options_get_number(&s->options, "set-titles")) |
|
server_set_title(c); |
|
|
|
if (c->message_string != NULL) |
|
redraw = status_message_redraw(c); |
|
else if (c->prompt_string != NULL) |
|
redraw = status_prompt_redraw(c); |
|
else |
|
redraw = status_redraw(c); |
|
if (!redraw) |
|
c->flags &= ~CLIENT_STATUS; |
|
} |
|
|
|
if (c->flags & CLIENT_REDRAW) { |
|
screen_redraw_screen(c, 0); |
|
c->flags &= ~CLIENT_STATUS; |
|
} else { |
|
TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { |
|
if (wp->flags & PANE_REDRAW) |
|
screen_redraw_pane(c, wp); |
|
} |
|
} |
|
|
|
if (c->flags & CLIENT_STATUS) |
|
screen_redraw_screen(c, 1); |
|
|
|
c->tty.flags |= flags; |
|
|
|
c->flags &= ~(CLIENT_REDRAW|CLIENT_STATUS); |
|
} |
|
|
|
/* Set client title. */ |
|
void |
|
server_set_title(struct client *c) |
|
{ |
|
struct session *s = c->session; |
|
const char *template; |
|
char *title; |
|
|
|
template = options_get_string(&s->options, "set-titles-string"); |
|
|
|
title = status_replace(c, template, time(NULL)); |
|
if (c->title == NULL || strcmp(title, c->title) != 0) { |
|
if (c->title != NULL) |
|
xfree(c->title); |
|
c->title = xstrdup(title); |
|
tty_set_title(&c->tty, c->title); |
|
} |
|
xfree(title); |
|
} |
|
|
|
/* Check for timers on client. */ |
|
void |
|
server_check_timers(struct client *c) |
|
{ |
|
struct session *s = c->session; |
|
struct job *job; |
|
struct timeval tv; |
|
u_int interval; |
|
|
|
if (gettimeofday(&tv, NULL) != 0) |
|
fatal("gettimeofday failed"); |
|
|
|
if (c->flags & CLIENT_IDENTIFY && timercmp(&tv, &c->identify_timer, >)) |
|
server_clear_identify(c); |
|
|
|
if (c->message_string != NULL && timercmp(&tv, &c->message_timer, >)) |
|
status_message_clear(c); |
|
|
|
if (c->message_string != NULL || c->prompt_string != NULL) { |
|
/* |
|
* Don't need timed redraw for messages/prompts so bail now. |
|
* The status timer isn't reset when they are redrawn anyway. |
|
*/ |
|
return; |
|
} |
|
if (!options_get_number(&s->options, "status")) |
|
return; |
|
|
|
/* Check timer; resolution is only a second so don't be too clever. */ |
|
interval = options_get_number(&s->options, "status-interval"); |
|
if (interval == 0) |
|
return; |
|
if (tv.tv_sec < c->status_timer.tv_sec || |
|
((u_int) tv.tv_sec) - c->status_timer.tv_sec >= interval) { |
|
/* Run the jobs for this client and schedule for redraw. */ |
|
RB_FOREACH(job, jobs, &c->status_jobs) |
|
job_run(job); |
|
c->flags |= CLIENT_STATUS; |
|
} |
|
} |
|
|
|
/* Fill client pollfds. */ |
/* Fill client pollfds. */ |
void |
void |
server_fill_clients(void) |
server_fill_clients(void) |
|
|
events |= POLLIN; |
events |= POLLIN; |
if (c->ibuf.w.queued > 0) |
if (c->ibuf.w.queued > 0) |
events |= POLLOUT; |
events |= POLLOUT; |
server_poll_add(c->ibuf.fd, events); |
server_poll_add( |
|
c->ibuf.fd, events, server_client_callback, c); |
} |
} |
|
|
if (c != NULL && !(c->flags & CLIENT_SUSPENDED) && |
if (c != NULL && !(c->flags & CLIENT_SUSPENDED) && |
|
|
events = POLLIN; |
events = POLLIN; |
if (BUFFER_USED(c->tty.out) > 0) |
if (BUFFER_USED(c->tty.out) > 0) |
events |= POLLOUT; |
events |= POLLOUT; |
server_poll_add(c->tty.fd, events); |
server_poll_add( |
|
c->tty.fd, events, server_client_callback, c); |
} |
} |
} |
} |
} |
} |
|
|
SLIST_FOREACH(job, &all_jobs, lentry) { |
SLIST_FOREACH(job, &all_jobs, lentry) { |
if (job->fd == -1) |
if (job->fd == -1) |
continue; |
continue; |
server_poll_add(job->fd, POLLIN); |
server_poll_add(job->fd, POLLIN, server_job_callback, job); |
} |
} |
} |
} |
|
|
/* Handle job fds. */ |
|
void |
|
server_handle_jobs(void) |
|
{ |
|
struct job *job; |
|
struct pollfd *pfd; |
|
|
|
SLIST_FOREACH(job, &all_jobs, lentry) { |
|
if (job->fd == -1) |
|
continue; |
|
if ((pfd = server_poll_lookup(job->fd)) == NULL) |
|
continue; |
|
if (buffer_poll(pfd, job->out, NULL) != 0) { |
|
close(job->fd); |
|
job->fd = -1; |
|
} |
|
} |
|
} |
|
|
|
/* Handle job fds. */ |
|
void |
|
server_check_jobs(void) |
|
{ |
|
struct job *job; |
|
|
|
restart: |
|
SLIST_FOREACH(job, &all_jobs, lentry) { |
|
if (job->flags & JOB_DONE || job->fd != -1 || job->pid != -1) |
|
continue; |
|
job->flags |= JOB_DONE; |
|
|
|
if (job->callbackfn != NULL) { |
|
job->callbackfn(job); |
|
goto restart; /* could be freed by callback */ |
|
} |
|
} |
|
} |
|
|
|
/* Handle client pollfds. */ |
|
void |
|
server_handle_clients(void) |
|
{ |
|
struct client *c; |
|
struct pollfd *pfd; |
|
u_int i; |
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
c = ARRAY_ITEM(&clients, i); |
|
|
|
if (c != NULL) { |
|
if ((pfd = server_poll_lookup(c->ibuf.fd)) == NULL) |
|
continue; |
|
if (pfd->revents & (POLLERR|POLLNVAL|POLLHUP)) { |
|
server_lost_client(c); |
|
continue; |
|
} |
|
|
|
if (pfd->revents & POLLOUT) { |
|
if (msgbuf_write(&c->ibuf.w) < 0) { |
|
server_lost_client(c); |
|
continue; |
|
} |
|
} |
|
|
|
if (c->flags & CLIENT_BAD) { |
|
if (c->ibuf.w.queued == 0) |
|
server_lost_client(c); |
|
continue; |
|
} else if (pfd->revents & POLLIN) { |
|
if (server_msg_dispatch(c) != 0) { |
|
server_lost_client(c); |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
if (c != NULL && !(c->flags & CLIENT_SUSPENDED) && |
|
c->tty.fd != -1 && c->session != NULL) { |
|
if ((pfd = server_poll_lookup(c->tty.fd)) == NULL) |
|
continue; |
|
if (buffer_poll(pfd, c->tty.in, c->tty.out) != 0) |
|
server_lost_client(c); |
|
else |
|
server_handle_client(c); |
|
} |
|
} |
|
} |
|
|
|
/* accept(2) and create new client. */ |
|
void |
|
server_accept_client(int srv_fd) |
|
{ |
|
struct sockaddr_storage sa; |
|
socklen_t slen = sizeof sa; |
|
int fd; |
|
|
|
fd = accept(srv_fd, (struct sockaddr *) &sa, &slen); |
|
if (fd == -1) { |
|
if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED) |
|
return; |
|
fatal("accept failed"); |
|
} |
|
if (sigterm) { |
|
close(fd); |
|
return; |
|
} |
|
server_create_client(fd); |
|
} |
|
|
|
/* Input data from client. */ |
|
void |
|
server_handle_client(struct client *c) |
|
{ |
|
struct window *w; |
|
struct window_pane *wp; |
|
struct screen *s; |
|
struct options *oo; |
|
struct timeval tv; |
|
struct key_binding *bd; |
|
struct keylist *keylist; |
|
struct mouse_event mouse; |
|
int key, status, xtimeout, mode, isprefix; |
|
u_int i; |
|
|
|
xtimeout = options_get_number(&c->session->options, "repeat-time"); |
|
if (xtimeout != 0 && c->flags & CLIENT_REPEAT) { |
|
if (gettimeofday(&tv, NULL) != 0) |
|
fatal("gettimeofday failed"); |
|
if (timercmp(&tv, &c->repeat_timer, >)) |
|
c->flags &= ~(CLIENT_PREFIX|CLIENT_REPEAT); |
|
} |
|
|
|
/* Process keys. */ |
|
keylist = options_get_data(&c->session->options, "prefix"); |
|
while (tty_keys_next(&c->tty, &key, &mouse) == 0) { |
|
if (c->session == NULL) |
|
return; |
|
|
|
c->session->activity = time(NULL); |
|
w = c->session->curw->window; |
|
wp = w->active; /* could die */ |
|
oo = &c->session->options; |
|
|
|
/* Special case: number keys jump to pane in identify mode. */ |
|
if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { |
|
wp = window_pane_at_index(w, key - '0'); |
|
if (wp != NULL && window_pane_visible(wp)) |
|
window_set_active_pane(w, wp); |
|
server_clear_identify(c); |
|
continue; |
|
} |
|
|
|
status_message_clear(c); |
|
server_clear_identify(c); |
|
if (c->prompt_string != NULL) { |
|
status_prompt_key(c, key); |
|
continue; |
|
} |
|
|
|
/* Check for mouse keys. */ |
|
if (key == KEYC_MOUSE) { |
|
if (options_get_number(oo, "mouse-select-pane")) { |
|
window_set_active_at(w, mouse.x, mouse.y); |
|
wp = w->active; |
|
} |
|
window_pane_mouse(wp, c, &mouse); |
|
continue; |
|
} |
|
|
|
/* Is this a prefix key? */ |
|
isprefix = 0; |
|
for (i = 0; i < ARRAY_LENGTH(keylist); i++) { |
|
if (key == ARRAY_ITEM(keylist, i)) { |
|
isprefix = 1; |
|
break; |
|
} |
|
} |
|
|
|
/* No previous prefix key. */ |
|
if (!(c->flags & CLIENT_PREFIX)) { |
|
if (isprefix) |
|
c->flags |= CLIENT_PREFIX; |
|
else { |
|
/* Try as a non-prefix key binding. */ |
|
if ((bd = key_bindings_lookup(key)) == NULL) |
|
window_pane_key(wp, c, key); |
|
else |
|
key_bindings_dispatch(bd, c); |
|
} |
|
continue; |
|
} |
|
|
|
/* Prefix key already pressed. Reset prefix and lookup key. */ |
|
c->flags &= ~CLIENT_PREFIX; |
|
if ((bd = key_bindings_lookup(key | KEYC_PREFIX)) == NULL) { |
|
/* If repeating, treat this as a key, else ignore. */ |
|
if (c->flags & CLIENT_REPEAT) { |
|
c->flags &= ~CLIENT_REPEAT; |
|
if (isprefix) |
|
c->flags |= CLIENT_PREFIX; |
|
else |
|
window_pane_key(wp, c, key); |
|
} |
|
continue; |
|
} |
|
|
|
/* If already repeating, but this key can't repeat, skip it. */ |
|
if (c->flags & CLIENT_REPEAT && !bd->can_repeat) { |
|
c->flags &= ~CLIENT_REPEAT; |
|
if (isprefix) |
|
c->flags |= CLIENT_PREFIX; |
|
else |
|
window_pane_key(wp, c, key); |
|
continue; |
|
} |
|
|
|
/* If this key can repeat, reset the repeat flags and timer. */ |
|
if (xtimeout != 0 && bd->can_repeat) { |
|
c->flags |= CLIENT_PREFIX|CLIENT_REPEAT; |
|
|
|
tv.tv_sec = xtimeout / 1000; |
|
tv.tv_usec = (xtimeout % 1000) * 1000L; |
|
if (gettimeofday(&c->repeat_timer, NULL) != 0) |
|
fatal("gettimeofday failed"); |
|
timeradd(&c->repeat_timer, &tv, &c->repeat_timer); |
|
} |
|
|
|
/* Dispatch the command. */ |
|
key_bindings_dispatch(bd, c); |
|
} |
|
if (c->session == NULL) |
|
return; |
|
w = c->session->curw->window; |
|
wp = w->active; |
|
oo = &c->session->options; |
|
s = wp->screen; |
|
|
|
/* |
|
* Update cursor position and mode settings. The scroll region and |
|
* attributes are cleared across poll(2) as this is the most likely |
|
* time a user may interrupt tmux, for example with ~^Z in ssh(1). This |
|
* is a compromise between excessive resets and likelihood of an |
|
* interrupt. |
|
* |
|
* tty_region/tty_reset/tty_update_mode already take care of not |
|
* resetting things that are already in their default state. |
|
*/ |
|
tty_region(&c->tty, 0, c->tty.sy - 1); |
|
|
|
status = options_get_number(oo, "status"); |
|
if (!window_pane_visible(wp) || wp->yoff + s->cy >= c->tty.sy - status) |
|
tty_cursor(&c->tty, 0, 0); |
|
else |
|
tty_cursor(&c->tty, wp->xoff + s->cx, wp->yoff + s->cy); |
|
|
|
mode = s->mode; |
|
if (TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry) != NULL && |
|
options_get_number(oo, "mouse-select-pane")) |
|
mode |= MODE_MOUSE; |
|
tty_update_mode(&c->tty, mode); |
|
tty_reset(&c->tty); |
|
} |
|
|
|
/* Lost a client. */ |
|
void |
|
server_lost_client(struct client *c) |
|
{ |
|
u_int i; |
|
|
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
if (ARRAY_ITEM(&clients, i) == c) |
|
ARRAY_SET(&clients, i, NULL); |
|
} |
|
log_debug("lost client %d", c->ibuf.fd); |
|
|
|
/* |
|
* If CLIENT_TERMINAL hasn't been set, then tty_init hasn't been called |
|
* and tty_free might close an unrelated fd. |
|
*/ |
|
if (c->flags & CLIENT_TERMINAL) |
|
tty_free(&c->tty); |
|
|
|
screen_free(&c->status); |
|
job_tree_free(&c->status_jobs); |
|
|
|
if (c->title != NULL) |
|
xfree(c->title); |
|
|
|
if (c->message_string != NULL) |
|
xfree(c->message_string); |
|
|
|
if (c->prompt_string != NULL) |
|
xfree(c->prompt_string); |
|
if (c->prompt_buffer != NULL) |
|
xfree(c->prompt_buffer); |
|
for (i = 0; i < ARRAY_LENGTH(&c->prompt_hdata); i++) |
|
xfree(ARRAY_ITEM(&c->prompt_hdata, i)); |
|
ARRAY_FREE(&c->prompt_hdata); |
|
|
|
if (c->cwd != NULL) |
|
xfree(c->cwd); |
|
|
|
close(c->ibuf.fd); |
|
imsg_clear(&c->ibuf); |
|
|
|
for (i = 0; i < ARRAY_LENGTH(&dead_clients); i++) { |
|
if (ARRAY_ITEM(&dead_clients, i) == NULL) { |
|
ARRAY_SET(&dead_clients, i, c); |
|
break; |
|
} |
|
} |
|
if (i == ARRAY_LENGTH(&dead_clients)) |
|
ARRAY_ADD(&dead_clients, c); |
|
c->flags |= CLIENT_DEAD; |
|
|
|
recalculate_sizes(); |
|
} |
|
|
|
/* Free dead, unreferenced clients and sessions. */ |
/* Free dead, unreferenced clients and sessions. */ |
void |
void |
server_clean_dead(void) |
server_clean_dead(void) |
|
|
} |
} |
} |
} |
|
|
/* Handle window data. */ |
/* Call any once-per-second timers. */ |
void |
void |
server_handle_window(struct window *w, struct window_pane *wp) |
server_second_timers(void) |
{ |
{ |
struct session *s; |
struct window *w; |
u_int i; |
struct window_pane *wp; |
int update; |
|
|
|
window_pane_parse(wp); |
|
|
|
if ((w->flags & (WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_CONTENT)) == 0) |
|
return; |
|
|
|
update = 0; |
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { |
|
s = ARRAY_ITEM(&sessions, i); |
|
if (s == NULL || !session_has(s, w)) |
|
continue; |
|
|
|
update += server_check_window_bell(s, w); |
|
update += server_check_window_activity(s, w); |
|
update += server_check_window_content(s, w, wp); |
|
} |
|
if (update) |
|
server_status_window(w); |
|
|
|
w->flags &= ~(WINDOW_BELL|WINDOW_ACTIVITY|WINDOW_CONTENT); |
|
} |
|
|
|
int |
|
server_check_window_bell(struct session *s, struct window *w) |
|
{ |
|
struct client *c; |
|
u_int i; |
|
int action, visual; |
|
|
|
if (!(w->flags & WINDOW_BELL)) |
|
return (0); |
|
if (session_alert_has_window(s, w, WINDOW_BELL)) |
|
return (0); |
|
session_alert_add(s, w, WINDOW_BELL); |
|
|
|
action = options_get_number(&s->options, "bell-action"); |
|
switch (action) { |
|
case BELL_ANY: |
|
if (s->flags & SESSION_UNATTACHED) |
|
break; |
|
visual = options_get_number(&s->options, "visual-bell"); |
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
c = ARRAY_ITEM(&clients, i); |
|
if (c == NULL || c->session != s) |
|
continue; |
|
if (!visual) { |
|
tty_putcode(&c->tty, TTYC_BEL); |
|
continue; |
|
} |
|
if (c->session->curw->window == w) { |
|
status_message_set(c, "Bell in current window"); |
|
continue; |
|
} |
|
status_message_set(c, "Bell in window %u", |
|
winlink_find_by_window(&s->windows, w)->idx); |
|
} |
|
break; |
|
case BELL_CURRENT: |
|
if (s->flags & SESSION_UNATTACHED) |
|
break; |
|
visual = options_get_number(&s->options, "visual-bell"); |
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
c = ARRAY_ITEM(&clients, i); |
|
if (c == NULL || c->session != s) |
|
continue; |
|
if (c->session->curw->window != w) |
|
continue; |
|
if (!visual) { |
|
tty_putcode(&c->tty, TTYC_BEL); |
|
continue; |
|
} |
|
status_message_set(c, "Bell in current window"); |
|
} |
|
break; |
|
} |
|
return (1); |
|
} |
|
|
|
int |
|
server_check_window_activity(struct session *s, struct window *w) |
|
{ |
|
struct client *c; |
|
u_int i; |
|
|
|
if (!(w->flags & WINDOW_ACTIVITY)) |
|
return (0); |
|
|
|
if (!options_get_number(&w->options, "monitor-activity")) |
|
return (0); |
|
|
|
if (session_alert_has_window(s, w, WINDOW_ACTIVITY)) |
|
return (0); |
|
if (s->curw->window == w) |
|
return (0); |
|
|
|
session_alert_add(s, w, WINDOW_ACTIVITY); |
|
if (s->flags & SESSION_UNATTACHED) |
|
return (0); |
|
if (options_get_number(&s->options, "visual-activity")) { |
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
c = ARRAY_ITEM(&clients, i); |
|
if (c == NULL || c->session != s) |
|
continue; |
|
status_message_set(c, "Activity in window %u", |
|
winlink_find_by_window(&s->windows, w)->idx); |
|
} |
|
} |
|
|
|
return (1); |
|
} |
|
|
|
int |
|
server_check_window_content( |
|
struct session *s, struct window *w, struct window_pane *wp) |
|
{ |
|
struct client *c; |
|
u_int i; |
|
char *found, *ptr; |
|
|
|
if (!(w->flags & WINDOW_ACTIVITY)) /* activity for new content */ |
|
return (0); |
|
|
|
ptr = options_get_string(&w->options, "monitor-content"); |
|
if (ptr == NULL || *ptr == '\0') |
|
return (0); |
|
|
|
if (session_alert_has_window(s, w, WINDOW_CONTENT)) |
|
return (0); |
|
if (s->curw->window == w) |
|
return (0); |
|
|
|
if ((found = window_pane_search(wp, ptr, NULL)) == NULL) |
|
return (0); |
|
xfree(found); |
|
|
|
session_alert_add(s, w, WINDOW_CONTENT); |
|
if (s->flags & SESSION_UNATTACHED) |
|
return (0); |
|
if (options_get_number(&s->options, "visual-content")) { |
|
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { |
|
c = ARRAY_ITEM(&clients, i); |
|
if (c == NULL || c->session != s) |
|
continue; |
|
status_message_set(c, "Content in window %u", |
|
winlink_find_by_window(&s->windows, w)->idx); |
|
} |
|
} |
|
|
|
return (1); |
|
} |
|
|
|
/* Check if window still exists. */ |
|
void |
|
server_check_window(struct window *w) |
|
{ |
|
struct window_pane *wp, *wq; |
|
struct options *oo = &w->options; |
|
struct session *s; |
|
struct winlink *wl; |
|
u_int i; |
u_int i; |
int destroyed; |
|
|
|
destroyed = 1; |
if (options_get_number(&global_s_options, "lock-server")) |
|
server_lock_server(); |
|
else |
|
server_lock_sessions(); |
|
|
wp = TAILQ_FIRST(&w->panes); |
for (i = 0; i < ARRAY_LENGTH(&windows); i++) { |
while (wp != NULL) { |
w = ARRAY_ITEM(&windows, i); |
wq = TAILQ_NEXT(wp, entry); |
if (w == NULL) |
/* |
|
* If the pane has died and the remain-on-exit flag is not set, |
|
* remove the pane; otherwise, if the flag is set, don't allow |
|
* the window to be destroyed (or it'll close when the last |
|
* pane dies). |
|
*/ |
|
if (wp->fd == -1 && !options_get_number(oo, "remain-on-exit")) { |
|
layout_close_pane(wp); |
|
window_remove_pane(w, wp); |
|
server_redraw_window(w); |
|
} else |
|
destroyed = 0; |
|
wp = wq; |
|
} |
|
|
|
if (!destroyed) |
|
return; |
|
|
|
for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { |
|
s = ARRAY_ITEM(&sessions, i); |
|
if (s == NULL) |
|
continue; |
continue; |
if (!session_has(s, w)) |
|
continue; |
|
|
|
restart: |
TAILQ_FOREACH(wp, &w->panes, entry) { |
/* Detach window and either redraw or kill clients. */ |
if (wp->mode != NULL && wp->mode->timer != NULL) |
RB_FOREACH(wl, winlinks, &s->windows) { |
wp->mode->timer(wp); |
if (wl->window != w) |
|
continue; |
|
if (session_detach(s, wl)) { |
|
server_destroy_session_group(s); |
|
break; |
|
} |
|
server_redraw_session(s); |
|
server_status_session_group(s); |
|
goto restart; |
|
} |
} |
} |
} |
|
|
recalculate_sizes(); |
|
} |
} |
|
|
/* Lock the server if ALL sessions have hit the time limit. */ |
/* Lock the server if ALL sessions have hit the time limit. */ |
|
|
if (timeout > 0 && t > s->activity + timeout) { |
if (timeout > 0 && t > s->activity + timeout) { |
server_lock_session(s); |
server_lock_session(s); |
recalculate_sizes(); |
recalculate_sizes(); |
} |
|
} |
|
} |
|
|
|
/* 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); |
|
} |
} |
} |
} |
} |
} |