diff --git a/src/core/execute.c b/src/core/execute.c index 911c3690428..5b55557f4e8 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3878,15 +3878,19 @@ int exec_spawn(Unit *unit, unit->manager->user_lookup_fds[1], &exit_status); - if (r < 0) + if (r < 0) { + const char *status = + exit_status_to_string(exit_status, + EXIT_STATUS_GLIBC | EXIT_STATUS_SYSTEMD); + log_struct_errno(LOG_ERR, r, "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR, LOG_UNIT_ID(unit), LOG_UNIT_INVOCATION_ID(unit), LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", - exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), - command->path), + status, command->path), "EXECUTABLE=%s", command->path); + } _exit(exit_status); } diff --git a/src/core/main.c b/src/core/main.c index 0674e00ab01..0698f893fd9 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -221,16 +221,19 @@ _noreturn_ static void crash(int sig) { r = wait_for_terminate(pid, &status); if (r < 0) log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); - else if (status.si_code != CLD_DUMPED) + else if (status.si_code != CLD_DUMPED) { + const char *s = status.si_code == CLD_EXITED + ? exit_status_to_string(status.si_status, EXIT_STATUS_GLIBC) + : signal_to_string(status.si_status); + log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", signal_to_string(sig), - pid, sigchld_code_to_string(status.si_code), - status.si_status, - strna(status.si_code == CLD_EXITED - ? exit_status_to_string(status.si_status, EXIT_STATUS_MINIMAL) - : signal_to_string(status.si_status))); - else - log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); + pid, + sigchld_code_to_string(status.si_code), + status.si_status, strna(s)); + } else + log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", + signal_to_string(sig), pid); } } diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c index 58ebc3ca4d6..51f23800274 100644 --- a/src/shared/exit-status.c +++ b/src/shared/exit-status.c @@ -8,8 +8,7 @@ #include "macro.h" #include "set.h" -const char* exit_status_to_string(int status, ExitStatusLevel level) { - +const ExitStatusMapping exit_status_mappings[256] = { /* Exit status ranges: * * 0…1 │ ISO C, EXIT_SUCCESS + EXIT_FAILURE @@ -25,224 +24,100 @@ const char* exit_status_to_string(int status, ExitStatusLevel level) { * │ signal or such, and we follow that logic here.) */ - switch (status) { /* We always cover the ISO C ones */ + [EXIT_SUCCESS] = { "SUCCESS", EXIT_STATUS_GLIBC }, + [EXIT_FAILURE] = { "FAILURE", EXIT_STATUS_GLIBC }, - case EXIT_SUCCESS: - return "SUCCESS"; + [EXIT_CHDIR] = { "CHDIR", EXIT_STATUS_SYSTEMD }, + [EXIT_NICE] = { "NICE", EXIT_STATUS_SYSTEMD }, + [EXIT_FDS] = { "FDS", EXIT_STATUS_SYSTEMD }, + [EXIT_EXEC] = { "EXEC", EXIT_STATUS_SYSTEMD }, + [EXIT_MEMORY] = { "MEMORY", EXIT_STATUS_SYSTEMD }, + [EXIT_LIMITS] = { "LIMITS", EXIT_STATUS_SYSTEMD }, + [EXIT_OOM_ADJUST] = { "OOM_ADJUST", EXIT_STATUS_SYSTEMD }, + [EXIT_SIGNAL_MASK] = { "SIGNAL_MASK", EXIT_STATUS_SYSTEMD }, + [EXIT_STDIN] = { "STDIN", EXIT_STATUS_SYSTEMD }, + [EXIT_STDOUT] = { "STDOUT", EXIT_STATUS_SYSTEMD }, + [EXIT_CHROOT] = { "CHROOT", EXIT_STATUS_SYSTEMD }, + [EXIT_IOPRIO] = { "IOPRIO", EXIT_STATUS_SYSTEMD }, + [EXIT_TIMERSLACK] = { "TIMERSLACK", EXIT_STATUS_SYSTEMD }, + [EXIT_SECUREBITS] = { "SECUREBITS", EXIT_STATUS_SYSTEMD }, + [EXIT_SETSCHEDULER] = { "SETSCHEDULER", EXIT_STATUS_SYSTEMD }, + [EXIT_CPUAFFINITY] = { "CPUAFFINITY", EXIT_STATUS_SYSTEMD }, + [EXIT_GROUP] = { "GROUP", EXIT_STATUS_SYSTEMD }, + [EXIT_USER] = { "USER", EXIT_STATUS_SYSTEMD }, + [EXIT_CAPABILITIES] = { "CAPABILITIES", EXIT_STATUS_SYSTEMD }, + [EXIT_CGROUP] = { "CGROUP", EXIT_STATUS_SYSTEMD }, + [EXIT_SETSID] = { "SETSID", EXIT_STATUS_SYSTEMD }, + [EXIT_CONFIRM] = { "CONFIRM", EXIT_STATUS_SYSTEMD }, + [EXIT_STDERR] = { "STDERR", EXIT_STATUS_SYSTEMD }, + [EXIT_PAM] = { "PAM", EXIT_STATUS_SYSTEMD }, + [EXIT_NETWORK] = { "NETWORK", EXIT_STATUS_SYSTEMD }, + [EXIT_NAMESPACE] = { "NAMESPACE", EXIT_STATUS_SYSTEMD }, + [EXIT_NO_NEW_PRIVILEGES] = { "NO_NEW_PRIVILEGES", EXIT_STATUS_SYSTEMD }, + [EXIT_SECCOMP] = { "SECCOMP", EXIT_STATUS_SYSTEMD }, + [EXIT_SELINUX_CONTEXT] = { "SELINUX_CONTEXT", EXIT_STATUS_SYSTEMD }, + [EXIT_PERSONALITY] = { "PERSONALITY", EXIT_STATUS_SYSTEMD }, + [EXIT_APPARMOR_PROFILE] = { "APPARMOR", EXIT_STATUS_SYSTEMD }, + [EXIT_ADDRESS_FAMILIES] = { "ADDRESS_FAMILIES", EXIT_STATUS_SYSTEMD }, + [EXIT_RUNTIME_DIRECTORY] = { "RUNTIME_DIRECTORY", EXIT_STATUS_SYSTEMD }, + [EXIT_CHOWN] = { "CHOWN", EXIT_STATUS_SYSTEMD }, + [EXIT_SMACK_PROCESS_LABEL] = { "SMACK_PROCESS_LABEL", EXIT_STATUS_SYSTEMD }, + [EXIT_KEYRING] = { "KEYRING", EXIT_STATUS_SYSTEMD }, + [EXIT_STATE_DIRECTORY] = { "STATE_DIRECTORY", EXIT_STATUS_SYSTEMD }, + [EXIT_CACHE_DIRECTORY] = { "CACHE_DIRECTORY", EXIT_STATUS_SYSTEMD }, + [EXIT_LOGS_DIRECTORY] = { "LOGS_DIRECTORY", EXIT_STATUS_SYSTEMD }, + [EXIT_CONFIGURATION_DIRECTORY] = { "CONFIGURATION_DIRECTORY", EXIT_STATUS_SYSTEMD }, + [EXIT_NUMA_POLICY] = { "NUMA_POLICY", EXIT_STATUS_SYSTEMD }, + [EXIT_EXCEPTION] = { "EXCEPTION", EXIT_STATUS_SYSTEMD }, - case EXIT_FAILURE: - return "FAILURE"; + [EXIT_INVALIDARGUMENT] = { "INVALIDARGUMENT", EXIT_STATUS_LSB }, + [EXIT_NOTIMPLEMENTED] = { "NOTIMPLEMENTED", EXIT_STATUS_LSB }, + [EXIT_NOPERMISSION] = { "NOPERMISSION", EXIT_STATUS_LSB }, + [EXIT_NOTINSTALLED] = { "NOTINSTALLED", EXIT_STATUS_LSB }, + [EXIT_NOTCONFIGURED] = { "NOTCONFIGURED", EXIT_STATUS_LSB }, + [EXIT_NOTRUNNING] = { "NOTRUNNING", EXIT_STATUS_LSB }, + + [EX_USAGE] = { "USAGE", EXIT_STATUS_BSD }, + [EX_DATAERR] = { "DATAERR", EXIT_STATUS_BSD }, + [EX_NOINPUT] = { "NOINPUT", EXIT_STATUS_BSD }, + [EX_NOUSER] = { "NOUSER", EXIT_STATUS_BSD }, + [EX_NOHOST] = { "NOHOST", EXIT_STATUS_BSD }, + [EX_UNAVAILABLE] = { "UNAVAILABLE", EXIT_STATUS_BSD }, + [EX_SOFTWARE] = { "SOFTWARE", EXIT_STATUS_BSD }, + [EX_OSERR] = { "OSERR", EXIT_STATUS_BSD }, + [EX_OSFILE] = { "OSFILE", EXIT_STATUS_BSD }, + [EX_CANTCREAT] = { "CANTCREAT", EXIT_STATUS_BSD }, + [EX_IOERR] = { "IOERR", EXIT_STATUS_BSD }, + [EX_TEMPFAIL] = { "TEMPFAIL", EXIT_STATUS_BSD }, + [EX_PROTOCOL] = { "PROTOCOL", EXIT_STATUS_BSD }, + [EX_NOPERM] = { "NOPERM", EXIT_STATUS_BSD }, + [EX_CONFIG] = { "CONFIG", EXIT_STATUS_BSD }, +}; + +const char* exit_status_to_string(int code, ExitStatusClass class) { + if (code < 0 || (size_t) code >= ELEMENTSOF(exit_status_mappings)) + return NULL; + return FLAGS_SET(exit_status_mappings[code].class, class) ? exit_status_mappings[code].name : NULL; +} + +const char* exit_status_class(int code) { + if (code < 0 || (size_t) code >= ELEMENTSOF(exit_status_mappings)) + return NULL; + + switch (exit_status_mappings[code].class) { + case EXIT_STATUS_GLIBC: + return "glibc"; + case EXIT_STATUS_SYSTEMD: + return "systemd"; + case EXIT_STATUS_LSB: + return "LSB"; + case EXIT_STATUS_BSD: + return "BSD"; + default: return NULL; } - - if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB, EXIT_STATUS_FULL)) { - switch (status) { /* Optionally we cover our own ones */ - - case EXIT_CHDIR: - return "CHDIR"; - - case EXIT_NICE: - return "NICE"; - - case EXIT_FDS: - return "FDS"; - - case EXIT_EXEC: - return "EXEC"; - - case EXIT_MEMORY: - return "MEMORY"; - - case EXIT_LIMITS: - return "LIMITS"; - - case EXIT_OOM_ADJUST: - return "OOM_ADJUST"; - - case EXIT_SIGNAL_MASK: - return "SIGNAL_MASK"; - - case EXIT_STDIN: - return "STDIN"; - - case EXIT_STDOUT: - return "STDOUT"; - - case EXIT_CHROOT: - return "CHROOT"; - - case EXIT_IOPRIO: - return "IOPRIO"; - - case EXIT_TIMERSLACK: - return "TIMERSLACK"; - - case EXIT_SECUREBITS: - return "SECUREBITS"; - - case EXIT_SETSCHEDULER: - return "SETSCHEDULER"; - - case EXIT_CPUAFFINITY: - return "CPUAFFINITY"; - - case EXIT_GROUP: - return "GROUP"; - - case EXIT_USER: - return "USER"; - - case EXIT_CAPABILITIES: - return "CAPABILITIES"; - - case EXIT_CGROUP: - return "CGROUP"; - - case EXIT_SETSID: - return "SETSID"; - - case EXIT_CONFIRM: - return "CONFIRM"; - - case EXIT_STDERR: - return "STDERR"; - - case EXIT_PAM: - return "PAM"; - - case EXIT_NETWORK: - return "NETWORK"; - - case EXIT_NAMESPACE: - return "NAMESPACE"; - - case EXIT_NO_NEW_PRIVILEGES: - return "NO_NEW_PRIVILEGES"; - - case EXIT_SECCOMP: - return "SECCOMP"; - - case EXIT_SELINUX_CONTEXT: - return "SELINUX_CONTEXT"; - - case EXIT_PERSONALITY: - return "PERSONALITY"; - - case EXIT_APPARMOR_PROFILE: - return "APPARMOR"; - - case EXIT_ADDRESS_FAMILIES: - return "ADDRESS_FAMILIES"; - - case EXIT_RUNTIME_DIRECTORY: - return "RUNTIME_DIRECTORY"; - - case EXIT_CHOWN: - return "CHOWN"; - - case EXIT_SMACK_PROCESS_LABEL: - return "SMACK_PROCESS_LABEL"; - - case EXIT_KEYRING: - return "KEYRING"; - - case EXIT_STATE_DIRECTORY: - return "STATE_DIRECTORY"; - - case EXIT_CACHE_DIRECTORY: - return "CACHE_DIRECTORY"; - - case EXIT_LOGS_DIRECTORY: - return "LOGS_DIRECTORY"; - - case EXIT_CONFIGURATION_DIRECTORY: - return "CONFIGURATION_DIRECTORY"; - - case EXIT_NUMA_POLICY: - return "NUMA_POLICY"; - - case EXIT_EXCEPTION: - return "EXCEPTION"; - } - } - - if (IN_SET(level, EXIT_STATUS_LSB, EXIT_STATUS_FULL)) { - switch (status) { /* Optionally we support LSB ones */ - - case EXIT_INVALIDARGUMENT: - return "INVALIDARGUMENT"; - - case EXIT_NOTIMPLEMENTED: - return "NOTIMPLEMENTED"; - - case EXIT_NOPERMISSION: - return "NOPERMISSION"; - - case EXIT_NOTINSTALLED: - return "NOTINSTALLED"; - - case EXIT_NOTCONFIGURED: - return "NOTCONFIGURED"; - - case EXIT_NOTRUNNING: - return "NOTRUNNING"; - } - } - - if (level == EXIT_STATUS_FULL) { - switch (status) { /* Optionally, we support BSD exit statusses */ - - case EX_USAGE: - return "USAGE"; - - case EX_DATAERR: - return "DATAERR"; - - case EX_NOINPUT: - return "NOINPUT"; - - case EX_NOUSER: - return "NOUSER"; - - case EX_NOHOST: - return "NOHOST"; - - case EX_UNAVAILABLE: - return "UNAVAILABLE"; - - case EX_SOFTWARE: - return "SOFTWARE"; - - case EX_OSERR: - return "OSERR"; - - case EX_OSFILE: - return "OSFILE"; - - case EX_CANTCREAT: - return "CANTCREAT"; - - case EX_IOERR: - return "IOERR"; - - case EX_TEMPFAIL: - return "TEMPFAIL"; - - case EX_PROTOCOL: - return "PROTOCOL"; - - case EX_NOPERM: - return "NOPERM"; - - case EX_CONFIG: - return "CONFIG"; - } - } - - return NULL; } bool is_clean_exit(int code, int status, ExitClean clean, ExitStatusSet *success_status) { - if (code == CLD_EXITED) return status == 0 || (success_status && diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h index 5637e6aa04d..425f8415234 100644 --- a/src/shared/exit-status.h +++ b/src/shared/exit-status.h @@ -7,8 +7,8 @@ #include "macro.h" #include "set.h" -/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with the LSB - * 'status' verb exit codes which are defined very differently. For details see: +/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with + * the LSB 'status' verb exit codes which are defined very differently. For details see: * * https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */ @@ -25,8 +25,8 @@ enum { /* BSD's sysexits.h defines a couple EX_xyz exit codes in the range 64 … 78 */ - /* The LSB suggests that error codes >= 200 are "reserved". We use them here under the assumption that they - * hence are unused by init scripts. */ + /* The LSB suggests that error codes >= 200 are "reserved". We use them here under the assumption + * that they hence are unused by init scripts. */ EXIT_CHDIR = 200, EXIT_NICE, EXIT_FDS, @@ -74,19 +74,28 @@ enum { EXIT_EXCEPTION = 255, /* Whenever we want to propagate an abnormal/signal exit, in line with bash */ }; -typedef enum ExitStatusLevel { - EXIT_STATUS_MINIMAL, /* only cover libc EXIT_STATUS/EXIT_FAILURE */ - EXIT_STATUS_SYSTEMD, /* cover libc and systemd's own exit codes */ - EXIT_STATUS_LSB, /* cover libc, systemd's own and LSB exit codes */ - EXIT_STATUS_FULL, /* cover libc, systemd's own, LSB and BSD (EX_xyz) exit codes */ -} ExitStatusLevel; +typedef enum ExitStatusClass { + EXIT_STATUS_GLIBC = 1 << 0, /* libc EXIT_STATUS/EXIT_FAILURE */ + EXIT_STATUS_SYSTEMD = 1 << 1, /* systemd's own exit codes */ + EXIT_STATUS_LSB = 1 << 2, /* LSB exit codes */ + EXIT_STATUS_BSD = 1 << 3, /* BSD (EX_xyz) exit codes */ + EXIT_STATUS_FULL = EXIT_STATUS_GLIBC | EXIT_STATUS_SYSTEMD | EXIT_STATUS_LSB | EXIT_STATUS_BSD, +} ExitStatusClass; typedef struct ExitStatusSet { Set *status; Set *signal; } ExitStatusSet; -const char* exit_status_to_string(int status, ExitStatusLevel level) _const_; +const char* exit_status_to_string(int code, ExitStatusClass class) _const_; +const char* exit_status_class(int code) _const_; + +typedef struct ExitStatusMapping { + const char *name; + ExitStatusClass class; +} ExitStatusMapping; + +extern const ExitStatusMapping exit_status_mappings[256]; typedef enum ExitClean { EXIT_CLEAN_DAEMON, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 95a85245946..b9a7d488bd4 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4380,7 +4380,7 @@ static void print_status_info( printf("status=%i", p->status); - c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD); + c = exit_status_to_string(p->status, EXIT_STATUS_GLIBC | EXIT_STATUS_SYSTEMD); if (c) printf("/%s", c); @@ -4421,7 +4421,8 @@ static void print_status_info( printf("status=%i", i->exit_status); - c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD); + c = exit_status_to_string(i->exit_status, + EXIT_STATUS_GLIBC | EXIT_STATUS_SYSTEMD); if (c) printf("/%s", c); diff --git a/src/test/meson.build b/src/test/meson.build index 0595cfe37a6..5625e682cf1 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -305,6 +305,10 @@ tests += [ [], []], + [['src/test/test-exit-status.c'], + [], + []], + [['src/test/test-specifier.c'], [], []], diff --git a/src/test/test-exit-status.c b/src/test/test-exit-status.c new file mode 100644 index 00000000000..bab0596580c --- /dev/null +++ b/src/test/test-exit-status.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "exit-status.h" +#include "tests.h" + +static void test_exit_status_to_string(void) { + log_info("/* %s */", __func__); + + for (int i = -1; i <= 256; i++) { + const char *s, *class; + + s = exit_status_to_string(i, EXIT_STATUS_FULL); + class = exit_status_class(i); + log_info("%d: %s%s%s%s", + i, s ?: "-", + class ? " (" : "", class ?: "", class ? ")" : ""); + } +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + test_exit_status_to_string(); + + return 0; +}