]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/journal/journalctl.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / journal / journalctl.c
index 6f841efb6929f38e5ba296584ba491d5ef5eb652..a8e90f80bfac684b4132aca7c40df18ee12a4013 100644 (file)
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
@@ -52,6 +53,7 @@
 #include "journal-def.h"
 #include "journal-internal.h"
 #include "journal-qrcode.h"
+#include "journal-util.h"
 #include "journal-vacuum.h"
 #include "journal-verify.h"
 #include "locale-util.h"
@@ -103,8 +105,8 @@ static const char *arg_directory = NULL;
 static char **arg_file = NULL;
 static bool arg_file_stdin = false;
 static int arg_priorities = 0xFF;
-static const char *arg_verify_key = NULL;
-#ifdef HAVE_GCRYPT
+static char *arg_verify_key = NULL;
+#if HAVE_GCRYPT
 static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
 static bool arg_force = false;
 #endif
@@ -122,6 +124,7 @@ static const char *arg_machine = NULL;
 static uint64_t arg_vacuum_size = 0;
 static uint64_t arg_vacuum_n_files = 0;
 static usec_t arg_vacuum_time = 0;
+static char **arg_output_fields = NULL;
 
 static enum {
         ACTION_SHOW,
@@ -192,7 +195,7 @@ static int add_matches_for_device(sd_journal *j, const char *devpath) {
                         continue;
                 }
 
-                match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL);
+                match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname);
                 if (!match)
                         return log_oom();
 
@@ -247,7 +250,7 @@ static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset
                 if (r >= 0)
                         x += 32;
 
-                if (*x != '-' && *x != '+' && *x != 0)
+                if (!IN_SET(*x, 0, '-', '+'))
                         return -EINVAL;
 
                 if (*x != 0) {
@@ -298,8 +301,9 @@ static void help(void) {
                "     --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-precise,\n"
-               "                             short-iso, short-full, short-monotonic, short-unix,\n"
-               "                             verbose, export, json, json-pretty, json-sse, cat)\n"
+               "                             short-iso, short-iso-precise, short-full,\n"
+               "                             short-monotonic, short-unix, verbose, export,\n"
+               "                             json, json-pretty, json-sse, cat)\n"
                "     --utc                 Express time in Coordinated Universal Time (UTC)\n"
                "  -x --catalog             Add message explanations where available\n"
                "     --no-full             Ellipsize fields\n"
@@ -310,8 +314,8 @@ static void help(void) {
                "  -m --merge               Show entries from all available journals\n"
                "  -D --directory=PATH      Show journal files from directory\n"
                "     --file=PATH           Show journal file\n"
-               "     --root=ROOT           Operate on catalog files below a root directory\n"
-#ifdef HAVE_GCRYPT
+               "     --root=ROOT           Operate on files below a root directory\n"
+#if HAVE_GCRYPT
                "     --interval=TIME       Time interval for changing the FSS sealing key\n"
                "     --verify-key=KEY      Specify FSS verification key\n"
                "     --force               Override of the FSS key pair with --setup-keys\n"
@@ -334,7 +338,7 @@ static void help(void) {
                "     --dump-catalog        Show entries in the message catalog\n"
                "     --update-catalog      Update the message catalog database\n"
                "     --new-id128           Generate a new 128-bit ID\n"
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
                "     --setup-keys          Generate a new FSS key pair\n"
 #endif
                , program_invocation_short_name);
@@ -375,6 +379,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VACUUM_FILES,
                 ARG_VACUUM_TIME,
                 ARG_NO_HOSTNAME,
+                ARG_OUTPUT_FIELDS,
         };
 
         static const struct option options[] = {
@@ -433,6 +438,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "vacuum-files",   required_argument, NULL, ARG_VACUUM_FILES   },
                 { "vacuum-time",    required_argument, NULL, ARG_VACUUM_TIME    },
                 { "no-hostname",    no_argument,       NULL, ARG_NO_HOSTNAME    },
+                { "output-fields",  required_argument, NULL, ARG_OUTPUT_FIELDS  },
                 {}
         };
 
@@ -475,11 +481,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 return -EINVAL;
                         }
 
-                        if (arg_output == OUTPUT_EXPORT ||
-                            arg_output == OUTPUT_JSON ||
-                            arg_output == OUTPUT_JSON_PRETTY ||
-                            arg_output == OUTPUT_JSON_SSE ||
-                            arg_output == OUTPUT_CAT)
+                        if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_CAT))
                                 arg_quiet = true;
 
                         break;
@@ -671,7 +673,7 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_action = ACTION_VACUUM;
                         break;
 
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
                 case ARG_FORCE:
                         arg_force = true;
                         break;
@@ -683,7 +685,13 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case ARG_VERIFY_KEY:
                         arg_action = ACTION_VERIFY;
-                        arg_verify_key = optarg;
+                        r = free_and_strdup(&arg_verify_key, optarg);
+                        if (r < 0)
+                                return r;
+                        /* Use memset not string_erase so this doesn't look confusing
+                         * in ps or htop output. */
+                        memset(optarg, 'x', strlen(optarg));
+
                         arg_merge = false;
                         break;
 
@@ -838,6 +846,24 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_action = ACTION_SYNC;
                         break;
 
+                case ARG_OUTPUT_FIELDS: {
+                        _cleanup_strv_free_ char **v = NULL;
+
+                        v = strv_split(optarg, ",");
+                        if (!v)
+                                return log_oom();
+
+                        if (!arg_output_fields) {
+                                arg_output_fields = v;
+                                v = NULL;
+                        } else {
+                                r = strv_extend_strv(&arg_output_fields, v, true);
+                                if (r < 0)
+                                        return log_oom();
+                        }
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -848,8 +874,8 @@ static int parse_argv(int argc, char *argv[]) {
         if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT)
                 arg_lines = 10;
 
-        if (!!arg_directory + !!arg_file + !!arg_machine > 1) {
-                log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one.");
+        if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root > 1) {
+                log_error("Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root.");
                 return -EINVAL;
         }
 
@@ -885,7 +911,7 @@ static int parse_argv(int argc, char *argv[]) {
                  * to users, and automatically turn --unit= into --user-unit= if combined with --user. */
                 r = strv_extend_strv(&arg_user_units, arg_system_units, true);
                 if (r < 0)
-                        return -ENOMEM;
+                        return r;
 
                 arg_system_units = strv_free(arg_system_units);
         }
@@ -906,7 +932,7 @@ static int generate_new_id128(void) {
                SD_ID128_FORMAT_STR "\n\n"
                "As UUID:\n"
                "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
-               "As macro:\n"
+               "As man:sd-id128(3) macro:\n"
                "#define MESSAGE_XYZ SD_ID128_MAKE(",
                SD_ID128_FORMAT_VAL(id),
                SD_ID128_FORMAT_VAL(id));
@@ -938,21 +964,21 @@ static int add_matches(sd_journal *j, char **args) {
                         have_term = false;
 
                 } else if (path_is_absolute(*i)) {
-                        _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL;
-                        const char *path;
+                        _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
                         struct stat st;
 
-                        p = canonicalize_file_name(*i);
-                        path = p ?: *i;
+                        r = chase_symlinks(*i, NULL, 0, &p);
+                        if (r < 0)
+                                return log_error_errno(r, "Couldn't canonicalize path: %m");
 
-                        if (lstat(path, &st) < 0)
+                        if (lstat(p, &st) < 0)
                                 return log_error_errno(errno, "Couldn't stat file: %m");
 
                         if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
-                                if (executable_is_script(path, &interpreter) > 0) {
+                                if (executable_is_script(p, &interpreter) > 0) {
                                         _cleanup_free_ char *comm;
 
-                                        comm = strndup(basename(path), 15);
+                                        comm = strndup(basename(p), 15);
                                         if (!comm)
                                                 return log_oom();
 
@@ -968,7 +994,7 @@ static int add_matches(sd_journal *j, char **args) {
                                                         return log_oom();
                                         }
                                 } else {
-                                        t = strappend("_EXE=", path);
+                                        t = strappend("_EXE=", p);
                                         if (!t)
                                                 return log_oom();
                                 }
@@ -979,7 +1005,7 @@ static int add_matches(sd_journal *j, char **args) {
                                         r = sd_journal_add_match(j, t2, 0);
 
                         } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
-                                r = add_matches_for_device(j, path);
+                                r = add_matches_for_device(j, p);
                                 if (r < 0)
                                         return r;
                         } else {
@@ -1091,8 +1117,10 @@ static int discover_next_boot(sd_journal *j,
                 r = sd_journal_previous(j);
         if (r < 0)
                 return r;
-        else if (r == 0)
+        else if (r == 0) {
+                log_debug("Whoopsie! We found a boot ID but can't read its last entry.");
                 return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */
+        }
 
         r = sd_journal_get_realtime_usec(j, &next_boot->last);
         if (r < 0)
@@ -1112,7 +1140,7 @@ static int get_boots(
 
         bool skip_once;
         int r, count = 0;
-        BootId *head = NULL, *tail = NULL;
+        BootId *head = NULL, *tail = NULL, *id;
         const bool advance_older = boot_id && offset <= 0;
         sd_id128_t previous_boot_id;
 
@@ -1203,6 +1231,13 @@ static int get_boots(
                                 break;
                         }
                 } else {
+                        LIST_FOREACH(boot_list, id, head) {
+                                if (sd_id128_equal(id->id, current->id)) {
+                                        /* boot id already stored, something wrong with the journal files */
+                                        /* exiting as otherwise this problem would cause forever loop */
+                                        goto finish;
+                                }
+                        }
                         LIST_INSERT_AFTER(boot_list, head, tail, current);
                         tail = current;
                         current = NULL;
@@ -1267,7 +1302,7 @@ static int add_boot(sd_journal *j) {
          * We can do this only when we logs are coming from the current machine,
          * so take the slow path if log location is specified. */
         if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) &&
-            !arg_directory && !arg_file)
+            !arg_directory && !arg_file && !arg_root)
 
                 return add_match_this_boot(j, arg_machine);
 
@@ -1548,7 +1583,7 @@ static int add_syslog_identifier(sd_journal *j) {
 }
 
 static int setup_keys(void) {
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
         size_t mpk_size, seed_size, state_size, i;
         uint8_t *mpk, *seed, *state;
         int fd = -1, r;
@@ -1559,7 +1594,7 @@ static int setup_keys(void) {
         struct stat st;
 
         r = stat("/var/log/journal", &st);
-        if (r < 0 && errno != ENOENT && errno != ENOTDIR)
+        if (r < 0 && !IN_SET(errno, ENOENT, ENOTDIR))
                 return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal");
 
         if (r < 0 || !S_ISDIR(st.st_mode)) {
@@ -1632,7 +1667,7 @@ static int setup_keys(void) {
         n /= arg_interval;
 
         safe_close(fd);
-        fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC);
+        fd = mkostemp_safe(k);
         if (fd < 0) {
                 r = log_error_errno(fd, "Failed to open %s: %m", k);
                 goto finish;
@@ -1684,9 +1719,9 @@ static int setup_keys(void) {
                         "at a safe location and should not be saved locally on disk.\n"
                         "\n\t%s",
                         ansi_highlight(), ansi_normal(),
+                        p,
                         ansi_highlight(), ansi_normal(),
-                        ansi_highlight_red(),
-                        p);
+                        ansi_highlight_red());
                 fflush(stderr);
         }
         for (i = 0; i < seed_size; i++) {
@@ -1714,7 +1749,7 @@ static int setup_keys(void) {
                 } else
                         fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine));
 
-#ifdef HAVE_QRENCODE
+#if HAVE_QRENCODE
                 /* If this is not an UTF-8 system don't print any QR codes */
                 if (is_locale_utf8()) {
                         fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr);
@@ -1756,7 +1791,7 @@ static int verify(sd_journal *j) {
                 int k;
                 usec_t first = 0, validated = 0, last = 0;
 
-#ifdef HAVE_GCRYPT
+#if HAVE_GCRYPT
                 if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
                         log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
 #endif
@@ -1790,129 +1825,6 @@ static int verify(sd_journal *j) {
         return r;
 }
 
-static int access_check_var_log_journal(sd_journal *j) {
-#ifdef HAVE_ACL
-        _cleanup_strv_free_ char **g = NULL;
-        const char* dir;
-#endif
-        int r;
-
-        assert(j);
-
-        if (arg_quiet)
-                return 0;
-
-        /* If we are root, we should have access, don't warn. */
-        if (getuid() == 0)
-                return 0;
-
-        /* If we are in the 'systemd-journal' group, we should have
-         * access too. */
-        r = in_group("systemd-journal");
-        if (r < 0)
-                return log_error_errno(r, "Failed to check if we are in the 'systemd-journal' group: %m");
-        if (r > 0)
-                return 0;
-
-#ifdef HAVE_ACL
-        if (laccess("/run/log/journal", F_OK) >= 0)
-                dir = "/run/log/journal";
-        else
-                dir = "/var/log/journal";
-
-        /* If we are in any of the groups listed in the journal ACLs,
-         * then all is good, too. Let's enumerate all groups from the
-         * default ACL of the directory, which generally should allow
-         * access to most journal files too. */
-        r = acl_search_groups(dir, &g);
-        if (r < 0)
-                return log_error_errno(r, "Failed to search journal ACL: %m");
-        if (r > 0)
-                return 0;
-
-        /* Print a pretty list, if there were ACLs set. */
-        if (!strv_isempty(g)) {
-                _cleanup_free_ char *s = NULL;
-
-                /* Thre are groups in the ACL, let's list them */
-                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 groups '%s' can see all messages.\n"
-                           "      Pass -q to turn off this notice.", s);
-                return 1;
-        }
-#endif
-
-        /* If no ACLs were found, print a short version of the message. */
-        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.");
-
-        return 1;
-}
-
-static int access_check(sd_journal *j) {
-        Iterator it;
-        void *code;
-        char *path;
-        int r = 0;
-
-        assert(j);
-
-        if (hashmap_isempty(j->errors)) {
-                if (ordered_hashmap_isempty(j->files))
-                        log_notice("No journal files were found.");
-
-                return 0;
-        }
-
-        if (hashmap_contains(j->errors, INT_TO_PTR(-EACCES))) {
-                (void) access_check_var_log_journal(j);
-
-                if (ordered_hashmap_isempty(j->files))
-                        r = log_error_errno(EACCES, "No journal files were opened due to insufficient permissions.");
-        }
-
-        HASHMAP_FOREACH_KEY(path, code, j->errors, it) {
-                int err;
-
-                err = abs(PTR_TO_INT(code));
-
-                switch (err) {
-                case EACCES:
-                        continue;
-
-                case ENODATA:
-                        log_warning_errno(err, "Journal file %s is truncated, ignoring file.", path);
-                        break;
-
-                case EPROTONOSUPPORT:
-                        log_warning_errno(err, "Journal file %s uses an unsupported feature, ignoring file.", path);
-                        break;
-
-                case EBADMSG:
-                        log_warning_errno(err, "Journal file %s corrupted, ignoring file.", path);
-                        break;
-
-                default:
-                        log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path);
-                        break;
-                }
-        }
-
-        return r;
-}
-
 static int flush_to_var(void) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -2161,6 +2073,8 @@ int main(int argc, char *argv[]) {
 
         if (arg_directory)
                 r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
+        else if (arg_root)
+                r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT);
         else if (arg_file_stdin) {
                 int ifd = STDIN_FILENO;
                 r = sd_journal_open_files_fd(&j, &ifd, 1, 0);
@@ -2222,7 +2136,7 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        r = access_check(j);
+        r = journal_access_check_and_warn(j, arg_quiet);
         if (r < 0)
                 goto finish;
 
@@ -2255,7 +2169,7 @@ int main(int argc, char *argv[]) {
                 if (r < 0)
                         goto finish;
 
-                printf("Archived and active journals take up %s on disk.\n",
+                printf("Archived and active journals take up %s in the file system.\n",
                        format_bytes(sbytes, sizeof(sbytes), bytes));
                 goto finish;
         }
@@ -2274,7 +2188,7 @@ int main(int argc, char *argv[]) {
                         if (d->is_root)
                                 continue;
 
-                        q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, true);
+                        q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, !arg_quiet);
                         if (q < 0) {
                                 log_error_errno(q, "Failed to vacuum %s: %m", d->path);
                                 r = q;
@@ -2307,7 +2221,7 @@ int main(int argc, char *argv[]) {
         if (arg_boot_offset != 0 &&
             sd_journal_has_runtime_files(j) > 0 &&
             sd_journal_has_persistent_files(j) == 0) {
-                log_info("Specifying boot ID has no effect, no persistent journal was found");
+                log_info("Specifying boot ID or boot offset has no effect, no persistent journal was found.");
                 r = 0;
                 goto finish;
         }
@@ -2469,20 +2383,13 @@ int main(int argc, char *argv[]) {
                 log_error_errno(r, "Failed to iterate through journal: %m");
                 goto finish;
         }
-        if (r == 0) {
-                if (arg_follow)
-                        need_seek = true;
-                else {
-                        if (!arg_quiet)
-                                printf("-- No entries --\n");
-                        goto finish;
-                }
-        }
+        if (r == 0)
+                need_seek = true;
 
         if (!arg_follow)
                 pager_open(arg_no_pager, arg_pager_end);
 
-        if (!arg_quiet) {
+        if (!arg_quiet && (arg_lines != 0 || arg_follow)) {
                 usec_t start, end;
                 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
 
@@ -2567,7 +2474,7 @@ int main(int argc, char *argv[]) {
                                 arg_utc * OUTPUT_UTC |
                                 arg_no_hostname * OUTPUT_NO_HOSTNAME;
 
-                        r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
+                        r = output_journal(stdout, j, arg_output, 0, flags, arg_output_fields, &ellipsized);
                         need_seek = true;
                         if (r == -EADDRNOTAVAIL)
                                 break;
@@ -2578,6 +2485,9 @@ int main(int argc, char *argv[]) {
                 }
 
                 if (!arg_follow) {
+                        if (n_shown == 0 && !arg_quiet)
+                                printf("-- No entries --\n");
+
                         if (arg_show_cursor) {
                                 _cleanup_free_ char *cursor = NULL;
 
@@ -2591,6 +2501,7 @@ int main(int argc, char *argv[]) {
                         break;
                 }
 
+                fflush(stdout);
                 r = sd_journal_wait(j, (uint64_t) -1);
                 if (r < 0) {
                         log_error_errno(r, "Couldn't wait for journal event: %m");
@@ -2601,6 +2512,7 @@ int main(int argc, char *argv[]) {
         }
 
 finish:
+        fflush(stdout);
         pager_close();
 
         strv_free(arg_file);
@@ -2608,8 +2520,10 @@ finish:
         strv_free(arg_syslog_identifier);
         strv_free(arg_system_units);
         strv_free(arg_user_units);
+        strv_free(arg_output_fields);
 
         free(arg_root);
+        free(arg_verify_key);
 
         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }