X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fjournalctl.c;h=da4f51021d87853df227d08a52395d9f1a068442;hp=5f17f45cac9e4a96ad490b0f375e66828ac0616f;hb=cf244689e9d1ab50082c9ddd0f3c4d1eb982badc;hpb=cec736d21ff86c4ac81b4d306ddba2120333818c diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 5f17f45ca..da4f51021 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -22,65 +22,526 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include -#include "journal-file.h" +#include "sd-journal.h" +#include "log.h" +#include "util.h" +#include "build.h" +#include "pager.h" -int main(int argc, char *argv[]) { +#define PRINT_THRESHOLD 128 + +static enum { + OUTPUT_SHORT, + OUTPUT_VERBOSE, + OUTPUT_EXPORT, + OUTPUT_JSON, + _OUTPUT_MAX +} arg_output = OUTPUT_SHORT; + +static bool arg_follow = false; +static bool arg_show_all = false; +static bool arg_no_pager = false; + +static bool contains_unprintable(const void *p, size_t l) { + const char *j; + + for (j = p; j < (const char *) p + l; j++) + if (*j < ' ' || *j >= 127) + return true; + + return false; +} + +static int output_short(sd_journal *j, unsigned line) { + int r; + uint64_t realtime; + time_t t; + struct tm tm; + char buf[64]; + const void *data; + size_t length; + size_t n = 0; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime: %s", strerror(-r)); + return r; + } + + t = (time_t) (realtime / USEC_PER_SEC); + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) { + log_error("Failed to format time."); + return -EINVAL; + } + + fputs(buf, stdout); + n += strlen(buf); + + if (sd_journal_get_data(j, "_HOSTNAME", &data, &length) >= 0 && + (arg_show_all || (!contains_unprintable(data, length) && + length < PRINT_THRESHOLD))) { + printf(" %.*s", (int) length - 10, ((const char*) data) + 10); + n += length - 10 + 1; + } + + if (sd_journal_get_data(j, "MESSAGE", &data, &length) >= 0) { + if (arg_show_all) + printf(" %.*s", (int) length - 8, ((const char*) data) + 8); + else if (contains_unprintable(data, length)) + fputs(" [blob data]", stdout); + else if (length - 8 + n < columns()) + printf(" %.*s", (int) length - 8, ((const char*) data) + 8); + else if (n < columns()) { + char *e; + + e = ellipsize_mem((const char *) data + 8, length - 8, columns() - n - 2, 90); + + if (!e) + printf(" %.*s", (int) length - 8, ((const char*) data) + 8); + else + printf(" %s", e); + + free(e); + } + } + + fputc('\n', stdout); + + return 0; +} + +static int output_verbose(sd_journal *j, unsigned line) { + const void *data; + size_t length; + char *cursor; + uint64_t realtime; + char ts[FORMAT_TIMESTAMP_MAX]; + int r; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + printf("%s [%s]\n", + format_timestamp(ts, sizeof(ts), realtime), + cursor); + + free(cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + if (!arg_show_all && (length > PRINT_THRESHOLD || + contains_unprintable(data, length))) { + const char *c; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + printf("\t%.*s=[blob data]\n", + (int) (c - (const char*) data), + (const char*) data); + } else + printf("\t%.*s\n", (int) length, (const char*) data); + } + + return 0; +} + +static int output_export(sd_journal *j, unsigned line) { + sd_id128_t boot_id; + char sid[33]; + int r; + usec_t realtime, monotonic; + char *cursor; + const void *data; + size_t length; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) { + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + printf(".cursor=%s\n" + ".realtime=%llu\n" + ".monotonic=%llu\n" + ".boot_id=%s\n", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + + free(cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + + if (contains_unprintable(data, length)) { + const char *c; + uint64_t le64; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fwrite(data, c - (const char*) data, 1, stdout); + fputc('\n', stdout); + le64 = htole64(length - (c - (const char*) data) - 1); + fwrite(&le64, sizeof(le64), 1, stdout); + fwrite(c + 1, length - (c - (const char*) data) - 1, 1, stdout); + } else + fwrite(data, length, 1, stdout); + + fputc('\n', stdout); + } + + fputc('\n', stdout); + + return 0; +} + +static void json_escape(const char* p, size_t l) { + + if (contains_unprintable(p, l)) { + bool not_first = false; + + fputs("[ ", stdout); + + while (l > 0) { + if (not_first) + printf(", %u", (uint8_t) *p); + else { + not_first = true; + printf("%u", (uint8_t) *p); + } + + p++; + l--; + } + + fputs(" ]", stdout); + } else { + fputc('\"', stdout); + + while (l > 0) { + if (*p == '"' || *p == '\\') { + fputc('\\', stdout); + fputc(*p, stdout); + } else + fputc(*p, stdout); + + p++; + l--; + } + + fputc('\"', stdout); + } +} + +static int output_json(sd_journal *j, unsigned line) { + uint64_t realtime, monotonic; + char *cursor; + const void *data; + size_t length; + sd_id128_t boot_id; + char sid[33]; int r; - JournalFile *f; - Object *o = NULL; + + assert(j); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) { + log_error("Failed to get realtime timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) { + log_error("Failed to get monotonic timestamp: %s", strerror(-r)); + return r; + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) { + log_error("Failed to get cursor: %s", strerror(-r)); + return r; + } + + if (line == 1) + fputc('\n', stdout); + else + fputs(",\n", stdout); + + printf("{\n" + "\t\".cursor\" : \"%s\",\n" + "\t\".realtime\" : %llu,\n" + "\t\".monotonic\" : %llu,\n" + "\t\".boot_id\" : \"%s\"", + cursor, + (unsigned long long) realtime, + (unsigned long long) monotonic, + sd_id128_to_string(boot_id, sid)); + + free(cursor); + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + const char *c; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fputs(",\n\t", stdout); + json_escape(data, c - (const char*) data); + fputs(" : ", stdout); + json_escape(c + 1, length - (c - (const char*) data) - 1); + } + + fputs("\n}", stdout); + fflush(stdout); + + return 0; +} + +static int (*output_funcs[_OUTPUT_MAX])(sd_journal*j, unsigned line) = { + [OUTPUT_SHORT] = output_short, + [OUTPUT_VERBOSE] = output_verbose, + [OUTPUT_EXPORT] = output_export, + [OUTPUT_JSON] = output_json +}; + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the login manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " -a --all Show all properties, including long and unprintable\n" + " -f --follow Follow journal\n" + " -o --output=STRING Change output mode (short, verbose, export, json)\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "follow", no_argument, NULL, 'f' }, + { "output", required_argument, NULL, 'o' }, + { "all", no_argument, NULL, 'a' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hfo:a", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'f': + arg_follow = true; + break; + + case 'o': + if (streq(optarg, "short")) + arg_output = OUTPUT_SHORT; + else if (streq(optarg, "verbose")) + arg_output = OUTPUT_VERBOSE; + else if (streq(optarg, "export")) + arg_output = OUTPUT_EXPORT; + else if (streq(optarg, "json")) + arg_output = OUTPUT_JSON; + else { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case 'a': + arg_show_all = true; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, i, fd; + sd_journal *j = NULL; + unsigned line = 0; + + log_set_max_level(LOG_DEBUG); + log_set_target(LOG_TARGET_CONSOLE); log_parse_environment(); log_open(); - r = journal_file_open("/var/log/journal/system.journal", O_RDONLY, 0644, &f); - if (r == -ENOENT) - r = journal_file_open("/run/log/journal/system.journal", O_RDONLY, 0644, &f); + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + r = sd_journal_open(&j, 0); if (r < 0) { log_error("Failed to open journal: %s", strerror(-r)); - return EXIT_FAILURE; + goto finish; } - for (;;) { - uint64_t offset; - uint64_t n, i; - - r = journal_file_next_entry(f, o, &o, &offset); + for (i = optind; i < argc; i++) { + r = sd_journal_add_match(j, argv[i], strlen(argv[i])); if (r < 0) { - log_error("Failed to read journal: %s", strerror(-r)); + log_error("Failed to add match: %s", strerror(-r)); goto finish; } + } - if (r == 0) - break; + fd = sd_journal_get_fd(j); + if (fd < 0) { + log_error("Failed to get wakeup fd: %s", strerror(-fd)); + goto finish; + } - printf("entry: %llu\n", (unsigned long long) le64toh(o->entry.seqnum)); + r = sd_journal_seek_head(j); + if (r < 0) { + log_error("Failed to seek to head: %s", strerror(-r)); + goto finish; + } - n = journal_file_entry_n_items(o); - for (i = 0; i < n; i++) { - uint64_t p, l; + if (!arg_no_pager && !arg_follow) { + columns(); + pager_open(); + } + + if (arg_output == OUTPUT_JSON) { + fputc('[', stdout); + fflush(stdout); + } + + for (;;) { + struct pollfd pollfd; + + for (;;) { + r = sd_journal_next(j); - p = le64toh(o->entry.items[i].object_offset); - r = journal_file_move_to_object(f, p, OBJECT_DATA, &o); if (r < 0) { - log_error("Failed to move to data: %s", strerror(-r)); + log_error("Failed to iterate through journal: %s", strerror(-r)); goto finish; } - l = o->object.size - offsetof(Object, data.payload); - printf("\t[%.*s]\n", (int) l, o->data.payload); + if (r == 0) + break; - r = journal_file_move_to_object(f, offset, OBJECT_ENTRY, &o); - if (r < 0) { - log_error("Failed to move back to entry: %s", strerror(-r)); + line ++; + + r = output_funcs[arg_output](j, line); + if (r < 0) goto finish; - } + } + + if (!arg_follow) + break; + + zero(pollfd); + pollfd.fd = fd; + pollfd.events = POLLIN; + + if (poll(&pollfd, 1, -1) < 0) { + if (errno == EINTR) + break; + + log_error("poll(): %m"); + r = -errno; + goto finish; + } + + r = sd_journal_process(j); + if (r < 0) { + log_error("Failed to process: %s", strerror(-r)); + goto finish; } } + if (arg_output == OUTPUT_JSON) + fputs("\n]\n", stdout); + finish: - journal_file_close(f); + if (j) + sd_journal_close(j); - return 0; + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }