X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fsystemctl.c;h=840e2ffc2d2f235b61983378c298b4265db54524;hp=fb3430048cd7f7aef5e9234832774513731f543a;hb=88e3dc903bd543a74b8699c1575b0da9eeab24a2;hpb=9a57c62944258c750d80bca4fe56de4dbab46d67 diff --git a/src/systemctl.c b/src/systemctl.c index fb3430048..840e2ffc2 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -33,7 +33,6 @@ #include #include #include - #include #include "log.h" @@ -56,11 +55,12 @@ #include "bus-errors.h" #include "build.h" #include "unit-name.h" +#include "pager.h" 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,10 +76,12 @@ 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; static int arg_signal = SIGTERM; +static const char *arg_root = NULL; static usec_t arg_when = 0; static enum action { ACTION_INVALID, @@ -107,13 +109,18 @@ static enum dot { DOT_ORDER, DOT_REQUIRE } arg_dot = DOT_ALL; +static enum transport { + TRANSPORT_NORMAL, + TRANSPORT_SSH, + TRANSPORT_POLKIT +} arg_transport = TRANSPORT_NORMAL; +static const char *arg_host = 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); static bool on_tty(void) { static int t = -1; @@ -121,7 +128,7 @@ static bool on_tty(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)) @@ -130,8 +137,18 @@ static bool on_tty(void) { return t; } +static void pager_open_if_enabled(void) { + on_tty(); + + if (!arg_no_pager) + pager_open(); +} + 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 */ @@ -141,23 +158,24 @@ 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 * 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); @@ -166,7 +184,38 @@ static void spawn_ask_password_agent(void) { 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); } } @@ -230,22 +279,6 @@ static int translate_bus_error_to_exit_status(int r, const DBusError *error) { return EXIT_FAILURE; } -static int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next) { - - assert(iter); - assert(data); - - if (dbus_message_iter_get_arg_type(iter) != type) - return -EIO; - - dbus_message_iter_get_basic(iter, data); - - if (!dbus_message_iter_next(iter) != !next) - return -EIO; - - return 0; -} - static void warn_wall(enum action action) { static const char *table[_ACTION_MAX] = { [ACTION_HALT] = "The system is going down for system halt NOW!", @@ -312,9 +345,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); @@ -329,7 +365,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)); @@ -341,7 +377,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { if (on_tty()) { printf("%-25s %-6s %-*s %-*s %-*s", "UNIT", "LOAD", active_len, "ACTIVE", sub_len, "SUB", job_len, "JOB"); - if (columns() >= 80+12 || arg_full) + if (columns() >= 80+12 || arg_full || !arg_no_pager) printf(" %s\n", "DESCRIPTION"); else printf("\n"); @@ -353,7 +389,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++; @@ -394,7 +430,7 @@ static void output_units_list(const struct unit_info *unit_infos, unsigned c) { if (u->job_id == 0) printf(" %-*s", job_len, ""); - if (arg_full) + if (arg_full || !arg_no_pager) printf(" %s", u->description); else printf(" %.*s", columns() - a - b - 1, u->description); @@ -428,7 +464,7 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) { assert(bus); - pager_open(); + pager_open_if_enabled(); if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", @@ -502,8 +538,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; @@ -747,7 +785,7 @@ static int dot(DBusConnection *bus, char **args, unsigned n) { " red = Conflicts\n" " green = After\n"); - if (isatty(fileno(stdout))) + if (on_tty()) log_notice("-- You probably want to process this output with graphviz' dot tool.\n" "-- Try a shell pipeline like 'systemctl dot | dot -Tsvg > systemd.svg'!\n"); @@ -776,7 +814,7 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) { assert(bus); - pager_open(); + pager_open_if_enabled(); if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", @@ -803,7 +841,7 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) { dbus_message_iter_recurse(&iter, &sub); - if (isatty(STDOUT_FILENO)) + if (on_tty()) printf("%4s %-25s %-15s %-7s\n", "JOB", "UNIT", "TYPE", "STATE"); while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { @@ -839,7 +877,7 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) { dbus_message_iter_next(&sub); } - if (isatty(STDOUT_FILENO)) + if (on_tty()) printf("\n%u jobs listed.\n", k); r = 0; @@ -1079,7 +1117,7 @@ finish: typedef struct WaitData { Set *set; - bool failed; + char *result; } WaitData; static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) { @@ -1103,26 +1141,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; } @@ -1163,7 +1227,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."); @@ -1175,10 +1238,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") && !streq(d.result, "skipped")) + 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") && !streq_ptr(d.result, "skipped")) + 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. */ @@ -1330,22 +1410,26 @@ static int start_unit(DBusConnection *bus, char **args, unsigned n) { if (arg_action == ACTION_SYSTEMCTL) { method = - streq(args[0], "stop") ? "StopUnit" : + streq(args[0], "stop") || + streq(args[0], "condstop") ? "StopUnit" : streq(args[0], "reload") ? "ReloadUnit" : streq(args[0], "restart") ? "RestartUnit" : + streq(args[0], "try-restart") || streq(args[0], "condrestart") ? "TryRestartUnit" : + streq(args[0], "reload-or-restart") ? "ReloadOrRestartUnit" : + streq(args[0], "reload-or-try-restart") || + streq(args[0], "condreload") || + streq(args[0], "force-reload") ? "ReloadOrTryRestartUnit" : "StartUnit"; 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])]; @@ -1471,6 +1555,7 @@ static int check_unit(DBusConnection *bus, char **args, unsigned n) { dbus_error_free(&error); dbus_message_unref(m); + m = NULL; continue; } @@ -1641,7 +1726,7 @@ static void exec_status_info_free(ExecStatusInfo *i) { } static int exec_status_info_deserialize(DBusMessageIter *sub, ExecStatusInfo *i) { - uint64_t start_timestamp, exit_timestamp; + uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic; DBusMessageIter sub2, sub3; const char*path; unsigned n; @@ -1695,7 +1780,9 @@ static int exec_status_info_deserialize(DBusMessageIter *sub, ExecStatusInfo *i) if (!dbus_message_iter_next(&sub2) || bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, true) < 0 || bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &start_timestamp, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &start_timestamp_monotonic, true) < 0 || bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &exit_timestamp, true) < 0 || + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT64, &exit_timestamp_monotonic, true) < 0 || bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &pid, true) < 0 || bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &code, true) < 0 || bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_INT32, &status, false) < 0) @@ -1744,6 +1831,9 @@ typedef struct UnitStatusInfo { int exit_code, exit_status; + usec_t condition_timestamp; + bool condition_result; + /* Socket */ unsigned n_accepted; unsigned n_connections; @@ -1835,6 +1925,16 @@ static void print_status_info(UnitStatusInfo *i) { else printf("\n"); + if (!i->condition_result && i->condition_timestamp > 0) { + s1 = format_timestamp_pretty(since1, sizeof(since1), i->condition_timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->condition_timestamp); + + if (s1) + printf("\t start condition failed at %s; %s\n", s2, s1); + else if (s2) + printf("\t start condition failed at %s\n", s2); + } + if (i->sysfs_path) printf("\t Device: %s\n", i->sysfs_path); if (i->where) @@ -1961,12 +2061,14 @@ static void print_status_info(UnitStatusInfo *i) { printf("\t CGroup: %s\n", i->default_control_group); - if ((c = columns()) > 18) - c -= 18; - else - c = 0; + if (arg_transport != TRANSPORT_SSH) { + if ((c = columns()) > 18) + c -= 18; + else + c = 0; - show_cgroup_by_path(i->default_control_group, "\t\t ", c); + show_cgroup_by_path(i->default_control_group, "\t\t ", c); + } } if (i->need_daemon_reload) @@ -1978,6 +2080,10 @@ static void print_status_info(UnitStatusInfo *i) { static int status_property(const char *name, DBusMessageIter *iter, UnitStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + switch (dbus_message_iter_get_arg_type(iter)) { case DBUS_TYPE_STRING: { @@ -1985,7 +2091,7 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn dbus_message_iter_get_basic(iter, &s); - if (s[0]) { + if (!isempty(s)) { if (streq(name, "Id")) i->id = s; else if (streq(name, "LoadState")) @@ -2030,6 +2136,8 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn i->accept = b; else if (streq(name, "NeedDaemonReload")) i->need_daemon_reload = b; + else if (streq(name, "ConditionResult")) + i->condition_result = b; break; } @@ -2087,6 +2195,8 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn i->inactive_exit_timestamp = (usec_t) u; else if (streq(name, "ActiveExitTimestamp")) i->active_exit_timestamp = (usec_t) u; + else if (streq(name, "ConditionTimestamp")) + i->condition_timestamp = (usec_t) u; break; } @@ -2140,74 +2250,6 @@ static int print_property(const char *name, DBusMessageIter *iter) { switch (dbus_message_iter_get_arg_type(iter)) { - case DBUS_TYPE_STRING: { - const char *s; - dbus_message_iter_get_basic(iter, &s); - - if (arg_all || s[0]) - printf("%s=%s\n", name, s); - - return 0; - } - - case DBUS_TYPE_BOOLEAN: { - dbus_bool_t b; - dbus_message_iter_get_basic(iter, &b); - printf("%s=%s\n", name, yes_no(b)); - - return 0; - } - - case DBUS_TYPE_UINT64: { - uint64_t u; - dbus_message_iter_get_basic(iter, &u); - - /* Yes, heuristics! But we can change this check - * should it turn out to not be sufficient */ - - if (strstr(name, "Timestamp")) { - char timestamp[FORMAT_TIMESTAMP_MAX], *t; - - if ((t = format_timestamp(timestamp, sizeof(timestamp), u)) || arg_all) - printf("%s=%s\n", name, strempty(t)); - } else if (strstr(name, "USec")) { - char timespan[FORMAT_TIMESPAN_MAX]; - - printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u)); - } else - printf("%s=%llu\n", name, (unsigned long long) u); - - return 0; - } - - case DBUS_TYPE_UINT32: { - uint32_t u; - dbus_message_iter_get_basic(iter, &u); - - if (strstr(name, "UMask") || strstr(name, "Mode")) - printf("%s=%04o\n", name, u); - else - printf("%s=%u\n", name, (unsigned) u); - - return 0; - } - - case DBUS_TYPE_INT32: { - int32_t i; - dbus_message_iter_get_basic(iter, &i); - - printf("%s=%i\n", name, (int) i); - return 0; - } - - case DBUS_TYPE_DOUBLE: { - double d; - dbus_message_iter_get_basic(iter, &d); - - printf("%s=%g\n", name, d); - return 0; - } - case DBUS_TYPE_STRUCT: { DBusMessageIter sub; dbus_message_iter_recurse(iter, &sub); @@ -2239,50 +2281,21 @@ static int print_property(const char *name, DBusMessageIter *iter) { case DBUS_TYPE_ARRAY: - if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) { - DBusMessageIter sub; - bool space = false; - - dbus_message_iter_recurse(iter, &sub); - if (arg_all || - dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { - printf("%s=", name); - - while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { - const char *s; - - assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(&sub, &s); - printf("%s%s", space ? " " : "", s); - - space = true; - dbus_message_iter_next(&sub); - } - - puts(""); - } - - return 0; - - } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_BYTE) { - DBusMessageIter sub; + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "EnvironmentFiles")) { + DBusMessageIter sub, sub2; dbus_message_iter_recurse(iter, &sub); - if (arg_all || - dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { - printf("%s=", name); - - while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { - uint8_t u; + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *path; + dbus_bool_t ignore; - assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE); - dbus_message_iter_get_basic(&sub, &u); - printf("%02x", u); + dbus_message_iter_recurse(&sub, &sub2); - dbus_message_iter_next(&sub); - } + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &path, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_BOOLEAN, &ignore, false) >= 0) + printf("EnvironmentFile=%s (ignore=%s)\n", path, yes_no(ignore)); - puts(""); + dbus_message_iter_next(&sub); } return 0; @@ -2373,6 +2386,9 @@ static int print_property(const char *name, DBusMessageIter *iter) { break; } + if (generic_print_property(name, iter, arg_all) > 0) + return 0; + if (arg_all) printf("%s=[unprintable]\n", name); @@ -2516,7 +2532,7 @@ static int show(DBusConnection *bus, char **args, unsigned n) { show_properties = !streq(args[0], "status"); if (show_properties) - pager_open(); + pager_open_if_enabled(); if (show_properties && n <= 1) { /* If not argument is specified inspect the manager @@ -2708,8 +2724,7 @@ static DBusHandlerResult monitor_filter(DBusConnection *connection, DBusMessage else printf("Unit %s removed.\n", id); - } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobNew") || - dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobNew")) { uint32_t id; const char *path; @@ -2718,10 +2733,22 @@ static DBusHandlerResult monitor_filter(DBusConnection *connection, DBusMessage DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) log_error("Failed to parse message: %s", bus_error_message(&error)); - else if (streq(dbus_message_get_member(message), "JobNew")) + else printf("Job %u added.\n", id); + + + } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) { + uint32_t id; + 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 - printf("Job %u removed.\n", id); + printf("Job %u removed (result=%s).\n", id, result); } else if (dbus_message_is_signal(message, "org.freedesktop.DBus.Properties", "PropertiesChanged")) { @@ -2901,7 +2928,7 @@ static int dump(DBusConnection *bus, char **args, unsigned n) { dbus_error_init(&error); - pager_open(); + pager_open_if_enabled(); if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", @@ -3178,6 +3205,13 @@ static int daemon_reload(DBusConnection *bus, char **args, unsigned n) { goto finish; } + if (streq(method, "Reexecute") && dbus_error_has_name(&error, DBUS_ERROR_NO_REPLY)) { + /* On reexecution, we expect a disconnect, not + * a reply */ + r = 0; + goto finish; + } + log_error("Failed to issue method call: %s", bus_error_message(&error)); r = -EIO; goto finish; @@ -3265,7 +3299,7 @@ static int show_enviroment(DBusConnection *bus, char **args, unsigned n) { dbus_error_init(&error); - pager_open(); + pager_open_if_enabled(); if (!(m = dbus_message_new_method_call( "org.freedesktop.systemd1", @@ -3476,6 +3510,7 @@ static int config_parse_also( unsigned line, const char *section, const char *lvalue, + int ltype, const char *rvalue, void *data, void *userdata) { @@ -3694,8 +3729,14 @@ static int remove_marked_symlinks(const char *config_path) { return r; } -static int create_symlink(const char *verb, const char *old_path, const char *new_path) { +static int create_symlink(const char *verb, const char *orig_old_path, const char *new_path) { int r; + const char *old_path; + + if (arg_root) + old_path = orig_old_path+strlen(arg_root); + else + old_path = orig_old_path; assert(old_path); assert(new_path); @@ -3809,6 +3850,7 @@ static int create_symlink(const char *verb, const char *old_path, const char *ne return 1; } + free(dest); return 0; } @@ -3890,11 +3932,11 @@ finish: static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo *i, const char *config_path) { const ConfigItem items[] = { - { "Alias", config_parse_strv, &i->aliases, "Install" }, - { "WantedBy", config_parse_strv, &i->wanted_by, "Install" }, - { "Also", config_parse_also, NULL, "Install" }, + { "Alias", config_parse_strv, 0, &i->aliases, "Install" }, + { "WantedBy", config_parse_strv, 0, &i->wanted_by, "Install" }, + { "Also", config_parse_also, 0, NULL, "Install" }, - { NULL, NULL, NULL, NULL } + { NULL, NULL, 0, NULL, NULL } }; char **p; @@ -3907,12 +3949,23 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo STRV_FOREACH(p, paths->unit_path) { int fd; + char *path, *should_free; + + if (arg_root) + should_free = path = strappend(arg_root, *p); + else { + should_free = NULL; + path = *p; + } - if (!(filename = path_make_absolute(i->name, *p))) { + if (!(filename = path_make_absolute(i->name, path))) { log_error("Out of memory"); return -ENOMEM; } + if (should_free) + free(should_free); + /* Ensure that we don't follow symlinks */ if ((fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOCTTY)) >= 0) if ((f = fdopen(fd, "re"))) @@ -3935,7 +3988,7 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo } if (!f) { -#if defined(TARGET_FEDORA) && defined (HAVE_SYSV_COMPAT) +#if (defined(TARGET_FEDORA) || defined(TARGET_MANDRIVA) || defined(TARGET_SUSE) || defined(TARGET_MEEGO) || defined(TARGET_ALTLINUX)) && defined (HAVE_SYSV_COMPAT) if (endswith(i->name, ".service")) { char *sysv; @@ -3947,7 +4000,14 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo } sysv[strlen(sysv) - sizeof(".service") + 1] = 0; - exists = access(sysv, F_OK) >= 0; + if (arg_root) { + char *tmp_path; + + tmp_path = strappend (arg_root, sysv); + exists = access (tmp_path, F_OK) >= 0; + free (tmp_path); + } else + exists = access(sysv, F_OK) >= 0; if (exists) { pid_t pid; @@ -3957,6 +4017,7 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo "/sbin/chkconfig", NULL, NULL, + NULL, NULL }; @@ -3965,9 +4026,11 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo argv[1] = file_name_from_path(sysv); argv[2] = streq(verb, "enable") ? "on" : - streq(verb, "disable") ? "off" : NULL; + streq(verb, "disable") ? "off" : "--level=5"; + if (arg_root) + argv[3] = strappend("--root=", arg_root); - log_info("Executing %s %s %s", argv[0], argv[1], strempty(argv[2])); + log_info("Executing %s %s %s %s", argv[0], argv[1], strempty(argv[2]), strempty(argv[3])); if ((pid = fork()) < 0) { log_error("Failed to fork: %m"); @@ -3984,10 +4047,15 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo return r; if (status.si_code == CLD_EXITED) { - if (status.si_status == 0 && (streq(verb, "enable") || streq(verb, "disable"))) + + if (streq(verb, "is-enabled")) + return status.si_status == 0 ? 1 : 0; + + if (status.si_status == 0) n_symlinks ++; return status.si_status == 0 ? 0 : -EINVAL; + } else return -EPROTO; } @@ -4008,6 +4076,16 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo return r; } + /* Consider unit files stored in /lib and /usr always enabled + * if they have no [Install] data. */ + if (streq(verb, "is-enabled") && + strv_isempty(i->aliases) && + strv_isempty(i->wanted_by) && + !path_startswith(filename, "/etc")) { + fclose(f); + return 1; + } + n_symlinks += strv_length(i->aliases); n_symlinks += strv_length(i->wanted_by); @@ -4029,20 +4107,24 @@ static int install_info_apply(const char *verb, LookupPaths *paths, InstallInfo } static char *get_config_path(void) { + char *ret; if (arg_user && arg_global) - return strdup(USER_CONFIG_UNIT_PATH); + ret = strdup(USER_CONFIG_UNIT_PATH); if (arg_user) { - char *p; - - if (user_config_home(&p) < 0) + if (user_config_home(&ret) < 0) return NULL; - - return p; } - return strdup(SYSTEM_CONFIG_UNIT_PATH); + ret = strdup(SYSTEM_CONFIG_UNIT_PATH); + if (arg_root) { + char *p; + p = strappend (arg_root, ret); + free (ret); + return p; + } else + return ret; } static int enable_unit(DBusConnection *bus, char **args, unsigned n) { @@ -4090,6 +4172,8 @@ static int enable_unit(DBusConnection *bus, char **args, unsigned n) { goto finish; } + r = 0; + while ((i = hashmap_first(will_install))) { int q; @@ -4123,7 +4207,7 @@ static int enable_unit(DBusConnection *bus, char **args, unsigned n) { /* Don't try to reload anything if we are called for system changes but the system wasn't booted with systemd */ (arg_user || sd_booted() > 0) && /* Don't try to reload anything if we are running in a chroot environment */ - (arg_user || running_in_chroot() <= 0) ) { + (arg_user || running_in_chroot() <= 0 || arg_root) ) { int q; if ((q = daemon_reload(bus, args, n)) < 0) @@ -4153,25 +4237,31 @@ 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" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " -H --host=[USER@]HOST\n" + " Show information for remote host\n" + " -P --privileged Acquire privileges before execution\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" - " --system Connect to system manager\n" - " --user Connect to user service manager\n" - " --order When generating graph for dot, show only order\n" - " --require When generating graph for dot, show only requirement\n" " --no-wall Don't send wall message before halt/power-off/reboot\n" - " --global Enable/disable unit files globally\n" " --no-reload When enabling/disabling unit files, don't reload daemon\n" " configuration\n" + " --no-pager Do not pipe output into a pager\n" " --no-ask-password\n" " Do not ask for system passwords\n" - " --kill-mode=MODE How to send signal\n" - " --kill-who=WHO Who to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" + " --order When generating graph for dot, show only order\n" + " --require When generating graph for dot, show only requirement\n" + " --root=path Use as the root file system\n" + " --system Connect to system manager\n" + " --user Connect to user service manager\n" + " --global Enable/disable unit files globally\n" " -f --force When enabling unit files, override existing symlinks\n" " When shutting down, execute action immediately\n" " --defaults When disabling unit files, remove default symlinks only\n\n" @@ -4293,6 +4383,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { enum { ARG_FAIL = 0x100, + ARG_IGNORE_DEPENDENCIES, ARG_VERSION, ARG_USER, ARG_SYSTEM, @@ -4302,12 +4393,14 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_NO_WALL, ARG_ORDER, ARG_REQUIRE, + ARG_ROOT, ARG_FULL, ARG_NO_RELOAD, ARG_DEFAULTS, ARG_KILL_MODE, ARG_KILL_WHO, - ARG_NO_ASK_PASSWORD + ARG_NO_ASK_PASSWORD, + ARG_FAILED }; static const struct option options[] = { @@ -4316,8 +4409,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 }, @@ -4327,13 +4422,16 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "quiet", no_argument, NULL, 'q' }, { "order", no_argument, NULL, ARG_ORDER }, { "require", no_argument, NULL, ARG_REQUIRE }, + { "root", required_argument, NULL, ARG_ROOT }, { "force", no_argument, NULL, 'f' }, { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, { "defaults", no_argument, NULL, ARG_DEFAULTS }, - { "kill-mode", required_argument, NULL, ARG_KILL_MODE }, + { "kill-mode", required_argument, NULL, ARG_KILL_MODE }, /* undocumented on purpose */ { "kill-who", required_argument, NULL, ARG_KILL_WHO }, { "signal", required_argument, NULL, 's' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "host", required_argument, NULL, 'H' }, + { "privileged",no_argument, NULL, 'P' }, { NULL, 0, NULL, 0 } }; @@ -4345,7 +4443,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { /* Only when running as systemctl we ask for passwords */ arg_ask_password = true; - while ((c = getopt_long(argc, argv, "ht:p:aqfs:", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:P", options, NULL)) >= 0) { switch (c) { @@ -4384,7 +4482,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: @@ -4415,10 +4517,18 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_dot = DOT_REQUIRE; break; + case ARG_ROOT: + arg_root = optarg; + break; + case ARG_FULL: arg_full = true; break; + case ARG_FAILED: + arg_failed = true; + break; + case 'q': arg_quiet = true; break; @@ -4459,6 +4569,15 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_ask_password = false; break; + case 'P': + arg_transport = TRANSPORT_POLKIT; + break; + + case 'H': + arg_transport = TRANSPORT_SSH; + arg_host = optarg; + break; + case '?': return -EINVAL; @@ -4468,6 +4587,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } } + if (arg_transport != TRANSPORT_NORMAL && arg_user) { + log_error("Cannot access user instance remotely."); + return -EINVAL; + } + return 1; } @@ -4570,10 +4694,10 @@ static int parse_time_spec(const char *t, usec_t *_u) { if (streq(t, "now")) *_u = 0; - else if (t[0] == '+') { + else if (!strchr(t, ':')) { uint64_t u; - if (safe_atou64(t + 1, &u) < 0) + if (safe_atou64(t, &u) < 0) return -EINVAL; *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; @@ -4614,6 +4738,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 { @@ -4651,7 +4787,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': @@ -4844,7 +4983,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; @@ -5044,12 +5186,14 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError { "cancel", MORE, 2, cancel_job }, { "start", MORE, 2, start_unit }, { "stop", MORE, 2, start_unit }, + { "condstop", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ { "reload", MORE, 2, start_unit }, { "restart", MORE, 2, start_unit }, { "try-restart", MORE, 2, start_unit }, { "reload-or-restart", MORE, 2, start_unit }, { "reload-or-try-restart", MORE, 2, start_unit }, { "force-reload", MORE, 2, start_unit }, /* For compatibility with SysV */ + { "condreload", MORE, 2, start_unit }, /* For compatibility with ALTLinux */ { "condrestart", MORE, 2, start_unit }, /* For compatibility with RH */ { "isolate", EQUAL, 2, start_unit }, { "kill", MORE, 2, kill_unit }, @@ -5141,17 +5285,23 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError /* Require a bus connection for all operations but * enable/disable */ - if (!streq(verbs[i].verb, "enable") && - !streq(verbs[i].verb, "disable") && - !bus) { - log_error("Failed to get D-Bus connection: %s", error->message); - return -EIO; + if (!streq(verbs[i].verb, "enable") && !streq(verbs[i].verb, "disable")) { + + if (running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + return 0; + } + + if (!bus) { + log_error("Failed to get D-Bus connection: %s", error->message); + return -EIO; + } } return verbs[i].dispatch(bus, argv + optind, left); } -static int send_shutdownd(usec_t t, char mode, bool warn, const char *message) { +static int send_shutdownd(usec_t t, char mode, bool dry_run, bool warn, const char *message) { int fd = -1; struct msghdr msghdr; struct iovec iovec; @@ -5161,6 +5311,7 @@ static int send_shutdownd(usec_t t, char mode, bool warn, const char *message) { zero(c); c.elapse = t; c.mode = mode; + c.dry_run = dry_run; c.warn_wall = warn; if (message) @@ -5172,7 +5323,7 @@ static int send_shutdownd(usec_t t, char mode, bool warn, const char *message) { zero(sockaddr); sockaddr.sa.sa_family = AF_UNIX; sockaddr.un.sun_path[0] = 0; - strncpy(sockaddr.un.sun_path+1, "/org/freedesktop/systemd1/shutdownd", sizeof(sockaddr.un.sun_path)-1); + strncpy(sockaddr.un.sun_path, "/run/systemd/shutdownd", sizeof(sockaddr.un.sun_path)); zero(iovec); iovec.iov_base = (char*) &c; @@ -5180,7 +5331,7 @@ static int send_shutdownd(usec_t t, char mode, bool warn, const char *message) { zero(msghdr); msghdr.msg_name = &sockaddr; - msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + 1 + sizeof("/org/freedesktop/systemd1/shutdownd") - 1; + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + sizeof("/run/systemd/shutdownd") - 1; msghdr.msg_iov = &iovec; msghdr.msg_iovlen = 1; @@ -5256,6 +5407,7 @@ static int halt_main(DBusConnection *bus) { arg_action == ACTION_HALT ? 'H' : arg_action == ACTION_POWEROFF ? 'P' : 'r', + arg_dry, !arg_no_wall, m); free(m); @@ -5329,83 +5481,17 @@ static int runlevel_main(void) { return 0; } -static void pager_open(void) { - int fd[2]; - const char *pager; - - if (pager_pid > 0) - return; - - if (!on_tty() || arg_no_pager) - return; - - if ((pager = getenv("PAGER"))) - 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; - } - - pager_pid = fork(); - if (pager_pid < 0) { - log_error("Failed to fork pager: %m"); - close_pipe(fd); - return; - } - - /* In the child start the pager */ - if (pager_pid == 0) { - - dup2(fd[0], STDIN_FILENO); - close_pipe(fd); - - setenv("LESS", "FRSX", 0); - - prctl(PR_SET_PDEATHSIG, SIGTERM); - - if (pager) { - execlp(pager, pager, NULL); - execl("/bin/sh", "sh", "-c", pager, NULL); - } else { - /* 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); - } - - log_error("Unable to execute pager: %m"); - _exit(EXIT_FAILURE); - } - - /* Return in the parent */ - if (dup2(fd[1], STDOUT_FILENO) < 0) - log_error("Failed to duplicate pager pipe: %m"); - - close_pipe(fd); -} - -static void pager_close(void) { +static void agent_close(void) { siginfo_t dummy; - if (pager_pid <= 0) + if (agent_pid <= 0) return; - /* Inform pager that we are done */ - fclose(stdout); - wait_for_terminate(pager_pid, &dummy); - pager_pid = 0; + /* Inform agent that we are done */ + kill(agent_pid, SIGTERM); + kill(agent_pid, SIGCONT); + wait_for_terminate(agent_pid, &dummy); + agent_pid = 0; } int main(int argc, char*argv[]) { @@ -5433,7 +5519,22 @@ int main(int argc, char*argv[]) { goto finish; } - bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error); + if (running_in_chroot() > 0 && arg_action != ACTION_SYSTEMCTL) { + log_info("Running in chroot, ignoring request."); + retval = 0; + goto finish; + } + + if (arg_transport == TRANSPORT_NORMAL) + bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error); + else if (arg_transport == TRANSPORT_POLKIT) { + bus_connect_system_polkit(&bus, &error); + private_bus = false; + } else if (arg_transport == TRANSPORT_SSH) { + bus_connect_system_ssh(NULL, arg_host, &bus, &error); + private_bus = false; + } else + assert_not_reached("Uh, invalid transport..."); switch (arg_action) { @@ -5444,6 +5545,7 @@ int main(int argc, char*argv[]) { case ACTION_HALT: case ACTION_POWEROFF: case ACTION_REBOOT: + case ACTION_KEXEC: r = halt_main(bus); break; @@ -5463,7 +5565,7 @@ int main(int argc, char*argv[]) { break; case ACTION_CANCEL_SHUTDOWN: - r = send_shutdownd(0, 0, false, NULL); + r = send_shutdownd(0, 0, false, false, NULL); break; case ACTION_INVALID: @@ -5489,6 +5591,7 @@ finish: strv_free(arg_property); pager_close(); + agent_close(); return retval; }