X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fsystemctl.c;h=ab41566577167f3fe9997328197e831abd1ef496;hp=b33a89f63d76d9bd3ae05d28db2f5b35a75aca4d;hb=abebb5af9a7f5da77425aab1ecadd9bfcad53009;hpb=8e20e31a65ec9e637abf3821087946e9160001ac diff --git a/src/systemctl.c b/src/systemctl.c index b33a89f63..ab4156657 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -108,10 +108,17 @@ 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); @@ -132,7 +139,10 @@ static bool on_tty(void) { } 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 */ @@ -150,16 +160,11 @@ static void spawn_ask_password_agent(void) { /* 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; @@ -202,7 +207,9 @@ static void spawn_ask_password_agent(void) { close(fd); } - execv(args[0], (char **) args); + 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); } } @@ -380,7 +387,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"); @@ -433,7 +440,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); @@ -1248,7 +1255,7 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) { 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")) + else if (!streq(d.result, "done") && !streq(d.result, "skipped")) log_error("Job failed. See system logs and 'systemctl status' for details."); } @@ -1256,7 +1263,7 @@ static int wait_for_jobs(DBusConnection *bus, Set *s) { r = -ETIME; else if (streq_ptr(d.result, "canceled")) r = -ECANCELED; - else if (!streq_ptr(d.result, "done")) + else if (!streq_ptr(d.result, "done") && !streq_ptr(d.result, "skipped")) r = -EIO; else r = 0; @@ -1413,13 +1420,19 @@ 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"; @@ -1552,6 +1565,7 @@ static int check_unit(DBusConnection *bus, char **args, unsigned n) { dbus_error_free(&error); dbus_message_unref(m); + m = NULL; continue; } @@ -1722,7 +1736,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; @@ -1776,7 +1790,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) @@ -1825,6 +1841,9 @@ typedef struct UnitStatusInfo { int exit_code, exit_status; + usec_t condition_timestamp; + bool condition_result; + /* Socket */ unsigned n_accepted; unsigned n_connections; @@ -1916,6 +1935,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) @@ -2042,12 +2071,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) @@ -2111,6 +2142,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; } @@ -2168,6 +2201,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; } @@ -2246,7 +2281,7 @@ static int print_property(const char *name, DBusMessageIter *iter) { /* Yes, heuristics! But we can change this check * should it turn out to not be sufficient */ - if (strstr(name, "Timestamp")) { + if (endswith(name, "Timestamp")) { char timestamp[FORMAT_TIMESTAMP_MAX], *t; if ((t = format_timestamp(timestamp, sizeof(timestamp), u)) || arg_all) @@ -2368,6 +2403,25 @@ static int print_property(const char *name, DBusMessageIter *iter) { return 0; + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "EnvironmentFiles")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *path; + dbus_bool_t ignore; + + dbus_message_iter_recurse(&sub, &sub2); + + 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)); + + dbus_message_iter_next(&sub); + } + + return 0; + } else if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Paths")) { DBusMessageIter sub, sub2; @@ -2789,8 +2843,20 @@ 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; + + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_UINT32, &id, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + log_error("Failed to parse message: %s", bus_error_message(&error)); + 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; @@ -2800,10 +2866,8 @@ static DBusHandlerResult monitor_filter(DBusConnection *connection, DBusMessage 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")) - printf("Job %u added.\n", id); 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")) { @@ -3260,6 +3324,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; @@ -3558,6 +3629,7 @@ static int config_parse_also( unsigned line, const char *section, const char *lvalue, + int ltype, const char *rvalue, void *data, void *userdata) { @@ -3891,6 +3963,7 @@ static int create_symlink(const char *verb, const char *old_path, const char *ne return 1; } + free(dest); return 0; } @@ -3972,11 +4045,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; @@ -4017,7 +4090,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; @@ -4047,7 +4120,7 @@ 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"; log_info("Executing %s %s %s", argv[0], argv[1], strempty(argv[2])); @@ -4066,10 +4139,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; } @@ -4090,6 +4168,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); @@ -4172,6 +4260,8 @@ static int enable_unit(DBusConnection *bus, char **args, unsigned n) { goto finish; } + r = 0; + while ((i = hashmap_first(will_install))) { int q; @@ -4241,22 +4331,25 @@ static int systemctl_help(void) { " pending\n" " --ignore-dependencies\n" " When queueing a new job, ignore all its dependencies\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" + " -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" + " --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" @@ -4423,6 +4516,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "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 } }; @@ -4434,7 +4529,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) { @@ -4556,6 +4651,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; @@ -4565,6 +4669,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; } @@ -5159,12 +5268,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 }, @@ -5256,11 +5367,17 @@ 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); @@ -5287,7 +5404,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; @@ -5295,7 +5412,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; @@ -5447,6 +5564,7 @@ static int runlevel_main(void) { static void pager_open(void) { int fd[2]; const char *pager; + pid_t parent_pid; if (pager_pid > 0) return; @@ -5454,7 +5572,7 @@ static void pager_open(void) { if (!on_tty() || arg_no_pager) return; - if ((pager = getenv("PAGER"))) + if ((pager = getenv("SYSTEMD_PAGER")) || (pager = getenv("PAGER"))) if (!*pager || streq(pager, "cat")) return; @@ -5467,6 +5585,8 @@ static void pager_open(void) { return; } + parent_pid = getpid(); + pager_pid = fork(); if (pager_pid < 0) { log_error("Failed to fork pager: %m"); @@ -5482,7 +5602,14 @@ static void pager_open(void) { 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); @@ -5519,10 +5646,24 @@ static void pager_close(void) { /* Inform pager that we are done */ fclose(stdout); + kill(pager_pid, SIGCONT); wait_for_terminate(pager_pid, &dummy); 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); + kill(agent_pid, SIGCONT); + wait_for_terminate(agent_pid, &dummy); + agent_pid = 0; +} + int main(int argc, char*argv[]) { int r, retval = EXIT_FAILURE; DBusConnection *bus = NULL; @@ -5548,7 +5689,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) { @@ -5605,6 +5761,7 @@ finish: strv_free(arg_property); pager_close(); + agent_close(); return retval; }