chiark / gitweb /
loginctl: add basic implementation of loginctl for introspecting controlling sessions...
[elogind.git] / src / loginctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <dbus/dbus.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <getopt.h>
27
28 #include "log.h"
29 #include "util.h"
30 #include "macro.h"
31 #include "pager.h"
32 #include "dbus-common.h"
33 #include "build.h"
34
35 static bool arg_no_pager = false;
36 static enum transport {
37         TRANSPORT_NORMAL,
38         TRANSPORT_SSH,
39         TRANSPORT_POLKIT
40 } arg_transport = TRANSPORT_NORMAL;
41 static const char *arg_host = NULL;
42
43 static bool on_tty(void) {
44         static int t = -1;
45
46         /* Note that this is invoked relatively early, before we start
47          * the pager. That means the value we return reflects whether
48          * we originally were started on a tty, not if we currently
49          * are. But this is intended, since we want colour and so on
50          * when run in our own pager. */
51
52         if (_unlikely_(t < 0))
53                 t = isatty(STDOUT_FILENO) > 0;
54
55         return t;
56 }
57
58 static void pager_open_if_enabled(void) {
59         on_tty();
60
61         if (!arg_no_pager)
62                 pager_open();
63 }
64
65 static int list_sessions(DBusConnection *bus, char **args, unsigned n) {
66         DBusMessage *m = NULL, *reply = NULL;
67         DBusError error;
68         int r;
69         DBusMessageIter iter, sub, sub2;
70         unsigned k = 0;
71
72         dbus_error_init(&error);
73
74         assert(bus);
75
76         pager_open_if_enabled();
77
78         m = dbus_message_new_method_call(
79                         "org.freedesktop.login1",
80                         "/org/freedesktop/login1",
81                         "org.freedesktop.login1.Manager",
82                         "ListSessions");
83         if (!m) {
84                 log_error("Could not allocate message.");
85                 return -ENOMEM;
86         }
87
88         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
89         if (!reply) {
90                 log_error("Failed to issue method call: %s", bus_error_message(&error));
91                 r = -EIO;
92                 goto finish;
93         }
94
95         if (!dbus_message_iter_init(reply, &iter) ||
96             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
97             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
98                 log_error("Failed to parse reply.");
99                 r = -EIO;
100                 goto finish;
101         }
102
103         dbus_message_iter_recurse(&iter, &sub);
104
105         if (on_tty())
106                 printf("%10s %10s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT");
107
108         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
109                 const char *id, *user, *seat, *object;
110                 uint32_t uid;
111
112                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
113                         log_error("Failed to parse reply.");
114                         r = -EIO;
115                         goto finish;
116                 }
117
118                 dbus_message_iter_recurse(&sub, &sub2);
119
120                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 ||
121                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
122                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
123                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
124                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
125                         log_error("Failed to parse reply.");
126                         r = -EIO;
127                         goto finish;
128                 }
129
130                 printf("%10s %10u %-16s %-16s\n", id, (unsigned) uid, user, seat);
131
132                 k++;
133
134                 dbus_message_iter_next(&sub);
135         }
136
137         if (on_tty())
138                 printf("\n%u sessions listed.\n", k);
139
140         r = 0;
141
142 finish:
143         if (m)
144                 dbus_message_unref(m);
145
146         if (reply)
147                 dbus_message_unref(reply);
148
149         dbus_error_free(&error);
150
151         return r;
152 }
153
154 static int list_users(DBusConnection *bus, char **args, unsigned n) {
155         DBusMessage *m = NULL, *reply = NULL;
156         DBusError error;
157         int r;
158         DBusMessageIter iter, sub, sub2;
159         unsigned k = 0;
160
161         dbus_error_init(&error);
162
163         assert(bus);
164
165         pager_open_if_enabled();
166
167         m = dbus_message_new_method_call(
168                         "org.freedesktop.login1",
169                         "/org/freedesktop/login1",
170                         "org.freedesktop.login1.Manager",
171                         "ListUsers");
172         if (!m) {
173                 log_error("Could not allocate message.");
174                 return -ENOMEM;
175         }
176
177         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
178         if (!reply) {
179                 log_error("Failed to issue method call: %s", bus_error_message(&error));
180                 r = -EIO;
181                 goto finish;
182         }
183
184         if (!dbus_message_iter_init(reply, &iter) ||
185             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
186             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
187                 log_error("Failed to parse reply.");
188                 r = -EIO;
189                 goto finish;
190         }
191
192         dbus_message_iter_recurse(&iter, &sub);
193
194         if (on_tty())
195                 printf("%10s %-16s\n", "UID", "USER");
196
197         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
198                 const char *user, *object;
199                 uint32_t uid;
200
201                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
202                         log_error("Failed to parse reply.");
203                         r = -EIO;
204                         goto finish;
205                 }
206
207                 dbus_message_iter_recurse(&sub, &sub2);
208
209                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
210                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
211                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
212                         log_error("Failed to parse reply.");
213                         r = -EIO;
214                         goto finish;
215                 }
216
217                 printf("%10u %-16s\n", (unsigned) uid, user);
218
219                 k++;
220
221                 dbus_message_iter_next(&sub);
222         }
223
224         if (on_tty())
225                 printf("\n%u users listed.\n", k);
226
227         r = 0;
228
229 finish:
230         if (m)
231                 dbus_message_unref(m);
232
233         if (reply)
234                 dbus_message_unref(reply);
235
236         dbus_error_free(&error);
237
238         return r;
239 }
240
241 static int list_seats(DBusConnection *bus, char **args, unsigned n) {
242         DBusMessage *m = NULL, *reply = NULL;
243         DBusError error;
244         int r;
245         DBusMessageIter iter, sub, sub2;
246         unsigned k = 0;
247
248         dbus_error_init(&error);
249
250         assert(bus);
251
252         pager_open_if_enabled();
253
254         m = dbus_message_new_method_call(
255                         "org.freedesktop.login1",
256                         "/org/freedesktop/login1",
257                         "org.freedesktop.login1.Manager",
258                         "ListSeats");
259         if (!m) {
260                 log_error("Could not allocate message.");
261                 return -ENOMEM;
262         }
263
264         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
265         if (!reply) {
266                 log_error("Failed to issue method call: %s", bus_error_message(&error));
267                 r = -EIO;
268                 goto finish;
269         }
270
271         if (!dbus_message_iter_init(reply, &iter) ||
272             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
273             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
274                 log_error("Failed to parse reply.");
275                 r = -EIO;
276                 goto finish;
277         }
278
279         dbus_message_iter_recurse(&iter, &sub);
280
281         if (on_tty())
282                 printf("%-16s\n", "SEAT");
283
284         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
285                 const char *seat, *object;
286
287                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
288                         log_error("Failed to parse reply.");
289                         r = -EIO;
290                         goto finish;
291                 }
292
293                 dbus_message_iter_recurse(&sub, &sub2);
294
295                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
296                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
297                         log_error("Failed to parse reply.");
298                         r = -EIO;
299                         goto finish;
300                 }
301
302                 printf("%-16s\n", seat);
303
304                 k++;
305
306                 dbus_message_iter_next(&sub);
307         }
308
309         if (on_tty())
310                 printf("\n%u seats listed.\n", k);
311
312         r = 0;
313
314 finish:
315         if (m)
316                 dbus_message_unref(m);
317
318         if (reply)
319                 dbus_message_unref(reply);
320
321         dbus_error_free(&error);
322
323         return r;
324 }
325
326 static int help(void) {
327
328         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
329                "Send control commands to or query the login manager.\n\n"
330                "  -h --help           Show this help\n"
331                "     --version        Show package version\n"
332                "  -H --host=[user@]host\n"
333                "                      Show information for remote host\n"
334                "  -P --privileged     Acquire privileges before execution\n"
335                "     --no-pager       Do not pipe output into a pager.\n"
336                "Commands:\n"
337                "  list-sessions                   List sessions\n"
338                "  list-users                      List users\n"
339                "  list-seats                      List seats\n",
340                program_invocation_short_name);
341
342         return 0;
343 }
344
345 static int parse_argv(int argc, char *argv[]) {
346
347         enum {
348                 ARG_VERSION = 0x100,
349                 ARG_NO_PAGER
350         };
351
352         static const struct option options[] = {
353                 { "help",      no_argument,       NULL, 'h'           },
354                 { "version",   no_argument,       NULL, ARG_VERSION   },
355                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
356                 { "host",      required_argument, NULL, 'H'           },
357                 { "privileged",no_argument,       NULL, 'P'           },
358                 { NULL,        0,                 NULL, 0             }
359         };
360
361         int c;
362
363         assert(argc >= 0);
364         assert(argv);
365
366         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
367
368                 switch (c) {
369
370                 case 'h':
371                         help();
372                         return 0;
373
374                 case ARG_VERSION:
375                         puts(PACKAGE_STRING);
376                         puts(DISTRIBUTION);
377                         puts(SYSTEMD_FEATURES);
378                         return 0;
379
380                 case ARG_NO_PAGER:
381                         arg_no_pager = true;
382                         break;
383
384                 case 'P':
385                         arg_transport = TRANSPORT_POLKIT;
386                         break;
387
388                 case 'H':
389                         arg_transport = TRANSPORT_SSH;
390                         arg_host = optarg;
391                         break;
392
393                 case '?':
394                         return -EINVAL;
395
396                 default:
397                         log_error("Unknown option code %c", c);
398                         return -EINVAL;
399                 }
400         }
401
402         return 1;
403 }
404
405 static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
406
407         static const struct {
408                 const char* verb;
409                 const enum {
410                         MORE,
411                         LESS,
412                         EQUAL
413                 } argc_cmp;
414                 const int argc;
415                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
416         } verbs[] = {
417                 { "list-sessions",         LESS,   1, list_sessions    },
418                 { "list-users",            EQUAL,  1, list_users       },
419                 { "list-seats",            EQUAL,  1, list_seats       },
420         };
421
422         int left;
423         unsigned i;
424
425         assert(argc >= 0);
426         assert(argv);
427         assert(error);
428
429         left = argc - optind;
430
431         if (left <= 0)
432                 /* Special rule: no arguments means "list-sessions" */
433                 i = 0;
434         else {
435                 if (streq(argv[optind], "help")) {
436                         help();
437                         return 0;
438                 }
439
440                 for (i = 0; i < ELEMENTSOF(verbs); i++)
441                         if (streq(argv[optind], verbs[i].verb))
442                                 break;
443
444                 if (i >= ELEMENTSOF(verbs)) {
445                         log_error("Unknown operation %s", argv[optind]);
446                         return -EINVAL;
447                 }
448         }
449
450         switch (verbs[i].argc_cmp) {
451
452         case EQUAL:
453                 if (left != verbs[i].argc) {
454                         log_error("Invalid number of arguments.");
455                         return -EINVAL;
456                 }
457
458                 break;
459
460         case MORE:
461                 if (left < verbs[i].argc) {
462                         log_error("Too few arguments.");
463                         return -EINVAL;
464                 }
465
466                 break;
467
468         case LESS:
469                 if (left > verbs[i].argc) {
470                         log_error("Too many arguments.");
471                         return -EINVAL;
472                 }
473
474                 break;
475
476         default:
477                 assert_not_reached("Unknown comparison operator.");
478         }
479
480         if (!bus) {
481                 log_error("Failed to get D-Bus connection: %s", error->message);
482                 return -EIO;
483         }
484
485         return verbs[i].dispatch(bus, argv + optind, left);
486 }
487
488 int main(int argc, char*argv[]) {
489         int r, retval = EXIT_FAILURE;
490         DBusConnection *bus = NULL;
491         DBusError error;
492
493         dbus_error_init(&error);
494
495         log_parse_environment();
496         log_open();
497
498         r = parse_argv(argc, argv);
499         if (r < 0)
500                 goto finish;
501         else if (r == 0) {
502                 retval = EXIT_SUCCESS;
503                 goto finish;
504         }
505
506         if (arg_transport == TRANSPORT_NORMAL)
507                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
508         else if (arg_transport == TRANSPORT_POLKIT)
509                 bus_connect_system_polkit(&bus, &error);
510         else if (arg_transport == TRANSPORT_SSH)
511                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
512         else
513                 assert_not_reached("Uh, invalid transport...");
514
515         r = loginctl_main(bus, argc, argv, &error);
516         retval = r < 0 ? EXIT_FAILURE : r;
517
518 finish:
519         if (bus) {
520                 dbus_connection_flush(bus);
521                 dbus_connection_close(bus);
522                 dbus_connection_unref(bus);
523         }
524
525         dbus_error_free(&error);
526         dbus_shutdown();
527
528         pager_close();
529
530         return retval;
531 }