diff --git a/cfg.c b/cfg.c index 572f8369..68894cb2 100644 --- a/cfg.c +++ b/cfg.c @@ -149,6 +149,20 @@ load_cfg(const char *path, struct cmd_q *cmdq, char **cause) return (found); } +static void print_cfg_errors(void) +{ + u_int i; + + for (i = 0; i < cfg_ncauses; i++) { + tmate_info("%s", cfg_causes[i]); + free(cfg_causes[i]); + } + + free(cfg_causes); + cfg_causes = NULL; + cfg_ncauses = 0; +} + void cfg_default_done(__unused struct cmd_q *cmdq) { @@ -158,6 +172,10 @@ cfg_default_done(__unused struct cmd_q *cmdq) #ifdef TMATE tmate_session_start(); + if (tmate_foreground && cfg_ncauses) { + print_cfg_errors(); + exit(1); + } #endif if (!RB_EMPTY(&sessions)) diff --git a/client.c b/client.c index 164abdd3..ad349972 100644 --- a/client.c +++ b/client.c @@ -32,6 +32,7 @@ #include #include "tmux.h" +#include "tmate.h" struct tmuxproc *client_proc; struct tmuxpeer *client_peer; @@ -213,8 +214,56 @@ client_exit_message(void) #ifdef TMATE extern const struct cmd_entry cmd_attach_session_entry; extern const struct cmd_entry cmd_new_session_entry; + +/* For foreground mode */ +static int __argc; +static char **__argv; #endif +static void _run_initial_client_cmd(int argc, char **argv) +{ + struct cmd_q *cmd_q; + struct cmd_list *cmdlist; + char *cause; + const char *default_argv[] = {"new-session"}; + + if (argc == 0) { + argc = 1; + argv = (char **)default_argv; + } + + cmd_q = cmdq_new(NULL); /* No client */ + + if ((cmdlist = cmd_list_parse(argc, (char **)argv, NULL, 0, &cause)) == NULL) { + tmate_fatal("%s", cause); + } + + cmdq_run(cmd_q, cmdlist, NULL); + cmd_list_free(cmdlist); + cmdq_free(cmd_q); + + /* error messages land in cfg_causes */ + extern char **cfg_causes; + extern u_int cfg_ncauses; + int has_error = !!cfg_ncauses; + for (u_int i = 0; i < cfg_ncauses; i++) { + tmate_info("%s", cfg_causes[i]); + free(cfg_causes[i]); + } + + free(cfg_causes); + cfg_causes = NULL; + cfg_ncauses = 0; + + if (has_error) + exit(1); +} + +void run_initial_client_cmd(void) +{ + _run_initial_client_cmd(__argc, __argv); +} + /* Client main loop. */ int client_main(struct event_base *base, int argc, char **argv, int flags, @@ -232,6 +281,8 @@ client_main(struct event_base *base, int argc, char **argv, int flags, size_t size; #ifdef TMATE int cant_nest = 0; + __argc = argc; + __argv = argv; #endif /* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */ diff --git a/cmd-new-session.c b/cmd-new-session.c index 357ffed1..fb2f7eab 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -131,6 +131,9 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) if (c == NULL) detached = 1; + if (tmate_foreground) + detached = 1; + /* Is this client already attached? */ already_attached = 0; if (c != NULL && c->session != NULL) diff --git a/log.c b/log.c index 018348cf..bc798778 100644 --- a/log.c +++ b/log.c @@ -32,6 +32,11 @@ static int log_level; static void log_event_cb(int, const char *); static void log_vwrite(const char *, va_list); +static int is_log_stdout(void) +{ + return fileno(log_file) <= 2; +} + /* Log callback for libevent. */ static void log_event_cb(__unused int severity, const char *msg) @@ -53,6 +58,18 @@ log_get_level(void) return (log_level); } +void +log_open_fp(FILE *f) +{ + if (log_file != NULL && !is_log_stdout()) + fclose(log_file); + + log_file = f; + + setvbuf(log_file, NULL, _IOLBF, 0); + event_set_log_callback(log_event_cb); +} + /* Open logging to file. */ void log_open(const char *name) @@ -62,24 +79,18 @@ log_open(const char *name) if (log_level == 0) return; - if (log_file != NULL) - fclose(log_file); - xasprintf(&path, "tmate-%s-%ld.log", name, (long)getpid()); - log_file = fopen(path, "w"); + FILE *f = fopen(path, "w"); free(path); - if (log_file == NULL) - return; - - setvbuf(log_file, NULL, _IOLBF, 0); - event_set_log_callback(log_event_cb); + if (f) + log_open_fp(f); } /* Close logging. */ void log_close(void) { - if (log_file != NULL) + if (log_file != NULL && !is_log_stdout()) fclose(log_file); log_file = NULL; @@ -102,9 +113,16 @@ log_vwrite(const char *msg, va_list ap) exit(1); gettimeofday(&tv, NULL); - if (fprintf(log_file, "%lld.%06d %s\n", (long long)tv.tv_sec, - (int)tv.tv_usec, out) == -1) - exit(1); + + if (is_log_stdout()) { + if (fprintf(log_file, "%s\n", out) == -1) + exit(1); + } else { + if (fprintf(log_file, "%lld.%06d %s\n", (long long)tv.tv_sec, + (int)tv.tv_usec, out) == -1) + exit(1); + } + fflush(log_file); free(out); @@ -113,10 +131,13 @@ log_vwrite(const char *msg, va_list ap) /* Log a debug message. */ void -log_debug(const char *msg, ...) +log_emit(int level, const char *msg, ...) { va_list ap; + if (log_level < level) + return; + va_start(ap, msg); log_vwrite(msg, ap); va_end(ap); diff --git a/proc.c b/proc.c index 27880f57..edd4a6f8 100644 --- a/proc.c +++ b/proc.c @@ -172,7 +172,7 @@ proc_start(const char *name, struct event_base *base, int forkflag, struct tmuxproc *tp; struct utsname u; - if (forkflag) { + if (forkflag && !tmate_foreground) { switch (fork()) { case -1: fatal("fork failed"); @@ -189,7 +189,13 @@ proc_start(const char *name, struct event_base *base, int forkflag, fatalx("event_reinit failed"); } - log_open(name); + if (tmate_foreground) { + if (forkflag) + clear_signals(0); + log_open_fp(stdout); + } else { + log_open(name); + } #ifdef HAVE_SETPROCTITLE setproctitle("%s (%s)", name, socket_path); diff --git a/server.c b/server.c index a2378959..05706769 100644 --- a/server.c +++ b/server.c @@ -152,14 +152,16 @@ server_start(struct event_base *base, int lockfd, char *lockfile) { int pair[2]; - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) - fatal("socketpair failed"); + if (!tmate_foreground) + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) + fatal("socketpair failed"); server_proc = proc_start("server", base, 1, server_signal); if (server_proc == NULL) { close(pair[1]); return (pair[0]); } + close(pair[0]); if (log_get_level() > 3) @@ -190,7 +192,8 @@ server_start(struct event_base *base, int lockfd, char *lockfile) if (server_fd == -1) fatal("couldn't create socket"); server_update_socket(); - server_client_create(pair[1]); + if (!tmate_foreground) + server_client_create(pair[1]); if (lockfd >= 0) { unlink(lockfile); @@ -207,11 +210,15 @@ server_start(struct event_base *base, int lockfd, char *lockfile) server_add_accept(0); + if (tmate_foreground) + run_initial_client_cmd(); + proc_loop(server_proc, server_loop); status_prompt_save_history(); #ifdef TMATE unlink(socket_path); #endif + exit(0); } @@ -361,6 +368,7 @@ server_signal(int sig) int fd; switch (sig) { + case SIGINT: case SIGTERM: server_exit = 1; server_send_exit(); diff --git a/session.c b/session.c index 5a3b867a..91fb9c1d 100644 --- a/session.c +++ b/session.c @@ -215,10 +215,6 @@ session_destroy(struct session *s) log_debug("session %s destroyed", s->name); -#ifdef TMATE - tmate_write_fin(); -#endif - RB_REMOVE(sessions, &sessions, s); notify_session_closed(s); @@ -240,6 +236,23 @@ session_destroy(struct session *s) free((void *)s->cwd); session_unref(s); + +#ifdef TMATE + if (tmate_foreground && !server_exit) { + tmate_info("Shell exited, restarting"); + /* + * throttle restarts. This is a blocking sleep. + * It's simpler than using a timer, but fairly harmless + * from a blocking perspective. + */ + usleep(500*1000); + next_session_id = 0; + run_initial_client_cmd(); + } else { + tmate_info("Session terminated"); + tmate_write_fin(); + } +#endif } /* Check a session name is valid: not empty and no colons or periods. */ diff --git a/signal.c b/signal.c index 19938638..f20a0257 100644 --- a/signal.c +++ b/signal.c @@ -24,6 +24,7 @@ #include "tmux.h" +struct event ev_sigint; struct event ev_sighup; struct event ev_sigchld; struct event ev_sigcont; @@ -40,15 +41,23 @@ set_signals(void (*handler)(int, short, void *), void *arg) sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_IGN; +#ifndef TMATE if (sigaction(SIGINT, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif if (sigaction(SIGPIPE, &sigact, NULL) != 0) fatal("sigaction failed"); if (sigaction(SIGUSR2, &sigact, NULL) != 0) fatal("sigaction failed"); +#ifndef TMATE if (sigaction(SIGTSTP, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif +#ifdef TMATE + signal_set(&ev_sigint, SIGINT, handler, arg); + signal_add(&ev_sigint, NULL); +#endif signal_set(&ev_sighup, SIGHUP, handler, arg); signal_add(&ev_sighup, NULL); signal_set(&ev_sigchld, SIGCHLD, handler, arg); @@ -72,16 +81,24 @@ clear_signals(int after_fork) sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; sigact.sa_handler = SIG_DFL; +#ifndef TMATE if (sigaction(SIGINT, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif if (sigaction(SIGPIPE, &sigact, NULL) != 0) fatal("sigaction failed"); if (sigaction(SIGUSR2, &sigact, NULL) != 0) fatal("sigaction failed"); +#ifndef TMATE if (sigaction(SIGTSTP, &sigact, NULL) != 0) fatal("sigaction failed"); +#endif if (after_fork) { +#ifdef TMATE + if (sigaction(SIGINT, &sigact, NULL) != 0) + fatal("sigaction failed"); +#endif if (sigaction(SIGHUP, &sigact, NULL) != 0) fatal("sigaction failed"); if (sigaction(SIGCHLD, &sigact, NULL) != 0) @@ -95,6 +112,7 @@ clear_signals(int after_fork) if (sigaction(SIGWINCH, &sigact, NULL) != 0) fatal("sigaction failed"); } else { + event_del(&ev_sigint); event_del(&ev_sighup); event_del(&ev_sigchld); event_del(&ev_sigcont); diff --git a/tmate-msg.c b/tmate-msg.c index 880e3905..a47f9e4d 100644 --- a/tmate-msg.c +++ b/tmate-msg.c @@ -56,7 +56,7 @@ void __tmate_status_message(const char *fmt, va_list ap) char *message; xvasprintf(&message, fmt, ap); - tmate_debug("%s", message); + tmate_info("%s", message); TAILQ_FOREACH(c, &clients, entry) { if (c && !(c->flags & CLIENT_READONLY)) diff --git a/tmate-session.c b/tmate-session.c index 106687e1..5ab13511 100644 --- a/tmate-session.c +++ b/tmate-session.c @@ -96,7 +96,7 @@ static void lookup_and_connect(void) tmate_server_host = options_get_string(global_options, "tmate-server-host"); - tmate_info("Looking up %s...", tmate_server_host); + tmate_debug("Looking up %s...", tmate_server_host); (void)evdns_getaddrinfo(tmate_session.ev_dnsbase, tmate_server_host, NULL, &hints, dns_cb, (void *)tmate_server_host); } diff --git a/tmate-ssh-client.c b/tmate-ssh-client.c index e33f4b17..66eb1716 100644 --- a/tmate-ssh-client.c +++ b/tmate-ssh-client.c @@ -171,12 +171,12 @@ static void request_passphrase(struct tmate_ssh_client *client) #define KEEPALIVE_IDLE 20 #define KEEPALIVE_INTVL 10 -static void setup_socket(int fd) +static void tune_socket_opts(int fd) { #define SSO(level, optname, val) ({ \ int _flag = val; \ if (setsockopt(fd, level, optname, &(_flag), sizeof(int)) < 0) { \ - tmate_warn("setsockopt(" #level ", " #optname ", %d) failed", val); \ + tmate_debug("setsockopt(" #level ", " #optname ", %d) failed", val); \ } \ }) @@ -197,7 +197,7 @@ static void setup_socket(int fd) #undef SSO } -static void init_conn_fd(struct tmate_ssh_client *client) +static void init_conn_fd(struct tmate_ssh_client *client, bool tune_socket) { int fd; @@ -207,7 +207,9 @@ static void init_conn_fd(struct tmate_ssh_client *client) if ((fd = ssh_get_fd(client->session)) < 0) return; - setup_socket(fd); + if (tune_socket) + tune_socket_opts(fd); + event_set(&client->ev_ssh, fd, EV_READ | EV_PERSIST, __on_ssh_client_event, client); event_add(&client->ev_ssh, NULL); @@ -267,14 +269,14 @@ static void on_ssh_client_event(struct tmate_ssh_client *client) case SSH_CONNECT: switch (ssh_connect(session)) { case SSH_AGAIN: - init_conn_fd(client); + init_conn_fd(client, false); return; case SSH_ERROR: kill_ssh_client(client, "Error connecting: %s", ssh_get_error(session)); return; case SSH_OK: - init_conn_fd(client); + init_conn_fd(client, true); tmate_debug("Establishing connection to %s", client->server_ip); client->state = SSH_AUTH_SERVER; diff --git a/tmate.h b/tmate.h index 7deb0ba6..9bf8e975 100644 --- a/tmate.h +++ b/tmate.h @@ -9,10 +9,9 @@ #include "tmux.h" -#define tmate_debug(...) log_debug("[tmate] D " __VA_ARGS__) -#define tmate_warn(...) log_debug("[tmate] W " __VA_ARGS__) -#define tmate_info(...) log_debug("[tmate] I " __VA_ARGS__) -#define tmate_fatal(...) fatalx("[tmate] " __VA_ARGS__) +#define tmate_debug(...) log_emit(LOG_DEBUG, __VA_ARGS__) +#define tmate_info(...) log_emit(LOG_INFO, __VA_ARGS__) +#define tmate_fatal(...) fatalx( __VA_ARGS__) /* tmate-msgpack.c */ diff --git a/tmux.c b/tmux.c index d7cf8d97..60341f84 100644 --- a/tmux.c +++ b/tmux.c @@ -48,7 +48,11 @@ __dead void usage(void); static char *make_label(const char *); #ifndef HAVE___PROGNAME -char *__progname = (char *) "tmux"; +char *__progname = (char *) "tmate"; +#endif + +#ifdef TMATE +int tmate_foreground; #endif __dead void @@ -228,7 +232,7 @@ main(int argc, char **argv) #endif label = path = NULL; - while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUVv")) != -1) { + while ((opt = getopt(argc, argv, "2c:CdFf:lL:qS:uUVv")) != -1) { switch (opt) { case '2': flags |= CLIENT_256COLOURS; @@ -268,6 +272,11 @@ main(int argc, char **argv) case 'v': log_add_level(); break; + case 'F': + tmate_foreground = 1; + log_add_level(); + unsetenv("TMUX"); + break; default: usage(); } diff --git a/tmux.h b/tmux.h index 1ab784b7..3956e240 100644 --- a/tmux.h +++ b/tmux.h @@ -1549,6 +1549,9 @@ extern struct options *global_w_options; extern struct environ *global_environ; extern struct timeval start_time; extern const char *socket_path; +#ifdef TMATE +extern int tmate_foreground; +#endif const char *getshell(void); int checkshell(const char *); int areshell(const char *); @@ -1875,6 +1878,7 @@ void signal_waiting_clients(const char *name); void cmd_wait_for_flush(void); /* client.c */ +void run_initial_client_cmd(void); int client_main(struct event_base *, int, char **, int, const char *); /* key-bindings.c */ @@ -1902,6 +1906,7 @@ void alerts_queue(struct window *, int); void alerts_check_session(struct session *); /* server.c */ +extern int server_exit; extern struct tmuxproc *server_proc; extern struct clients clients; extern struct cmd_find_state marked_pane; @@ -2356,9 +2361,14 @@ struct event_base *osdep_event_init(void); /* log.c */ void log_add_level(void); int log_get_level(void); +void log_open_fp(FILE *f); void log_open(const char *); void log_close(void); -void printflike(1, 2) log_debug(const char *, ...); +#define LOG_ERROR 0 +#define LOG_INFO 1 +#define LOG_DEBUG 2 +#define log_debug(...) log_emit(LOG_DEBUG+1, __VA_ARGS__) +void printflike(2, 3) log_emit(int level, const char *, ...); __dead void printflike(1, 2) fatal(const char *, ...); __dead void printflike(1, 2) fatalx(const char *, ...);