service: add options RestartPreventExitStatus and SuccessExitStatus
In some cases, like wrong configuration, restarting after error does not help, so administrator can specify statuses by RestartPreventExitStatus which will not cause restart of a service. Sometimes you have non-standart exit status, so this can be specified by SuccessfulExitStatus.
This commit is contained in:
parent
d98cc1f29f
commit
96342de68d
|
@ -579,6 +579,20 @@
|
||||||
hit a timeout.</para></listitem>
|
hit a timeout.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>RestartPreventExitStatus=</varname></term>
|
||||||
|
<listitem><para>Specify exit status list, which
|
||||||
|
will prevent service from restart. Codes are
|
||||||
|
separated by whitespace (e.g. "1 6 SIGKILL").</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>SuccessExitStatus=</varname></term>
|
||||||
|
<listitem><para>Specify exit status list, which
|
||||||
|
will be considered as successful exit. Codes are
|
||||||
|
separated by whitespace (e.g. "1 6 SIGKILL").</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>PermissionsStartOnly=</varname></term>
|
<term><varname>PermissionsStartOnly=</varname></term>
|
||||||
<listitem><para>Takes a boolean
|
<listitem><para>Takes a boolean
|
||||||
|
|
|
@ -158,6 +158,8 @@ Service.PermissionsStartOnly, config_parse_bool, 0,
|
||||||
Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only)
|
Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only)
|
||||||
Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit)
|
Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit)
|
||||||
Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid)
|
Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid)
|
||||||
|
Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_ignore_status)
|
||||||
|
Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status)
|
||||||
m4_ifdef(`HAVE_SYSV_COMPAT',
|
m4_ifdef(`HAVE_SYSV_COMPAT',
|
||||||
`Service.SysVStartPriority, config_parse_sysv_priority, 0, offsetof(Service, sysv_start_priority)',
|
`Service.SysVStartPriority, config_parse_sysv_priority, 0, offsetof(Service, sysv_start_priority)',
|
||||||
`Service.SysVStartPriority, config_parse_warn_compat, 0, 0')
|
`Service.SysVStartPriority, config_parse_warn_compat, 0, 0')
|
||||||
|
|
|
@ -1225,7 +1225,7 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
|
|
||||||
m->control_pid = 0;
|
m->control_pid = 0;
|
||||||
|
|
||||||
if (is_clean_exit(code, status))
|
if (is_clean_exit(code, status, NULL))
|
||||||
f = MOUNT_SUCCESS;
|
f = MOUNT_SUCCESS;
|
||||||
else if (code == CLD_EXITED)
|
else if (code == CLD_EXITED)
|
||||||
f = MOUNT_FAILURE_EXIT_CODE;
|
f = MOUNT_FAILURE_EXIT_CODE;
|
||||||
|
|
|
@ -294,6 +294,16 @@ static void service_done(Unit *u) {
|
||||||
s->control_command = NULL;
|
s->control_command = NULL;
|
||||||
s->main_command = NULL;
|
s->main_command = NULL;
|
||||||
|
|
||||||
|
set_free(s->restart_ignore_status.code);
|
||||||
|
s->restart_ignore_status.code = NULL;
|
||||||
|
set_free(s->restart_ignore_status.signal);
|
||||||
|
s->restart_ignore_status.signal = NULL;
|
||||||
|
|
||||||
|
set_free(s->success_status.code);
|
||||||
|
s->success_status.code = NULL;
|
||||||
|
set_free(s->success_status.signal);
|
||||||
|
s->success_status.signal = NULL;
|
||||||
|
|
||||||
/* This will leak a process, but at least no memory or any of
|
/* This will leak a process, but at least no memory or any of
|
||||||
* our resources */
|
* our resources */
|
||||||
service_unwatch_main_pid(s);
|
service_unwatch_main_pid(s);
|
||||||
|
@ -1902,7 +1912,12 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
|
||||||
(s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) ||
|
(s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) ||
|
||||||
(s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) ||
|
(s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) ||
|
||||||
(s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL ||
|
(s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL ||
|
||||||
s->result == SERVICE_FAILURE_CORE_DUMP)))) {
|
s->result == SERVICE_FAILURE_CORE_DUMP))) &&
|
||||||
|
(s->result != SERVICE_FAILURE_EXIT_CODE ||
|
||||||
|
!set_contains(s->restart_ignore_status.code, INT_TO_PTR(s->main_exec_status.status))) &&
|
||||||
|
(s->result != SERVICE_FAILURE_SIGNAL ||
|
||||||
|
!set_contains(s->restart_ignore_status.signal, INT_TO_PTR(s->main_exec_status.status)))
|
||||||
|
) {
|
||||||
|
|
||||||
r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch);
|
r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
@ -2874,7 +2889,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
assert(s);
|
assert(s);
|
||||||
assert(pid >= 0);
|
assert(pid >= 0);
|
||||||
|
|
||||||
if (UNIT(s)->fragment_path ? is_clean_exit(code, status) : is_clean_exit_lsb(code, status))
|
if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) :
|
||||||
|
is_clean_exit_lsb(code, status, &s->success_status))
|
||||||
f = SERVICE_SUCCESS;
|
f = SERVICE_SUCCESS;
|
||||||
else if (code == CLD_EXITED)
|
else if (code == CLD_EXITED)
|
||||||
f = SERVICE_FAILURE_EXIT_CODE;
|
f = SERVICE_FAILURE_EXIT_CODE;
|
||||||
|
|
|
@ -28,6 +28,7 @@ typedef struct Service Service;
|
||||||
#include "ratelimit.h"
|
#include "ratelimit.h"
|
||||||
#include "service.h"
|
#include "service.h"
|
||||||
#include "kill.h"
|
#include "kill.h"
|
||||||
|
#include "exit-status.h"
|
||||||
|
|
||||||
typedef enum ServiceState {
|
typedef enum ServiceState {
|
||||||
SERVICE_DEAD,
|
SERVICE_DEAD,
|
||||||
|
@ -115,6 +116,8 @@ struct Service {
|
||||||
|
|
||||||
ServiceType type;
|
ServiceType type;
|
||||||
ServiceRestart restart;
|
ServiceRestart restart;
|
||||||
|
ExitStatusSet restart_ignore_status;
|
||||||
|
ExitStatusSet success_status;
|
||||||
|
|
||||||
/* If set we'll read the main daemon PID from this file */
|
/* If set we'll read the main daemon PID from this file */
|
||||||
char *pid_file;
|
char *pid_file;
|
||||||
|
|
|
@ -1884,7 +1884,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
|
|
||||||
s->control_pid = 0;
|
s->control_pid = 0;
|
||||||
|
|
||||||
if (is_clean_exit(code, status))
|
if (is_clean_exit(code, status, NULL))
|
||||||
f = SOCKET_SUCCESS;
|
f = SOCKET_SUCCESS;
|
||||||
else if (code == CLD_EXITED)
|
else if (code == CLD_EXITED)
|
||||||
f = SOCKET_FAILURE_EXIT_CODE;
|
f = SOCKET_FAILURE_EXIT_CODE;
|
||||||
|
|
|
@ -917,7 +917,7 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) {
|
||||||
|
|
||||||
s->control_pid = 0;
|
s->control_pid = 0;
|
||||||
|
|
||||||
if (is_clean_exit(code, status))
|
if (is_clean_exit(code, status, NULL))
|
||||||
f = SWAP_SUCCESS;
|
f = SWAP_SUCCESS;
|
||||||
else if (code == CLD_EXITED)
|
else if (code == CLD_EXITED)
|
||||||
f = SWAP_FAILURE_EXIT_CODE;
|
f = SWAP_FAILURE_EXIT_CODE;
|
||||||
|
|
|
@ -145,7 +145,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
s = hashmap_remove(pids, UINT_TO_PTR(si.si_pid));
|
s = hashmap_remove(pids, UINT_TO_PTR(si.si_pid));
|
||||||
if (s) {
|
if (s) {
|
||||||
if (!is_clean_exit(si.si_code, si.si_status)) {
|
if (!is_clean_exit(si.si_code, si.si_status, NULL)) {
|
||||||
if (si.si_code == CLD_EXITED)
|
if (si.si_code == CLD_EXITED)
|
||||||
log_error("/bin/mount for %s exited with exit status %i.", s, si.si_status);
|
log_error("/bin/mount for %s exited with exit status %i.", s, si.si_status);
|
||||||
else
|
else
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "path-util.h"
|
#include "path-util.h"
|
||||||
|
#include "set.h"
|
||||||
|
#include "exit-status.h"
|
||||||
|
|
||||||
int config_item_table_lookup(
|
int config_item_table_lookup(
|
||||||
void *table,
|
void *table,
|
||||||
|
@ -933,3 +935,71 @@ int config_parse_level(
|
||||||
*o = (*o & LOG_FACMASK) | x;
|
*o = (*o & LOG_FACMASK) | x;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int config_parse_set_status(
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
char *w;
|
||||||
|
size_t l;
|
||||||
|
char *state;
|
||||||
|
int r;
|
||||||
|
ExitStatusSet *status_set = data;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(data);
|
||||||
|
|
||||||
|
FOREACH_WORD(w, l, rvalue, state) {
|
||||||
|
int val;
|
||||||
|
char *temp = strndup(w, l);
|
||||||
|
if (!temp)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
r = safe_atoi(temp, &val);
|
||||||
|
if (r < 0) {
|
||||||
|
val = signal_from_string_try_harder(temp);
|
||||||
|
free(temp);
|
||||||
|
if (val > 0) {
|
||||||
|
if (!status_set->signal) {
|
||||||
|
status_set->signal = set_new(trivial_hash_func, trivial_compare_func);
|
||||||
|
if (!status_set->signal)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
r = set_put(status_set->signal, INT_TO_PTR(val));
|
||||||
|
if (r < 0) {
|
||||||
|
log_error("[%s:%u] Unable to store: %s", filename, line, w);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_error("[%s:%u] Failed to parse value: %s", filename, line, w);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
free(temp);
|
||||||
|
if(val < 0 || val > 255)
|
||||||
|
log_warning("[%s:%u] Value %d is outside range 0-255, ignoring", filename, line, val);
|
||||||
|
else {
|
||||||
|
if (!status_set->code) {
|
||||||
|
status_set->code = set_new(trivial_hash_func, trivial_compare_func);
|
||||||
|
if (!status_set->code)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
r = set_put(status_set->code, INT_TO_PTR(val));
|
||||||
|
if (r < 0) {
|
||||||
|
log_error("[%s:%u] Unable to store: %s", filename, line, w);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ int config_parse_nsec(const char *filename, unsigned line, const char *section,
|
||||||
int config_parse_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
int config_parse_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||||
int config_parse_facility(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
int config_parse_facility(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||||
int config_parse_level(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
int config_parse_level(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||||
|
int config_parse_set_status(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
|
||||||
|
|
||||||
#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
|
#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \
|
||||||
int function( \
|
int function( \
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include "exit-status.h"
|
#include "exit-status.h"
|
||||||
|
#include "set.h"
|
||||||
|
#include "macro.h"
|
||||||
|
|
||||||
const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
|
const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
|
||||||
|
|
||||||
|
@ -158,10 +160,12 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool is_clean_exit(int code, int status) {
|
bool is_clean_exit(int code, int status, ExitStatusSet *success_status) {
|
||||||
|
|
||||||
if (code == CLD_EXITED)
|
if (code == CLD_EXITED)
|
||||||
return status == 0;
|
return status == 0 ||
|
||||||
|
(success_status &&
|
||||||
|
set_contains(success_status->code, INT_TO_PTR(status)));
|
||||||
|
|
||||||
/* If a daemon does not implement handlers for some of the
|
/* If a daemon does not implement handlers for some of the
|
||||||
* signals that's not considered an unclean shutdown */
|
* signals that's not considered an unclean shutdown */
|
||||||
|
@ -170,14 +174,16 @@ bool is_clean_exit(int code, int status) {
|
||||||
status == SIGHUP ||
|
status == SIGHUP ||
|
||||||
status == SIGINT ||
|
status == SIGINT ||
|
||||||
status == SIGTERM ||
|
status == SIGTERM ||
|
||||||
status == SIGPIPE;
|
status == SIGPIPE ||
|
||||||
|
(success_status &&
|
||||||
|
set_contains(success_status->signal, INT_TO_PTR(status)));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_clean_exit_lsb(int code, int status) {
|
bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) {
|
||||||
|
|
||||||
if (is_clean_exit(code, status))
|
if (is_clean_exit(code, status, success_status))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include "set.h"
|
||||||
typedef enum ExitStatus {
|
typedef enum ExitStatus {
|
||||||
/* EXIT_SUCCESS defined by libc */
|
/* EXIT_SUCCESS defined by libc */
|
||||||
/* EXIT_FAILURE defined by libc */
|
/* EXIT_FAILURE defined by libc */
|
||||||
|
@ -77,7 +77,12 @@ typedef enum ExitStatusLevel {
|
||||||
EXIT_STATUS_FULL = EXIT_STATUS_LSB
|
EXIT_STATUS_FULL = EXIT_STATUS_LSB
|
||||||
} ExitStatusLevel;
|
} ExitStatusLevel;
|
||||||
|
|
||||||
|
typedef struct ExitStatusSet {
|
||||||
|
Set *code;
|
||||||
|
Set *signal;
|
||||||
|
} ExitStatusSet;
|
||||||
|
|
||||||
const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level);
|
const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level);
|
||||||
|
|
||||||
bool is_clean_exit(int code, int status);
|
bool is_clean_exit(int code, int status, ExitStatusSet *success_status);
|
||||||
bool is_clean_exit_lsb(int code, int status);
|
bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status);
|
||||||
|
|
|
@ -378,6 +378,21 @@ void* hashmap_get(Hashmap *h, const void *key) {
|
||||||
return e->value;
|
return e->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hashmap_contains(Hashmap *h, const void *key) {
|
||||||
|
unsigned hash;
|
||||||
|
struct hashmap_entry *e;
|
||||||
|
|
||||||
|
if (!h)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hash = h->hash_func(key) % NBUCKETS;
|
||||||
|
|
||||||
|
if (!(e = hash_scan(h, hash, key)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void* hashmap_remove(Hashmap *h, const void *key) {
|
void* hashmap_remove(Hashmap *h, const void *key) {
|
||||||
struct hashmap_entry *e;
|
struct hashmap_entry *e;
|
||||||
unsigned hash;
|
unsigned hash;
|
||||||
|
|
|
@ -53,6 +53,7 @@ int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t
|
||||||
int hashmap_put(Hashmap *h, const void *key, void *value);
|
int hashmap_put(Hashmap *h, const void *key, void *value);
|
||||||
int hashmap_replace(Hashmap *h, const void *key, void *value);
|
int hashmap_replace(Hashmap *h, const void *key, void *value);
|
||||||
void* hashmap_get(Hashmap *h, const void *key);
|
void* hashmap_get(Hashmap *h, const void *key);
|
||||||
|
bool hashmap_contains(Hashmap *h, const void *key);
|
||||||
void* hashmap_remove(Hashmap *h, const void *key);
|
void* hashmap_remove(Hashmap *h, const void *key);
|
||||||
void* hashmap_remove_value(Hashmap *h, const void *key, void *value);
|
void* hashmap_remove_value(Hashmap *h, const void *key, void *value);
|
||||||
int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
|
int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
|
||||||
|
|
|
@ -57,6 +57,10 @@ void *set_get(Set *s, void *value) {
|
||||||
return hashmap_get(MAKE_HASHMAP(s), value);
|
return hashmap_get(MAKE_HASHMAP(s), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool set_contains(Set *s, void *value) {
|
||||||
|
return hashmap_contains(MAKE_HASHMAP(s), value);
|
||||||
|
}
|
||||||
|
|
||||||
void *set_remove(Set *s, void *value) {
|
void *set_remove(Set *s, void *value) {
|
||||||
return hashmap_remove(MAKE_HASHMAP(s), value);
|
return hashmap_remove(MAKE_HASHMAP(s), value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_
|
||||||
int set_put(Set *s, void *value);
|
int set_put(Set *s, void *value);
|
||||||
int set_replace(Set *s, void *value);
|
int set_replace(Set *s, void *value);
|
||||||
void *set_get(Set *s, void *value);
|
void *set_get(Set *s, void *value);
|
||||||
|
bool set_contains(Set *s, void *value);
|
||||||
void *set_remove(Set *s, void *value);
|
void *set_remove(Set *s, void *value);
|
||||||
int set_remove_and_put(Set *s, void *old_value, void *new_value);
|
int set_remove_and_put(Set *s, void *old_value, void *new_value);
|
||||||
|
|
||||||
|
|
|
@ -4357,7 +4357,7 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
|
if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) {
|
||||||
if (!is_clean_exit(si.si_code, si.si_status)) {
|
if (!is_clean_exit(si.si_code, si.si_status, NULL)) {
|
||||||
if (si.si_code == CLD_EXITED)
|
if (si.si_code == CLD_EXITED)
|
||||||
log_error("%s exited with exit status %i.", path, si.si_status);
|
log_error("%s exited with exit status %i.", path, si.si_status);
|
||||||
else
|
else
|
||||||
|
|
|
@ -2148,7 +2148,7 @@ static void print_status_info(UnitStatusInfo *i) {
|
||||||
printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t));
|
printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t));
|
||||||
free(t);
|
free(t);
|
||||||
|
|
||||||
good = is_clean_exit_lsb(p->code, p->status);
|
good = is_clean_exit_lsb(p->code, p->status, NULL);
|
||||||
if (!good) {
|
if (!good) {
|
||||||
on = ansi_highlight_red(true);
|
on = ansi_highlight_red(true);
|
||||||
off = ansi_highlight_red(false);
|
off = ansi_highlight_red(false);
|
||||||
|
|
Loading…
Reference in New Issue