Ejemplo n.º 1
0
Archivo: tlog-rec.c Proyecto: opuk/tlog
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, &timestamp) == 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, &timestamp);
    tlog_pkt_init_window(&pkt, &timestamp,
                         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, &timestamp);
                tlog_pkt_init_window(&pkt, &timestamp,
                                     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, &timestamp);

        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, &timestamp,
                                     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, &timestamp,
                                     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;
}
Ejemplo n.º 2
0
tlog_grc
tlog_json_msg_read(struct tlog_json_msg *msg, struct tlog_pkt *pkt,
                   uint8_t *io_buf, size_t io_size)
{
    const char                 *timing_ptr;
    size_t                      io_len = 0;
    bool                        io_full = false;
    bool                        pkt_output;

    assert(tlog_json_msg_is_valid(msg));
    assert(!tlog_json_msg_is_void(msg));
    assert(tlog_pkt_is_valid(pkt));
    assert(tlog_pkt_is_void(pkt));
    assert(io_buf != NULL);
    assert(io_size >= TLOG_JSON_MSG_IO_SIZE_MIN);

    /* Until the I/O buffer (io_buf/io_size) is full */
    do {
        /*
         * Read next timing record if the current one is spent
         */
        if (msg->rem == 0) {
            char            type_buf[2];
            char            type;
            int             read;
            uint64_t        first_val;
            uint64_t        second_val;
            struct timespec delay;

            /* Skip leading whitespace */
            while (true) {
                switch (*msg->timing_ptr) {
                case ' ':
                case '\f':
                case '\n':
                case '\r':
                case '\t':
                case '\v':
                    msg->timing_ptr++;
                    continue;
                }
                break;
            }

            /* Modify timing pointer on the side to be able to rollback */
            timing_ptr = msg->timing_ptr;

            /* If reached the end of timing and so the end of data */
            if (*timing_ptr == 0) {
                /* Return whatever we have */
                break;
            }

            if (sscanf(timing_ptr, "%1[][><+=]%" SCNu64 "%n",
                       type_buf, &first_val, &read) < 2) {
                return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
            }
            type = *type_buf;
            timing_ptr += read;

            if (type == '[' || type == ']') {
                if (sscanf(timing_ptr, "/%" SCNu64 "%n", &second_val, &read) < 1) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                timing_ptr += read;
            } else if (type == '=') {
                if (sscanf(timing_ptr, "x%" SCNu64 "%n", &second_val, &read) < 1) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                timing_ptr += read;
            } else {
                second_val = 0;
            }

            /* If it is a delay record */
            if (type == '+') {
                if (first_val != 0) {
                    if (first_val > TLOG_DELAY_MAX_MS_NUM) {
                        return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                    }
                    /* If there was I/O already */
                    if (io_len > 0) {
                        /*
                         * We gotta return the old pos packet and re-read
                         * delay record next time
                         */
                        break;
                    }
                    delay.tv_sec = first_val / 1000;
                    delay.tv_nsec = first_val % 1000 * 1000000;
                    tlog_timespec_add(&msg->pos, &delay, &msg->pos);
                }
                /* Timing record consumed */
                msg->timing_ptr = timing_ptr;
                /* Read next timing record - no I/O from this one */
                continue;
            /* If it is a window record */
            } else if (type == '=') {
                /* If there was I/O already */
                if (io_len > 0) {
                    /*
                     * We gotta return the I/O packet and re-read
                     * window record next time
                     */
                    break;
                }
                /* Check extents */
                if (first_val > USHRT_MAX || second_val > USHRT_MAX) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                /* Return window packet */
                tlog_pkt_init_window(pkt, &msg->pos,
                                     (unsigned short int)first_val,
                                     (unsigned short int)second_val);
                /* Timing record consumed */
                msg->timing_ptr = timing_ptr;
                return TLOG_RC_OK;
            /* If it is a text input record */
            } else if (type == '<') {
                if (first_val > SIZE_MAX) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                msg->output = false;
                msg->binary = false;
                msg->rem = first_val;
                msg->ptxt_ptr = &msg->in_txt_ptr;
                msg->ptxt_len = &msg->in_txt_len;
            /* If it is a binary input record */
            } else if (type == '[') {
                if (first_val > SIZE_MAX || second_val > SIZE_MAX) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                msg->output = false;
                msg->binary = true;
                msg->rem = second_val;
                msg->ptxt_ptr = &msg->in_txt_ptr;
                msg->ptxt_len = &msg->in_txt_len;
                msg->bin_obj = msg->in_bin_obj;
                msg->pbin_pos = &msg->in_bin_pos;
            /* If it is a text output record */
            } else if (type == '>') {
                if (first_val > SIZE_MAX) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                msg->output = true;
                msg->binary = false;
                msg->rem = first_val;
                msg->ptxt_ptr = &msg->out_txt_ptr;
                msg->ptxt_len = &msg->out_txt_len;
            /* If it is a binary output record */
            } else if (type == ']') {
                if (first_val > SIZE_MAX || second_val > SIZE_MAX) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
                }
                msg->output = true;
                msg->binary = true;
                msg->rem = second_val;
                msg->ptxt_ptr = &msg->out_txt_ptr;
                msg->ptxt_len = &msg->out_txt_len;
                msg->bin_obj = msg->out_bin_obj;
                msg->pbin_pos = &msg->out_bin_pos;
            } else {
                assert(false);
                return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TIMING;
            }

            /* Timing record consumed */
            msg->timing_ptr = timing_ptr;

            if (msg->binary) {
                size_t l;
                /* Skip replacement characters */
                for (; first_val > 0; first_val--) {
                    /* If not enough text */
                    if (*msg->ptxt_len == 0) {
                        return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT;
                    }
                    l = tlog_json_msg_utf8_len(*(uint8_t *)*msg->ptxt_ptr);
                    /* If found invalid UTF-8 character in text */
                    if (l == 0) {
                        return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT;
                    }
                    /* If character crosses text boundary */
                    if (l > *msg->ptxt_len) {
                        return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT;
                    }
                    *msg->ptxt_len -= l;
                    *msg->ptxt_ptr += l;
                }
            }

            /* Ignore zero I/O */
            if (msg->rem == 0) {
                continue;
            }
        }

        /* Stop if the packet I/O is in different direction */
        if (io_len == 0) {
            pkt_output = msg->output;
        } else if (pkt_output != msg->output) {
            /* Return whatever we have */
            break;
        }

        /*
         * Append (a piece of) I/O to the output buffer
         */
        if (msg->binary) {
            struct json_object *o;
            int32_t n;

            for (; msg->rem > 0; msg->rem--, io_len++, (*msg->pbin_pos)++) {
                o = json_object_array_get_idx(msg->bin_obj, *msg->pbin_pos);
                /* If not enough bytes */
                if (o == NULL) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_BIN;
                }
                /* If supposed byte is not an int */
                if (json_object_get_type(o) != json_type_int) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_BIN;
                }
                n = json_object_get_int(o);
                /* If supposed byte value is out of range */
                if (n < 0 || n > UINT8_MAX) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_BIN;
                }
                io_buf[io_len] = (uint8_t)n;
                /* If the I/O buffer is full */
                if (io_len >= io_size) {
                    io_full = true;
                    break;
                }
            }
        } else {
            uint8_t b;
            size_t l;

            while (msg->rem > 0) {
                b = *(uint8_t *)*msg->ptxt_ptr;
                l = tlog_json_msg_utf8_len(b);
                /* If found invalid UTF-8 character in text */
                if (l == 0) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT;
                }
                /* If character crosses text boundary */
                if (l > *msg->ptxt_len) {
                    return TLOG_RC_JSON_MSG_FIELD_INVALID_VALUE_TXT;
                }
                /* If character crosses the I/O buffer boundary */
                if (io_len + l > io_size) {
                    io_full = true;
                    break;
                }
                while (true) {
                    io_buf[io_len] = b;
                    io_len++;
                    (*msg->ptxt_len)--;
                    (*msg->ptxt_ptr)++;
                    l--;
                    if (l == 0) {
                        break;
                    }
                    b = *(uint8_t *)*msg->ptxt_ptr;
                }
                msg->rem--;
            }
        }
    } while (!io_full);

    if (io_len > 0) {
        tlog_pkt_init_io(pkt, &msg->pos, pkt_output, io_buf, false, io_len);
    }

    return TLOG_RC_OK;
}