X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Fsystemctl.c;h=eacc63e94dd2c45c98f2e96b5ea5d2c506968fe7;hb=4dba533a137486719fb385c45fc2ff75593fdf53;hp=4768fb20ad24053ab1b5eeda33818a2863c4af41;hpb=be8088a2fb41467f66978f86b11bf47c546f24ed;p=elogind.git diff --git a/src/systemctl.c b/src/systemctl.c index 4768fb20a..eacc63e94 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -60,11 +60,12 @@ 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_no_block = false; +static bool arg_no_pager = false; static bool arg_no_wtmp = false; static bool arg_no_sync = false; static bool arg_no_wall = false; @@ -75,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; @@ -109,11 +111,20 @@ static enum dot { 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; @@ -131,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 @@ -141,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); @@ -156,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); } @@ -302,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); @@ -319,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)); @@ -343,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++; @@ -418,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", @@ -490,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; @@ -764,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", @@ -1329,9 +1384,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])]; @@ -1601,6 +1654,8 @@ finish: } typedef struct ExecStatusInfo { + char *name; + char *path; char **argv; @@ -1618,6 +1673,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); @@ -1830,15 +1886,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; @@ -1853,7 +1925,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 && @@ -2069,6 +2144,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; @@ -2474,6 +2554,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 */ @@ -2857,6 +2940,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", @@ -3219,6 +3304,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", @@ -4105,11 +4192,15 @@ 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" " --system Connect to system manager\n" " --user Connect to user service manager\n" " --order When generating graph for dot, show only order\n" @@ -4244,11 +4335,13 @@ static int systemctl_parse_argv(int argc, char *argv[]) { enum { ARG_FAIL = 0x100, + ARG_IGNORE_DEPENDENCIES, ARG_VERSION, ARG_USER, ARG_SYSTEM, ARG_GLOBAL, ARG_NO_BLOCK, + ARG_NO_PAGER, ARG_NO_WALL, ARG_ORDER, ARG_REQUIRE, @@ -4257,7 +4350,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[] = { @@ -4266,12 +4360,15 @@ 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 }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "no-wall", no_argument, NULL, ARG_NO_WALL }, { "quiet", no_argument, NULL, 'q' }, { "order", no_argument, NULL, ARG_ORDER }, @@ -4333,7 +4430,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: @@ -4348,6 +4449,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_no_block = true; break; + case ARG_NO_PAGER: + arg_no_pager = true; + break; + case ARG_NO_WALL: arg_no_wall = true; break; @@ -4364,6 +4469,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; @@ -4559,6 +4668,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 { @@ -4596,7 +4717,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': @@ -4789,7 +4913,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; @@ -5274,6 +5401,85 @@ 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) { + siginfo_t dummy; + + if (pager_pid <= 0) + return; + + /* Inform pager that we are done */ + fclose(stdout); + wait_for_terminate(pager_pid, &dummy); + pager_pid = 0; +} + int main(int argc, char*argv[]) { int r, retval = EXIT_FAILURE; DBusConnection *bus = NULL; @@ -5310,6 +5516,7 @@ int main(int argc, char*argv[]) { case ACTION_HALT: case ACTION_POWEROFF: case ACTION_REBOOT: + case ACTION_KEXEC: r = halt_main(bus); break; @@ -5354,5 +5561,7 @@ finish: strv_free(arg_property); + pager_close(); + return retval; }