LCOV - code coverage report
Current view: top level - coredump - coredumpctl.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 25 567 4.4 %
Date: 2019-08-22 15:41:25 Functions: 4 19 21.1 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : 
       3             : #include <fcntl.h>
       4             : #include <getopt.h>
       5             : #include <locale.h>
       6             : #include <stdio.h>
       7             : #include <string.h>
       8             : #include <unistd.h>
       9             : 
      10             : #include "sd-bus.h"
      11             : #include "sd-journal.h"
      12             : #include "sd-messages.h"
      13             : 
      14             : #include "alloc-util.h"
      15             : #include "bus-error.h"
      16             : #include "bus-util.h"
      17             : #include "compress.h"
      18             : #include "def.h"
      19             : #include "fd-util.h"
      20             : #include "fs-util.h"
      21             : #include "journal-internal.h"
      22             : #include "journal-util.h"
      23             : #include "log.h"
      24             : #include "macro.h"
      25             : #include "main-func.h"
      26             : #include "pager.h"
      27             : #include "parse-util.h"
      28             : #include "path-util.h"
      29             : #include "pretty-print.h"
      30             : #include "process-util.h"
      31             : #include "rlimit-util.h"
      32             : #include "sigbus.h"
      33             : #include "signal-util.h"
      34             : #include "string-util.h"
      35             : #include "strv.h"
      36             : #include "terminal-util.h"
      37             : #include "tmpfile-util.h"
      38             : #include "user-util.h"
      39             : #include "util.h"
      40             : #include "verbs.h"
      41             : 
      42             : #define SHORT_BUS_CALL_TIMEOUT_USEC (3 * USEC_PER_SEC)
      43             : 
      44             : static usec_t arg_since = USEC_INFINITY, arg_until = USEC_INFINITY;
      45             : static const char* arg_field = NULL;
      46             : static const char *arg_debugger = NULL;
      47             : static const char *arg_directory = NULL;
      48             : static PagerFlags arg_pager_flags = 0;
      49             : static int arg_no_legend = false;
      50             : static int arg_one = false;
      51             : static const char* arg_output = NULL;
      52             : static bool arg_reverse = false;
      53             : static bool arg_quiet = false;
      54             : 
      55           0 : static int add_match(sd_journal *j, const char *match) {
      56           0 :         _cleanup_free_ char *p = NULL;
      57             :         const char* prefix, *pattern;
      58             :         pid_t pid;
      59             :         int r;
      60             : 
      61           0 :         if (strchr(match, '='))
      62           0 :                 prefix = "";
      63           0 :         else if (strchr(match, '/')) {
      64           0 :                 r = path_make_absolute_cwd(match, &p);
      65           0 :                 if (r < 0)
      66           0 :                         return log_error_errno(r, "path_make_absolute_cwd(\"%s\"): %m", match);
      67             : 
      68           0 :                 match = p;
      69           0 :                 prefix = "COREDUMP_EXE=";
      70           0 :         } else if (parse_pid(match, &pid) >= 0)
      71           0 :                 prefix = "COREDUMP_PID=";
      72             :         else
      73           0 :                 prefix = "COREDUMP_COMM=";
      74             : 
      75           0 :         pattern = strjoina(prefix, match);
      76           0 :         log_debug("Adding match: %s", pattern);
      77           0 :         r = sd_journal_add_match(j, pattern, 0);
      78           0 :         if (r < 0)
      79           0 :                 return log_error_errno(r, "Failed to add match \"%s\": %m", match);
      80             : 
      81           0 :         return 0;
      82             : }
      83             : 
      84           0 : static int add_matches(sd_journal *j, char **matches) {
      85             :         char **match;
      86             :         int r;
      87             : 
      88           0 :         r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR, 0);
      89           0 :         if (r < 0)
      90           0 :                 return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR);
      91             : 
      92           0 :         r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR, 0);
      93           0 :         if (r < 0)
      94           0 :                 return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR);
      95             : 
      96           0 :         STRV_FOREACH(match, matches) {
      97           0 :                 r = add_match(j, *match);
      98           0 :                 if (r < 0)
      99           0 :                         return r;
     100             :         }
     101             : 
     102           0 :         return 0;
     103             : }
     104             : 
     105           0 : static int acquire_journal(sd_journal **ret, char **matches) {
     106           0 :         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
     107             :         int r;
     108             : 
     109           0 :         assert(ret);
     110             : 
     111           0 :         if (arg_directory) {
     112           0 :                 r = sd_journal_open_directory(&j, arg_directory, 0);
     113           0 :                 if (r < 0)
     114           0 :                         return log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory);
     115             :         } else {
     116           0 :                 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
     117           0 :                 if (r < 0)
     118           0 :                         return log_error_errno(r, "Failed to open journal: %m");
     119             :         }
     120             : 
     121           0 :         r = journal_access_check_and_warn(j, arg_quiet, true);
     122           0 :         if (r < 0)
     123           0 :                 return r;
     124             : 
     125           0 :         r = add_matches(j, matches);
     126           0 :         if (r < 0)
     127           0 :                 return r;
     128             : 
     129           0 :         if (DEBUG_LOGGING) {
     130           0 :                 _cleanup_free_ char *filter;
     131             : 
     132           0 :                 filter = journal_make_match_string(j);
     133           0 :                 log_debug("Journal filter: %s", filter);
     134             :         }
     135             : 
     136           0 :         *ret = TAKE_PTR(j);
     137             : 
     138           0 :         return 0;
     139             : }
     140             : 
     141           3 : static int help(void) {
     142           3 :         _cleanup_free_ char *link = NULL;
     143             :         int r;
     144             : 
     145           3 :         r = terminal_urlify_man("coredumpctl", "1", &link);
     146           3 :         if (r < 0)
     147           0 :                 return log_oom();
     148             : 
     149           3 :         printf("%s [OPTIONS...]\n\n"
     150             :                "List or retrieve coredumps from the journal.\n\n"
     151             :                "Flags:\n"
     152             :                "  -h --help              Show this help\n"
     153             :                "     --version           Print version string\n"
     154             :                "     --no-pager          Do not pipe output into a pager\n"
     155             :                "     --no-legend         Do not print the column headers\n"
     156             :                "     --debugger=DEBUGGER Use the given debugger\n"
     157             :                "  -1                     Show information about most recent entry only\n"
     158             :                "  -S --since=DATE        Only print coredumps since the date\n"
     159             :                "  -U --until=DATE        Only print coredumps until the date\n"
     160             :                "  -r --reverse           Show the newest entries first\n"
     161             :                "  -F --field=FIELD       List all values a certain field takes\n"
     162             :                "  -o --output=FILE       Write output to FILE\n"
     163             :                "  -D --directory=DIR     Use journal files from directory\n\n"
     164             :                "  -q --quiet             Do not show info messages and privilege warning\n"
     165             :                "Commands:\n"
     166             :                "  list [MATCHES...]  List available coredumps (default)\n"
     167             :                "  info [MATCHES...]  Show detailed information about one or more coredumps\n"
     168             :                "  dump [MATCHES...]  Print first matching coredump to stdout\n"
     169             :                "  debug [MATCHES...] Start a debugger for the first matching coredump\n"
     170             :                "\nSee the %s for details.\n"
     171             :                , program_invocation_short_name
     172             :                , link
     173             :         );
     174             : 
     175           3 :         return 0;
     176             : }
     177             : 
     178           4 : static int parse_argv(int argc, char *argv[]) {
     179             :         enum {
     180             :                 ARG_VERSION = 0x100,
     181             :                 ARG_NO_PAGER,
     182             :                 ARG_NO_LEGEND,
     183             :                 ARG_DEBUGGER,
     184             :         };
     185             : 
     186             :         int c, r;
     187             : 
     188             :         static const struct option options[] = {
     189             :                 { "help",         no_argument,       NULL, 'h'           },
     190             :                 { "version" ,     no_argument,       NULL, ARG_VERSION   },
     191             :                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER  },
     192             :                 { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND },
     193             :                 { "debugger",     required_argument, NULL, ARG_DEBUGGER  },
     194             :                 { "output",       required_argument, NULL, 'o'           },
     195             :                 { "field",        required_argument, NULL, 'F'           },
     196             :                 { "directory",    required_argument, NULL, 'D'           },
     197             :                 { "reverse",      no_argument,       NULL, 'r'           },
     198             :                 { "since",        required_argument, NULL, 'S'           },
     199             :                 { "until",        required_argument, NULL, 'U'           },
     200             :                 { "quiet",        no_argument,       NULL, 'q'           },
     201             :                 {}
     202             :         };
     203             : 
     204           4 :         assert(argc >= 0);
     205           4 :         assert(argv);
     206             : 
     207           4 :         while ((c = getopt_long(argc, argv, "ho:F:1D:rS:U:q", options, NULL)) >= 0)
     208           4 :                 switch(c) {
     209           3 :                 case 'h':
     210           3 :                         return help();
     211             : 
     212           0 :                 case ARG_VERSION:
     213           0 :                         return version();
     214             : 
     215           0 :                 case ARG_NO_PAGER:
     216           0 :                         arg_pager_flags |= PAGER_DISABLE;
     217           0 :                         break;
     218             : 
     219           0 :                 case ARG_NO_LEGEND:
     220           0 :                         arg_no_legend = true;
     221           0 :                         break;
     222             : 
     223           0 :                 case ARG_DEBUGGER:
     224           0 :                         arg_debugger = optarg;
     225           0 :                         break;
     226             : 
     227           0 :                 case 'o':
     228           0 :                         if (arg_output)
     229           0 :                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
     230             :                                                        "Cannot set output more than once.");
     231             : 
     232           0 :                         arg_output = optarg;
     233           0 :                         break;
     234             : 
     235           0 :                 case 'S':
     236           0 :                         r = parse_timestamp(optarg, &arg_since);
     237           0 :                         if (r < 0)
     238           0 :                                 return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
     239           0 :                         break;
     240             : 
     241           0 :                 case 'U':
     242           0 :                         r = parse_timestamp(optarg, &arg_until);
     243           0 :                         if (r < 0)
     244           0 :                                 return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg);
     245           0 :                         break;
     246             : 
     247           0 :                 case 'F':
     248           0 :                         if (arg_field)
     249           0 :                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
     250             :                                                        "Cannot use --field/-F more than once.");
     251           0 :                         arg_field = optarg;
     252           0 :                         break;
     253             : 
     254           0 :                 case '1':
     255           0 :                         arg_one = true;
     256           0 :                         break;
     257             : 
     258           0 :                 case 'D':
     259           0 :                         arg_directory = optarg;
     260           0 :                         break;
     261             : 
     262           0 :                 case 'r':
     263           0 :                         arg_reverse = true;
     264           0 :                         break;
     265             : 
     266           0 :                 case 'q':
     267           0 :                         arg_quiet = true;
     268           0 :                         break;
     269             : 
     270           1 :                 case '?':
     271           1 :                         return -EINVAL;
     272             : 
     273           0 :                 default:
     274           0 :                         assert_not_reached("Unhandled option");
     275             :                 }
     276             : 
     277           0 :         if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY &&
     278           0 :             arg_since > arg_until)
     279           0 :                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
     280             :                                        "--since= must be before --until=.");
     281             : 
     282           0 :         return 1;
     283             : }
     284             : 
     285           0 : static int retrieve(const void *data,
     286             :                     size_t len,
     287             :                     const char *name,
     288             :                     char **var) {
     289             : 
     290             :         size_t ident;
     291             :         char *v;
     292             : 
     293           0 :         ident = strlen(name) + 1; /* name + "=" */
     294             : 
     295           0 :         if (len < ident)
     296           0 :                 return 0;
     297             : 
     298           0 :         if (memcmp(data, name, ident - 1) != 0)
     299           0 :                 return 0;
     300             : 
     301           0 :         if (((const char*) data)[ident - 1] != '=')
     302           0 :                 return 0;
     303             : 
     304           0 :         v = strndup((const char*)data + ident, len - ident);
     305           0 :         if (!v)
     306           0 :                 return log_oom();
     307             : 
     308           0 :         free_and_replace(*var, v);
     309           0 :         return 1;
     310             : }
     311             : 
     312           0 : static int print_field(FILE* file, sd_journal *j) {
     313             :         const void *d;
     314             :         size_t l;
     315             : 
     316           0 :         assert(file);
     317           0 :         assert(j);
     318             : 
     319           0 :         assert(arg_field);
     320             : 
     321             :         /* A (user-specified) field may appear more than once for a given entry.
     322             :          * We will print all of the occurrences.
     323             :          * This is different below for fields that systemd-coredump uses,
     324             :          * because they cannot meaningfully appear more than once.
     325             :          */
     326           0 :         SD_JOURNAL_FOREACH_DATA(j, d, l) {
     327           0 :                 _cleanup_free_ char *value = NULL;
     328             :                 int r;
     329             : 
     330           0 :                 r = retrieve(d, l, arg_field, &value);
     331           0 :                 if (r < 0)
     332           0 :                         return r;
     333           0 :                 if (r > 0)
     334           0 :                         fprintf(file, "%s\n", value);
     335             :         }
     336             : 
     337           0 :         return 0;
     338             : }
     339             : 
     340             : #define RETRIEVE(d, l, name, arg)                    \
     341             :         {                                            \
     342             :                 int _r = retrieve(d, l, name, &arg); \
     343             :                 if (_r < 0)                          \
     344             :                         return _r;                   \
     345             :                 if (_r > 0)                          \
     346             :                         continue;                    \
     347             :         }
     348             : 
     349           0 : static int print_list(FILE* file, sd_journal *j, int had_legend) {
     350             :         _cleanup_free_ char
     351           0 :                 *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL,
     352           0 :                 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
     353           0 :                 *filename = NULL, *truncated = NULL, *coredump = NULL;
     354             :         const void *d;
     355             :         size_t l;
     356             :         usec_t t;
     357             :         char buf[FORMAT_TIMESTAMP_MAX];
     358             :         int r;
     359             :         const char *present;
     360             :         bool normal_coredump;
     361             : 
     362           0 :         assert(file);
     363           0 :         assert(j);
     364             : 
     365           0 :         SD_JOURNAL_FOREACH_DATA(j, d, l) {
     366           0 :                 RETRIEVE(d, l, "MESSAGE_ID", mid);
     367           0 :                 RETRIEVE(d, l, "COREDUMP_PID", pid);
     368           0 :                 RETRIEVE(d, l, "COREDUMP_UID", uid);
     369           0 :                 RETRIEVE(d, l, "COREDUMP_GID", gid);
     370           0 :                 RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl);
     371           0 :                 RETRIEVE(d, l, "COREDUMP_EXE", exe);
     372           0 :                 RETRIEVE(d, l, "COREDUMP_COMM", comm);
     373           0 :                 RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
     374           0 :                 RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
     375           0 :                 RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
     376           0 :                 RETRIEVE(d, l, "COREDUMP", coredump);
     377             :         }
     378             : 
     379           0 :         if (!pid && !uid && !gid && !sgnl && !exe && !comm && !cmdline && !filename) {
     380           0 :                 log_warning("Empty coredump log entry");
     381           0 :                 return -EINVAL;
     382             :         }
     383             : 
     384           0 :         r = sd_journal_get_realtime_usec(j, &t);
     385           0 :         if (r < 0)
     386           0 :                 return log_error_errno(r, "Failed to get realtime timestamp: %m");
     387             : 
     388           0 :         format_timestamp(buf, sizeof(buf), t);
     389             : 
     390           0 :         if (!had_legend && !arg_no_legend)
     391           0 :                 fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n",
     392             :                         FORMAT_TIMESTAMP_WIDTH, "TIME",
     393             :                         6, "PID",
     394             :                         5, "UID",
     395             :                         5, "GID",
     396             :                         3, "SIG",
     397             :                         9, "COREFILE",
     398             :                            "EXE");
     399             : 
     400           0 :         normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
     401             : 
     402           0 :         if (filename)
     403           0 :                 if (access(filename, R_OK) == 0)
     404           0 :                         present = "present";
     405           0 :                 else if (errno == ENOENT)
     406           0 :                         present = "missing";
     407             :                 else
     408           0 :                         present = "error";
     409           0 :         else if (coredump)
     410           0 :                 present = "journal";
     411           0 :         else if (normal_coredump)
     412           0 :                 present = "none";
     413             :         else
     414           0 :                 present = "-";
     415             : 
     416           0 :         if (STR_IN_SET(present, "present", "journal") && truncated && parse_boolean(truncated) > 0)
     417           0 :                 present = "truncated";
     418             : 
     419           0 :         fprintf(file, "%-*s %*s %*s %*s %*s %-*s %s\n",
     420             :                 FORMAT_TIMESTAMP_WIDTH, buf,
     421             :                 6, strna(pid),
     422             :                 5, strna(uid),
     423             :                 5, strna(gid),
     424           0 :                 3, normal_coredump ? strna(sgnl) : "-",
     425             :                 9, present,
     426           0 :                 strna(exe ?: (comm ?: cmdline)));
     427             : 
     428           0 :         return 0;
     429             : }
     430             : 
     431           0 : static int print_info(FILE *file, sd_journal *j, bool need_space) {
     432             :         _cleanup_free_ char
     433           0 :                 *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL,
     434           0 :                 *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL,
     435           0 :                 *unit = NULL, *user_unit = NULL, *session = NULL,
     436           0 :                 *boot_id = NULL, *machine_id = NULL, *hostname = NULL,
     437           0 :                 *slice = NULL, *cgroup = NULL, *owner_uid = NULL,
     438           0 :                 *message = NULL, *timestamp = NULL, *filename = NULL,
     439           0 :                 *truncated = NULL, *coredump = NULL;
     440             :         const void *d;
     441             :         size_t l;
     442             :         bool normal_coredump;
     443             :         int r;
     444             : 
     445           0 :         assert(file);
     446           0 :         assert(j);
     447             : 
     448           0 :         SD_JOURNAL_FOREACH_DATA(j, d, l) {
     449           0 :                 RETRIEVE(d, l, "MESSAGE_ID", mid);
     450           0 :                 RETRIEVE(d, l, "COREDUMP_PID", pid);
     451           0 :                 RETRIEVE(d, l, "COREDUMP_UID", uid);
     452           0 :                 RETRIEVE(d, l, "COREDUMP_GID", gid);
     453           0 :                 RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl);
     454           0 :                 RETRIEVE(d, l, "COREDUMP_EXE", exe);
     455           0 :                 RETRIEVE(d, l, "COREDUMP_COMM", comm);
     456           0 :                 RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline);
     457           0 :                 RETRIEVE(d, l, "COREDUMP_UNIT", unit);
     458           0 :                 RETRIEVE(d, l, "COREDUMP_USER_UNIT", user_unit);
     459           0 :                 RETRIEVE(d, l, "COREDUMP_SESSION", session);
     460           0 :                 RETRIEVE(d, l, "COREDUMP_OWNER_UID", owner_uid);
     461           0 :                 RETRIEVE(d, l, "COREDUMP_SLICE", slice);
     462           0 :                 RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup);
     463           0 :                 RETRIEVE(d, l, "COREDUMP_TIMESTAMP", timestamp);
     464           0 :                 RETRIEVE(d, l, "COREDUMP_FILENAME", filename);
     465           0 :                 RETRIEVE(d, l, "COREDUMP_TRUNCATED", truncated);
     466           0 :                 RETRIEVE(d, l, "COREDUMP", coredump);
     467           0 :                 RETRIEVE(d, l, "_BOOT_ID", boot_id);
     468           0 :                 RETRIEVE(d, l, "_MACHINE_ID", machine_id);
     469           0 :                 RETRIEVE(d, l, "_HOSTNAME", hostname);
     470           0 :                 RETRIEVE(d, l, "MESSAGE", message);
     471             :         }
     472             : 
     473           0 :         if (need_space)
     474           0 :                 fputs("\n", file);
     475             : 
     476           0 :         normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR);
     477             : 
     478           0 :         if (comm)
     479           0 :                 fprintf(file,
     480             :                         "           PID: %s%s%s (%s)\n",
     481             :                         ansi_highlight(), strna(pid), ansi_normal(), comm);
     482             :         else
     483           0 :                 fprintf(file,
     484             :                         "           PID: %s%s%s\n",
     485             :                         ansi_highlight(), strna(pid), ansi_normal());
     486             : 
     487           0 :         if (uid) {
     488             :                 uid_t n;
     489             : 
     490           0 :                 if (parse_uid(uid, &n) >= 0) {
     491           0 :                         _cleanup_free_ char *u = NULL;
     492             : 
     493           0 :                         u = uid_to_name(n);
     494           0 :                         fprintf(file,
     495             :                                 "           UID: %s (%s)\n",
     496             :                                 uid, u);
     497             :                 } else {
     498           0 :                         fprintf(file,
     499             :                                 "           UID: %s\n",
     500             :                                 uid);
     501             :                 }
     502             :         }
     503             : 
     504           0 :         if (gid) {
     505             :                 gid_t n;
     506             : 
     507           0 :                 if (parse_gid(gid, &n) >= 0) {
     508           0 :                         _cleanup_free_ char *g = NULL;
     509             : 
     510           0 :                         g = gid_to_name(n);
     511           0 :                         fprintf(file,
     512             :                                 "           GID: %s (%s)\n",
     513             :                                 gid, g);
     514             :                 } else {
     515           0 :                         fprintf(file,
     516             :                                 "           GID: %s\n",
     517             :                                 gid);
     518             :                 }
     519             :         }
     520             : 
     521           0 :         if (sgnl) {
     522             :                 int sig;
     523           0 :                 const char *name = normal_coredump ? "Signal" : "Reason";
     524             : 
     525           0 :                 if (normal_coredump && safe_atoi(sgnl, &sig) >= 0)
     526           0 :                         fprintf(file, "        %s: %s (%s)\n", name, sgnl, signal_to_string(sig));
     527             :                 else
     528           0 :                         fprintf(file, "        %s: %s\n", name, sgnl);
     529             :         }
     530             : 
     531           0 :         if (timestamp) {
     532             :                 usec_t u;
     533             : 
     534           0 :                 r = safe_atou64(timestamp, &u);
     535           0 :                 if (r >= 0) {
     536             :                         char absolute[FORMAT_TIMESTAMP_MAX], relative[FORMAT_TIMESPAN_MAX];
     537             : 
     538           0 :                         fprintf(file,
     539             :                                 "     Timestamp: %s (%s)\n",
     540             :                                 format_timestamp(absolute, sizeof(absolute), u),
     541             :                                 format_timestamp_relative(relative, sizeof(relative), u));
     542             : 
     543             :                 } else
     544           0 :                         fprintf(file, "     Timestamp: %s\n", timestamp);
     545             :         }
     546             : 
     547           0 :         if (cmdline)
     548           0 :                 fprintf(file, "  Command Line: %s\n", cmdline);
     549           0 :         if (exe)
     550           0 :                 fprintf(file, "    Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal());
     551           0 :         if (cgroup)
     552           0 :                 fprintf(file, " Control Group: %s\n", cgroup);
     553           0 :         if (unit)
     554           0 :                 fprintf(file, "          Unit: %s\n", unit);
     555           0 :         if (user_unit)
     556           0 :                 fprintf(file, "     User Unit: %s\n", user_unit);
     557           0 :         if (slice)
     558           0 :                 fprintf(file, "         Slice: %s\n", slice);
     559           0 :         if (session)
     560           0 :                 fprintf(file, "       Session: %s\n", session);
     561           0 :         if (owner_uid) {
     562             :                 uid_t n;
     563             : 
     564           0 :                 if (parse_uid(owner_uid, &n) >= 0) {
     565           0 :                         _cleanup_free_ char *u = NULL;
     566             : 
     567           0 :                         u = uid_to_name(n);
     568           0 :                         fprintf(file,
     569             :                                 "     Owner UID: %s (%s)\n",
     570             :                                 owner_uid, u);
     571             :                 } else {
     572           0 :                         fprintf(file,
     573             :                                 "     Owner UID: %s\n",
     574             :                                 owner_uid);
     575             :                 }
     576             :         }
     577           0 :         if (boot_id)
     578           0 :                 fprintf(file, "       Boot ID: %s\n", boot_id);
     579           0 :         if (machine_id)
     580           0 :                 fprintf(file, "    Machine ID: %s\n", machine_id);
     581           0 :         if (hostname)
     582           0 :                 fprintf(file, "      Hostname: %s\n", hostname);
     583             : 
     584           0 :         if (filename) {
     585             :                 bool inacc, trunc;
     586             : 
     587           0 :                 inacc = access(filename, R_OK) < 0;
     588           0 :                 trunc = truncated && parse_boolean(truncated) > 0;
     589             : 
     590           0 :                 if (inacc || trunc)
     591           0 :                         fprintf(file, "       Storage: %s%s (%s%s%s)%s\n",
     592             :                                 ansi_highlight_red(),
     593             :                                 filename,
     594             :                                 inacc ? "inaccessible" : "",
     595           0 :                                 inacc && trunc ? ", " : "",
     596             :                                 trunc ? "truncated" : "",
     597             :                                 ansi_normal());
     598             :                 else
     599           0 :                         fprintf(file, "       Storage: %s\n", filename);
     600             :         }
     601             : 
     602           0 :         else if (coredump)
     603           0 :                 fprintf(file, "       Storage: journal\n");
     604             :         else
     605           0 :                 fprintf(file, "       Storage: none\n");
     606             : 
     607           0 :         if (message) {
     608           0 :                 _cleanup_free_ char *m = NULL;
     609             : 
     610           0 :                 m = strreplace(message, "\n", "\n                ");
     611             : 
     612           0 :                 fprintf(file, "       Message: %s\n", strstrip(m ?: message));
     613             :         }
     614             : 
     615           0 :         return 0;
     616             : }
     617             : 
     618           0 : static int focus(sd_journal *j) {
     619             :         int r;
     620             : 
     621           0 :         r = sd_journal_seek_tail(j);
     622           0 :         if (r == 0)
     623           0 :                 r = sd_journal_previous(j);
     624           0 :         if (r < 0)
     625           0 :                 return log_error_errno(r, "Failed to search journal: %m");
     626           0 :         if (r == 0)
     627           0 :                 return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
     628             :                                        "No match found.");
     629           0 :         return r;
     630             : }
     631             : 
     632           0 : static int print_entry(sd_journal *j, unsigned n_found, bool verb_is_info) {
     633           0 :         assert(j);
     634             : 
     635           0 :         if (verb_is_info)
     636           0 :                 return print_info(stdout, j, n_found);
     637           0 :         else if (arg_field)
     638           0 :                 return print_field(stdout, j);
     639             :         else
     640           0 :                 return print_list(stdout, j, n_found);
     641             : }
     642             : 
     643           0 : static int dump_list(int argc, char **argv, void *userdata) {
     644           0 :         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
     645           0 :         unsigned n_found = 0;
     646             :         bool verb_is_info;
     647             :         int r;
     648             : 
     649           0 :         verb_is_info = (argc >= 1 && streq(argv[0], "info"));
     650             : 
     651           0 :         r = acquire_journal(&j, argv + 1);
     652           0 :         if (r < 0)
     653           0 :                 return r;
     654             : 
     655           0 :         (void) pager_open(arg_pager_flags);
     656             : 
     657             :         /* The coredumps are likely to compressed, and for just
     658             :          * listing them we don't need to decompress them, so let's
     659             :          * pick a fairly low data threshold here */
     660           0 :         sd_journal_set_data_threshold(j, 4096);
     661             : 
     662             :         /* "info" without pattern implies "-1" */
     663           0 :         if (arg_one || (verb_is_info && argc == 1)) {
     664           0 :                 r = focus(j);
     665           0 :                 if (r < 0)
     666           0 :                         return r;
     667             : 
     668           0 :                 return print_entry(j, 0, verb_is_info);
     669             :         } else {
     670           0 :                 if (arg_since != USEC_INFINITY && !arg_reverse)
     671           0 :                         r = sd_journal_seek_realtime_usec(j, arg_since);
     672           0 :                 else if (arg_until != USEC_INFINITY && arg_reverse)
     673           0 :                         r = sd_journal_seek_realtime_usec(j, arg_until);
     674           0 :                 else if (arg_reverse)
     675           0 :                         r = sd_journal_seek_tail(j);
     676             :                 else
     677           0 :                         r = sd_journal_seek_head(j);
     678           0 :                 if (r < 0)
     679           0 :                         return log_error_errno(r, "Failed to seek to date: %m");
     680             : 
     681             :                 for (;;) {
     682           0 :                         if (!arg_reverse)
     683           0 :                                 r = sd_journal_next(j);
     684             :                         else
     685           0 :                                 r = sd_journal_previous(j);
     686             : 
     687           0 :                         if (r < 0)
     688           0 :                                 return log_error_errno(r, "Failed to iterate through journal: %m");
     689             : 
     690           0 :                         if (r == 0)
     691           0 :                                 break;
     692             : 
     693           0 :                         if (arg_until != USEC_INFINITY && !arg_reverse) {
     694             :                                 usec_t usec;
     695             : 
     696           0 :                                 r = sd_journal_get_realtime_usec(j, &usec);
     697           0 :                                 if (r < 0)
     698           0 :                                         return log_error_errno(r, "Failed to determine timestamp: %m");
     699           0 :                                 if (usec > arg_until)
     700           0 :                                         continue;
     701             :                         }
     702             : 
     703           0 :                         if (arg_since != USEC_INFINITY && arg_reverse) {
     704             :                                 usec_t usec;
     705             : 
     706           0 :                                 r = sd_journal_get_realtime_usec(j, &usec);
     707           0 :                                 if (r < 0)
     708           0 :                                         return log_error_errno(r, "Failed to determine timestamp: %m");
     709           0 :                                 if (usec < arg_since)
     710           0 :                                         continue;
     711             :                         }
     712             : 
     713           0 :                         r = print_entry(j, n_found++, verb_is_info);
     714           0 :                         if (r < 0)
     715           0 :                                 return r;
     716             :                 }
     717             : 
     718           0 :                 if (!arg_field && n_found <= 0) {
     719           0 :                         if (!arg_quiet)
     720           0 :                                 log_notice("No coredumps found.");
     721           0 :                         return -ESRCH;
     722             :                 }
     723             :         }
     724             : 
     725           0 :         return 0;
     726             : }
     727             : 
     728           0 : static int save_core(sd_journal *j, FILE *file, char **path, bool *unlink_temp) {
     729             :         const char *data;
     730           0 :         _cleanup_free_ char *filename = NULL;
     731             :         size_t len;
     732             :         int r, fd;
     733           0 :         _cleanup_close_ int fdt = -1;
     734           0 :         char *temp = NULL;
     735             : 
     736           0 :         assert(!(file && path));         /* At most one can be specified */
     737           0 :         assert(!!path == !!unlink_temp); /* Those must be specified together */
     738             : 
     739             :         /* Look for a coredump on disk first. */
     740           0 :         r = sd_journal_get_data(j, "COREDUMP_FILENAME", (const void**) &data, &len);
     741           0 :         if (r == 0) {
     742           0 :                 r = retrieve(data, len, "COREDUMP_FILENAME", &filename);
     743           0 :                 if (r < 0)
     744           0 :                         return r;
     745           0 :                 assert(r > 0);
     746             : 
     747           0 :                 if (access(filename, R_OK) < 0)
     748           0 :                         return log_error_errno(errno, "File \"%s\" is not readable: %m", filename);
     749             : 
     750           0 :                 if (path && !endswith(filename, ".xz") && !endswith(filename, ".lz4")) {
     751           0 :                         *path = TAKE_PTR(filename);
     752             : 
     753           0 :                         return 0;
     754             :                 }
     755             : 
     756             :         } else {
     757           0 :                 if (r != -ENOENT)
     758           0 :                         return log_error_errno(r, "Failed to retrieve COREDUMP_FILENAME field: %m");
     759             :                 /* Check that we can have a COREDUMP field. We still haven't set a high
     760             :                  * data threshold, so we'll get a few kilobytes at most.
     761             :                  */
     762             : 
     763           0 :                 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
     764           0 :                 if (r == -ENOENT)
     765           0 :                         return log_error_errno(r, "Coredump entry has no core attached (neither internally in the journal nor externally on disk).");
     766           0 :                 if (r < 0)
     767           0 :                         return log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
     768             :         }
     769             : 
     770           0 :         if (path) {
     771             :                 const char *vt;
     772             : 
     773             :                 /* Create a temporary file to write the uncompressed core to. */
     774             : 
     775           0 :                 r = var_tmp_dir(&vt);
     776           0 :                 if (r < 0)
     777           0 :                         return log_error_errno(r, "Failed to acquire temporary directory path: %m");
     778             : 
     779           0 :                 temp = path_join(vt, "coredump-XXXXXX");
     780           0 :                 if (!temp)
     781           0 :                         return log_oom();
     782             : 
     783           0 :                 fdt = mkostemp_safe(temp);
     784           0 :                 if (fdt < 0)
     785           0 :                         return log_error_errno(fdt, "Failed to create temporary file: %m");
     786           0 :                 log_debug("Created temporary file %s", temp);
     787             : 
     788           0 :                 fd = fdt;
     789             :         } else {
     790             :                 /* If neither path or file are specified, we will write to stdout. Let's now check
     791             :                  * if stdout is connected to a tty. We checked that the file exists, or that the
     792             :                  * core might be stored in the journal. In this second case, if we found the entry,
     793             :                  * in all likelihood we will be able to access the COREDUMP= field.  In either case,
     794             :                  * we stop before doing any "real" work, i.e. before starting decompression or
     795             :                  * reading from the file or creating temporary files.
     796             :                  */
     797           0 :                 if (!file) {
     798           0 :                         if (on_tty())
     799           0 :                                 return log_error_errno(SYNTHETIC_ERRNO(ENOTTY),
     800             :                                                        "Refusing to dump core to tty"
     801             :                                                        " (use shell redirection or specify --output).");
     802           0 :                         file = stdout;
     803             :                 }
     804             : 
     805           0 :                 fd = fileno(file);
     806             :         }
     807             : 
     808           0 :         if (filename) {
     809             : #if HAVE_XZ || HAVE_LZ4
     810           0 :                 _cleanup_close_ int fdf;
     811             : 
     812           0 :                 fdf = open(filename, O_RDONLY | O_CLOEXEC);
     813           0 :                 if (fdf < 0) {
     814           0 :                         r = log_error_errno(errno, "Failed to open %s: %m", filename);
     815           0 :                         goto error;
     816             :                 }
     817             : 
     818           0 :                 r = decompress_stream(filename, fdf, fd, -1);
     819           0 :                 if (r < 0) {
     820           0 :                         log_error_errno(r, "Failed to decompress %s: %m", filename);
     821           0 :                         goto error;
     822             :                 }
     823             : #else
     824             :                 log_error("Cannot decompress file. Compiled without compression support.");
     825             :                 r = -EOPNOTSUPP;
     826             :                 goto error;
     827             : #endif
     828             :         } else {
     829             :                 ssize_t sz;
     830             : 
     831             :                 /* We want full data, nothing truncated. */
     832           0 :                 sd_journal_set_data_threshold(j, 0);
     833             : 
     834           0 :                 r = sd_journal_get_data(j, "COREDUMP", (const void**) &data, &len);
     835           0 :                 if (r < 0)
     836           0 :                         return log_error_errno(r, "Failed to retrieve COREDUMP field: %m");
     837             : 
     838           0 :                 assert(len >= 9);
     839           0 :                 data += 9;
     840           0 :                 len -= 9;
     841             : 
     842           0 :                 sz = write(fd, data, len);
     843           0 :                 if (sz < 0) {
     844           0 :                         r = log_error_errno(errno, "Failed to write output: %m");
     845           0 :                         goto error;
     846             :                 }
     847           0 :                 if (sz != (ssize_t) len) {
     848           0 :                         log_error("Short write to output.");
     849           0 :                         r = -EIO;
     850           0 :                         goto error;
     851             :                 }
     852             :         }
     853             : 
     854           0 :         if (temp) {
     855           0 :                 *path = temp;
     856           0 :                 *unlink_temp = true;
     857             :         }
     858           0 :         return 0;
     859             : 
     860           0 : error:
     861           0 :         if (temp) {
     862           0 :                 (void) unlink(temp);
     863           0 :                 log_debug("Removed temporary file %s", temp);
     864             :         }
     865           0 :         return r;
     866             : }
     867             : 
     868           0 : static int dump_core(int argc, char **argv, void *userdata) {
     869           0 :         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
     870           0 :         _cleanup_fclose_ FILE *f = NULL;
     871             :         int r;
     872             : 
     873           0 :         if (arg_field) {
     874           0 :                 log_error("Option --field/-F only makes sense with list");
     875           0 :                 return -EINVAL;
     876             :         }
     877             : 
     878           0 :         r = acquire_journal(&j, argv + 1);
     879           0 :         if (r < 0)
     880           0 :                 return r;
     881             : 
     882           0 :         r = focus(j);
     883           0 :         if (r < 0)
     884           0 :                 return r;
     885             : 
     886           0 :         if (arg_output) {
     887           0 :                 f = fopen(arg_output, "we");
     888           0 :                 if (!f)
     889           0 :                         return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", arg_output);
     890             :         }
     891             : 
     892           0 :         print_info(f ? stdout : stderr, j, false);
     893             : 
     894           0 :         r = save_core(j, f, NULL, NULL);
     895           0 :         if (r < 0)
     896           0 :                 return r;
     897             : 
     898           0 :         r = sd_journal_previous(j);
     899           0 :         if (r > 0 && !arg_quiet)
     900           0 :                 log_notice("More than one entry matches, ignoring rest.");
     901             : 
     902           0 :         return 0;
     903             : }
     904             : 
     905           0 : static int run_debug(int argc, char **argv, void *userdata) {
     906           0 :         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
     907           0 :         _cleanup_free_ char *exe = NULL, *path = NULL, *debugger = NULL;
     908           0 :         bool unlink_path = false;
     909             :         const char *data, *fork_name;
     910             :         size_t len;
     911             :         pid_t pid;
     912             :         int r;
     913             : 
     914           0 :         if (!arg_debugger) {
     915             :                 char *env_debugger;
     916             : 
     917           0 :                 env_debugger = getenv("SYSTEMD_DEBUGGER");
     918           0 :                 if (env_debugger)
     919           0 :                         arg_debugger = env_debugger;
     920             :                 else
     921           0 :                         arg_debugger = "gdb";
     922             :         }
     923             : 
     924           0 :         debugger = strdup(arg_debugger);
     925           0 :         if (!debugger)
     926           0 :                 return -ENOMEM;
     927             : 
     928           0 :         if (arg_field) {
     929           0 :                 log_error("Option --field/-F only makes sense with list");
     930           0 :                 return -EINVAL;
     931             :         }
     932             : 
     933           0 :         r = acquire_journal(&j, argv + 1);
     934           0 :         if (r < 0)
     935           0 :                 return r;
     936             : 
     937           0 :         r = focus(j);
     938           0 :         if (r < 0)
     939           0 :                 return r;
     940             : 
     941           0 :         print_info(stdout, j, false);
     942           0 :         fputs("\n", stdout);
     943             : 
     944           0 :         r = sd_journal_get_data(j, "COREDUMP_EXE", (const void**) &data, &len);
     945           0 :         if (r < 0)
     946           0 :                 return log_error_errno(r, "Failed to retrieve COREDUMP_EXE field: %m");
     947             : 
     948           0 :         assert(len > STRLEN("COREDUMP_EXE="));
     949           0 :         data += STRLEN("COREDUMP_EXE=");
     950           0 :         len -= STRLEN("COREDUMP_EXE=");
     951             : 
     952           0 :         exe = strndup(data, len);
     953           0 :         if (!exe)
     954           0 :                 return log_oom();
     955             : 
     956           0 :         if (endswith(exe, " (deleted)")) {
     957           0 :                 log_error("Binary already deleted.");
     958           0 :                 return -ENOENT;
     959             :         }
     960             : 
     961           0 :         if (!path_is_absolute(exe)) {
     962           0 :                 log_error("Binary is not an absolute path.");
     963           0 :                 return -ENOENT;
     964             :         }
     965             : 
     966           0 :         r = save_core(j, NULL, &path, &unlink_path);
     967           0 :         if (r < 0)
     968           0 :                 return r;
     969             : 
     970             :         /* Don't interfere with gdb and its handling of SIGINT. */
     971           0 :         (void) ignore_signals(SIGINT, -1);
     972             : 
     973           0 :         fork_name = strjoina("(", debugger, ")");
     974             : 
     975           0 :         r = safe_fork(fork_name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
     976           0 :         if (r < 0)
     977           0 :                 goto finish;
     978           0 :         if (r == 0) {
     979           0 :                 execlp(debugger, debugger, exe, "-c", path, NULL);
     980           0 :                 log_open();
     981           0 :                 log_error_errno(errno, "Failed to invoke %s: %m", debugger);
     982           0 :                 _exit(EXIT_FAILURE);
     983             :         }
     984             : 
     985           0 :         r = wait_for_terminate_and_check(debugger, pid, WAIT_LOG_ABNORMAL);
     986             : 
     987           0 : finish:
     988           0 :         (void) default_signals(SIGINT, -1);
     989             : 
     990           0 :         if (unlink_path) {
     991           0 :                 log_debug("Removed temporary file %s", path);
     992           0 :                 (void) unlink(path);
     993             :         }
     994             : 
     995           0 :         return r;
     996             : }
     997             : 
     998           0 : static int check_units_active(void) {
     999           0 :         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
    1000           0 :         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
    1001           0 :         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
    1002           0 :         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
    1003           0 :         int c = 0, r;
    1004             :         const char *id, *state, *substate;
    1005             : 
    1006           0 :         if (arg_quiet)
    1007           0 :                 return false;
    1008             : 
    1009           0 :         r = sd_bus_default_system(&bus);
    1010           0 :         if (r < 0)
    1011           0 :                 return log_error_errno(r, "Failed to acquire bus: %m");
    1012             : 
    1013           0 :         r = sd_bus_message_new_method_call(
    1014             :                         bus,
    1015             :                         &m,
    1016             :                         "org.freedesktop.systemd1",
    1017             :                         "/org/freedesktop/systemd1",
    1018             :                         "org.freedesktop.systemd1.Manager",
    1019             :                         "ListUnitsByPatterns");
    1020           0 :         if (r < 0)
    1021           0 :                 return bus_log_create_error(r);
    1022             : 
    1023           0 :         r = sd_bus_message_append_strv(m, NULL);
    1024           0 :         if (r < 0)
    1025           0 :                 return bus_log_create_error(r);
    1026             : 
    1027           0 :         r = sd_bus_message_append_strv(m, STRV_MAKE("systemd-coredump@*.service"));
    1028           0 :         if (r < 0)
    1029           0 :                 return bus_log_create_error(r);
    1030             : 
    1031           0 :         r = sd_bus_call(bus, m, SHORT_BUS_CALL_TIMEOUT_USEC, &error, &reply);
    1032           0 :         if (r < 0)
    1033           0 :                 return log_error_errno(r, "Failed to check if any systemd-coredump@.service units are running: %s",
    1034             :                                        bus_error_message(&error, r));
    1035             : 
    1036           0 :         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
    1037           0 :         if (r < 0)
    1038           0 :                 return bus_log_parse_error(r);
    1039             : 
    1040           0 :         while ((r = sd_bus_message_read(
    1041             :                                 reply, "(ssssssouso)",
    1042             :                                 &id,  NULL,  NULL,  &state,  &substate,
    1043             :                                 NULL,  NULL,  NULL,  NULL,  NULL)) > 0) {
    1044           0 :                 bool found = !STR_IN_SET(state, "inactive", "dead", "failed");
    1045           0 :                 log_debug("Unit %s is %s/%s, %scounting it.", id, state, substate, found ? "" : "not ");
    1046           0 :                 c += found;
    1047             :         }
    1048           0 :         if (r < 0)
    1049           0 :                 return bus_log_parse_error(r);
    1050             : 
    1051           0 :         r = sd_bus_message_exit_container(reply);
    1052           0 :         if (r < 0)
    1053           0 :                 return bus_log_parse_error(r);
    1054             : 
    1055           0 :         return c;
    1056             : }
    1057             : 
    1058           0 : static int coredumpctl_main(int argc, char *argv[]) {
    1059             : 
    1060             :         static const Verb verbs[] = {
    1061             :                 { "list",  VERB_ANY, VERB_ANY, VERB_DEFAULT, dump_list },
    1062             :                 { "info",  VERB_ANY, VERB_ANY, 0,            dump_list },
    1063             :                 { "dump",  VERB_ANY, VERB_ANY, 0,            dump_core },
    1064             :                 { "debug", VERB_ANY, VERB_ANY, 0,            run_debug },
    1065             :                 { "gdb",   VERB_ANY, VERB_ANY, 0,            run_debug },
    1066             :                 {}
    1067             :         };
    1068             : 
    1069           0 :         return dispatch_verb(argc, argv, verbs, NULL);
    1070             : }
    1071             : 
    1072           4 : static int run(int argc, char *argv[]) {
    1073             :         int r, units_active;
    1074             : 
    1075           4 :         setlocale(LC_ALL, "");
    1076           4 :         log_show_color(true);
    1077           4 :         log_parse_environment();
    1078           4 :         log_open();
    1079             : 
    1080             :         /* The journal merging logic potentially needs a lot of fds. */
    1081           4 :         (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE);
    1082             : 
    1083           4 :         r = parse_argv(argc, argv);
    1084           4 :         if (r <= 0)
    1085           4 :                 return r;
    1086             : 
    1087           0 :         sigbus_install();
    1088             : 
    1089           0 :         units_active = check_units_active(); /* error is treated the same as 0 */
    1090             : 
    1091           0 :         r = coredumpctl_main(argc, argv);
    1092             : 
    1093           0 :         if (units_active > 0)
    1094           0 :                 printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n",
    1095             :                        ansi_highlight_red(),
    1096             :                        units_active, units_active == 1 ? "unit is running" : "units are running",
    1097             :                        ansi_normal());
    1098           0 :         return r;
    1099             : }
    1100             : 
    1101           4 : DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

Generated by: LCOV version 1.14