static tlog_grc tlog_journal_json_reader_init(struct tlog_json_reader *reader, va_list ap) { struct tlog_journal_json_reader *journal_json_reader = (struct tlog_journal_json_reader*)reader; uint64_t since = va_arg(ap, uint64_t); uint64_t until = va_arg(ap, uint64_t); const char * const *match_sym_list = va_arg(ap, const char * const *); int sd_rc; tlog_grc grc; /* Create JSON tokener */ journal_json_reader->tok = json_tokener_new(); if (journal_json_reader->tok == NULL) { grc = TLOG_GRC_ERRNO; goto error; } /* Open journal */ sd_rc = sd_journal_open(&journal_json_reader->journal, 0); if (sd_rc < 0) { grc = TLOG_GRC_FROM(systemd, sd_rc); goto error; } /* Add matches */ sd_rc = tlog_journal_add_match_sym_list(journal_json_reader->journal, match_sym_list); if (sd_rc < 0) { grc = TLOG_GRC_FROM(systemd, sd_rc); goto error; } /* Seek to "since" timestamp */ sd_rc = sd_journal_seek_realtime_usec(journal_json_reader->journal, since); if (sd_rc < 0) { grc = TLOG_GRC_FROM(systemd, sd_rc); goto error; } /* Store "until" timestamp */ journal_json_reader->until = until; return TLOG_RC_OK; error: tlog_journal_json_reader_cleanup(reader); return grc; }
/** * Get fully-qualified name of this host. * * @param pfqdn Location for the dynamically-allocated name. * * @return Status code. */ static tlog_grc get_fqdn(char **pfqdn) { char hostname[HOST_NAME_MAX + 1]; struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_flags = AI_CANONNAME}; struct addrinfo *info; int gai_error; assert(pfqdn != NULL); /* Get hostname */ if (gethostname(hostname, sizeof(hostname)) < 0) return TLOG_GRC_ERRNO; /* Resolve hostname to FQDN */ gai_error = getaddrinfo(hostname, NULL, &hints, &info); if (gai_error != 0) return TLOG_GRC_FROM(gai, gai_error); /* Duplicate retrieved FQDN */ *pfqdn = strdup(info->ai_canonname); freeaddrinfo(info); if (*pfqdn == NULL) return TLOG_GRC_ERRNO; return TLOG_RC_OK; }
static tlog_grc tlog_journal_json_writer_write(struct tlog_json_writer *writer, size_t id, const uint8_t *buf, size_t len) { struct tlog_journal_json_writer *journal_json_writer = (struct tlog_journal_json_writer*)writer; int sd_rc; #define BASE_ARGS \ "PRIORITY=%d", journal_json_writer->priority, \ "MESSAGE=%.*s", len, buf if (journal_json_writer->augment) { sd_rc = sd_journal_send( "TLOG_REC=%s", journal_json_writer->recording, "TLOG_USER=%s", journal_json_writer->username, "TLOG_SESSION=%u", journal_json_writer->session_id, "TLOG_ID=%zu", id, BASE_ARGS, NULL); } else { sd_rc = sd_journal_send(BASE_ARGS, NULL); } #undef BASE_ARGS return (sd_rc < 0) ? TLOG_GRC_FROM(systemd, sd_rc) : TLOG_RC_OK; }
/** * Format an ElasticSearch request URL prefix: the URL ending with "from=", * ready for the addition of the message index. * * @param purl_pfx The location for the dynamically-allocated URL prefix. * @param curl The CURL handle to escape with. * @param base_url The base URL to request ElasticSearch, without the query * or the fragment parts. * @param query The query string to send to ElastiSearch. * @param size Number of messages to request from ElasticSearch in one * HTTP request. * * @return Global return code. */ static tlog_grc tlog_es_reader_format_url_pfx(char **purl_pfx, CURL *curl, const char *base_url, const char *query, size_t size) { static const char *sort = "id:asc"; tlog_grc grc; char *url_pfx; char *esc_query = NULL; char *esc_sort = NULL; assert(purl_pfx != NULL); assert(curl != NULL); assert(tlog_es_reader_base_url_is_valid(base_url)); assert(query != NULL); assert(size >= TLOG_ES_READER_SIZE_MIN); #define ESC(_token) \ do { \ esc_##_token = curl_easy_escape(curl, _token, 0); \ if (esc_##_token == NULL) { \ grc = TLOG_GRC_FROM(errno, ENOMEM); \ goto cleanup; \ } \ } while (0) ESC(query); ESC(sort); #undef ESC if (asprintf(&url_pfx, "%s?q=%s&fields=&_source&sort=%s&size=%zu&from=", base_url, esc_query, esc_sort, size) < 0) { grc = TLOG_GRC_FROM(errno, ENOMEM); goto cleanup; } *purl_pfx = url_pfx; grc = TLOG_RC_OK; cleanup: if (esc_sort != NULL) { curl_free(esc_sort); } if (esc_query != NULL) { curl_free(esc_query); } return grc; }
static tlog_grc tlog_es_reader_init(struct tlog_reader *reader, va_list ap) { struct tlog_es_reader *es_reader = (struct tlog_es_reader*)reader; const char *base_url = va_arg(ap, const char *); const char *query = va_arg(ap, const char *); size_t size = va_arg(ap, size_t); CURLcode rc; tlog_grc grc; assert(tlog_es_reader_base_url_is_valid(base_url)); assert(query != NULL); assert(size >= TLOG_ES_READER_SIZE_MIN); /* Create and initialize CURL handle */ es_reader->curl = curl_easy_init(); if (es_reader->curl == NULL) { grc = TLOG_RC_ES_READER_CURL_INIT_FAILED; goto error; } rc = curl_easy_setopt(es_reader->curl, CURLOPT_WRITEFUNCTION, tlog_es_reader_write_func); if (rc != CURLE_OK) { grc = TLOG_GRC_FROM(curl, rc); goto error; } /* Format URL prefix */ grc = tlog_es_reader_format_url_pfx(&es_reader->url_pfx, es_reader->curl, base_url, query, size); if (grc != TLOG_RC_OK) { goto error; } /* Set request size */ es_reader->size = size; /* Create JSON tokener */ es_reader->tok = json_tokener_new(); if (es_reader->tok == NULL) { grc = TLOG_GRC_ERRNO; goto error; } return TLOG_RC_OK; error: tlog_es_reader_cleanup(reader); return grc; }
/** * Get process session ID. * * @param pid Location for the retrieved session ID. * * @return Global return code. */ static tlog_grc get_session_id(unsigned int *pid) { FILE *file; int orig_errno; int rc; assert(pid != NULL); file = fopen("/proc/self/sessionid", "r"); if (file == NULL) return TLOG_RC_FAILURE; rc = fscanf(file, "%u", pid); orig_errno = errno; fclose(file); if (rc == 1) return TLOG_RC_OK; if (rc == 0) return TLOG_GRC_FROM(errno, EINVAL); return TLOG_GRC_FROM(errno, orig_errno); }
/** * Format an ElasticSearch request URL for the current message index of a * reader. * * @param reader The reader to format the URL for. * @param purl The location for the dynamically-allocated URL. * * @return Global return code. */ static tlog_grc tlog_es_reader_format_url(struct tlog_es_reader *es_reader, char **purl) { char *url; assert(tlog_es_reader_is_valid((struct tlog_reader*)es_reader)); if (asprintf(&url, "%s%zu", es_reader->url_pfx, es_reader->idx) < 0) { return TLOG_GRC_FROM(errno, ENOMEM); } *purl = url; return TLOG_RC_OK; }
/** * Read the memory reader text as a JSON object line, don't consume * terminating newline. The line must not start with whitespace. * * @param mem_json_reader The memory reader to parse the object for. * @param pobject Location for the parsed object pointer. * * @return Global return code. */ static tlog_grc tlog_mem_json_reader_read_json(struct tlog_mem_json_reader *mem_json_reader, struct json_object **pobject) { const char *p; struct json_object *object; enum json_tokener_error jerr; assert(tlog_mem_json_reader_is_valid( (struct tlog_json_reader *)mem_json_reader)); assert(pobject != NULL); json_tokener_reset(mem_json_reader->tok); /* Look for a terminating newline */ for (p = mem_json_reader->pos; p < mem_json_reader->end && *p != '\n'; p++); /* If the line is empty */ if (p == mem_json_reader->pos) { /* Report EOF */ *pobject = NULL; return TLOG_RC_OK; } /* Parse the line */ object = json_tokener_parse_ex(mem_json_reader->tok, mem_json_reader->pos, p - mem_json_reader->pos); mem_json_reader->pos = p; if (object == NULL) { jerr = json_tokener_get_error(mem_json_reader->tok); return (jerr == json_tokener_continue) ? TLOG_RC_MEM_JSON_READER_INCOMPLETE_LINE : TLOG_GRC_FROM(json, jerr); } /* Return the parsed object */ *pobject = object; return TLOG_RC_OK; }
tlog_grc tlog_journal_json_reader_read(struct tlog_json_reader *reader, struct json_object **pobject) { struct tlog_journal_json_reader *journal_json_reader = (struct tlog_journal_json_reader*)reader; tlog_grc grc; int sd_rc; struct json_object *object = NULL; /* If we ran out of time limit */ if (journal_json_reader->last > journal_json_reader->until) { goto exit; } /* Advance to the next entry */ sd_rc = sd_journal_next(journal_json_reader->journal); /* If failed */ if (sd_rc < 0) { grc = TLOG_GRC_FROM(systemd, sd_rc); goto cleanup; /* If got an entry */ } else if (sd_rc > 0) { const char *field_ptr; size_t field_len; const char *message_ptr; size_t message_len; /* Advance entry counter */ journal_json_reader->entry++; /* Get the entry realtime timestamp */ sd_rc = sd_journal_get_realtime_usec(journal_json_reader->journal, &journal_json_reader->last); if (sd_rc < 0) { grc = TLOG_GRC_FROM(systemd, sd_rc); goto cleanup; } if (journal_json_reader->last > journal_json_reader->until) { goto exit; } /* Get the entry message field data */ sd_rc = sd_journal_get_data(journal_json_reader->journal, "MESSAGE", (const void **)&field_ptr, &field_len); if (sd_rc < 0) { grc = TLOG_GRC_FROM(systemd, sd_rc); goto cleanup; } /* Extract the message */ message_ptr = (const char *)memchr(field_ptr, '=', field_len); if (message_ptr == NULL) { grc = TLOG_RC_FAILURE; goto cleanup; } message_ptr++; message_len = field_len - (message_ptr - field_ptr); /* Parse the message */ object = json_tokener_parse_ex(journal_json_reader->tok, message_ptr, message_len); if (object == NULL) { grc = TLOG_GRC_FROM( json, json_tokener_get_error(journal_json_reader->tok)); goto cleanup; } } exit: *pobject = object; object = NULL; grc = TLOG_RC_OK; cleanup: json_object_put(object); return grc; }
/** * Refill the retrieved message array of a reader. * * @param reader The reader to refill the message array for. * * @return Global return code. */ static tlog_grc tlog_es_reader_refill_array(struct tlog_es_reader *es_reader) { tlog_grc grc; char *url = NULL; struct tlog_es_reader_write_data data; CURLcode rc; assert(tlog_es_reader_is_valid((struct tlog_reader *)es_reader)); /* Free the previous array, if any */ if (es_reader->array != NULL) { json_object_put(es_reader->array); es_reader->array = NULL; es_reader->array_len = 0; } /* Format request URL */ grc = tlog_es_reader_format_url(es_reader, &url); if (grc != TLOG_RC_OK) { goto cleanup; } /* Set request URL */ rc = curl_easy_setopt(es_reader->curl, CURLOPT_URL, url); if (rc != CURLE_OK) { grc = TLOG_GRC_FROM(curl, rc); goto cleanup; } /* Set callback data */ data.tok = es_reader->tok; data.rc = json_tokener_success; data.obj = NULL; rc = curl_easy_setopt(es_reader->curl, CURLOPT_WRITEDATA, &data); if (rc != CURLE_OK) { grc = TLOG_GRC_FROM(curl, rc); goto cleanup; } /* Perform the request */ rc = curl_easy_perform(es_reader->curl); if (rc != CURLE_OK) { if (rc == CURLE_WRITE_ERROR && data.rc != json_tokener_success) { grc = TLOG_GRC_FROM(json, data.rc); } else { grc = TLOG_GRC_FROM(curl, rc); } goto cleanup; } /* If no data was read */ if (data.obj == NULL) { es_reader->array = NULL; es_reader->array_len = 0; } else { struct json_object *obj; /* Extract the array */ if (!json_object_object_get_ex(data.obj, "hits", &obj) || !json_object_object_get_ex(obj, "hits", &obj) || json_object_get_type(obj) != json_type_array) { grc = TLOG_RC_ES_READER_REPLY_INVALID; goto cleanup; } es_reader->array = json_object_get(obj); es_reader->array_len = json_object_array_length(obj); } es_reader->array_idx = es_reader->idx; grc = TLOG_RC_OK; cleanup: if (data.obj != NULL) { json_object_put(data.obj); } free(url); return grc; }
/** * Read the fd reader text as (a part of) a JSON object line, don't consume * terminating newline. * * @param fd_json_reader The fd reader to parse the object for. * @param pobject Location for the parsed object pointer. * * @return Global return code. */ static tlog_grc tlog_fd_json_reader_read_json(struct tlog_fd_json_reader *fd_json_reader, struct json_object **pobject) { tlog_grc grc; char *p; struct json_object *object; enum json_tokener_error jerr; bool got_text = false; assert(tlog_fd_json_reader_is_valid( (struct tlog_json_reader *)fd_json_reader)); assert(pobject != NULL); json_tokener_reset(fd_json_reader->tok); /* Until EOF */ do { /* If the buffer is not empty */ if (fd_json_reader->pos < fd_json_reader->end) { /* We got something to parse */ got_text = true; /* Look for a terminating newline */ for (p = fd_json_reader->pos; p < fd_json_reader->end && *p != '\n'; p++); /* Parse the next piece */ object = json_tokener_parse_ex(fd_json_reader->tok, fd_json_reader->pos, p - fd_json_reader->pos); fd_json_reader->pos = p; /* If we finished parsing an object */ if (object != NULL) { *pobject = object; return TLOG_RC_OK; } else { jerr = json_tokener_get_error(fd_json_reader->tok); /* If object is not finished */ if (jerr == json_tokener_continue) { /* If we encountered an object-terminating newline */ if (p < fd_json_reader->end) { return TLOG_RC_FD_JSON_READER_INCOMPLETE_LINE; } } else { return TLOG_GRC_FROM(json, jerr); } } } grc = tlog_fd_json_reader_refill_buf(fd_json_reader); if (grc != TLOG_RC_OK) return grc; } while (fd_json_reader->end > fd_json_reader->buf); if (got_text) { return TLOG_RC_FD_JSON_READER_INCOMPLETE_LINE; } else { *pobject = NULL; return TLOG_RC_OK; } }
/** * Transfer and log terminal data until interrupted or either end closes. * * @param tty_source TTY data source. * @param log_sink Log sink. * @param tty_sink TTY data sink. * @param latency Number of seconds to wait before flushing logged data. * @param log_mask Logging mask with bits indexed by enum tlog_log_item. * * @return Global return code. */ static tlog_grc transfer(struct tlog_source *tty_source, struct tlog_sink *log_sink, struct tlog_sink *tty_sink, unsigned int latency, unsigned int log_mask) { const int exit_sig[] = {SIGINT, SIGTERM, SIGHUP}; tlog_grc return_grc = TLOG_RC_OK; tlog_grc grc; size_t i, j; struct sigaction sa; bool alarm_set = false; bool log_pending = false; sig_atomic_t last_alarm_caught = 0; sig_atomic_t new_alarm_caught; struct tlog_pkt pkt = TLOG_PKT_VOID; struct tlog_pkt_pos tty_pos = TLOG_PKT_POS_VOID; struct tlog_pkt_pos log_pos = TLOG_PKT_POS_VOID; /* Setup signal handlers to terminate gracefully */ for (i = 0; i < TLOG_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 < TLOG_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 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); /* * Transfer I/O and window changes */ while (exit_signum == 0) { /* Handle latency limit */ new_alarm_caught = alarm_caught; if (new_alarm_caught != last_alarm_caught) { alarm_set = false; grc = tlog_sink_flush(log_sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed flushing log: %s\n", tlog_grc_strerror(grc)); return_grc = grc; goto cleanup; } last_alarm_caught = new_alarm_caught; log_pending = false; } else if (log_pending && !alarm_set) { alarm(latency); alarm_set = true; } /* Deliver logged data if any */ if (tlog_pkt_pos_cmp(&tty_pos, &log_pos) < 0) { grc = tlog_sink_write(tty_sink, &pkt, &tty_pos, &log_pos); if (grc != TLOG_RC_OK) { if (grc == TLOG_GRC_FROM(errno, EINTR)) { continue; } else if (grc != TLOG_GRC_FROM(errno, EBADF) && grc != TLOG_GRC_FROM(errno, EINVAL)) { fprintf(stderr, "Failed writing terminal data: %s\n", tlog_grc_strerror(grc)); return_grc = grc; } break; } continue; } /* Log the received data, if any */ if (tlog_pkt_pos_is_in(&log_pos, &pkt)) { /* If asked to log this type of packet */ if (log_mask & (1 << tlog_log_item_from_pkt(&pkt))) { grc = tlog_sink_write(log_sink, &pkt, &log_pos, NULL); if (grc != TLOG_RC_OK && grc != TLOG_GRC_FROM(errno, EINTR)) { fprintf(stderr, "Failed logging terminal data: %s\n", tlog_grc_strerror(grc)); return_grc = grc; goto cleanup; } log_pending = true; } else { tlog_pkt_pos_move_past(&log_pos, &pkt); } continue; } /* Read new data */ tlog_pkt_cleanup(&pkt); log_pos = TLOG_PKT_POS_VOID; tty_pos = TLOG_PKT_POS_VOID; grc = tlog_source_read(tty_source, &pkt); if (grc != TLOG_RC_OK) { if (grc == TLOG_GRC_FROM(errno, EINTR)) { continue; } else if (grc != TLOG_GRC_FROM(errno, EBADF) && grc != TLOG_GRC_FROM(errno, EIO)) { fprintf(stderr, "Failed reading terminal data: %s\n", tlog_grc_strerror(grc)); return_grc = grc; } break; } else if (tlog_pkt_is_void(&pkt)) { break; } } /* Cut the log (write incomplete characters as binary) */ grc = tlog_sink_cut(log_sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed cutting-off the log: %s\n", tlog_grc_strerror(grc)); if (return_grc == TLOG_RC_OK) { return_grc = grc; } goto cleanup; } /* Flush the log */ grc = tlog_sink_flush(log_sink); if (grc != TLOG_RC_OK) { fprintf(stderr, "Failed flushing the log: %s\n", tlog_grc_strerror(grc)); if (return_grc == TLOG_RC_OK) { return_grc = grc; } goto cleanup; } cleanup: tlog_pkt_cleanup(&pkt); /* Stop the timer */ alarm(0); /* Restore signal handlers */ signal(SIGALRM, SIG_DFL); for (i = 0; i < TLOG_ARRAY_SIZE(exit_sig); i++) { sigaction(exit_sig[i], NULL, &sa); if (sa.sa_handler != SIG_IGN) signal(exit_sig[i], SIG_DFL); } return return_grc; }
static tlog_grc tlog_json_sink_flush(struct tlog_sink *sink) { struct tlog_json_sink *json_sink = (struct tlog_json_sink *)sink; tlog_grc grc; char pos_buf[32]; int len; struct timespec pos; if (tlog_json_chunk_is_empty(&json_sink->chunk)) { return TLOG_RC_OK; } /* Write terminating metadata records to reserved space */ tlog_json_chunk_flush(&json_sink->chunk); tlog_timespec_sub(&json_sink->chunk.first_ts, &json_sink->start, &pos); if (pos.tv_sec == 0) { len = snprintf(pos_buf, sizeof(pos_buf), "%ld", pos.tv_nsec / 1000000); } else { len = snprintf(pos_buf, sizeof(pos_buf), "%lld%03ld", (long long int)pos.tv_sec, pos.tv_nsec / 1000000); } if ((size_t)len >= sizeof(pos_buf)) { return TLOG_GRC_FROM(errno, ENOMEM); } len = snprintf( (char *)json_sink->message_buf, json_sink->message_len, "{" "\"ver\":" "\"2.2\"," "\"host\":" "\"%s\"," "\"rec\":" "\"%s\"," "\"user\":" "\"%s\"," "\"term\":" "\"%s\"," "\"session\":" "%u," "\"id\":" "%zu," "\"pos\":" "%s," "\"timing\":" "\"%.*s\"," "\"in_txt\":" "\"%.*s\"," "\"in_bin\":" "[%.*s]," "\"out_txt\":" "\"%.*s\"," "\"out_bin\":" "[%.*s]" "}\n", json_sink->hostname, json_sink->recording, json_sink->username, json_sink->terminal, json_sink->session_id, json_sink->message_id, pos_buf, (int)(json_sink->chunk.timing_ptr - json_sink->chunk.timing_buf), json_sink->chunk.timing_buf, (int)json_sink->chunk.input.txt_len, json_sink->chunk.input.txt_buf, (int)json_sink->chunk.input.bin_len, json_sink->chunk.input.bin_buf, (int)json_sink->chunk.output.txt_len, json_sink->chunk.output.txt_buf, (int)json_sink->chunk.output.bin_len, json_sink->chunk.output.bin_buf); if (len < 0) { return TLOG_RC_FAILURE; } if ((size_t)len >= json_sink->message_len) { return TLOG_GRC_FROM(errno, ENOMEM); } grc = tlog_json_writer_write(json_sink->writer, json_sink->message_id, json_sink->message_buf, len); if (grc != TLOG_RC_OK) { return grc; } json_sink->message_id++; tlog_json_chunk_empty(&json_sink->chunk); return TLOG_RC_OK; }