X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=src%2Floginctl.c;h=89762b66b01cf8f9592ba417313d4ec6eaccc2fe;hb=75d3fc60f88e08bf953063819a8a04b881d6db23;hp=022f69e7eb909568f7afcbf0e9e4c3213d09bfee;hpb=abca4822916b85ae5b0b2bef5d458ea2225d25ab;p=elogind.git diff --git a/src/loginctl.c b/src/loginctl.c index 022f69e7e..89762b66b 100644 --- a/src/loginctl.c +++ b/src/loginctl.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "log.h" #include "util.h" @@ -31,8 +32,15 @@ #include "pager.h" #include "dbus-common.h" #include "build.h" +#include "strv.h" +#include "cgroup-show.h" +#include "sysfs-show.h" +static char **arg_property = NULL; +static bool arg_all = false; static bool arg_no_pager = false; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; static enum transport { TRANSPORT_NORMAL, TRANSPORT_SSH, @@ -56,6 +64,8 @@ static bool on_tty(void) { } static void pager_open_if_enabled(void) { + + /* Cache result before we open the pager */ on_tty(); if (!arg_no_pager) @@ -323,207 +333,1580 @@ finish: return r; } -static int help(void) { +typedef struct SessionStatusInfo { + const char *id; + uid_t uid; + const char *name; + usec_t timestamp; + const char *control_group; + int vtnr; + const char *seat; + const char *tty; + const char *display; + bool remote; + const char *remote_host; + const char *remote_user; + const char *service; + pid_t leader; + const char *type; + bool active; +} SessionStatusInfo; + +typedef struct UserStatusInfo { + uid_t uid; + const char *name; + usec_t timestamp; + const char *control_group; + const char *state; + char **sessions; + const char *display; +} UserStatusInfo; + +typedef struct SeatStatusInfo { + const char *id; + const char *active_session; + char **sessions; +} SeatStatusInfo; + +static void print_session_status_info(SessionStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + assert(i); + + printf("%s - ", strna(i->id)); + + if (i->name) + printf("%s (%u)\n", i->name, (unsigned) i->uid); + else + printf("%u\n", (unsigned) i->uid); - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Send control commands to or query the login manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -H --host=[user@]host\n" - " Show information for remote host\n" - " -P --privileged Acquire privileges before execution\n" - " --no-pager Do not pipe output into a pager.\n" - "Commands:\n" - " list-sessions List sessions\n" - " list-users List users\n" - " list-seats List seats\n", - program_invocation_short_name); + s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp); - return 0; -} + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); -static int parse_argv(int argc, char *argv[]) { + if (i->leader > 0) { + char *t = NULL; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER - }; + printf("\t Leader: %u", (unsigned) i->leader); - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "host", required_argument, NULL, 'H' }, - { "privileged",no_argument, NULL, 'P' }, - { NULL, 0, NULL, 0 } - }; + get_process_name(i->leader, &t); + if (t) { + printf(" (%s)", t); + free(t); + } - int c; + printf("\n"); + } - assert(argc >= 0); - assert(argv); + if (i->seat) { + printf("\t Seat: %s", i->seat); - while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) { + if (i->vtnr > 0) + printf("; vc%i", i->vtnr); - switch (c) { + printf("\n"); + } - case 'h': - help(); - return 0; + if (i->tty) + printf("\t TTY: %s\n", i->tty); + else if (i->display) + printf("\t Display: %s\n", i->display); - case ARG_VERSION: - puts(PACKAGE_STRING); - puts(DISTRIBUTION); - puts(SYSTEMD_FEATURES); - return 0; + if (i->remote_host && i->remote_user) + printf("\t Remote: %s@%s\n", i->remote_user, i->remote_host); + else if (i->remote_host) + printf("\t Remote: %s\n", i->remote_host); + else if (i->remote_user) + printf("\t Remote: user %s\n", i->remote_user); + else if (i->remote) + printf("\t Remote: Yes\n"); - case ARG_NO_PAGER: - arg_no_pager = true; - break; + if (i->service) { + printf("\t Service: %s", i->service); - case 'P': - arg_transport = TRANSPORT_POLKIT; - break; + if (i->type) + printf("; type %s", i->type); - case 'H': - arg_transport = TRANSPORT_SSH; - arg_host = optarg; - break; + printf("\n"); + } else if (i->type) + printf("\t Type: %s\n", i->type); - case '?': - return -EINVAL; + printf("\t Active: %s\n", yes_no(i->active)); - default: - log_error("Unknown option code %c", c); - return -EINVAL; + if (i->control_group) { + unsigned c; + + printf("\t CGroup: %s\n", i->control_group); + + if (arg_transport != TRANSPORT_SSH) { + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_by_path(i->control_group, "\t\t ", c); } } - - return 1; } -static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) { +static void print_user_status_info(UserStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + assert(i); - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(DBusConnection *bus, char **args, unsigned n); - } verbs[] = { - { "list-sessions", LESS, 1, list_sessions }, - { "list-users", EQUAL, 1, list_users }, - { "list-seats", EQUAL, 1, list_seats }, - }; + if (i->name) + printf("%s (%u)\n", i->name, (unsigned) i->uid); + else + printf("%u\n", (unsigned) i->uid); - int left; - unsigned i; + s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp); - assert(argc >= 0); - assert(argv); - assert(error); + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); - left = argc - optind; + if (!isempty(i->state)) + printf("\t State: %s\n", i->state); - if (left <= 0) - /* Special rule: no arguments means "list-sessions" */ - i = 0; - else { - if (streq(argv[optind], "help")) { - help(); - return 0; + if (!strv_isempty(i->sessions)) { + char **l; + printf("\tSessions:"); + + STRV_FOREACH(l, i->sessions) { + if (streq_ptr(*l, i->display)) + printf(" *%s", *l); + else + printf(" %s", *l); } - for (i = 0; i < ELEMENTSOF(verbs); i++) - if (streq(argv[optind], verbs[i].verb)) - break; + printf("\n"); + } - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation %s", argv[optind]); - return -EINVAL; + if (i->control_group) { + unsigned c; + + printf("\t CGroup: %s\n", i->control_group); + + if (arg_transport != TRANSPORT_SSH) { + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_by_path(i->control_group, "\t\t ", c); } } +} - switch (verbs[i].argc_cmp) { +static void print_seat_status_info(SeatStatusInfo *i) { + assert(i); - case EQUAL: - if (left != verbs[i].argc) { - log_error("Invalid number of arguments."); - return -EINVAL; + printf("%s\n", strna(i->id)); + + if (!strv_isempty(i->sessions)) { + char **l; + printf("\tSessions:"); + + STRV_FOREACH(l, i->sessions) { + if (streq_ptr(*l, i->active_session)) + printf(" *%s", *l); + else + printf(" %s", *l); } + printf("\n"); + } + + if (arg_transport != TRANSPORT_SSH) { + unsigned c; + + c = columns(); + if (c > 21) + c -= 21; + else + c = 0; + + printf("\t Devices:\n"); + + show_sysfs(i->id, "\t\t ", c); + } +} + +static int status_property_session(const char *name, DBusMessageIter *iter, SessionStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + else if (streq(name, "Name")) + i->name = s; + else if (streq(name, "ControlGroupPath")) + i->control_group = s; + else if (streq(name, "TTY")) + i->tty = s; + else if (streq(name, "Display")) + i->display = s; + else if (streq(name, "RemoteHost")) + i->remote_host = s; + else if (streq(name, "RemoteUser")) + i->remote_user = s; + else if (streq(name, "Service")) + i->service = s; + else if (streq(name, "Type")) + i->type = s; + } break; + } - case MORE: - if (left < verbs[i].argc) { - log_error("Too few arguments."); - return -EINVAL; + case DBUS_TYPE_UINT32: { + uint32_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "VTNr")) + i->vtnr = (int) u; + else if (streq(name, "Leader")) + i->leader = (pid_t) u; + + break; + } + + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t b; + + dbus_message_iter_get_basic(iter, &b); + + if (streq(name, "Remote")) + i->remote = b; + else if (streq(name, "Active")) + i->active = b; + + break; + } + + case DBUS_TYPE_UINT64: { + uint64_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "Timestamp")) + i->timestamp = (usec_t) u; + + break; + } + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "User")) { + uint32_t u; + + dbus_message_iter_get_basic(&sub, &u); + i->uid = (uid_t) u; + + } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Seat")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (!isempty(s)) + i->seat = s; } break; + } + } - case LESS: - if (left > verbs[i].argc) { - log_error("Too many arguments."); - return -EINVAL; + return 0; +} + +static int status_property_user(const char *name, DBusMessageIter *iter, UserStatusInfo *i) { + assert(name); + assert(iter); + assert(i); + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRING: { + const char *s; + + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Name")) + i->name = s; + else if (streq(name, "ControlGroupPath")) + i->control_group = s; + else if (streq(name, "State")) + i->state = s; } + break; + } + + case DBUS_TYPE_UINT32: { + uint32_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "UID")) + i->uid = (uid_t) u; break; + } - default: - assert_not_reached("Unknown comparison operator."); + case DBUS_TYPE_UINT64: { + uint64_t u; + + dbus_message_iter_get_basic(iter, &u); + + if (streq(name, "Timestamp")) + i->timestamp = (usec_t) u; + + break; } - if (!bus) { - log_error("Failed to get D-Bus connection: %s", error->message); - return -EIO; + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Display")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (!isempty(s)) + i->display = s; + } + + break; } - return verbs[i].dispatch(bus, argv + optind, left); + case DBUS_TYPE_ARRAY: { + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *id; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) { + char **l; + + l = strv_append(i->sessions, id); + if (!l) + return -ENOMEM; + + strv_free(i->sessions); + i->sessions = l; + } + + dbus_message_iter_next(&sub); + } + + return 0; + } + } + } + + return 0; } -int main(int argc, char*argv[]) { - int r, retval = EXIT_FAILURE; - DBusConnection *bus = NULL; - DBusError error; +static int status_property_seat(const char *name, DBusMessageIter *iter, SeatStatusInfo *i) { + assert(name); + assert(iter); + assert(i); - dbus_error_init(&error); + switch (dbus_message_iter_get_arg_type(iter)) { - log_parse_environment(); - log_open(); + case DBUS_TYPE_STRING: { + const char *s; - r = parse_argv(argc, argv); - if (r < 0) - goto finish; - else if (r == 0) { - retval = EXIT_SUCCESS; - goto finish; + dbus_message_iter_get_basic(iter, &s); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + } + break; } - if (arg_transport == TRANSPORT_NORMAL) - bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); - else if (arg_transport == TRANSPORT_POLKIT) - bus_connect_system_polkit(&bus, &error); - else if (arg_transport == TRANSPORT_SSH) - bus_connect_system_ssh(NULL, arg_host, &bus, &error); - else - assert_not_reached("Uh, invalid transport..."); + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; - r = loginctl_main(bus, argc, argv, &error); - retval = r < 0 ? EXIT_FAILURE : r; + dbus_message_iter_recurse(iter, &sub); -finish: - if (bus) { - dbus_connection_flush(bus); - dbus_connection_close(bus); - dbus_connection_unref(bus); + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "ActiveSession")) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (!isempty(s)) + i->active_session = s; + } + + break; } - dbus_error_free(&error); - dbus_shutdown(); + case DBUS_TYPE_ARRAY: { + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) { + DBusMessageIter sub, sub2; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *id; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) { + char **l; + + l = strv_append(i->sessions, id); + if (!l) + return -ENOMEM; + + strv_free(i->sessions); + i->sessions = l; + } + + dbus_message_iter_next(&sub); + } + + return 0; + } + } + } + + return 0; +} + +static int print_property(const char *name, DBusMessageIter *iter) { + assert(name); + assert(iter); + + if (arg_property && !strv_find(arg_property, name)) + return 0; + + switch (dbus_message_iter_get_arg_type(iter)) { + + case DBUS_TYPE_STRUCT: { + DBusMessageIter sub; + + dbus_message_iter_recurse(iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && + (streq(name, "Display") || streq(name, "ActiveSession"))) { + const char *s; + + dbus_message_iter_get_basic(&sub, &s); + + if (arg_all || !isempty(s)) + printf("%s=%s\n", name, s); + return 0; + } + break; + } + + case DBUS_TYPE_ARRAY: + + if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) { + DBusMessageIter sub, sub2; + bool found = false; + + dbus_message_iter_recurse(iter, &sub); + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) { + const char *id; + const char *path; + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 && + bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) { + if (found) + printf(" %s", id); + else { + printf("%s=%s", name, id); + found = true; + } + } + + dbus_message_iter_next(&sub); + } + + if (!found && arg_all) + printf("%s=\n", name); + else if (found) + printf("\n"); + + return 0; + } + + break; + } + + if (generic_print_property(name, iter, arg_all) > 0) + return 0; + + if (arg_all) + printf("%s=[unprintable]\n", name); + + return 0; +} + +static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) { + DBusMessage *m = NULL, *reply = NULL; + const char *interface = ""; + int r; + DBusError error; + DBusMessageIter iter, sub, sub2, sub3; + SessionStatusInfo session_info; + UserStatusInfo user_info; + SeatStatusInfo seat_info; + + assert(bus); + assert(path); + assert(new_line); + + zero(session_info); + zero(user_info); + zero(seat_info); + + dbus_error_init(&error); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + path, + "org.freedesktop.DBus.Properties", + "GetAll"); + if (!m) { + log_error("Could not allocate message."); + r = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + r = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + r = -EIO; + goto finish; + } + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (*new_line) + printf("\n"); + + *new_line = true; + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + const char *name; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub, &sub2); + + if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_recurse(&sub2, &sub3); + + if (show_properties) + r = print_property(name, &sub3); + else if (strstr(verb, "session")) + r = status_property_session(name, &sub3, &session_info); + else if (strstr(verb, "user")) + r = status_property_user(name, &sub3, &user_info); + else + r = status_property_seat(name, &sub3, &seat_info); + + if (r < 0) { + log_error("Failed to parse reply."); + r = -EIO; + goto finish; + } + + dbus_message_iter_next(&sub); + } + + if (!show_properties) { + if (strstr(verb, "session")) + print_session_status_info(&session_info); + else if (strstr(verb, "user")) + print_user_status_info(&user_info); + else + print_seat_status_info(&seat_info); + } + + strv_free(seat_info.sessions); + strv_free(user_info.sessions); + + r = 0; + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return r; +} + +static int show(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + int r, ret = 0; + DBusError error; + unsigned i; + bool show_properties, new_line = false; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + show_properties = !strstr(args[0], "status"); + + if (show_properties) + pager_open_if_enabled(); + + if (show_properties && n <= 1) { + /* If not argument is specified inspect the manager + * itself */ + + ret = show_one(args[0], bus, "/org/freedesktop/login1", show_properties, &new_line); + goto finish; + } + + for (i = 1; i < n; i++) { + const char *path = NULL; + + if (strstr(args[0], "session")) { + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetSession"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + } else if (strstr(args[0], "user")) { + uid_t uid; + uint32_t u; + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("User %s unknown.", args[i]); + goto finish; + } + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetUser"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + } else { + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "GetSeat"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + if (!dbus_message_get_args(reply, &error, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse reply: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + r = show_one(args[0], bus, path, show_properties, &new_line); + if (r != 0) + ret = r; + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +static int activate(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 1; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + streq(args[0], "lock-session") ? "LockSession" : + streq(args[0], "unlock-session") ? "UnlockSession" : + streq(args[0], "terminate-session") ? "TerminateSession" : + "ActivateSession"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int kill_session(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "KillSession"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_STRING, &arg_kill_who, + DBUS_TYPE_INT32, arg_signal, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int enable_linger(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + dbus_bool_t b, interactive = true; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + b = streq(args[0], "enable-linger"); + + for (i = 1; i < n; i++) { + DBusMessage *reply; + uint32_t u; + uid_t uid; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetUserLinger"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("Failed to resolve user %s: %s", args[i], strerror(-ret)); + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_BOOLEAN, &b, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int terminate_user(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 1; i < n; i++) { + uint32_t u; + uid_t uid; + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "TerminateUser"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("Failed to look up user %s: %s", args[i], strerror(-ret)); + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int kill_user(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < n; i++) { + DBusMessage *reply; + uid_t uid; + uint32_t u; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "KillUser"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL); + if (ret < 0) { + log_error("Failed to look up user %s: %s", args[i], strerror(-ret)); + goto finish; + } + + u = (uint32_t) uid; + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT32, &u, + DBUS_TYPE_INT32, arg_signal, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int attach(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + dbus_bool_t interactive = true; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 2; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "AttachDevice"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[1], + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int flush_devices(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL, *reply = NULL; + int ret = 0; + DBusError error; + dbus_bool_t interactive = true; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "FlushDevices"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_BOOLEAN, &interactive, + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +static int terminate_seat(DBusConnection *bus, char **args, unsigned n) { + DBusMessage *m = NULL; + int ret = 0; + DBusError error; + unsigned i; + + assert(bus); + assert(args); + + dbus_error_init(&error); + + for (i = 1; i < n; i++) { + DBusMessage *reply; + + m = dbus_message_new_method_call( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "TerminateSeat"); + if (!m) { + log_error("Could not allocate message."); + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &args[i], + DBUS_TYPE_INVALID)) { + log_error("Could not append arguments to message."); + ret = -ENOMEM; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); + if (!reply) { + log_error("Failed to issue method call: %s", bus_error_message(&error)); + ret = -EIO; + goto finish; + } + + dbus_message_unref(m); + dbus_message_unref(reply); + m = reply = NULL; + } + +finish: + if (m) + dbus_message_unref(m); + + dbus_error_free(&error); + + return ret; +} + +static int help(void) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the login manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all properties, including empty ones\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" + " --no-pager Do not pipe output into a pager\n\n" + "Commands:\n" + " list-sessions List sessions\n" + " session-status [ID...] Show session status\n" + " show-session [ID...] Show properties of one or more sessions\n" + " activate [ID] Activate a session\n" + " lock-session [ID...] Screen lock one or more sessions\n" + " unlock-session [ID...] Screen unlock one or more sessions\n" + " terminate-session [ID...] Terminate one or more sessions\n" + " kill-session [ID...] Send signal to processes of a session\n" + " list-users List users\n" + " user-status [USER...] Show user status\n" + " show-user [USER...] Show properties of one or more users\n" + " enable-linger [USER...] Enable linger state of one or more users\n" + " disable-linger [USER...] Disable linger state of one or more users\n" + " terminate-user [USER...] Terminate all sessions of one or more users\n" + " kill-user [USER...] Send signal to processes of a user\n" + " list-seats List seats\n" + " seat-status [NAME...] Show seat status\n" + " show-seat [NAME...] Show properties of one or more seats\n" + " attach [NAME] [DEVICE...] Attach one or more devices to a seat\n" + " flush-devices Flush all device associations\n" + " terminate-seat [NAME...] Terminate all sessions on one or more seats\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_KILL_WHO + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "privileged",no_argument, NULL, 'P' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp:as:H:P", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(DISTRIBUTION); + puts(SYSTEMD_FEATURES); + return 0; + + case 'p': { + char **l; + + l = strv_append(arg_property, optarg); + if (!l) + return -ENOMEM; + + strv_free(arg_property); + arg_property = l; + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + } + + case 'a': + arg_all = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case 'P': + arg_transport = TRANSPORT_POLKIT; + break; + + case 'H': + arg_transport = TRANSPORT_SSH; + arg_host = optarg; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + } + + return 1; +} + +static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(DBusConnection *bus, char **args, unsigned n); + } verbs[] = { + { "list-sessions", LESS, 1, list_sessions }, + { "session-status", MORE, 2, show }, + { "show-session", MORE, 1, show }, + { "activate", EQUAL, 2, activate }, + { "lock-session", MORE, 2, activate }, + { "unlock-session", MORE, 2, activate }, + { "terminate-session", MORE, 2, activate }, + { "kill-session", MORE, 2, kill_session }, + { "list-users", EQUAL, 1, list_users }, + { "user-status", MORE, 2, show }, + { "show-user", MORE, 1, show }, + { "enable-linger", MORE, 2, enable_linger }, + { "disable-linger", MORE, 2, enable_linger }, + { "terminate-user", MORE, 2, terminate_user }, + { "kill-user", MORE, 2, kill_user }, + { "list-seats", EQUAL, 1, list_seats }, + { "seat-status", MORE, 2, show }, + { "show-seat", MORE, 1, show }, + { "attach", MORE, 3, attach }, + { "flush-devices", EQUAL, 1, flush_devices }, + { "terminate-seat", MORE, 2, terminate_seat }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + assert(error); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "list-sessions" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + if (!bus) { + log_error("Failed to get D-Bus connection: %s", error->message); + return -EIO; + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +int main(int argc, char*argv[]) { + int r, retval = EXIT_FAILURE; + DBusConnection *bus = NULL; + DBusError error; + + dbus_error_init(&error); + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + goto finish; + else if (r == 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (arg_transport == TRANSPORT_NORMAL) + bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + else if (arg_transport == TRANSPORT_POLKIT) + bus_connect_system_polkit(&bus, &error); + else if (arg_transport == TRANSPORT_SSH) + bus_connect_system_ssh(NULL, arg_host, &bus, &error); + else + assert_not_reached("Uh, invalid transport..."); + + r = loginctl_main(bus, argc, argv, &error); + retval = r < 0 ? EXIT_FAILURE : r; + +finish: + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + + dbus_error_free(&error); + dbus_shutdown(); + + strv_free(arg_property); pager_close();