blob: 5b631746a93785796fb8a1b2186b293fa18e3c8f [file] [log] [blame]
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>
#include "log_uploader.h"
#include "utils.h"
int logmark_once(const char* output_path, const char* version_path,
const char* ntp_sync_path) {
char buf[1024];
char version[64];
struct tm timeinfo;
struct timespec curr_time;
if (read_file_as_string(version_path, version, sizeof(version)) < 0) {
strcpy(version, "unknown-version");
}
rstrip(version);
memset(&timeinfo, 0, sizeof(timeinfo));
memset(&curr_time, 0, sizeof(curr_time));
clock_gettime(CLOCK_REALTIME, &curr_time);
localtime_r(&curr_time.tv_sec, &timeinfo);
snprintf(buf, sizeof(buf), "<7>T: %s %d.%03d %02d/%02d %02d:%02d:%02d "
"ntp=%d\n",
version, (int)curr_time.tv_sec, (int)(curr_time.tv_nsec/1000000L),
timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min,
timeinfo.tm_sec, path_exists(ntp_sync_path));
int rv = write_to_file(output_path, buf);
if (rv < 0) {
perror(output_path);
return -1;
}
return 0;
}
char* parse_and_consume_log_data(struct log_parse_params* params) {
unsigned long log_buffer_size = params->total_read;
int wrote_start_marker = 0;
char *last_start_marker, *last_start_before_end_marker;
int check_for_markers = (params->last_log_counter == 0);
struct line_data parsed_line;
memset(&parsed_line, 0, sizeof(parsed_line));
ssize_t num_read;
if (check_for_markers)
last_start_marker = last_start_before_end_marker = NULL;
params->total_read = 0;
while (1) {
// Make sure we have room in our main buffer for another line if we
// don't then just stop reading now. This should never really happen,
// but we should protect against it anyways.
// Use 2x so that we can also log data about missing entries if we
// need to do that as well.
if (params->total_read + 2*params->line_buffer_size >= log_buffer_size) {
break;
}
if (!params->last_line_valid) {
// Read the next line from /dev/kmsg
num_read = params->read_log_data(params->line_buffer,
params->line_buffer_size, params->user_data);
} else {
// We have our start marker in the line buffer from the last run
// of the loop, just leave it there and we'll parse it.
params->last_line_valid = 0;
num_read = 0;
}
if (num_read < 0) {
if (errno == EAGAIN) {
if (!wrote_start_marker) {
// Write out the start marker.
if (write_to_file(params->dev_kmsg_path, LOG_MARKER_START_LINE) < 0) {
perror("start marker");
return NULL;
}
if (logmark_once(params->dev_kmsg_path, params->version_path,
params->ntp_synced_path) < 0) {
fprintf(stderr, "failed to execute logmark-once properly\n");
return NULL;
}
wrote_start_marker = 1;
continue;
} else {
// We have finished reading everything, exit the loop.
// But this should never really happen because we should find
// out start marker instead and exit that way.
break;
}
} else if (errno == EPIPE) {
// This means the last message we read is no longer there,
// it will then reset us to the beginning so just continue on
// and we will log that we lost messages. This also will happen
// the first time /dev/kmsg is opened.
continue;
} else if (errno == EINVAL) {
// This should never happen because we use an 8192 size buf which is
// the same size as the kernel uses for the temp buf to copy the result
// to. So if this happens...that likely means there is kernel
// corruption.
perror("kernel memory possibly corrupted, devkmsg_read");
continue;
} else {
perror("/dev/kmsg");
return NULL;
}
}
// Parse the data on the line to get the extra information
if (parse_line_data(params->line_buffer, &parsed_line)) {
// We don't want to be fatal if we fail to parse a line for some
// reason because we'd then repeatedly fail on that line when we
// were restarted. Instead just use it the way it is.
memcpy(params->log_buffer + params->total_read, params->line_buffer,
num_read);
params->total_read += num_read;
} else {
if (wrote_start_marker && strstr(parsed_line.text, LOG_MARKER_START)) {
// We just read back in our start marker that we wrote out. Leave
// it in the buffer and stop reading at this point.
params->last_line_valid = 1;
break;
}
// Successfully parsed a log line, check to see if we're at the right
// spot relative to the counter.
int time_sec = (int) (parsed_line.ts_nsec / 1000000);
int time_usec = (int) (parsed_line.ts_nsec % 1000000);
if (params->last_log_counter > 0) {
if (parsed_line.seq > params->last_log_counter + 1) {
// Put this in what we upload so when viewing it then we
// know there was loss and its interleaved at the correct spot.
params->total_read += snprintf(params->log_buffer +
params->total_read, log_buffer_size - params->total_read,
"<7>[%5d.%06d] WARNING: missed %" PRIu64 " log entries\n",
time_sec, time_usec,
parsed_line.seq - params->last_log_counter - 1);
} else if (!params->config->upload_all &&
parsed_line.seq <= params->last_log_counter) {
// Skip this entry because we've already read it and uploaded
// it. This'll happen if we crash and are restarted.
continue;
}
}
// If we don't have our tracking working then we need to look at the
// markers in the log to avoid uploading tons of duplicate data.
// This should only happen after a reboot.
if (check_for_markers) {
if (strstr(parsed_line.text, LOG_MARKER_END) && last_start_marker) {
last_start_before_end_marker = last_start_marker;
last_start_marker = NULL;
} else if (strstr(parsed_line.text, LOG_MARKER_START)) {
last_start_marker = params->log_buffer + params->total_read;
}
}
params->total_read += snprintf(params->log_buffer +
params->total_read, log_buffer_size - params->total_read,
"<%d>[%5d.%06d] %s", parsed_line.level, time_sec, time_usec,
parsed_line.text);
params->last_log_counter = parsed_line.seq;
}
}
params->log_buffer[params->total_read] = '\0';
if (check_for_markers && last_start_before_end_marker) {
// We have duplicate data from an upload (should have been before
// a reboot which just occurred). Only upload what's been there
// since the last start marker before the last end marker. We will
// still upload duplicate data, but it would only be what was logged
// during the timeframe of us writing the start marker and consuming
// all the log data which should be a very short timeframe.
int dupe_size = last_start_before_end_marker - params->log_buffer;
params->total_read -= dupe_size;
return last_start_before_end_marker;
}
return params->log_buffer;
}