static tlog_grc run(const char *progname, struct json_object *conf) { tlog_grc grc; struct json_object *obj; int64_t num; unsigned int latency; unsigned int log_mask; const char *shell_path; char **shell_argv = NULL; const struct timespec delay_min = TLOG_DELAY_MIN_TIMESPEC; clockid_t clock_id; struct timespec timestamp; struct tlog_sink *log_sink = NULL; struct tlog_sink *tty_sink = NULL; struct tlog_source *tty_source = NULL; ssize_t rc; int master_fd; pid_t child_pid; bool term_attrs_set = false; struct termios orig_termios; struct termios raw_termios; struct winsize winsize; assert(progname != NULL); /* Check for the help flag */ if (json_object_object_get_ex(conf, "help", &obj)) { if (json_object_get_boolean(obj)) { tlog_rec_conf_cmd_help(stdout, progname); grc = TLOG_RC_OK; goto cleanup; } } /* Prepare shell command line */ grc = get_shell(conf, &shell_path, &shell_argv); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed building shell command line: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Read the log latency */ if (!json_object_object_get_ex(conf, "latency", &obj)) { fprintf(stderr, "Log latency is not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } num = json_object_get_int64(obj); latency = (unsigned int)num; /* Read log mask */ if (!json_object_object_get_ex(conf, "log", &obj)) { fprintf(stderr, "Logged data set parameters are not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } grc = get_log_mask(obj, &log_mask); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed reading log mask: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Create the log sink */ grc = create_log_sink(&log_sink, conf); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating log sink: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* * Choose the clock: try to use coarse monotonic clock (which is faster), * if it provides the required resolution. */ if (clock_getres(CLOCK_MONOTONIC_COARSE, ×tamp) == 0 && tlog_timespec_cmp(×tamp, &delay_min) <= 0) { clock_id = CLOCK_MONOTONIC_COARSE; } else if (clock_getres(CLOCK_MONOTONIC, NULL) == 0) { clock_id = CLOCK_MONOTONIC; } else { fprintf(stderr, "No clock to use\n"); goto cleanup; } /* Get terminal attributes */ rc = tcgetattr(STDOUT_FILENO, &orig_termios); if (rc < 0) { fprintf(stderr, "Failed retrieving tty attributes: %s\n", strerror(errno)); goto cleanup; } /* Get terminal window size */ rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize); if (rc < 0) { fprintf(stderr, "Failed retrieving tty window size: %s\n", strerror(errno)); goto cleanup; } /* Fork a child under a slave pty */ child_pid = forkpty(&master_fd, NULL, &orig_termios, &winsize); if (child_pid < 0) { grc = TLOG_GRC_ERRNO; fprintf(stderr, "Failed forking a pty: %s\n", tlog_grc_strerror(grc)); goto cleanup; } else if (child_pid == 0) { /* * Child */ execv(shell_path, shell_argv); grc = TLOG_GRC_ERRNO; fprintf(stderr, "Failed executing the shell: %s", tlog_grc_strerror(grc)); goto cleanup; } /* * Parent */ /* Read and output the notice */ if (json_object_object_get_ex(conf, "notice", &obj)) { fprintf(stderr, "%s", json_object_get_string(obj)); } /* Switch the terminal to raw mode */ raw_termios = orig_termios; raw_termios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); raw_termios.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK); raw_termios.c_oflag &= ~OPOST; raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0; rc = tcsetattr(STDOUT_FILENO, TCSAFLUSH, &raw_termios); if (rc < 0) { grc = TLOG_GRC_ERRNO; fprintf(stderr, "Failed setting tty attributes: %s\n", tlog_grc_strerror(grc)); goto cleanup; } term_attrs_set = true; /* Create the TTY source */ grc = tlog_tty_source_create(&tty_source, STDIN_FILENO, master_fd, STDOUT_FILENO, 4096, clock_id); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating TTY source: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Create the TTY sink */ grc = tlog_tty_sink_create(&tty_sink, master_fd, STDOUT_FILENO, master_fd); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating TTY sink: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Transfer and log the data until interrupted or either end is closed */ grc = transfer(tty_source, log_sink, tty_sink, latency, log_mask); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed transferring TTY data: %s\n", tlog_grc_strerror(grc)); goto cleanup; } grc = TLOG_RC_OK; cleanup: tlog_sink_destroy(log_sink); tlog_sink_destroy(tty_sink); tlog_source_destroy(tty_source); /* Free shell argv list */ if (shell_argv != NULL) { for (size_t i = 0; shell_argv[i] != NULL; i++) { free(shell_argv[i]); } free(shell_argv); } /* Restore terminal attributes */ if (term_attrs_set) { rc = tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig_termios); if (rc < 0 && errno != EBADF) { fprintf(stderr, "Failed restoring tty attributes: %s\n", strerror(errno)); return 1; } } return grc; }
int main(int argc, char **argv) { const int exit_sig[] = {SIGINT, SIGTERM, SIGHUP}; char *fqdn = NULL; struct passwd *passwd; struct tlog_writer *writer = NULL; clockid_t clock_id; struct timespec timestamp; struct tlog_sink *sink = NULL; tlog_grc grc; ssize_t rc; int master_fd; pid_t child_pid; unsigned int session_id; sig_atomic_t last_sigwinch_caught = 0; sig_atomic_t new_sigwinch_caught; sig_atomic_t last_alarm_caught = 0; sig_atomic_t new_alarm_caught; bool alarm_set = false; bool io_pending = false; bool term_attrs_set = false; struct termios orig_termios; struct termios raw_termios; struct winsize winsize; struct winsize new_winsize; size_t i, j; struct sigaction sa; struct pollfd fds[FD_IDX_NUM]; uint8_t input_buf[BUF_SIZE]; size_t input_pos = 0; size_t input_len = 0; uint8_t output_buf[BUF_SIZE]; size_t output_pos = 0; size_t output_len = 0; struct tlog_pkt pkt = TLOG_PKT_VOID; int status = 1; (void)argv; if (argc > 1) { fprintf(stderr, "Arguments are not accepted\n"); goto cleanup; } /* Get host FQDN */ grc = get_fqdn(&fqdn); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed retrieving host FQDN: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get session ID */ grc = get_session_id(&session_id); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed retrieving session ID: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Open syslog */ openlog("tlog", LOG_NDELAY, LOG_LOCAL0); /* Create the syslog writer */ grc = tlog_syslog_writer_create(&writer, LOG_INFO); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating syslog writer: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get effective user entry */ errno = 0; passwd = getpwuid(geteuid()); if (passwd == NULL) { if (errno == 0) fprintf(stderr, "User entry not found\n"); else fprintf(stderr, "Failed retrieving user entry: %s\n", strerror(errno)); goto cleanup; } /* * Choose the clock: try to use coarse monotonic clock (which is faster), * if it provides resolution of at least one millisecond. */ if (clock_getres(CLOCK_MONOTONIC_COARSE, ×tamp) == 0 && timestamp.tv_sec == 0 && timestamp.tv_nsec < 1000000) { clock_id = CLOCK_MONOTONIC_COARSE; } else if (clock_getres(CLOCK_MONOTONIC, NULL) == 0) { clock_id = CLOCK_MONOTONIC; } else { fprintf(stderr, "No clock to use\n"); goto cleanup; } /* Create the log sink */ grc = tlog_sink_create(&sink, writer, fqdn, passwd->pw_name, session_id, BUF_SIZE); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating log sink: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get terminal attributes */ rc = tcgetattr(STDOUT_FILENO, &orig_termios); if (rc < 0) { fprintf(stderr, "Failed retrieving tty attributes: %s\n", strerror(errno)); goto cleanup; } /* Get terminal window size */ rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize); if (rc < 0) { fprintf(stderr, "Failed retrieving tty window size: %s\n", strerror(errno)); goto cleanup; } /* Fork a child under a slave pty */ child_pid = forkpty(&master_fd, NULL, &orig_termios, &winsize); if (child_pid < 0) { fprintf(stderr, "Failed forking a pty: %s\n", strerror(errno)); goto cleanup; } else if (child_pid == 0) { /* * Child */ execl("/bin/bash", "/bin/bash", NULL); fprintf(stderr, "Failed to execute the shell: %s", strerror(errno)); goto cleanup; } /* * Parent */ /* Log initial window size */ clock_gettime(clock_id, ×tamp); tlog_pkt_init_window(&pkt, ×tamp, winsize.ws_col, winsize.ws_row); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging window size: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Setup signal handlers to terminate gracefully */ for (i = 0; i < ARRAY_SIZE(exit_sig); i++) { sigaction(exit_sig[i], NULL, &sa); if (sa.sa_handler != SIG_IGN) { sa.sa_handler = exit_sighandler; sigemptyset(&sa.sa_mask); for (j = 0; j < ARRAY_SIZE(exit_sig); j++) sigaddset(&sa.sa_mask, exit_sig[j]); /* NOTE: no SA_RESTART on purpose */ sa.sa_flags = 0; sigaction(exit_sig[i], &sa, NULL); } } /* Setup WINCH signal handler */ sa.sa_handler = winch_sighandler; sigemptyset(&sa.sa_mask); /* NOTE: no SA_RESTART on purpose */ sa.sa_flags = 0; sigaction(SIGWINCH, &sa, NULL); /* Setup ALRM signal handler */ sa.sa_handler = alarm_sighandler; sigemptyset(&sa.sa_mask); /* NOTE: no SA_RESTART on purpose */ sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); /* Switch the terminal to raw mode */ raw_termios = orig_termios; raw_termios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); raw_termios.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK); raw_termios.c_oflag &= ~OPOST; raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0; rc = tcsetattr(STDOUT_FILENO, TCSAFLUSH, &raw_termios); if (rc < 0) { fprintf(stderr, "Failed setting tty attributes: %s\n", strerror(errno)); goto cleanup; } term_attrs_set = true; /* * Transfer I/O and window changes */ fds[FD_IDX_STDIN].fd = STDIN_FILENO; fds[FD_IDX_STDIN].events = POLLIN; fds[FD_IDX_MASTER].fd = master_fd; fds[FD_IDX_MASTER].events = POLLIN; while (exit_signum == 0) { /* * Handle SIGWINCH */ new_sigwinch_caught = sigwinch_caught; if (new_sigwinch_caught != last_sigwinch_caught) { /* Retrieve window size */ rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &new_winsize); if (rc < 0) { if (errno == EBADF) status = 0; else fprintf(stderr, "Failed retrieving window size: %s\n", strerror(errno)); break; } /* If window size has changed */ if (new_winsize.ws_row != winsize.ws_row || new_winsize.ws_col != winsize.ws_col) { /* Log window size */ clock_gettime(clock_id, ×tamp); tlog_pkt_init_window(&pkt, ×tamp, new_winsize.ws_col, new_winsize.ws_row); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging window size: %s\n", tlog_grc_strerror(grc)); break; } /* Propagate window size */ rc = ioctl(master_fd, TIOCSWINSZ, &new_winsize); if (rc < 0) { if (errno == EBADF) status = 0; else fprintf(stderr, "Failed setting window size: %s\n", strerror(errno)); break; } winsize = new_winsize; } /* Mark SIGWINCH processed */ last_sigwinch_caught = new_sigwinch_caught; } /* * Deliver I/O */ clock_gettime(clock_id, ×tamp); if (input_pos < input_len) { rc = write(master_fd, input_buf + input_pos, input_len - input_pos); if (rc >= 0) { if (rc > 0) { /* Log delivered input */ tlog_pkt_init_io(&pkt, ×tamp, false, input_buf + input_pos, false, (size_t)rc); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging input: %s\n", tlog_grc_strerror(grc)); break; } io_pending = true; input_pos += rc; } /* If interrupted by a signal handler */ if (input_pos < input_len) continue; /* Exhausted */ input_pos = input_len = 0; } else { if (errno == EINTR) continue; else if (errno == EBADF || errno == EINVAL) status = 0; else fprintf(stderr, "Failed to write to master: %s\n", strerror(errno)); break; }; } if (output_pos < output_len) { rc = write(STDOUT_FILENO, output_buf + output_pos, output_len - output_pos); if (rc >= 0) { if (rc > 0) { /* Log delivered output */ tlog_pkt_init_io(&pkt, ×tamp, true, output_buf + output_pos, false, (size_t)rc); grc = tlog_sink_write(sink, &pkt); tlog_pkt_cleanup(&pkt); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed logging output: %s\n", tlog_grc_strerror(grc)); break; } io_pending = true; output_pos += rc; } /* If interrupted by a signal handler */ if (output_pos < output_len) continue; /* Exhausted */ output_pos = output_len = 0; } else { if (errno == EINTR) continue; else if (errno == EBADF || errno == EINVAL) status = 0; else fprintf(stderr, "Failed to write to stdout: %s\n", strerror(errno)); break; }; } /* * Handle I/O latency limit */ new_alarm_caught = alarm_caught; if (new_alarm_caught != last_alarm_caught) { alarm_set = false; grc = tlog_sink_flush(sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed flushing I/O log: %s\n", tlog_grc_strerror(grc)); goto cleanup; } last_alarm_caught = new_alarm_caught; io_pending = false; } else if (io_pending && !alarm_set) { alarm(IO_LATENCY); alarm_set = true; } /* * Wait for I/O */ rc = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); if (rc < 0) { if (errno == EINTR) continue; else fprintf(stderr, "Failed waiting for I/O: %s\n", strerror(errno)); break; } /* * Retrieve I/O * * NOTE: Reading master first in case child went away. * Otherwise writing to it can block */ if (fds[FD_IDX_MASTER].revents & (POLLIN | POLLHUP | POLLERR)) { rc = read(master_fd, output_buf, sizeof(output_buf)); if (rc <= 0) { if (rc < 0 && errno == EINTR) continue; status = 0; break; } output_len = rc; } if (fds[FD_IDX_STDIN].revents & (POLLIN | POLLHUP | POLLERR)) { rc = read(STDIN_FILENO, input_buf, sizeof(input_buf)); if (rc <= 0) { if (rc < 0 && errno == EINTR) continue; status = 0; break; } input_len = rc; } } /* Cut I/O log (write incomplete characters as binary) */ grc = tlog_sink_cut(sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed cutting-off I/O log: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Flush I/O log */ grc = tlog_sink_flush(sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed flushing I/O log: %s\n", tlog_grc_strerror(grc)); goto cleanup; } cleanup: tlog_sink_destroy(sink); tlog_writer_destroy(writer); free(fqdn); /* Restore signal handlers */ for (i = 0; i < ARRAY_SIZE(exit_sig); i++) { sigaction(exit_sig[i], NULL, &sa); if (sa.sa_handler != SIG_IGN) signal(exit_sig[i], SIG_DFL); } /* Restore terminal attributes */ if (term_attrs_set) { rc = tcsetattr(STDOUT_FILENO, TCSAFLUSH, &orig_termios); if (rc < 0 && errno != EBADF) { fprintf(stderr, "Failed restoring tty attributes: %s\n", strerror(errno)); return 1; } } /* Reproduce the exit signal to get proper exit status */ if (exit_signum != 0) raise(exit_signum); return status; }
/** * Create the log sink according to configuration. * * @param psink Location for the created sink pointer. * @param conf Configuration JSON object. * * @return Global return code. */ static tlog_grc create_log_sink(struct tlog_sink **psink, struct json_object *conf) { tlog_grc grc; int64_t num; const char *str; struct json_object *obj; struct tlog_sink *sink = NULL; struct tlog_json_writer *writer = NULL; int fd = -1; char *fqdn = NULL; struct passwd *passwd; unsigned int session_id; /* * Create the writer */ if (!json_object_object_get_ex(conf, "writer", &obj)) { fprintf(stderr, "Writer type is not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } str = json_object_get_string(obj); if (strcmp(str, "file") == 0) { struct json_object *conf_file; /* Get file writer conf container */ if (!json_object_object_get_ex(conf, "file", &conf_file)) { fprintf(stderr, "File writer parameters are not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } /* Get the file path */ if (!json_object_object_get_ex(conf_file, "path", &obj)) { fprintf(stderr, "Log file path is not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } str = json_object_get_string(obj); /* Open the file */ fd = open(str, O_WRONLY | O_CREAT | O_APPEND, S_IRWXU | S_IRWXG); if (fd < 0) { grc = TLOG_GRC_ERRNO; fprintf(stderr, "Failed opening log file \"%s\": %s\n", str, tlog_grc_strerror(grc)); goto cleanup; } /* Create the writer, letting it take over the FD */ grc = tlog_fd_json_writer_create(&writer, fd, true); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating file writer: %s\n", tlog_grc_strerror(grc)); goto cleanup; } fd = -1; } else if (strcmp(str, "syslog") == 0) { struct json_object *conf_syslog; int facility; int priority; /* Get syslog writer conf container */ if (!json_object_object_get_ex(conf, "syslog", &conf_syslog)) { fprintf(stderr, "Syslog writer parameters are not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } /* Get facility */ if (!json_object_object_get_ex(conf_syslog, "facility", &obj)) { fprintf(stderr, "Syslog facility is not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } str = json_object_get_string(obj); facility = tlog_syslog_facility_from_str(str); if (facility < 0) { fprintf(stderr, "Unknown syslog facility: %s\n", str); grc = TLOG_RC_FAILURE; goto cleanup; } /* Get priority */ if (!json_object_object_get_ex(conf_syslog, "priority", &obj)) { fprintf(stderr, "Syslog priority is not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } str = json_object_get_string(obj); priority = tlog_syslog_priority_from_str(str); if (priority < 0) { fprintf(stderr, "Unknown syslog priority: %s\n", str); grc = TLOG_RC_FAILURE; goto cleanup; } /* Create the writer */ openlog("tlog", LOG_NDELAY, facility); grc = tlog_syslog_json_writer_create(&writer, priority); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating syslog writer: %s\n", tlog_grc_strerror(grc)); goto cleanup; } } else { fprintf(stderr, "Unknown writer type: %s\n", str); grc = TLOG_RC_FAILURE; goto cleanup; } /* * Create the sink */ /* Get host FQDN */ grc = get_fqdn(&fqdn); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed retrieving host FQDN: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get session ID */ grc = get_session_id(&session_id); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed retrieving session ID: %s\n", tlog_grc_strerror(grc)); goto cleanup; } /* Get effective user entry */ errno = 0; passwd = getpwuid(geteuid()); if (passwd == NULL) { if (errno == 0) { grc = TLOG_RC_FAILURE; fprintf(stderr, "User entry not found\n"); } else { grc = TLOG_GRC_ERRNO; fprintf(stderr, "Failed retrieving user entry: %s\n", tlog_grc_strerror(grc)); } goto cleanup; } /* Get the maximum payload size */ if (!json_object_object_get_ex(conf, "payload", &obj)) { fprintf(stderr, "Maximum payload size is not specified\n"); grc = TLOG_RC_FAILURE; goto cleanup; } num = json_object_get_int64(obj); /* Create the sink, letting it take over the writer */ grc = tlog_json_sink_create(&sink, writer, true, fqdn, passwd->pw_name, session_id, (size_t)num); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed creating log sink: %s\n", tlog_grc_strerror(grc)); goto cleanup; } writer = NULL; *psink = sink; sink = NULL; grc = TLOG_RC_OK; cleanup: if (fd >= 0) { close(fd); } tlog_json_writer_destroy(writer); free(fqdn); tlog_sink_destroy(sink); return grc; }