diff options
Diffstat (limited to 'server-client.c')
-rw-r--r-- | server-client.c | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/server-client.c b/server-client.c new file mode 100644 index 00000000..f031be14 --- /dev/null +++ b/server-client.c @@ -0,0 +1,728 @@ +/* $Id: server-client.c,v 1.1 2009-10-23 17:49:47 tcunha Exp $ */ + +/* + * Copyright (c) 2009 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 <fcntl.h> +#include <string.h> +#include <paths.h> +#include <unistd.h> + +#include "tmux.h" + +void server_client_handle_data(struct client *); +void server_client_check_redraw(struct client *); +void server_client_set_title(struct client *); +void server_client_check_timers(struct client *); + +int server_client_msg_dispatch(struct client *); +void server_client_msg_command(struct client *, struct msg_command_data *); +void server_client_msg_identify( + struct client *, struct msg_identify_data *, int); +void server_client_msg_shell(struct client *); + +void printflike2 server_client_msg_error(struct cmd_ctx *, const char *, ...); +void printflike2 server_client_msg_print(struct cmd_ctx *, const char *, ...); +void printflike2 server_client_msg_info(struct cmd_ctx *, const char *, ...); + + +/* Create a new client. */ +void +server_client_create(int fd) +{ + struct client *c; + int mode; + u_int i; + + 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"); + + c = xcalloc(1, sizeof *c); + c->references = 0; + imsg_init(&c->ibuf, fd); + + if (gettimeofday(&c->tv, NULL) != 0) + fatal("gettimeofday failed"); + + ARRAY_INIT(&c->prompt_hdata); + + c->tty.fd = -1; + c->title = NULL; + + c->session = NULL; + c->tty.sx = 80; + c->tty.sy = 24; + + 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; + } + } + ARRAY_ADD(&clients, c); + log_debug("new client %d", fd); +} + +/* Lost a client. */ +void +server_client_lost(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(); +} + +/* Process a single client event. */ +void +server_client_callback(int fd, int events, void *data) +{ + struct client *c = data; + + if (fd == c->ibuf.fd) { + if (events & (POLLERR|POLLNVAL|POLLHUP)) + goto client_lost; + + if (events & POLLOUT && msgbuf_write(&c->ibuf.w) < 0) + goto client_lost; + + if (c->flags & CLIENT_BAD) { + if (c->ibuf.w.queued == 0) + goto client_lost; + return; + } + + if (events & POLLIN && server_client_msg_dispatch(c) != 0) + goto client_lost; + } + + if (c->tty.fd != -1 && fd == c->tty.fd) { + if (c->flags & CLIENT_SUSPENDED || c->session == NULL) + return; + + if (buffer_poll(fd, events, c->tty.in, c->tty.out) != 0) + goto client_lost; + server_client_handle_data(c); + } + + return; + +client_lost: + server_client_lost(c); +} + +/* Input data from client. */ +void +server_client_handle_data(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); +} + +/* Client functions that need to happen every loop. */ +void +server_client_loop(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_client_check_timers(c); + server_client_check_redraw(c); + } + + /* + * Any windows will have been redrawn as part of clients, so clear + * their flags now. + */ + 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 client redraws. */ +void +server_client_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_client_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_client_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 client timers. */ +void +server_client_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; + } +} + +/* Dispatch message from client. */ +int +server_client_msg_dispatch(struct client *c) +{ + struct imsg imsg; + struct msg_command_data commanddata; + struct msg_identify_data identifydata; + struct msg_environ_data environdata; + ssize_t n, datalen; + + if ((n = imsg_read(&c->ibuf)) == -1 || n == 0) + return (-1); + + for (;;) { + if ((n = imsg_get(&c->ibuf, &imsg)) == -1) + return (-1); + if (n == 0) + return (0); + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + + if (imsg.hdr.peerid != PROTOCOL_VERSION) { + server_write_client(c, MSG_VERSION, NULL, 0); + c->flags |= CLIENT_BAD; + imsg_free(&imsg); + continue; + } + + log_debug("got %d from client %d", imsg.hdr.type, c->ibuf.fd); + switch (imsg.hdr.type) { + case MSG_COMMAND: + if (datalen != sizeof commanddata) + fatalx("bad MSG_COMMAND size"); + memcpy(&commanddata, imsg.data, sizeof commanddata); + + server_client_msg_command(c, &commanddata); + break; + case MSG_IDENTIFY: + if (datalen != sizeof identifydata) + fatalx("bad MSG_IDENTIFY size"); + if (imsg.fd == -1) + fatalx("MSG_IDENTIFY missing fd"); + memcpy(&identifydata, imsg.data, sizeof identifydata); + + server_client_msg_identify(c, &identifydata, imsg.fd); + break; + case MSG_RESIZE: + if (datalen != 0) + fatalx("bad MSG_RESIZE size"); + + tty_resize(&c->tty); + recalculate_sizes(); + server_redraw_client(c); + break; + case MSG_EXITING: + if (datalen != 0) + fatalx("bad MSG_EXITING size"); + + c->session = NULL; + tty_close(&c->tty); + server_write_client(c, MSG_EXITED, NULL, 0); + break; + case MSG_WAKEUP: + case MSG_UNLOCK: + if (datalen != 0) + fatalx("bad MSG_WAKEUP size"); + + if (!(c->flags & CLIENT_SUSPENDED)) + break; + c->flags &= ~CLIENT_SUSPENDED; + tty_start_tty(&c->tty); + server_redraw_client(c); + recalculate_sizes(); + if (c->session != NULL) + c->session->activity = time(NULL); + break; + case MSG_ENVIRON: + if (datalen != sizeof environdata) + fatalx("bad MSG_ENVIRON size"); + memcpy(&environdata, imsg.data, sizeof environdata); + + environdata.var[(sizeof environdata.var) - 1] = '\0'; + if (strchr(environdata.var, '=') != NULL) + environ_put(&c->environ, environdata.var); + break; + case MSG_SHELL: + if (datalen != 0) + fatalx("bad MSG_SHELL size"); + + server_client_msg_shell(c); + break; + default: + fatalx("unexpected message"); + } + + imsg_free(&imsg); + } +} + +/* Callback to send error message to client. */ +void printflike2 +server_client_msg_error(struct cmd_ctx *ctx, const char *fmt, ...) +{ + struct msg_print_data data; + va_list ap; + + va_start(ap, fmt); + xvsnprintf(data.msg, sizeof data.msg, fmt, ap); + va_end(ap); + + server_write_client(ctx->cmdclient, MSG_ERROR, &data, sizeof data); +} + +/* Callback to send print message to client. */ +void printflike2 +server_client_msg_print(struct cmd_ctx *ctx, const char *fmt, ...) +{ + struct msg_print_data data; + va_list ap; + + va_start(ap, fmt); + xvsnprintf(data.msg, sizeof data.msg, fmt, ap); + va_end(ap); + + server_write_client(ctx->cmdclient, MSG_PRINT, &data, sizeof data); +} + +/* Callback to send print message to client, if not quiet. */ +void printflike2 +server_client_msg_info(struct cmd_ctx *ctx, const char *fmt, ...) +{ + struct msg_print_data data; + va_list ap; + + if (be_quiet) + return; + + va_start(ap, fmt); + xvsnprintf(data.msg, sizeof data.msg, fmt, ap); + va_end(ap); + + server_write_client(ctx->cmdclient, MSG_PRINT, &data, sizeof data); +} + +/* Handle command message. */ +void +server_client_msg_command(struct client *c, struct msg_command_data *data) +{ + struct cmd_ctx ctx; + struct cmd_list *cmdlist = NULL; + struct cmd *cmd; + int argc; + char **argv, *cause; + + if (c->session != NULL) + c->session->activity = time(NULL); + + ctx.error = server_client_msg_error; + ctx.print = server_client_msg_print; + ctx.info = server_client_msg_info; + + ctx.msgdata = data; + ctx.curclient = NULL; + + ctx.cmdclient = c; + + argc = data->argc; + data->argv[(sizeof data->argv) - 1] = '\0'; + if (cmd_unpack_argv(data->argv, sizeof data->argv, argc, &argv) != 0) { + server_client_msg_error(&ctx, "command too long"); + goto error; + } + + if (argc == 0) { + argc = 1; + argv = xcalloc(1, sizeof *argv); + *argv = xstrdup("new-session"); + } + + if ((cmdlist = cmd_list_parse(argc, argv, &cause)) == NULL) { + server_client_msg_error(&ctx, "%s", cause); + cmd_free_argv(argc, argv); + goto error; + } + cmd_free_argv(argc, argv); + + if (data->pid != -1) { + TAILQ_FOREACH(cmd, cmdlist, qentry) { + if (cmd->entry->flags & CMD_CANTNEST) { + server_client_msg_error(&ctx, + "sessions should be nested with care. " + "unset $TMUX to force"); + goto error; + } + } + } + + if (cmd_list_exec(cmdlist, &ctx) != 1) + server_write_client(c, MSG_EXIT, NULL, 0); + cmd_list_free(cmdlist); + return; + +error: + if (cmdlist != NULL) + cmd_list_free(cmdlist); + server_write_client(c, MSG_EXIT, NULL, 0); +} + +/* Handle identify message. */ +void +server_client_msg_identify( + struct client *c, struct msg_identify_data *data, int fd) +{ + c->cwd = NULL; + data->cwd[(sizeof data->cwd) - 1] = '\0'; + if (*data->cwd != '\0') + c->cwd = xstrdup(data->cwd); + + data->term[(sizeof data->term) - 1] = '\0'; + tty_init(&c->tty, fd, data->term); + if (data->flags & IDENTIFY_UTF8) + c->tty.flags |= TTY_UTF8; + if (data->flags & IDENTIFY_256COLOURS) + c->tty.term_flags |= TERM_256COLOURS; + else if (data->flags & IDENTIFY_88COLOURS) + c->tty.term_flags |= TERM_88COLOURS; + if (data->flags & IDENTIFY_HASDEFAULTS) + c->tty.term_flags |= TERM_HASDEFAULTS; + + tty_resize(&c->tty); + + c->flags |= CLIENT_TERMINAL; +} + +/* Handle shell message. */ +void +server_client_msg_shell(struct client *c) +{ + struct msg_shell_data data; + const char *shell; + + shell = options_get_string(&global_s_options, "default-shell"); + + if (*shell == '\0' || areshell(shell)) + shell = _PATH_BSHELL; + if (strlcpy(data.shell, shell, sizeof data.shell) >= sizeof data.shell) + strlcpy(data.shell, _PATH_BSHELL, sizeof data.shell); + + server_write_client(c, MSG_SHELL, &data, sizeof data); + c->flags |= CLIENT_BAD; /* it will die after exec */ +} |