From cfbc22abd0525570a6e58968d518ea9a7d0403ba Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Oct 2012 16:42:46 +0200 Subject: [PATCH] journalctl: implement --since= and --until for filtering by time --- .gitignore | 1 + Makefile.am | 12 ++- TODO | 2 + man/journalctl.xml | 35 +++++++- src/journal/journalctl.c | 125 +++++++++++++++++++++------- src/shared/util.c | 175 +++++++++++++++++++++++++++++++++++++-- src/shared/util.h | 2 + src/test/test-date.c | 69 +++++++++++++++ 8 files changed, 380 insertions(+), 41 deletions(-) create mode 100644 src/test/test-date.c diff --git a/.gitignore b/.gitignore index 13d2df465..71359ce6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/test-date /install-tree /systemd-journal-gatewayd /test-mmap-cache diff --git a/Makefile.am b/Makefile.am index c23afdf17..e332183a6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1157,14 +1157,16 @@ noinst_PROGRAMS += \ test-watchdog \ test-unit-name \ test-log \ - test-unit-file + test-unit-file \ + test-date TESTS += \ test-job-type \ test-env-replace \ test-strv \ test-unit-name \ - test-unit-file + test-unit-file \ + test-data test_engine_SOURCES = \ src/test/test-engine.c @@ -1226,6 +1228,12 @@ test_log_SOURCES = \ test_log_LDADD = \ libsystemd-core.la +test_date_SOURCES = \ + src/test/test-date.c + +test_date_LDADD = \ + libsystemd-core.la + test_daemon_SOURCES = \ src/test/test-daemon.c diff --git a/TODO b/TODO index 5943c9a48..f3f66ff9a 100644 --- a/TODO +++ b/TODO @@ -19,6 +19,8 @@ F18: Features: +* _SOURCE_MONOTONIC_TIMESTAMP entries from the kernel seem to be off by 1000000 + * document unit_name_mangle() * add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible diff --git a/man/journalctl.xml b/man/journalctl.xml index 62373d88a..3786fdf51 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -264,11 +264,42 @@ - Jump to the location - in the journal specified by the passed + Start showing entries + from the location in the journal + specified by the passed cursor. + + + + + Start showing entries + newer or of the specified date, + resp. older or of the specified + date. Date specifications should be of + the format "2012-10-30 18:17:16". If + the time part is omitted, 00:00:00 is + assumed. If only the seconds component + is omitted, :00 is assumed. If the + date component is ommitted, the + current day is assumed. Alternatively + the strings + yesterday, + today, + tomorrow are + understood, which refer to 00:00:00 of + the day before the current day, the + current day, resp the day after the + current day. now + refers to the current time. Finally, + relative times may be specified, + prefixed with - or + +, referring to + times before resp. after the current + time. + + diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 04cebff54..54ee6d8cc 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -58,7 +58,7 @@ static OutputMode arg_output = OUTPUT_SHORT; static bool arg_follow = false; static bool arg_show_all = false; static bool arg_no_pager = false; -static int arg_lines = -1; +static unsigned arg_lines = 0; static bool arg_no_tail = false; static bool arg_quiet = false; static bool arg_merge = false; @@ -70,6 +70,8 @@ static const char *arg_verify_key = NULL; #ifdef HAVE_GCRYPT static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; #endif +static usec_t arg_since, arg_until; +static bool arg_since_set = false, arg_until_set = false; static enum { ACTION_SHOW, @@ -88,7 +90,9 @@ static int help(void) { " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " -a --all Show all fields, including long and unprintable\n" - " -c --cursor=CURSOR Jump to the specified cursor\n" + " -c --cursor=CURSOR Start showing entries from specified cursor\n" + " --since=DATE Start showing entries newer or of the specified date\n" + " --until=DATE Stop showing entries older or of the specified date\n" " -f --follow Follow journal\n" " -n --lines[=INTEGER] Number of journal entries to show\n" " --no-tail Show all lines, even in follow mode\n" @@ -126,7 +130,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_INTERVAL, ARG_VERIFY, ARG_VERIFY_KEY, - ARG_DISK_USAGE + ARG_DISK_USAGE, + ARG_SINCE, + ARG_UNTIL }; static const struct option options[] = { @@ -151,6 +157,8 @@ static int parse_argv(int argc, char *argv[]) { { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, { "cursor", required_argument, NULL, 'c' }, + { "since", required_argument, NULL, ARG_SINCE }, + { "until", required_argument, NULL, ARG_UNTIL }, { NULL, 0, NULL, 0 } }; @@ -197,8 +205,8 @@ static int parse_argv(int argc, char *argv[]) { case 'n': if (optarg) { - r = safe_atoi(optarg, &arg_lines); - if (r < 0 || arg_lines < 0) { + r = safe_atou(optarg, &arg_lines); + if (r < 0 || arg_lines <= 0) { log_error("Failed to parse lines '%s'", optarg); return -EINVAL; } @@ -324,6 +332,24 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_SINCE: + r = parse_timestamp(optarg, &arg_since); + if (r < 0) { + log_error("Failed to parse timestamp: %s", optarg); + return -EINVAL; + } + arg_since_set = true; + break; + + case ARG_UNTIL: + r = parse_timestamp(optarg, &arg_until); + if (r < 0) { + log_error("Failed to parse timestamp: %s", optarg); + return -EINVAL; + } + arg_until_set = true; + break; + case '?': return -EINVAL; @@ -333,9 +359,19 @@ static int parse_argv(int argc, char *argv[]) { } } - if (arg_follow && !arg_no_tail && arg_lines < 0) + if (arg_follow && !arg_no_tail && arg_lines <= 0) arg_lines = 10; + if (arg_since_set && arg_until_set && arg_since_set > arg_until_set) { + log_error("--since= must be before --until=."); + return -EINVAL; + } + + if (arg_cursor && arg_since_set) { + log_error("Please specify either --since= or --cursor=, not both."); + return -EINVAL; + } + return 1; } @@ -734,6 +770,7 @@ int main(int argc, char *argv[]) { sd_id128_t previous_boot_id; bool previous_boot_id_valid = false; bool have_pager; + unsigned n_shown = 0; log_parse_environment(); log_open(); @@ -815,36 +852,24 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; - if (!arg_quiet) { - usec_t start, end; - char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; - - r = sd_journal_get_cutoff_realtime_usec(j, &start, &end); + if (arg_cursor) { + r = sd_journal_seek_cursor(j, arg_cursor); if (r < 0) { - log_error("Failed to get cutoff: %s", strerror(-r)); + log_error("Failed to seek to cursor: %s", strerror(-r)); goto finish; } - if (r > 0) { - if (arg_follow) - printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start)); - else - printf("Logs begin at %s, end at %s.\n", - format_timestamp(start_buf, sizeof(start_buf), start), - format_timestamp(end_buf, sizeof(end_buf), end)); - } - } + r = sd_journal_next(j); - if (arg_cursor) { - r = sd_journal_seek_cursor(j, arg_cursor); + } else if (arg_since_set) { + r = sd_journal_seek_realtime_usec(j, arg_since); if (r < 0) { - log_error("Failed to seek to cursor: %s", strerror(-r)); + log_error("Failed to seek to date: %s", strerror(-r)); goto finish; } - r = sd_journal_next(j); - } else if (arg_lines >= 0) { + } else if (arg_lines > 0) { r = sd_journal_seek_tail(j); if (r < 0) { log_error("Failed to seek to tail: %s", strerror(-r)); @@ -871,12 +896,34 @@ int main(int argc, char *argv[]) { on_tty(); have_pager = !arg_no_pager && !arg_follow && pager_open(); + if (!arg_quiet) { + usec_t start, end; + char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; + + r = sd_journal_get_cutoff_realtime_usec(j, &start, &end); + if (r < 0) { + log_error("Failed to get cutoff: %s", strerror(-r)); + goto finish; + } + + if (r > 0) { + if (arg_follow) + printf("---- Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start)); + else + printf("---- Logs begin at %s, end at %s.\n", + format_timestamp(start_buf, sizeof(start_buf), start), + format_timestamp(end_buf, sizeof(end_buf), end)); + } + } + for (;;) { for (;;) { - int flags = - arg_show_all * OUTPUT_SHOW_ALL | - have_pager * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR; + int flags; + + if (arg_lines > 0 && n_shown >= arg_lines) { + r = 0; + goto finish; + } if (need_seek) { r = sd_journal_next(j); @@ -889,6 +936,16 @@ int main(int argc, char *argv[]) { if (r == 0) break; + if (arg_until_set) { + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) { + log_error("Failed to determine timestamp: %s", strerror(-r)); + goto finish; + } + } + if (!arg_merge) { sd_id128_t boot_id; @@ -896,18 +953,24 @@ int main(int argc, char *argv[]) { if (r >= 0) { if (previous_boot_id_valid && !sd_id128_equal(boot_id, previous_boot_id)) - printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n"); + printf(ANSI_HIGHLIGHT_ON "---- Reboot ----" ANSI_HIGHLIGHT_OFF "\n"); previous_boot_id = boot_id; previous_boot_id_valid = true; } } + flags = + arg_show_all * OUTPUT_SHOW_ALL | + have_pager * OUTPUT_FULL_WIDTH | + on_tty() * OUTPUT_COLOR; + r = output_journal(stdout, j, arg_output, 0, flags); if (r < 0) goto finish; need_seek = true; + n_shown++; } if (!arg_follow) diff --git a/src/shared/util.c b/src/shared/util.c index 6310aec8a..15481b6b9 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -2733,16 +2733,31 @@ int parse_usec(const char *t, usec_t *usec) { const char *suffix; usec_t usec; } table[] = { + { "seconds", USEC_PER_SEC }, + { "second", USEC_PER_SEC }, { "sec", USEC_PER_SEC }, { "s", USEC_PER_SEC }, + { "minutes", USEC_PER_MINUTE }, + { "minute", USEC_PER_MINUTE }, { "min", USEC_PER_MINUTE }, + { "months", USEC_PER_MONTH }, + { "month", USEC_PER_MONTH }, + { "msec", USEC_PER_MSEC }, + { "ms", USEC_PER_MSEC }, + { "m", USEC_PER_MINUTE }, + { "hours", USEC_PER_HOUR }, + { "hour", USEC_PER_HOUR }, { "hr", USEC_PER_HOUR }, { "h", USEC_PER_HOUR }, + { "days", USEC_PER_DAY }, + { "day", USEC_PER_DAY }, { "d", USEC_PER_DAY }, + { "weeks", USEC_PER_WEEK }, + { "week", USEC_PER_WEEK }, { "w", USEC_PER_WEEK }, - { "msec", USEC_PER_MSEC }, - { "ms", USEC_PER_MSEC }, - { "m", USEC_PER_MINUTE }, + { "years", USEC_PER_YEAR }, + { "year", USEC_PER_YEAR }, + { "y", USEC_PER_YEAR }, { "usec", 1ULL }, { "us", 1ULL }, { "", USEC_PER_SEC }, /* default is sec */ @@ -2796,16 +2811,31 @@ int parse_nsec(const char *t, nsec_t *nsec) { const char *suffix; nsec_t nsec; } table[] = { + { "seconds", NSEC_PER_SEC }, + { "second", NSEC_PER_SEC }, { "sec", NSEC_PER_SEC }, { "s", NSEC_PER_SEC }, + { "minutes", NSEC_PER_MINUTE }, + { "minute", NSEC_PER_MINUTE }, { "min", NSEC_PER_MINUTE }, + { "months", NSEC_PER_MONTH }, + { "month", NSEC_PER_MONTH }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "hours", NSEC_PER_HOUR }, + { "hour", NSEC_PER_HOUR }, { "hr", NSEC_PER_HOUR }, { "h", NSEC_PER_HOUR }, + { "days", NSEC_PER_DAY }, + { "day", NSEC_PER_DAY }, { "d", NSEC_PER_DAY }, + { "weeks", NSEC_PER_WEEK }, + { "week", NSEC_PER_WEEK }, { "w", NSEC_PER_WEEK }, - { "msec", NSEC_PER_MSEC }, - { "ms", NSEC_PER_MSEC }, - { "m", NSEC_PER_MINUTE }, + { "years", NSEC_PER_YEAR }, + { "year", NSEC_PER_YEAR }, + { "y", NSEC_PER_YEAR }, { "usec", NSEC_PER_USEC }, { "us", NSEC_PER_USEC }, { "nsec", 1ULL }, @@ -5888,3 +5918,136 @@ bool string_is_safe(const char *p) { return true; } + +int parse_timestamp(const char *t, usec_t *usec) { + const char *k; + struct tm tm, copy; + time_t x; + usec_t plus = 0, minus = 0, ret; + int r; + + /* + * Allowed syntaxes: + * + * 2012-09-22 16:34:22 + * 2012-09-22 16:34 (seconds will be set to 0) + * 2012-09-22 (time will be set to 00:00:00) + * 16:34:22 (date will be set to today) + * 16:34 (date will be set to today, seconds to 0) + * now + * yesterday (time is set to 00:00:00) + * today (time is set to 00:00:00) + * tomorrow (time is set to 00:00:00) + * +5min + * -5days + * + */ + + assert(t); + assert(usec); + + x = time(NULL); + assert_se(localtime_r(&x, &tm)); + + if (streq(t, "now")) + goto finish; + + else if (streq(t, "today")) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (streq(t, "yesterday")) { + tm.tm_mday --; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (streq(t, "tomorrow")) { + tm.tm_mday ++; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + + } else if (t[0] == '+') { + + r = parse_usec(t+1, &plus); + if (r < 0) + return r; + + goto finish; + } else if (t[0] == '-') { + + r = parse_usec(t+1, &minus); + if (r < 0) + return r; + + goto finish; + } + + copy = tm; + k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto finish; + } + + tm = copy; + k = strptime(t, "%H:%M:%S", &tm); + if (k && *k == 0) + goto finish; + + tm = copy; + k = strptime(t, "%H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto finish; + } + + return -EINVAL; + +finish: + x = mktime(&tm); + if (x == (time_t) -1) + return -EINVAL; + + ret = (usec_t) x * USEC_PER_SEC; + + ret += plus; + if (ret > minus) + ret -= minus; + else + ret = 0; + + *usec = ret; + + return 0; +} diff --git a/src/shared/util.h b/src/shared/util.h index cbded0861..50911ebb3 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -561,3 +561,5 @@ _malloc_ static inline void *memdup_multiply(const void *p, size_t a, size_t b) bool filename_is_safe(const char *p); bool string_is_safe(const char *p); + +int parse_timestamp(const char *t, usec_t *usec); diff --git a/src/test/test-date.c b/src/test/test-date.c new file mode 100644 index 000000000..57f8b371d --- /dev/null +++ b/src/test/test-date.c @@ -0,0 +1,69 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +int main(int argc, char *argv[]) { + + usec_t t; + char buf[FORMAT_TIMESTAMP_MAX]; + + assert_se(parse_timestamp("17:41", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("18:42:44", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("12-10-02 12:13:14", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("12-10-2 12:13:14", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("12-10-03 12:13", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("2012-12-30 18:42", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("2012-10-02", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("now", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("yesterday", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("today", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("tomorrow", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("+2d", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(parse_timestamp("+2y 4d", &t) >= 0); + log_info("%s", format_timestamp(buf, sizeof(buf), t)); + + return 0; +}