]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journalctl: support --lines=+N for showing the oldest N entries
authorMike Yuan <me@yhndnzj.com>
Thu, 10 Aug 2023 17:41:03 +0000 (01:41 +0800)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 16 Aug 2023 12:05:19 +0000 (14:05 +0200)
After f58269510727964cb5c10e7d2f9849c442ea1f80, the wrong behavior
occurred when --since= and --lines= are both specified is fixed.
However, it seems that the old behavior is already being somewhat
widely used, and the function itself makes sense, i.e. to allow --lines=
to output the first N journal entries.

Therefore, let's support prefixing the number for --lines= with '+',
and provide such functionality.

Related: #28746

man/journalctl.xml
src/journal/journalctl.c

index 70d8508cf0c9c24f10a204341eb583c32a1d0f5f..d6f227898392a31238e713f8847f8db2100fc8ba 100644 (file)
         sensitive. This can be overridden with the <option>--case-sensitive</option> option, see
         below.</para>
 
-        <para>When used with <option>--lines=</option>, <option>--reverse</option> is implied.</para></listitem>
+        <para>When used with <option>--lines=</option> (not prefixed with <literal>+</literal>),
+        <option>--reverse</option> is implied.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><option>-n</option></term>
         <term><option>--lines=</option></term>
 
-        <listitem><para>Show the most recent journal events and limit the number of events shown. If
-        <option>--follow</option> is used, this option is implied. The argument is a positive integer or
-        <literal>all</literal> to disable line limiting. The default value is 10 if no argument is
-        given.</para>
+        <listitem><para>Show the most recent journal events and limit the number of events shown. The argument
+        is a positive integer or <literal>all</literal> to disable the limit. Additionally, if the number is
+        prefixed with <literal>+</literal>, the oldest journal events are used instead. The default value is
+        10 if no argument is given.</para>
 
-        <para>When used with <option>--grep=</option>, <option>--reverse</option> is implied.</para></listitem>
+        <para>If <option>--follow</option> is used, this option is implied. When not prefixed with <literal>+</literal>
+        and used with <option>--grep=</option>, <option>--reverse</option> is implied.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 0e21f89da1cc091a9a5d3714643f2a2122df7450..a4e34fa0b12ab16615f3f6681d9e0287eb993882 100644 (file)
@@ -95,6 +95,7 @@ static bool arg_full = true;
 static bool arg_all = false;
 static PagerFlags arg_pager_flags = 0;
 static int arg_lines = ARG_LINES_DEFAULT;
+static int arg_lines_oldest = false;
 static bool arg_no_tail = false;
 static bool arg_truncate_newline = false;
 static bool arg_quiet = false;
@@ -298,6 +299,44 @@ static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset
         return 1;
 }
 
+static int parse_lines(const char *arg, bool graceful) {
+        const char *l;
+        int n, r;
+
+        assert(arg || graceful);
+
+        if (!arg)
+                goto default_noarg;
+
+        if (streq(arg, "all")) {
+                arg_lines = ARG_LINES_ALL;
+                return 1;
+        }
+
+        l = startswith(arg, "+");
+
+        r = safe_atoi(l ?: arg, &n);
+        if (r < 0 || n < 0) {
+                if (graceful)
+                        goto default_noarg;
+
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --lines='%s'.", arg);
+        }
+
+        arg_lines = n;
+        arg_lines_oldest = !!l;
+
+        return 1;
+
+default_noarg:
+        arg_lines = 10;
+        return 0;
+}
+
+static bool arg_lines_needs_seek_end(void) {
+        return arg_lines >= 0 && !arg_lines_oldest;
+}
+
 static int help_facilities(void) {
         if (!arg_quiet)
                 puts("Available facilities:");
@@ -358,7 +397,7 @@ static int help(void) {
                "                               json, json-pretty, json-sse, json-seq, cat,\n"
                "                               with-unit)\n"
                "     --output-fields=LIST    Select fields to print in verbose/export/json modes\n"
-               "  -n --lines[=INTEGER]       Number of journal entries to show\n"
+               "  -n --lines[=[+]INTEGER]    Number of journal entries to show\n"
                "  -r --reverse               Show the newest entries first\n"
                "     --show-cursor           Print the cursor after all the entries\n"
                "     --utc                   Express time in Coordinated Universal Time (UTC)\n"
@@ -592,33 +631,11 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'n':
-                        if (optarg) {
-                                if (streq(optarg, "all"))
-                                        arg_lines = ARG_LINES_ALL;
-                                else {
-                                        r = safe_atoi(optarg, &arg_lines);
-                                        if (r < 0 || arg_lines < 0)
-                                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse lines '%s'", optarg);
-                                }
-                        } else {
-                                arg_lines = 10;
-
-                                /* Hmm, no argument? Maybe the next
-                                 * word on the command line is
-                                 * supposed to be the argument? Let's
-                                 * see if there is one, and is
-                                 * parsable. */
-                                if (optind < argc) {
-                                        int n;
-                                        if (streq(argv[optind], "all")) {
-                                                arg_lines = ARG_LINES_ALL;
-                                                optind++;
-                                        } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) {
-                                                arg_lines = n;
-                                                optind++;
-                                        }
-                                }
-                        }
+                        r = parse_lines(optarg ?: optind < argc ? argv[optind] : NULL, !optarg);
+                        if (r < 0)
+                                return r;
+                        if (r > 0 && !optarg)
+                                optind++;
 
                         break;
 
@@ -1083,7 +1100,11 @@ static int parse_argv(int argc, char *argv[]) {
 
         if (arg_follow && arg_reverse)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Please specify either --reverse= or --follow=, not both.");
+                                       "Please specify either --reverse or --follow, not both.");
+
+        if (arg_lines >= 0 && arg_lines_oldest && (arg_reverse || arg_follow))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "--lines=+N is unsupported when --reverse or --follow is specified.");
 
         if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -1110,11 +1131,12 @@ static int parse_argv(int argc, char *argv[]) {
                 if (r < 0)
                         return r;
 
-                /* When --grep is used along with --lines, we don't know how many lines we can print.
-                 * So we search backwards and count until enough lines have been printed or we hit the head.
+                /* When --grep is used along with --lines without '+', i.e. when we start from the end of the
+                 * journal, we don't know how many lines we can print. So we search backwards and count until
+                 * enough lines have been printed or we hit the head.
                  * An exception is that --follow might set arg_lines, so let's not imply --reverse
                  * if that is specified. */
-                if (arg_lines >= 0 && !arg_follow)
+                if (arg_lines_needs_seek_end() && !arg_follow)
                         arg_reverse = true;
         }
 
@@ -2667,8 +2689,8 @@ static int run(int argc, char *argv[]) {
                                 arg_lines = 0;
                 }
 
-        } else if (arg_until_set && (arg_reverse || arg_lines >= 0)) {
-                /* If both --until and any of --reverse and --lines is specified, things get
+        } else if (arg_until_set && (arg_reverse || arg_lines_needs_seek_end())) {
+                /* If both --until and any of --reverse and --lines=N is specified, things get
                  * a little tricky. We seek to the place of --until first. If only --reverse or
                  * --reverse and --lines is specified, we search backwards and let the output
                  * counter handle --lines for us. If only --lines is used, we just jump backwards
@@ -2680,7 +2702,7 @@ static int run(int argc, char *argv[]) {
 
                 if (arg_reverse)
                         r = sd_journal_previous(j);
-                else /* arg_lines >= 0 */
+                else /* arg_lines_needs_seek_end */
                         r = sd_journal_previous_skip(j, arg_lines);
 
         } else if (arg_reverse) {
@@ -2690,7 +2712,7 @@ static int run(int argc, char *argv[]) {
 
                 r = sd_journal_previous(j);
 
-        } else if (arg_lines >= 0) {
+        } else if (arg_lines_needs_seek_end()) {
                 r = sd_journal_seek_tail(j);
                 if (r < 0)
                         return log_error_errno(r, "Failed to seek to tail: %m");