From 23b03685a0f2ec2de3b4c0e2ed97285f3f347f32 Mon Sep 17 00:00:00 2001 From: Nicolas Viennot Date: Wed, 10 Jul 2013 22:48:24 -0400 Subject: [PATCH] Quick and dirty Record/Replay feature --- Makefile.am | 1 + tmate-encoder.c | 6 +++- tmate-replayer.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++ tmate-slave.c | 66 +++++++++++++++++++++++++++++++++++++++---- tmate-ssh-client.c | 21 ++++++++++++++ tmate.h | 22 +++++++++++++++ 6 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 tmate-replayer.c diff --git a/Makefile.am b/Makefile.am index 81f75b11..8f0e3166 100644 --- a/Makefile.am +++ b/Makefile.am @@ -181,6 +181,7 @@ dist_tmate_slave_SOURCES = \ tmate-debug.c \ tmate-decoder.c \ tmate-encoder.c \ + tmate-replayer.c \ tmate-slave.c \ tmate-ssh-client-pty.c \ tmate-ssh-client.c \ diff --git a/tmate-encoder.c b/tmate-encoder.c index f6ff6e07..98b83202 100644 --- a/tmate-encoder.c +++ b/tmate-encoder.c @@ -26,7 +26,11 @@ void tmate_encoder_init(struct tmate_encoder *encoder) msgpack_pack_raw_body(pk, str, __strlen); \ } while(0) -#define pack(what, ...) msgpack_pack_##what(&tmate_encoder->pk, __VA_ARGS__) +/* tmate_encoder may be NULL when replaying a session */ +#define pack(what, ...) do { \ + if (tmate_encoder) \ + msgpack_pack_##what(&tmate_encoder->pk, __VA_ARGS__); \ +} while(0) void printflike1 tmate_notify(const char *fmt, ...) { diff --git a/tmate-replayer.c b/tmate-replayer.c new file mode 100644 index 00000000..cd7fb3fb --- /dev/null +++ b/tmate-replayer.c @@ -0,0 +1,70 @@ +#include +#include +#include "tmate.h" + +#ifdef TMATE_RECORD_REPLAY + +#define READ_MAX_SIZE 1024 +#define TIMER_INERVAL_USEC 1000 + +extern int server_shutdown; +extern void server_send_shutdown(void); + +static void start_timer(struct tmate_replayer *replayer); + +static void on_read_timer(evutil_socket_t fd, short what, void *arg) +{ + struct tmate_replayer *replayer = arg; + char *buf; + ssize_t len; + + if (replayer->log_fd < 0) { + evtimer_del(&replayer->ev_read_timer); + server_shutdown = 1; + server_send_shutdown(); + return; + } + + tmate_decoder_get_buffer(replayer->decoder, &buf, &len); + if (len == 0) + tmate_fatal("Decoder buffer full"); + + if (len > READ_MAX_SIZE) + len = READ_MAX_SIZE; + + len = read(replayer->log_fd, buf, len); + if (len < 0) + tmate_fatal("cannot read from replay log file"); + + if (len == 0) { + tmate_info("Replay file reached EOF"); + replayer->log_fd = -1; + } else { + tmate_decoder_commit(replayer->decoder, len); + } + + start_timer(replayer); +} + +static void start_timer(struct tmate_replayer *replayer) +{ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = TIMER_INERVAL_USEC; + + evtimer_assign(&replayer->ev_read_timer, ev_base, + on_read_timer, replayer); + evtimer_add(&replayer->ev_read_timer, &tv); +} + +void tmate_replayer_init(struct tmate_replayer *replayer, + struct tmate_decoder *decoder, + int log_fd) +{ + replayer->decoder = decoder; + replayer->log_fd = log_fd; + + start_timer(replayer); +} + +#endif diff --git a/tmate-slave.c b/tmate-slave.c index 3da63b4d..c77d5985 100644 --- a/tmate-slave.c +++ b/tmate-slave.c @@ -22,6 +22,11 @@ int tmux_socket_fd; const char *tmate_session_token = "main"; const char *tmate_session_token_ro = "ro-main"; +#ifdef TMATE_RECORD_REPLAY +int tmate_session_log_fd; +static void tmate_replay_slave_server(const char *replay_file); +#endif + static char *log_path; /* NULL means stderr */ static char *cmdline; static char *cmdline_end; @@ -33,7 +38,7 @@ extern int client_connect(char *path, int start_server); static void usage(void) { - fprintf(stderr, "usage: tmate-slave [-k keys_dir] [-l logfile] [-p PORT] [-v]\n"); + fprintf(stderr, "usage: tmate-slave [-k keys_dir] [-l logfile] [-p PORT] [-r logfile] [-v]\n"); } void tmate_reopen_logfile(void) @@ -59,8 +64,11 @@ int main(int argc, char **argv, char **envp) int opt; int port = TMATE_DEFAULT_PORT; const char *keys_dir = "keys"; +#ifdef TMATE_RECORD_REPLAY + const char *replay_file = NULL; +#endif - while ((opt = getopt(argc, argv, "p:l:vk:")) != -1) { + while ((opt = getopt(argc, argv, "p:l:vk:r:")) != -1) { switch (opt) { case 'p': port = atoi(optarg); @@ -74,6 +82,13 @@ int main(int argc, char **argv, char **envp) case 'v': debug_level++; break; + case 'r': +#ifdef TMATE_RECORD_REPLAY + replay_file = optarg; +#else + fprintf(stderr, "Record/Replay not enabled\n"); +#endif + break; default: usage(); return 1; @@ -85,16 +100,23 @@ int main(int argc, char **argv, char **envp) tmate_reopen_logfile(); + tmate_preload_trace_lib(); + if ((dev_urandom_fd = open("/dev/urandom", O_RDONLY)) < 0) tmate_fatal("Cannot open /dev/urandom"); +#ifdef TMATE_RECORD_REPLAY + if (replay_file) { + tmate_replay_slave_server(replay_file); + return 0; + } +#endif + if ((mkdir(TMATE_WORKDIR, 0700) < 0 && errno != EEXIST) || (mkdir(TMATE_WORKDIR "/sessions", 0700) < 0 && errno != EEXIST) || (mkdir(TMATE_WORKDIR "/jail", 0700) < 0 && errno != EEXIST)) tmate_fatal("Cannot prepare session in " TMATE_WORKDIR); - tmate_preload_trace_lib(); - tmate_ssh_server_main(keys_dir, port); return 0; } @@ -257,10 +279,36 @@ static void jail(void) static void setup_ncurse(int fd, const char *name) { int error; - if (setupterm(name, fd, &error) != OK) + if (setupterm((char *)name, fd, &error) != OK) tmate_fatal("Cannot setup terminal"); } +#ifdef TMATE_RECORD_REPLAY +static void tmate_replay_slave_server(const char *replay_file) +{ + struct tmate_decoder decoder; + struct tmate_replayer replayer; + + tmate_debug("Replaying slave server with %s", replay_file); + + tmux_socket_fd = server_create_socket(); + if (tmux_socket_fd < 0) + tmate_fatal("Cannot create to the tmux socket"); + + tmate_session_log_fd = open(replay_file, O_RDONLY); + if (tmate_session_log_fd < 0) + tmate_fatal("cannot open session-dump.log"); + + ev_base = osdep_event_init(); + + tmate_decoder_init(&decoder); + tmate_replayer_init(&replayer, &decoder, tmate_session_log_fd); + + tmux_server_init(IDENTIFY_UTF8 | IDENTIFY_256COLOURS); + /* never reached */ +} +#endif + static void tmate_spawn_slave_server(struct tmate_ssh_client *client) { char *token; @@ -290,6 +338,14 @@ static void tmate_spawn_slave_server(struct tmate_ssh_client *client) setup_ncurse(STDOUT_FILENO, "screen-256color"); close_fds_except((int[]){tmux_socket_fd, ssh_get_fd(client->session), fileno(log_file)}, 7); + +#ifdef TMATE_RECORD_REPLAY + tmate_session_log_fd = open("session-log.log", + O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (tmate_session_log_fd < 0) + tmate_fatal("cannot open session-dump.log"); +#endif + jail(); ev_base = osdep_event_init(); diff --git a/tmate-ssh-client.c b/tmate-ssh-client.c index e27fe130..e0f33455 100644 --- a/tmate-ssh-client.c +++ b/tmate-ssh-client.c @@ -12,6 +12,25 @@ extern void server_send_shutdown(void); server_send_shutdown(); \ } while(0) +#ifdef TMATE_RECORD_REPLAY +static void record_session_data(const char *buf, size_t len) +{ + ssize_t ret; + + while (len > 0) { + ret = write(tmate_session_log_fd, buf, len); + if (ret < 0) + tmate_fatal("cannot save recording of the session"); + + buf += ret; + len -= ret; + } +} +#else +static inline void record_session_data(const char *buf, size_t len) +{} +#endif + static void consume_channel(struct tmate_ssh_client *client) { char *buf; @@ -37,6 +56,8 @@ static void consume_channel(struct tmate_ssh_client *client) if (len == 0) break; + record_session_data(buf, len); + tmate_decoder_commit(client->decoder, len); } } diff --git a/tmate.h b/tmate.h index 3f781206..03bfb6ab 100644 --- a/tmate.h +++ b/tmate.h @@ -7,6 +7,8 @@ #include "tmux.h" +#define TMATE_RECORD_REPLAY /* useful to debug crashes */ + #define tmate_debug(str, ...) log_debug("[tmate] " str, ##__VA_ARGS__) #define tmate_info(str, ...) log_info("[tmate] " str, ##__VA_ARGS__) #define tmate_fatal(str, ...) \ @@ -73,6 +75,22 @@ extern void tmate_decoder_get_buffer(struct tmate_decoder *decoder, char **buf, size_t *len); extern void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len); +#ifdef TMATE_RECORD_REPLAY +/* tmate-replayer.c */ + +struct tmate_replayer { + struct tmate_decoder *decoder; + int log_fd; + + struct event ev_read_timer; +}; + +extern void tmate_replayer_init(struct tmate_replayer *replayer, + struct tmate_decoder *decoder, + int log_fd); +#endif + + /* tmate-ssh-client.c */ typedef struct ssh_session_struct* ssh_session; @@ -122,6 +140,10 @@ extern void tmate_flush_pty(struct tmate_ssh_client *client); #define TMATE_SSH_BANNER "tmate" #define TMATE_KEEPALIVE 60 +#ifdef TMATE_RECORD_REPLAY +extern int tmate_session_log_fd; +#endif + extern struct tmate_ssh_client tmate_client; extern void tmate_start_keepalive_timer(struct tmate_ssh_client *client); extern void tmate_ssh_server_main(const char *keys_dir, int port);