static const char *arg_type = NULL;
static char **arg_property = NULL;
static bool arg_all = false;
-static bool arg_fail = false;
+static const char *arg_job_mode = "replace";
static bool arg_user = false;
static bool arg_global = false;
static bool arg_immediate = false;
static bool arg_force = false;
static bool arg_defaults = false;
static bool arg_ask_password = false;
+static bool arg_failed = false;
static char **arg_wall = NULL;
static const char *arg_kill_who = NULL;
static const char *arg_kill_mode = NULL;
static bool private_bus = false;
static pid_t pager_pid = 0;
+static pid_t agent_pid = 0;
static int daemon_reload(DBusConnection *bus, char **args, unsigned n);
static void pager_open(void);
/* Note that this is invoked relatively early, before we start
* the pager. That means the value we return reflects whether
* we originally were started on a tty, not if we currently
- * are. But this is intended, since we want color, and so on
+ * are. But this is intended, since we want colour and so on
* when run in our own pager. */
if (_unlikely_(t < 0))
}
static void spawn_ask_password_agent(void) {
- pid_t parent, child;
+ pid_t parent;
+
+ if (agent_pid > 0)
+ return;
/* We check STDIN here, not STDOUT, since this is about input,
* not output */
if (!arg_ask_password)
return;
+ if (arg_user)
+ return;
+
parent = getpid();
/* Spawns a temporary TTY agent, making sure it goes away when
* we go away */
- if ((child = fork()) < 0)
+ if ((agent_pid = fork()) < 0)
return;
- if (child == 0) {
+ if (agent_pid == 0) {
/* In the child */
- const char * const args[] = {
- SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH,
- "--watch",
- NULL
- };
+ int fd;
+ bool stdout_is_tty, stderr_is_tty;
+ /* Make sure the agent goes away when the parent dies */
if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
_exit(EXIT_FAILURE);
if (getppid() != parent)
_exit(EXIT_SUCCESS);
- execv(args[0], (char **) args);
+ /* Don't leak fds to the agent */
+ close_all_fds(NULL, 0);
+
+ stdout_is_tty = isatty(STDOUT_FILENO);
+ stderr_is_tty = isatty(STDERR_FILENO);
+
+ if (!stdout_is_tty || !stderr_is_tty) {
+ /* Detach from stdout/stderr. and reopen
+ * /dev/tty for them. This is important to
+ * ensure that when systemctl is started via
+ * popen() or a similar call that expects to
+ * read EOF we actually do generate EOF and
+ * not delay this indefinitely by because we
+ * keep an unused copy of stdin around. */
+ if ((fd = open("/dev/tty", O_WRONLY)) < 0) {
+ log_error("Failed to open /dev/tty: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ if (!stdout_is_tty)
+ dup2(fd, STDOUT_FILENO);
+
+ if (!stderr_is_tty)
+ dup2(fd, STDERR_FILENO);
+
+ if (fd > 2)
+ close(fd);
+ }
+
+ execl(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL);
+
+ log_error("Unable to execute agent: %m");
_exit(EXIT_FAILURE);
}
}
return strcasecmp(u->id, v->id);
}
-static bool output_show_job(const struct unit_info *u) {
+static bool output_show_unit(const struct unit_info *u) {
const char *dot;
+ if (arg_failed)
+ return streq(u->active_state, "failed");
+
return (!arg_type || ((dot = strrchr(u->id, '.')) &&
streq(dot+1, arg_type))) &&
(arg_all || !(streq(u->active_state, "inactive") || u->following[0]) || u->job_id > 0);
job_len = sizeof("JOB")-1;
for (u = unit_infos; u < unit_infos + c; u++) {
- if (!output_show_job(u))
+ if (!output_show_unit(u))
continue;
active_len = MAX(active_len, strlen(u->active_state));
const char *on_loaded, *off_loaded;
const char *on_active, *off_active;
- if (!output_show_job(u))
+ if (!output_show_unit(u))
continue;
n_shown++;
c++;
}
- qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
- output_units_list(unit_infos, c);
+ if (c > 0) {
+ qsort(unit_infos, c, sizeof(struct unit_info), compare_unit_info);
+ output_units_list(unit_infos, c);
+ }
r = 0;
typedef struct WaitData {
Set *set;
- bool failed;
+ char *result;
} WaitData;
static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) {
} else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
uint32_t id;
- const char *path;
+ const char *path, *result;
dbus_bool_t success = true;
- if (!dbus_message_get_args(message, &error,
- DBUS_TYPE_UINT32, &id,
- DBUS_TYPE_OBJECT_PATH, &path,
- DBUS_TYPE_BOOLEAN, &success,
- DBUS_TYPE_INVALID))
- log_error("Failed to parse message: %s", bus_error_message(&error));
- else {
+ if (dbus_message_get_args(message, &error,
+ DBUS_TYPE_UINT32, &id,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_STRING, &result,
+ DBUS_TYPE_INVALID)) {
+ char *p;
+
+ if ((p = set_remove(d->set, (char*) path)))
+ free(p);
+
+ if (*result)
+ d->result = strdup(result);
+
+ goto finish;
+ }
+#ifndef LEGACY
+ dbus_error_free(&error);
+
+ if (dbus_message_get_args(message, &error,
+ DBUS_TYPE_UINT32, &id,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_BOOLEAN, &success,
+ DBUS_TYPE_INVALID)) {
char *p;
+ /* Compatibility with older systemd versions <
+ * 19 during upgrades. This should be dropped
+ * one day */
+
if ((p = set_remove(d->set, (char*) path)))
free(p);
if (!success)
- d->failed = true;
+ d->result = strdup("failed");
+
+ goto finish;
}
+#endif
+
+ log_error("Failed to parse message: %s", bus_error_message(&error));
}
+finish:
dbus_error_free(&error);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
zero(d);
d.set = s;
- d.failed = false;
if (!dbus_connection_add_filter(bus, wait_filter, &d, NULL)) {
log_error("Failed to add filter.");
dbus_connection_read_write_dispatch(bus, -1))
;
- if (!arg_quiet && d.failed)
- log_error("Job failed. See system logs and 'systemctl status' for details.");
+ if (!arg_quiet && d.result) {
+ if (streq(d.result, "timeout"))
+ log_error("Job timed out.");
+ else if (streq(d.result, "canceled"))
+ log_error("Job canceled.");
+ else if (streq(d.result, "dependency"))
+ log_error("A dependency job failed. See system logs for details.");
+ else if (!streq(d.result, "done"))
+ log_error("Job failed. See system logs and 'systemctl status' for details.");
+ }
+
+ if (streq_ptr(d.result, "timeout"))
+ r = -ETIME;
+ else if (streq_ptr(d.result, "canceled"))
+ r = -ECANCELED;
+ else if (!streq_ptr(d.result, "done"))
+ r = -EIO;
+ else
+ r = 0;
- r = d.failed ? -EIO : 0;
+ free(d.result);
finish:
/* This is slightly dirty, since we don't undo the filter registration. */
mode =
(streq(args[0], "isolate") ||
streq(args[0], "rescue") ||
- streq(args[0], "emergency")) ? "isolate" :
- arg_fail ? "fail" :
- "replace";
+ streq(args[0], "emergency")) ? "isolate" : arg_job_mode;
one_name = table[verb_to_action(args[0])];
}
typedef struct ExecStatusInfo {
+ char *name;
+
char *path;
char **argv;
static void exec_status_info_free(ExecStatusInfo *i) {
assert(i);
+ free(i->name);
free(i->path);
strv_free(i->argv);
free(i);
LIST_FOREACH(exec, p, i->exec) {
char *t;
+ bool good;
/* Only show exited processes here */
if (p->code == 0)
continue;
t = strv_join(p->argv, " ");
- printf("\t Process: %u (%s, code=%s, ", p->pid, strna(t), sigchld_code_to_string(p->code));
+ printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t));
free(t);
+#ifdef HAVE_SYSV_COMPAT
+ if (i->is_sysv)
+ good = is_clean_exit_lsb(p->code, p->status);
+ else
+#endif
+ good = is_clean_exit(p->code, p->status);
+
+ if (!good) {
+ on = ansi_highlight(true);
+ off = ansi_highlight(false);
+ } else
+ on = off = "";
+
+ printf("%s(code=%s, ", on, sigchld_code_to_string(p->code));
+
if (p->code == CLD_EXITED) {
const char *c;
} else
printf("signal=%s", signal_to_string(p->status));
- printf(")\n");
+
+ printf(")%s\n", off);
+
+ on = off = NULL;
if (i->main_pid == p->pid &&
i->start_timestamp == p->start_timestamp &&
if (!(info = new0(ExecStatusInfo, 1)))
return -ENOMEM;
+ if (!(info->name = strdup(name))) {
+ free(info);
+ return -ENOMEM;
+ }
+
if ((r = exec_status_info_deserialize(&sub, info)) < 0) {
free(info);
return r;
} else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobNew") ||
dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
uint32_t id;
- const char *path;
+ const char *path, *result;
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_UINT32, &id,
DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_STRING, &result,
DBUS_TYPE_INVALID))
log_error("Failed to parse message: %s", bus_error_message(&error));
else if (streq(dbus_message_get_member(message), "JobNew"))
" -t --type=TYPE List only units of a particular type\n"
" -p --property=NAME Show only properties by this name\n"
" -a --all Show all units/properties, including dead/empty ones\n"
+ " --failed Show only failed units\n"
" --full Don't ellipsize unit names on output\n"
" --fail When queueing a new job, fail if conflicting jobs are\n"
" pending\n"
+ " --ignore-dependencies\n"
+ " When queueing a new job, ignore all its dependencies\n"
" -q --quiet Suppress output\n"
" --no-block Do not wait until operation finished\n"
" --no-pager Do not pipe output into a pager.\n"
enum {
ARG_FAIL = 0x100,
+ ARG_IGNORE_DEPENDENCIES,
ARG_VERSION,
ARG_USER,
ARG_SYSTEM,
ARG_DEFAULTS,
ARG_KILL_MODE,
ARG_KILL_WHO,
- ARG_NO_ASK_PASSWORD
+ ARG_NO_ASK_PASSWORD,
+ ARG_FAILED
};
static const struct option options[] = {
{ "type", required_argument, NULL, 't' },
{ "property", required_argument, NULL, 'p' },
{ "all", no_argument, NULL, 'a' },
+ { "failed", no_argument, NULL, ARG_FAILED },
{ "full", no_argument, NULL, ARG_FULL },
{ "fail", no_argument, NULL, ARG_FAIL },
+ { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES },
{ "user", no_argument, NULL, ARG_USER },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "global", no_argument, NULL, ARG_GLOBAL },
break;
case ARG_FAIL:
- arg_fail = true;
+ arg_job_mode = "fail";
+ break;
+
+ case ARG_IGNORE_DEPENDENCIES:
+ arg_job_mode = "ignore-dependencies";
break;
case ARG_USER:
arg_full = true;
break;
+ case ARG_FAILED:
+ arg_failed = true;
+ break;
+
case 'q':
arg_quiet = true;
break;
return 0;
}
+static bool kexec_loaded(void) {
+ bool loaded = false;
+ char *s;
+
+ if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) {
+ if (s[0] == '1')
+ loaded = true;
+ free(s);
+ }
+ return loaded;
+}
+
static int shutdown_parse_argv(int argc, char *argv[]) {
enum {
break;
case 'r':
- arg_action = ACTION_REBOOT;
+ if (kexec_loaded())
+ arg_action = ACTION_KEXEC;
+ else
+ arg_action = ACTION_REBOOT;
break;
case 'h':
arg_action = ACTION_POWEROFF;
return halt_parse_argv(argc, argv);
} else if (strstr(program_invocation_short_name, "reboot")) {
- arg_action = ACTION_REBOOT;
+ if (kexec_loaded())
+ arg_action = ACTION_KEXEC;
+ else
+ arg_action = ACTION_REBOOT;
return halt_parse_argv(argc, argv);
} else if (strstr(program_invocation_short_name, "shutdown")) {
arg_action = ACTION_POWEROFF;
static void pager_open(void) {
int fd[2];
const char *pager;
+ pid_t parent_pid;
if (pager_pid > 0)
return;
return;
}
+ parent_pid = getpid();
+
pager_pid = fork();
if (pager_pid < 0) {
log_error("Failed to fork pager: %m");
setenv("LESS", "FRSX", 0);
- prctl(PR_SET_PDEATHSIG, SIGTERM);
+ /* Make sure the pager goes away when the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Check whether our parent died before we were able
+ * to set the death signal */
+ if (getppid() != parent_pid)
+ _exit(EXIT_SUCCESS);
if (pager) {
execlp(pager, pager, NULL);
pager_pid = 0;
}
+static void agent_close(void) {
+ siginfo_t dummy;
+
+ if (agent_pid <= 0)
+ return;
+
+ /* Inform agent that we are done */
+ kill(agent_pid, SIGTERM);
+ wait_for_terminate(agent_pid, &dummy);
+ agent_pid = 0;
+}
+
int main(int argc, char*argv[]) {
int r, retval = EXIT_FAILURE;
DBusConnection *bus = NULL;
case ACTION_HALT:
case ACTION_POWEROFF:
case ACTION_REBOOT:
+ case ACTION_KEXEC:
r = halt_main(bus);
break;
strv_free(arg_property);
pager_close();
+ agent_close();
return retval;
}