X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fjournalctl.c;h=c96d68dd4cbf1feeca0fbfcd15b152d3ac10d5aa;hp=0b3a79bee97e3140f2af54e9f4d77c449bc75c9d;hb=0db809489fd88a320ae1023ffe36a9965e9a91b2;hpb=3ba09ee8eb9453daf9228f612c9bcd59905fcf05 diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 0b3a79bee..c96d68dd4 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -35,9 +35,15 @@ #include #include +#ifdef HAVE_ACL +#include +#include "acl-util.h" +#endif + #include #include "log.h" +#include "logs-show.h" #include "util.h" #include "path-util.h" #include "build.h" @@ -56,6 +62,7 @@ #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_pager_end = false; static bool arg_follow = false; static bool arg_full = false; static bool arg_all = false; @@ -75,8 +82,11 @@ static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; static usec_t arg_since, arg_until; static bool arg_since_set = false, arg_until_set = false; static const char *arg_unit = NULL; +static bool arg_unit_system; static const char *arg_field = NULL; static bool arg_catalog = false; +static bool arg_reverse = false; +static const char *arg_root = NULL; static enum { ACTION_SHOW, @@ -86,6 +96,7 @@ static enum { ACTION_VERIFY, ACTION_DISK_USAGE, ACTION_LIST_CATALOG, + ACTION_DUMP_CATALOG, ACTION_UPDATE_CATALOG } arg_action = ACTION_SHOW; @@ -99,10 +110,13 @@ static int help(void) { " -c --cursor=CURSOR Start showing entries from specified cursor\n" " -b --this-boot Show data only from current boot\n" " -u --unit=UNIT Show data only from the specified unit\n" + " --user-unit=UNIT Show data only from the specified user session unit\n" " -p --priority=RANGE Show only messages within the specified priority range\n" + " -e --pager-end Immediately jump to end of the journal in the pager\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" + " -r --reverse Show the newest entries first\n" " -o --output=STRING Change journal output mode (short, short-monotonic,\n" " verbose, export, json, json-pretty, json-sse, cat)\n" " -x --catalog Add message explanations where available\n" @@ -112,6 +126,7 @@ static int help(void) { " --no-pager Do not pipe output into a pager\n" " -m --merge Show entries from all available journals\n" " -D --directory=PATH Show journal files from directory\n" + " --root=ROOT Operate on catalog files underneath the root ROOT\n" #ifdef HAVE_GCRYPT " --interval=TIME Time interval for changing the FSS sealing key\n" " --verify-key=KEY Specify FSS verification key\n" @@ -124,6 +139,7 @@ static int help(void) { " --disk-usage Show total disk usage\n" " -F --field=FIELD List all values a certain field takes\n" " --list-catalog Show message IDs of all entries in the message catalog\n" + " --dump-catalog Show entries in the message catalog\n" " --update-catalog Update the message catalog database\n" #ifdef HAVE_GCRYPT " --setup-keys Generate new FSS key pair\n" @@ -141,6 +157,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_PAGER, ARG_NO_TAIL, ARG_NEW_ID128, + ARG_ROOT, ARG_HEADER, ARG_FULL, ARG_SETUP_KEYS, @@ -150,7 +167,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_DISK_USAGE, ARG_SINCE, ARG_UNTIL, + ARG_USER_UNIT, ARG_LIST_CATALOG, + ARG_DUMP_CATALOG, ARG_UPDATE_CATALOG }; @@ -158,6 +177,7 @@ static int parse_argv(int argc, char *argv[]) { { "help", no_argument, NULL, 'h' }, { "version" , no_argument, NULL, ARG_VERSION }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "pager-end", no_argument, NULL, 'e' }, { "follow", no_argument, NULL, 'f' }, { "output", required_argument, NULL, 'o' }, { "all", no_argument, NULL, 'a' }, @@ -169,6 +189,7 @@ static int parse_argv(int argc, char *argv[]) { { "merge", no_argument, NULL, 'm' }, { "this-boot", no_argument, NULL, 'b' }, { "directory", required_argument, NULL, 'D' }, + { "root", required_argument, NULL, ARG_ROOT }, { "header", no_argument, NULL, ARG_HEADER }, { "priority", required_argument, NULL, 'p' }, { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, @@ -180,10 +201,13 @@ static int parse_argv(int argc, char *argv[]) { { "since", required_argument, NULL, ARG_SINCE }, { "until", required_argument, NULL, ARG_UNTIL }, { "unit", required_argument, NULL, 'u' }, + { "user-unit", required_argument, NULL, ARG_USER_UNIT }, { "field", required_argument, NULL, 'F' }, { "catalog", no_argument, NULL, 'x' }, { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, + { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, { "update-catalog",no_argument, NULL, ARG_UPDATE_CATALOG }, + { "reverse", no_argument, NULL, 'r' }, { NULL, 0, NULL, 0 } }; @@ -192,7 +216,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hfo:an::qmbD:p:c:u:F:x", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "hefo:an::qmbD:p:c:u:F:xr", options, NULL)) >= 0) { switch (c) { @@ -209,6 +233,14 @@ static int parse_argv(int argc, char *argv[]) { arg_no_pager = true; break; + case 'e': + arg_pager_end = true; + + if (arg_lines < 0) + arg_lines = 1000; + + break; + case 'f': arg_follow = true; break; @@ -290,6 +322,10 @@ static int parse_argv(int argc, char *argv[]) { arg_directory = optarg; break; + case ARG_ROOT: + arg_root = optarg; + break; + case 'c': arg_cursor = optarg; break; @@ -403,6 +439,12 @@ static int parse_argv(int argc, char *argv[]) { case 'u': arg_unit = optarg; + arg_unit_system = true; + break; + + case ARG_USER_UNIT: + arg_unit = optarg; + arg_unit_system = false; break; case '?': @@ -420,10 +462,18 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_LIST_CATALOG; break; + case ARG_DUMP_CATALOG: + arg_action = ACTION_DUMP_CATALOG; + break; + case ARG_UPDATE_CATALOG: arg_action = ACTION_UPDATE_CATALOG; break; + case 'r': + arg_reverse = true; + break; + default: log_error("Unknown option code %c", c); return -EINVAL; @@ -443,6 +493,11 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (arg_follow && arg_reverse) { + log_error("Please specify either --reverse= or --follow=, not both."); + return -EINVAL; + } + return 1; } @@ -462,30 +517,33 @@ static int generate_new_id128(void) { "As UUID:\n" "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n" "As macro:\n" - "#define MESSAGE_XYZ SD_ID128_MAKE(", + "#define MESSAGE_XYZ SD_ID128_MAKE(", SD_ID128_FORMAT_VAL(id), SD_ID128_FORMAT_VAL(id)); - for (i = 0; i < 16; i++) printf("%02x%s", id.bytes[i], i != 15 ? "," : ""); + fputs(")\n\n", stdout); - fputs(")\n", stdout); + printf("As Python constant:\n" + ">>> import uuid\n" + ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n", + SD_ID128_FORMAT_VAL(id)); return 0; } static int add_matches(sd_journal *j, char **args) { char **i; - int r; assert(j); STRV_FOREACH(i, args) { + int r; if (streq(*i, "+")) r = sd_journal_add_disjunction(j); else if (path_is_absolute(*i)) { - char *p, *t = NULL; + char _cleanup_free_ *p, *t = NULL; const char *path; struct stat st; @@ -493,7 +551,6 @@ static int add_matches(sd_journal *j, char **args) { path = p ? p : *i; if (stat(path, &st) < 0) { - free(p); log_error("Couldn't stat file: %m"); return -errno; } @@ -505,18 +562,14 @@ static int add_matches(sd_journal *j, char **args) { else if (S_ISBLK(st.st_mode)) asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev)); else { - free(p); log_error("File is not a device node, regular file or is not executable: %s", *i); return -EINVAL; } - free(p); - if (!t) return log_oom(); r = sd_journal_add_match(j, t, 0); - free(t); } else r = sd_journal_add_match(j, *i, 0); @@ -556,7 +609,7 @@ static int add_this_boot(sd_journal *j) { } static int add_unit(sd_journal *j) { - _cleanup_free_ char *m = NULL, *u = NULL; + _cleanup_free_ char *u = NULL; int r; assert(j); @@ -568,15 +621,12 @@ static int add_unit(sd_journal *j) { if (!u) return log_oom(); - m = strappend("_SYSTEMD_UNIT=", u); - if (!m) - return log_oom(); - - r = sd_journal_add_match(j, m, strlen(m)); - if (r < 0) { - log_error("Failed to add match: %s", strerror(-r)); + if (arg_unit_system) + r = add_matches_for_unit(j, u); + else + r = add_matches_for_user_unit(j, u, getuid()); + if (r < 0) return r; - } return 0; } @@ -841,29 +891,118 @@ static int verify(sd_journal *j) { return r; } -static int access_check(void) { - #ifdef HAVE_ACL - if (access("/var/log/journal", F_OK) < 0 && geteuid() != 0 && in_group("adm") <= 0) { - log_error("Unprivileged users can't see messages unless persistent log storage is enabled. Users in the group 'adm' can always see messages."); - return -EACCES; +static int access_check_var_log_journal(sd_journal *j) { + _cleanup_strv_free_ char **g = NULL; + bool have_access; + int r; + + assert(j); + + have_access = in_group("systemd-journal") > 0; + + if (!have_access) { + /* Let's enumerate all groups from the default ACL of + * the directory, which generally should allow access + * to most journal files too */ + r = search_acl_groups(&g, "/var/log/journal/", &have_access); + if (r < 0) + return r; } - if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0) - log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this notice off."); -#else - if (geteuid() != 0 && in_group("adm") <= 0) { - log_error("No access to messages. Only users in the group 'adm' can see messages."); - return -EACCES; + if (!have_access) { + + if (strv_isempty(g)) + log_notice("Hint: You are currently not seeing messages from other users and the system.\n" + " Users in the 'systemd-journal' group can see all messages. Pass -q to\n" + " turn off this notice."); + else { + _cleanup_free_ char *s = NULL; + + r = strv_extend(&g, "systemd-journal"); + if (r < 0) + return log_oom(); + + strv_sort(g); + strv_uniq(g); + + s = strv_join(g, "', '"); + if (!s) + return log_oom(); + + log_notice("Hint: You are currently not seeing messages from other users and the system.\n" + " Users in the groups '%s' can see all messages.\n" + " Pass -q to turn off this notice.", s); + } } -#endif return 0; } +#endif + +static int access_check(sd_journal *j) { + Iterator it; + void *code; + int r = 0; + + assert(j); + + if (set_isempty(j->errors)) { + if (hashmap_isempty(j->files)) + log_notice("No journal files were found."); + return 0; + } + + if (set_contains(j->errors, INT_TO_PTR(-EACCES))) { +#ifdef HAVE_ACL + /* If /var/log/journal doesn't even exist, + * unprivileged users have no access at all */ + if (access("/var/log/journal", F_OK) < 0 && + geteuid() != 0 && + in_group("systemd-journal") <= 0) { + log_error("Unprivileged users cannot access messages, unless persistent log storage is\n" + "enabled. Users in the 'systemd-journal' group may always access messages."); + return -EACCES; + } + + /* If /var/log/journal exists, try to pring a nice + notice if the user lacks access to it */ + if (!arg_quiet && geteuid() != 0) { + r = access_check_var_log_journal(j); + if (r < 0) + return r; + } +#else + if (geteuid() != 0 && in_group("systemd-journal") <= 0) { + log_error("Unprivileged users cannot access messages. Users in the 'systemd-journal' group\n" + "group may access messages."); + return -EACCES; + } +#endif + + if (hashmap_isempty(j->files)) { + log_error("No journal files were opened due to insufficient permissions."); + r = -EACCES; + } + } + + SET_FOREACH(code, j->errors, it) { + int err; + + err = -PTR_TO_INT(code); + assert(err > 0); + + if (err != EACCES) + log_warning("Error was encountered while opening journal files: %s", + strerror(err)); + } + + return r; +} int main(int argc, char *argv[]) { int r; - sd_journal *j = NULL; + sd_journal _cleanup_journal_close_ *j = NULL; bool need_seek = false; sd_id128_t previous_boot_id; bool previous_boot_id_valid = false, first_line = true; @@ -889,21 +1028,40 @@ int main(int argc, char *argv[]) { goto finish; } - if (arg_action == ACTION_LIST_CATALOG) { - r = catalog_list(stdout); - if (r < 0) - log_error("Failed to list catalog: %s", strerror(-r)); - goto finish; - } + if (arg_action == ACTION_UPDATE_CATALOG || + arg_action == ACTION_LIST_CATALOG || + arg_action == ACTION_DUMP_CATALOG) { - if (arg_action == ACTION_UPDATE_CATALOG) { - r = catalog_update(); - goto finish; - } + const char* database = CATALOG_DATABASE; + char _cleanup_free_ *copy = NULL; + if (arg_root) { + copy = strjoin(arg_root, "/", CATALOG_DATABASE, NULL); + if (!database) { + r = log_oom(); + goto finish; + } + path_kill_slashes(copy); + database = copy; + } + + if (arg_action == ACTION_UPDATE_CATALOG) { + r = catalog_update(database, arg_root, catalog_file_dirs); + if (r < 0) + log_error("Failed to list catalog: %s", strerror(-r)); + } else { + bool oneline = arg_action == ACTION_LIST_CATALOG; + + if (optind < argc) + r = catalog_list_items(stdout, database, + oneline, argv + optind); + else + r = catalog_list(stdout, database, oneline); + if (r < 0) + log_error("Failed to list catalog: %s", strerror(-r)); + } - r = access_check(); - if (r < 0) goto finish; + } if (arg_directory) r = sd_journal_open_directory(&j, arg_directory, 0); @@ -911,9 +1069,13 @@ int main(int argc, char *argv[]) { r = sd_journal_open(&j, arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY); if (r < 0) { log_error("Failed to open journal: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } + r = access_check(j); + if (r < 0) + return EXIT_FAILURE; + if (arg_action == ACTION_VERIFY) { r = verify(j); goto finish; @@ -921,8 +1083,7 @@ int main(int argc, char *argv[]) { if (arg_action == ACTION_PRINT_HEADER) { journal_print_header(j); - r = 0; - goto finish; + return EXIT_SUCCESS; } if (arg_action == ACTION_DISK_USAGE) { @@ -931,33 +1092,33 @@ int main(int argc, char *argv[]) { r = sd_journal_get_usage(j, &bytes); if (r < 0) - goto finish; + return EXIT_FAILURE; - printf("Journals take up %s on disk.\n", format_bytes(sbytes, sizeof(sbytes), bytes)); - r = 0; - goto finish; + printf("Journals take up %s on disk.\n", + format_bytes(sbytes, sizeof(sbytes), bytes)); + return EXIT_SUCCESS; } r = add_this_boot(j); if (r < 0) - goto finish; + return EXIT_FAILURE; r = add_unit(j); if (r < 0) - goto finish; + return EXIT_FAILURE; r = add_matches(j, argv + optind); if (r < 0) - goto finish; + return EXIT_FAILURE; r = add_priorities(j); if (r < 0) - goto finish; + return EXIT_FAILURE; /* Opening the fd now means the first sd_journal_wait() will actually wait */ r = sd_journal_get_fd(j); if (r < 0) - goto finish; + return EXIT_FAILURE; if (arg_field) { const void *data; @@ -966,7 +1127,7 @@ int main(int argc, char *argv[]) { r = sd_journal_query_unique(j, arg_field); if (r < 0) { log_error("Failed to query unique data objects: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { @@ -984,41 +1145,59 @@ int main(int argc, char *argv[]) { n_shown ++; } - r = 0; - goto finish; + return EXIT_SUCCESS; } if (arg_cursor) { r = sd_journal_seek_cursor(j, arg_cursor); if (r < 0) { log_error("Failed to seek to cursor: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } + if (!arg_reverse) + r = sd_journal_next(j); + else + r = sd_journal_previous(j); - r = sd_journal_next(j); - - } else if (arg_since_set) { + } else if (arg_since_set && !arg_reverse) { r = sd_journal_seek_realtime_usec(j, arg_since); if (r < 0) { log_error("Failed to seek to date: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } r = sd_journal_next(j); + } else if (arg_until_set && arg_reverse) { + r = sd_journal_seek_realtime_usec(j, arg_until); + if (r < 0) { + log_error("Failed to seek to date: %s", strerror(-r)); + return EXIT_FAILURE; + } + r = sd_journal_previous(j); + } else if (arg_lines >= 0) { r = sd_journal_seek_tail(j); if (r < 0) { log_error("Failed to seek to tail: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } r = sd_journal_previous_skip(j, arg_lines); + } else if (arg_reverse) { + r = sd_journal_seek_tail(j); + if (r < 0) { + log_error("Failed to seek to tail: %s", strerror(-r)); + return EXIT_FAILURE; + } + + r = sd_journal_previous(j); + } else { r = sd_journal_seek_head(j); if (r < 0) { log_error("Failed to seek to head: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } r = sd_journal_next(j); @@ -1026,11 +1205,11 @@ int main(int argc, char *argv[]) { if (r < 0) { log_error("Failed to iterate through journal: %s", strerror(-r)); - goto finish; + return EXIT_FAILURE; } if (!arg_no_pager && !arg_follow) - pager_open(); + pager_open(arg_pager_end); if (!arg_quiet) { usec_t start, end; @@ -1058,7 +1237,10 @@ int main(int argc, char *argv[]) { int flags; if (need_seek) { - r = sd_journal_next(j); + if (!arg_reverse) + r = sd_journal_next(j); + else + r = sd_journal_previous(j); if (r < 0) { log_error("Failed to iterate through journal: %s", strerror(-r)); goto finish; @@ -1068,7 +1250,7 @@ int main(int argc, char *argv[]) { if (r == 0) break; - if (arg_until_set) { + if (arg_until_set && !arg_reverse) { usec_t usec; r = sd_journal_get_realtime_usec(j, &usec); @@ -1080,6 +1262,18 @@ int main(int argc, char *argv[]) { goto finish; } + if (arg_since_set && arg_reverse) { + 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 (usec < arg_since) + goto finish; + } + if (!arg_merge) { sd_id128_t boot_id; @@ -1121,9 +1315,6 @@ int main(int argc, char *argv[]) { } finish: - if (j) - sd_journal_close(j); - pager_close(); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;