X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fsystemctl.c;h=b33a89f63d76d9bd3ae05d28db2f5b35a75aca4d;hb=8e20e31a65ec9e637abf3821087946e9160001ac;hp=59ea7490e767dfa879e3673a7875a9d4b2654e77;hpb=1888c9074ab1cb82c1719090561a31d7902df286;p=elogind.git diff --git a/src/systemctl.c b/src/systemctl.c index 59ea7490e..b33a89f63 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -60,7 +60,7 @@ 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; @@ -76,6 +76,7 @@ static bool arg_full = 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; @@ -113,10 +114,17 @@ static bool private_bus = false; static pid_t pager_pid = 0; static int daemon_reload(DBusConnection *bus, char **args, unsigned n); +static void pager_open(void); static bool on_tty(void) { static int t = -1; + /* 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 colour and so on + * when run in our own pager. */ + if (_unlikely_(t < 0)) t = isatty(STDOUT_FILENO) > 0; @@ -134,6 +142,9 @@ static void spawn_ask_password_agent(void) { if (!arg_ask_password) return; + if (arg_user) + return; + parent = getpid(); /* Spawns a temporary TTY agent, making sure it goes away when @@ -144,13 +155,16 @@ static void spawn_ask_password_agent(void) { if (child == 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); @@ -159,6 +173,35 @@ static void spawn_ask_password_agent(void) { if (getppid() != parent) _exit(EXIT_SUCCESS); + /* 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); + } + execv(args[0], (char **) args); _exit(EXIT_FAILURE); } @@ -305,9 +348,12 @@ static int compare_unit_info(const void *a, const void *b) { 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); @@ -322,7 +368,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { 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)); @@ -346,7 +392,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { 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++; @@ -421,6 +467,8 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) { assert(bus); + pager_open(); + if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -493,8 +541,10 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) { 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; @@ -767,6 +817,8 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) { assert(bus); + pager_open(); + if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -1068,7 +1120,7 @@ finish: typedef struct WaitData { Set *set; - bool failed; + char *result; } WaitData; static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) { @@ -1092,26 +1144,52 @@ static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *me } 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; } @@ -1152,7 +1230,6 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) { zero(d); d.set = s; - d.failed = false; if (!dbus_connection_add_filter(bus, wait_filter, &d, NULL)) { log_error("Failed to add filter."); @@ -1164,10 +1241,27 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) { 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. */ @@ -1332,9 +1426,7 @@ static int start_unit(DBusConnection *bus, char **args, unsigned n) { 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])]; @@ -1604,6 +1696,8 @@ finish: } typedef struct ExecStatusInfo { + char *name; + char *path; char **argv; @@ -1621,6 +1715,7 @@ typedef struct ExecStatusInfo { static void exec_status_info_free(ExecStatusInfo *i) { assert(i); + free(i->name); free(i->path); strv_free(i->argv); free(i); @@ -1833,15 +1928,31 @@ static void print_status_info(UnitStatusInfo *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; @@ -1856,7 +1967,10 @@ static void print_status_info(UnitStatusInfo *i) { } 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 && @@ -2072,6 +2186,11 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn 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; @@ -2477,6 +2596,9 @@ static int show(DBusConnection *bus, char **args, unsigned n) { show_properties = !streq(args[0], "status"); + if (show_properties) + pager_open(); + if (show_properties && n <= 1) { /* If not argument is specified inspect the manager * itself */ @@ -2670,11 +2792,12 @@ static DBusHandlerResult monitor_filter(DBusConnection *connection, DBusMessage } 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")) @@ -2860,6 +2983,8 @@ static int dump(DBusConnection *bus, char **args, unsigned n) { dbus_error_init(&error); + pager_open(); + if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -3222,6 +3347,8 @@ static int show_enviroment(DBusConnection *bus, char **args, unsigned n) { dbus_error_init(&error); + pager_open(); + if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -4108,9 +4235,12 @@ static int systemctl_help(void) { " -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" @@ -4248,6 +4378,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { enum { ARG_FAIL = 0x100, + ARG_IGNORE_DEPENDENCIES, ARG_VERSION, ARG_USER, ARG_SYSTEM, @@ -4262,7 +4393,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_DEFAULTS, ARG_KILL_MODE, ARG_KILL_WHO, - ARG_NO_ASK_PASSWORD + ARG_NO_ASK_PASSWORD, + ARG_FAILED }; static const struct option options[] = { @@ -4271,8 +4403,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "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 }, @@ -4339,7 +4473,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { 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: @@ -4374,6 +4512,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_full = true; break; + case ARG_FAILED: + arg_failed = true; + break; + case 'q': arg_quiet = true; break; @@ -4569,6 +4711,18 @@ static int parse_time_spec(const char *t, usec_t *_u) { 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 { @@ -4606,7 +4760,10 @@ static int shutdown_parse_argv(int argc, char *argv[]) { break; case 'r': - arg_action = ACTION_REBOOT; + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; break; case 'h': @@ -4799,7 +4956,10 @@ static int parse_argv(int argc, char *argv[]) { 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; @@ -5298,6 +5458,10 @@ static void pager_open(void) { if (!*pager || streq(pager, "cat")) return; + /* Determine and cache number of columns before we spawn the + * pager so that we get the value from the actual tty */ + columns(); + if (pipe(fd) < 0) { log_error("Failed to create pager pipe: %m"); return; @@ -5316,8 +5480,7 @@ static void pager_open(void) { dup2(fd[0], STDIN_FILENO); close_pipe(fd); - if (!getenv("LESS")) - setenv("LESS", "FRSX", 0); + setenv("LESS", "FRSX", 0); prctl(PR_SET_PDEATHSIG, SIGTERM); @@ -5325,7 +5488,14 @@ static void pager_open(void) { execlp(pager, pager, NULL); execl("/bin/sh", "sh", "-c", pager, NULL); } else { - execlp("sensible-pager", "sensible-pager", NULL); + /* Debian's alternatives command for pagers is + * called 'pager'. Note that we do not call + * sensible-pagers here, since that is just a + * shell script that implements a logic that + * is similar to this one anyway, but is + * Debian-specific. */ + execlp("pager", "pager", NULL); + execlp("less", "less", NULL); execlp("more", "more", NULL); } @@ -5370,8 +5540,6 @@ int main(int argc, char*argv[]) { goto finish; } - pager_open(); - /* /sbin/runlevel doesn't need to communicate via D-Bus, so * let's shortcut this */ if (arg_action == ACTION_RUNLEVEL) { @@ -5391,6 +5559,7 @@ int main(int argc, char*argv[]) { case ACTION_HALT: case ACTION_POWEROFF: case ACTION_REBOOT: + case ACTION_KEXEC: r = halt_main(bus); break;