]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/loginctl.c
loginctl: add various introspection functions
[thirdparty/systemd.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 #include <pwd.h>
28
29 #include "log.h"
30 #include "util.h"
31 #include "macro.h"
32 #include "pager.h"
33 #include "dbus-common.h"
34 #include "build.h"
35 #include "strv.h"
36 #include "cgroup-show.h"
37 #include "sysfs-show.h"
38
39 static char **arg_property = NULL;
40 static bool arg_all = false;
41 static bool arg_no_pager = false;
42 static const char *arg_kill_who = NULL;
43 static int arg_signal = SIGTERM;
44 static enum transport {
45 TRANSPORT_NORMAL,
46 TRANSPORT_SSH,
47 TRANSPORT_POLKIT
48 } arg_transport = TRANSPORT_NORMAL;
49 static const char *arg_host = NULL;
50
51 static bool on_tty(void) {
52 static int t = -1;
53
54 /* Note that this is invoked relatively early, before we start
55 * the pager. That means the value we return reflects whether
56 * we originally were started on a tty, not if we currently
57 * are. But this is intended, since we want colour and so on
58 * when run in our own pager. */
59
60 if (_unlikely_(t < 0))
61 t = isatty(STDOUT_FILENO) > 0;
62
63 return t;
64 }
65
66 static void pager_open_if_enabled(void) {
67 on_tty();
68
69 if (!arg_no_pager)
70 pager_open();
71 }
72
73 static int list_sessions(DBusConnection *bus, char **args, unsigned n) {
74 DBusMessage *m = NULL, *reply = NULL;
75 DBusError error;
76 int r;
77 DBusMessageIter iter, sub, sub2;
78 unsigned k = 0;
79
80 dbus_error_init(&error);
81
82 assert(bus);
83
84 pager_open_if_enabled();
85
86 m = dbus_message_new_method_call(
87 "org.freedesktop.login1",
88 "/org/freedesktop/login1",
89 "org.freedesktop.login1.Manager",
90 "ListSessions");
91 if (!m) {
92 log_error("Could not allocate message.");
93 return -ENOMEM;
94 }
95
96 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
97 if (!reply) {
98 log_error("Failed to issue method call: %s", bus_error_message(&error));
99 r = -EIO;
100 goto finish;
101 }
102
103 if (!dbus_message_iter_init(reply, &iter) ||
104 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
105 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
106 log_error("Failed to parse reply.");
107 r = -EIO;
108 goto finish;
109 }
110
111 dbus_message_iter_recurse(&iter, &sub);
112
113 if (on_tty())
114 printf("%10s %10s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT");
115
116 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
117 const char *id, *user, *seat, *object;
118 uint32_t uid;
119
120 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
121 log_error("Failed to parse reply.");
122 r = -EIO;
123 goto finish;
124 }
125
126 dbus_message_iter_recurse(&sub, &sub2);
127
128 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 ||
129 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
130 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
131 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
132 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
133 log_error("Failed to parse reply.");
134 r = -EIO;
135 goto finish;
136 }
137
138 printf("%10s %10u %-16s %-16s\n", id, (unsigned) uid, user, seat);
139
140 k++;
141
142 dbus_message_iter_next(&sub);
143 }
144
145 if (on_tty())
146 printf("\n%u sessions listed.\n", k);
147
148 r = 0;
149
150 finish:
151 if (m)
152 dbus_message_unref(m);
153
154 if (reply)
155 dbus_message_unref(reply);
156
157 dbus_error_free(&error);
158
159 return r;
160 }
161
162 static int list_users(DBusConnection *bus, char **args, unsigned n) {
163 DBusMessage *m = NULL, *reply = NULL;
164 DBusError error;
165 int r;
166 DBusMessageIter iter, sub, sub2;
167 unsigned k = 0;
168
169 dbus_error_init(&error);
170
171 assert(bus);
172
173 pager_open_if_enabled();
174
175 m = dbus_message_new_method_call(
176 "org.freedesktop.login1",
177 "/org/freedesktop/login1",
178 "org.freedesktop.login1.Manager",
179 "ListUsers");
180 if (!m) {
181 log_error("Could not allocate message.");
182 return -ENOMEM;
183 }
184
185 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
186 if (!reply) {
187 log_error("Failed to issue method call: %s", bus_error_message(&error));
188 r = -EIO;
189 goto finish;
190 }
191
192 if (!dbus_message_iter_init(reply, &iter) ||
193 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
194 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
195 log_error("Failed to parse reply.");
196 r = -EIO;
197 goto finish;
198 }
199
200 dbus_message_iter_recurse(&iter, &sub);
201
202 if (on_tty())
203 printf("%10s %-16s\n", "UID", "USER");
204
205 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
206 const char *user, *object;
207 uint32_t uid;
208
209 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
210 log_error("Failed to parse reply.");
211 r = -EIO;
212 goto finish;
213 }
214
215 dbus_message_iter_recurse(&sub, &sub2);
216
217 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
218 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
219 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
220 log_error("Failed to parse reply.");
221 r = -EIO;
222 goto finish;
223 }
224
225 printf("%10u %-16s\n", (unsigned) uid, user);
226
227 k++;
228
229 dbus_message_iter_next(&sub);
230 }
231
232 if (on_tty())
233 printf("\n%u users listed.\n", k);
234
235 r = 0;
236
237 finish:
238 if (m)
239 dbus_message_unref(m);
240
241 if (reply)
242 dbus_message_unref(reply);
243
244 dbus_error_free(&error);
245
246 return r;
247 }
248
249 static int list_seats(DBusConnection *bus, char **args, unsigned n) {
250 DBusMessage *m = NULL, *reply = NULL;
251 DBusError error;
252 int r;
253 DBusMessageIter iter, sub, sub2;
254 unsigned k = 0;
255
256 dbus_error_init(&error);
257
258 assert(bus);
259
260 pager_open_if_enabled();
261
262 m = dbus_message_new_method_call(
263 "org.freedesktop.login1",
264 "/org/freedesktop/login1",
265 "org.freedesktop.login1.Manager",
266 "ListSeats");
267 if (!m) {
268 log_error("Could not allocate message.");
269 return -ENOMEM;
270 }
271
272 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
273 if (!reply) {
274 log_error("Failed to issue method call: %s", bus_error_message(&error));
275 r = -EIO;
276 goto finish;
277 }
278
279 if (!dbus_message_iter_init(reply, &iter) ||
280 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
281 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
282 log_error("Failed to parse reply.");
283 r = -EIO;
284 goto finish;
285 }
286
287 dbus_message_iter_recurse(&iter, &sub);
288
289 if (on_tty())
290 printf("%-16s\n", "SEAT");
291
292 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
293 const char *seat, *object;
294
295 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
296 log_error("Failed to parse reply.");
297 r = -EIO;
298 goto finish;
299 }
300
301 dbus_message_iter_recurse(&sub, &sub2);
302
303 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
304 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
305 log_error("Failed to parse reply.");
306 r = -EIO;
307 goto finish;
308 }
309
310 printf("%-16s\n", seat);
311
312 k++;
313
314 dbus_message_iter_next(&sub);
315 }
316
317 if (on_tty())
318 printf("\n%u seats listed.\n", k);
319
320 r = 0;
321
322 finish:
323 if (m)
324 dbus_message_unref(m);
325
326 if (reply)
327 dbus_message_unref(reply);
328
329 dbus_error_free(&error);
330
331 return r;
332 }
333
334 typedef struct SessionStatusInfo {
335 const char *id;
336 uid_t uid;
337 const char *name;
338 usec_t timestamp;
339 const char *control_group;
340 int vtnr;
341 const char *seat;
342 const char *tty;
343 const char *display;
344 bool remote;
345 const char *remote_host;
346 const char *remote_user;
347 const char *service;
348 pid_t leader;
349 const char *type;
350 bool active;
351 } SessionStatusInfo;
352
353 typedef struct UserStatusInfo {
354 uid_t uid;
355 const char *name;
356 usec_t timestamp;
357 const char *control_group;
358 const char *state;
359 char **sessions;
360 const char *display;
361 } UserStatusInfo;
362
363 typedef struct SeatStatusInfo {
364 const char *id;
365 const char *active_session;
366 char **sessions;
367 } SeatStatusInfo;
368
369 static void print_session_status_info(SessionStatusInfo *i) {
370 char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1;
371 char since2[FORMAT_TIMESTAMP_MAX], *s2;
372 assert(i);
373
374 printf("%s - ", strna(i->id));
375
376 if (i->name)
377 printf("%s (%u)\n", i->name, (unsigned) i->uid);
378 else
379 printf("%u\n", (unsigned) i->uid);
380
381 s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp);
382 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
383
384 if (s1)
385 printf("\t Since: %s; %s\n", s2, s1);
386 else if (s2)
387 printf("\t Since: %s\n", s2);
388
389 if (i->leader > 0) {
390 char *t = NULL;
391
392 printf("\t Leader: %u", (unsigned) i->leader);
393
394 get_process_name(i->leader, &t);
395 if (t) {
396 printf(" (%s)", t);
397 free(t);
398 }
399
400 printf("\n");
401 }
402
403 if (i->seat) {
404 printf("\t Seat: %s", i->seat);
405
406 if (i->vtnr > 0)
407 printf("; vc%i", i->vtnr);
408
409 printf("\n");
410 }
411
412 if (i->tty)
413 printf("\t TTY: %s\n", i->tty);
414 else if (i->display)
415 printf("\t Display: %s\n", i->display);
416
417 if (i->remote_host && i->remote_user)
418 printf("\t Remote: %s@%s\n", i->remote_user, i->remote_host);
419 else if (i->remote_host)
420 printf("\t Remote: %s\n", i->remote_host);
421 else if (i->remote_user)
422 printf("\t Remote: user %s\n", i->remote_user);
423 else if (i->remote)
424 printf("\t Remote: Yes\n");
425
426 if (i->service) {
427 printf("\t Service: %s", i->service);
428
429 if (i->type)
430 printf("; type %s", i->type);
431
432 printf("\n");
433 } else if (i->type)
434 printf("\t Type: %s\n", i->type);
435
436 printf("\t Active: %s\n", yes_no(i->active));
437
438 if (i->control_group) {
439 unsigned c;
440
441 printf("\t CGroup: %s\n", i->control_group);
442
443 if (arg_transport != TRANSPORT_SSH) {
444 c = columns();
445 if (c > 18)
446 c -= 18;
447 else
448 c = 0;
449
450 show_cgroup_by_path(i->control_group, "\t\t ", c);
451 }
452 }
453 }
454
455 static void print_user_status_info(UserStatusInfo *i) {
456 char since1[FORMAT_TIMESTAMP_PRETTY_MAX], *s1;
457 char since2[FORMAT_TIMESTAMP_MAX], *s2;
458 assert(i);
459
460 if (i->name)
461 printf("%s (%u)\n", i->name, (unsigned) i->uid);
462 else
463 printf("%u\n", (unsigned) i->uid);
464
465 s1 = format_timestamp_pretty(since1, sizeof(since1), i->timestamp);
466 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
467
468 if (s1)
469 printf("\t Since: %s; %s\n", s2, s1);
470 else if (s2)
471 printf("\t Since: %s\n", s2);
472
473 if (!isempty(i->state))
474 printf("\t State: %s\n", i->state);
475
476 if (!strv_isempty(i->sessions)) {
477 char **l;
478 printf("\tSessions:");
479
480 STRV_FOREACH(l, i->sessions) {
481 if (streq_ptr(*l, i->display))
482 printf(" *%s", *l);
483 else
484 printf(" %s", *l);
485 }
486
487 printf("\n");
488 }
489
490 if (i->control_group) {
491 unsigned c;
492
493 printf("\t CGroup: %s\n", i->control_group);
494
495 if (arg_transport != TRANSPORT_SSH) {
496 c = columns();
497 if (c > 18)
498 c -= 18;
499 else
500 c = 0;
501
502 show_cgroup_by_path(i->control_group, "\t\t ", c);
503 }
504 }
505 }
506
507 static void print_seat_status_info(SeatStatusInfo *i) {
508 assert(i);
509
510 printf("%s\n", strna(i->id));
511
512 if (!strv_isempty(i->sessions)) {
513 char **l;
514 printf("\tSessions:");
515
516 STRV_FOREACH(l, i->sessions) {
517 if (streq_ptr(*l, i->active_session))
518 printf(" *%s", *l);
519 else
520 printf(" %s", *l);
521 }
522
523 printf("\n");
524 }
525
526 if (arg_transport != TRANSPORT_SSH) {
527 unsigned c;
528
529 c = columns();
530 if (c > 18)
531 c -= 18;
532 else
533 c = 0;
534
535 printf("\t Devices:\n");
536
537
538 show_sysfs(i->id, "\t\t ", c);
539 }
540 }
541
542 static int status_property_session(const char *name, DBusMessageIter *iter, SessionStatusInfo *i) {
543 assert(name);
544 assert(iter);
545 assert(i);
546
547 switch (dbus_message_iter_get_arg_type(iter)) {
548
549 case DBUS_TYPE_STRING: {
550 const char *s;
551
552 dbus_message_iter_get_basic(iter, &s);
553
554 if (!isempty(s)) {
555 if (streq(name, "Id"))
556 i->id = s;
557 else if (streq(name, "Name"))
558 i->name = s;
559 else if (streq(name, "ControlGroupPath"))
560 i->control_group = s;
561 else if (streq(name, "TTY"))
562 i->tty = s;
563 else if (streq(name, "Display"))
564 i->display = s;
565 else if (streq(name, "RemoteHost"))
566 i->remote_host = s;
567 else if (streq(name, "RemoteUser"))
568 i->remote_user = s;
569 else if (streq(name, "Service"))
570 i->service = s;
571 else if (streq(name, "Type"))
572 i->type = s;
573 }
574 break;
575 }
576
577 case DBUS_TYPE_UINT32: {
578 uint32_t u;
579
580 dbus_message_iter_get_basic(iter, &u);
581
582 if (streq(name, "VTNr"))
583 i->vtnr = (int) u;
584 else if (streq(name, "Leader"))
585 i->leader = (pid_t) u;
586
587 break;
588 }
589
590 case DBUS_TYPE_BOOLEAN: {
591 dbus_bool_t b;
592
593 dbus_message_iter_get_basic(iter, &b);
594
595 if (streq(name, "Remote"))
596 i->remote = b;
597 else if (streq(name, "Active"))
598 i->active = b;
599
600 break;
601 }
602
603 case DBUS_TYPE_UINT64: {
604 uint64_t u;
605
606 dbus_message_iter_get_basic(iter, &u);
607
608 if (streq(name, "Timestamp"))
609 i->timestamp = (usec_t) u;
610
611 break;
612 }
613
614 case DBUS_TYPE_STRUCT: {
615 DBusMessageIter sub;
616
617 dbus_message_iter_recurse(iter, &sub);
618
619 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "User")) {
620 uint32_t u;
621
622 dbus_message_iter_get_basic(&sub, &u);
623 i->uid = (uid_t) u;
624
625 } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Seat")) {
626 const char *s;
627
628 dbus_message_iter_get_basic(&sub, &s);
629
630 if (!isempty(s))
631 i->seat = s;
632 }
633
634 break;
635 }
636 }
637
638 return 0;
639 }
640
641 static int status_property_user(const char *name, DBusMessageIter *iter, UserStatusInfo *i) {
642 assert(name);
643 assert(iter);
644 assert(i);
645
646 switch (dbus_message_iter_get_arg_type(iter)) {
647
648 case DBUS_TYPE_STRING: {
649 const char *s;
650
651 dbus_message_iter_get_basic(iter, &s);
652
653 if (!isempty(s)) {
654 if (streq(name, "Name"))
655 i->name = s;
656 else if (streq(name, "ControlGroupPath"))
657 i->control_group = s;
658 else if (streq(name, "State"))
659 i->state = s;
660 }
661 break;
662 }
663
664 case DBUS_TYPE_UINT32: {
665 uint32_t u;
666
667 dbus_message_iter_get_basic(iter, &u);
668
669 if (streq(name, "UID"))
670 i->uid = (uid_t) u;
671
672 break;
673 }
674
675 case DBUS_TYPE_UINT64: {
676 uint64_t u;
677
678 dbus_message_iter_get_basic(iter, &u);
679
680 if (streq(name, "Timestamp"))
681 i->timestamp = (usec_t) u;
682
683 break;
684 }
685
686 case DBUS_TYPE_STRUCT: {
687 DBusMessageIter sub;
688
689 dbus_message_iter_recurse(iter, &sub);
690
691 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Display")) {
692 const char *s;
693
694 dbus_message_iter_get_basic(&sub, &s);
695
696 if (!isempty(s))
697 i->display = s;
698 }
699
700 break;
701 }
702
703 case DBUS_TYPE_ARRAY: {
704
705 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
706 DBusMessageIter sub, sub2;
707
708 dbus_message_iter_recurse(iter, &sub);
709 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
710 const char *id;
711 const char *path;
712
713 dbus_message_iter_recurse(&sub, &sub2);
714
715 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
716 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
717 char **l;
718
719 l = strv_append(i->sessions, id);
720 if (!l)
721 return -ENOMEM;
722
723 strv_free(i->sessions);
724 i->sessions = l;
725 }
726
727 dbus_message_iter_next(&sub);
728 }
729
730 return 0;
731 }
732 }
733 }
734
735 return 0;
736 }
737
738 static int status_property_seat(const char *name, DBusMessageIter *iter, SeatStatusInfo *i) {
739 assert(name);
740 assert(iter);
741 assert(i);
742
743 switch (dbus_message_iter_get_arg_type(iter)) {
744
745 case DBUS_TYPE_STRING: {
746 const char *s;
747
748 dbus_message_iter_get_basic(iter, &s);
749
750 if (!isempty(s)) {
751 if (streq(name, "Id"))
752 i->id = s;
753 }
754 break;
755 }
756
757 case DBUS_TYPE_STRUCT: {
758 DBusMessageIter sub;
759
760 dbus_message_iter_recurse(iter, &sub);
761
762 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "ActiveSession")) {
763 const char *s;
764
765 dbus_message_iter_get_basic(&sub, &s);
766
767 if (!isempty(s))
768 i->active_session = s;
769 }
770
771 break;
772 }
773
774 case DBUS_TYPE_ARRAY: {
775
776 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
777 DBusMessageIter sub, sub2;
778
779 dbus_message_iter_recurse(iter, &sub);
780 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
781 const char *id;
782 const char *path;
783
784 dbus_message_iter_recurse(&sub, &sub2);
785
786 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
787 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
788 char **l;
789
790 l = strv_append(i->sessions, id);
791 if (!l)
792 return -ENOMEM;
793
794 strv_free(i->sessions);
795 i->sessions = l;
796 }
797
798 dbus_message_iter_next(&sub);
799 }
800
801 return 0;
802 }
803 }
804 }
805
806 return 0;
807 }
808
809 static int print_property(const char *name, DBusMessageIter *iter) {
810 assert(name);
811 assert(iter);
812
813 if (arg_property && !strv_find(arg_property, name))
814 return 0;
815
816 switch (dbus_message_iter_get_arg_type(iter)) {
817
818 case DBUS_TYPE_STRUCT: {
819 DBusMessageIter sub;
820
821 dbus_message_iter_recurse(iter, &sub);
822
823 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING &&
824 (streq(name, "Display") || streq(name, "ActiveSession"))) {
825 const char *s;
826
827 dbus_message_iter_get_basic(&sub, &s);
828
829 if (arg_all || !isempty(s))
830 printf("%s=%s\n", name, s);
831 return 0;
832 }
833 break;
834 }
835
836 case DBUS_TYPE_ARRAY:
837
838 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
839 DBusMessageIter sub, sub2;
840 bool found = false;
841
842 dbus_message_iter_recurse(iter, &sub);
843 while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
844 const char *id;
845 const char *path;
846
847 dbus_message_iter_recurse(&sub, &sub2);
848
849 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
850 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
851 if (found)
852 printf(" %s", id);
853 else {
854 printf("%s=%s", name, id);
855 found = true;
856 }
857 }
858
859 dbus_message_iter_next(&sub);
860 }
861
862 if (!found && arg_all)
863 printf("%s=\n", name);
864 else if (found)
865 printf("\n");
866
867 return 0;
868 }
869
870 break;
871 }
872
873 if (generic_print_property(name, iter, arg_all) > 0)
874 return 0;
875
876 if (arg_all)
877 printf("%s=[unprintable]\n", name);
878
879 return 0;
880 }
881
882 static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
883 DBusMessage *m = NULL, *reply = NULL;
884 const char *interface = "";
885 int r;
886 DBusError error;
887 DBusMessageIter iter, sub, sub2, sub3;
888 SessionStatusInfo session_info;
889 UserStatusInfo user_info;
890 SeatStatusInfo seat_info;
891
892 assert(bus);
893 assert(path);
894 assert(new_line);
895
896 zero(session_info);
897 zero(user_info);
898 zero(seat_info);
899
900 dbus_error_init(&error);
901
902 m = dbus_message_new_method_call(
903 "org.freedesktop.login1",
904 path,
905 "org.freedesktop.DBus.Properties",
906 "GetAll");
907 if (!m) {
908 log_error("Could not allocate message.");
909 r = -ENOMEM;
910 goto finish;
911 }
912
913 if (!dbus_message_append_args(m,
914 DBUS_TYPE_STRING, &interface,
915 DBUS_TYPE_INVALID)) {
916 log_error("Could not append arguments to message.");
917 r = -ENOMEM;
918 goto finish;
919 }
920
921 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
922 if (!reply) {
923 log_error("Failed to issue method call: %s", bus_error_message(&error));
924 r = -EIO;
925 goto finish;
926 }
927
928 if (!dbus_message_iter_init(reply, &iter) ||
929 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
930 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
931 log_error("Failed to parse reply.");
932 r = -EIO;
933 goto finish;
934 }
935
936 dbus_message_iter_recurse(&iter, &sub);
937
938 if (*new_line)
939 printf("\n");
940
941 *new_line = true;
942
943 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
944 const char *name;
945
946 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
947 log_error("Failed to parse reply.");
948 r = -EIO;
949 goto finish;
950 }
951
952 dbus_message_iter_recurse(&sub, &sub2);
953
954 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
955 log_error("Failed to parse reply.");
956 r = -EIO;
957 goto finish;
958 }
959
960 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
961 log_error("Failed to parse reply.");
962 r = -EIO;
963 goto finish;
964 }
965
966 dbus_message_iter_recurse(&sub2, &sub3);
967
968 if (show_properties)
969 r = print_property(name, &sub3);
970 else if (strstr(verb, "session"))
971 r = status_property_session(name, &sub3, &session_info);
972 else if (strstr(verb, "user"))
973 r = status_property_user(name, &sub3, &user_info);
974 else
975 r = status_property_seat(name, &sub3, &seat_info);
976
977 if (r < 0) {
978 log_error("Failed to parse reply.");
979 r = -EIO;
980 goto finish;
981 }
982
983 dbus_message_iter_next(&sub);
984 }
985
986 if (!show_properties) {
987 if (strstr(verb, "session"))
988 print_session_status_info(&session_info);
989 else if (strstr(verb, "user"))
990 print_user_status_info(&user_info);
991 else
992 print_seat_status_info(&seat_info);
993 }
994
995 strv_free(seat_info.sessions);
996 strv_free(user_info.sessions);
997
998 r = 0;
999
1000 finish:
1001 if (m)
1002 dbus_message_unref(m);
1003
1004 if (reply)
1005 dbus_message_unref(reply);
1006
1007 dbus_error_free(&error);
1008
1009 return r;
1010 }
1011
1012 static int show(DBusConnection *bus, char **args, unsigned n) {
1013 DBusMessage *m = NULL, *reply = NULL;
1014 int r, ret = 0;
1015 DBusError error;
1016 unsigned i;
1017 bool show_properties, new_line = false;
1018
1019 assert(bus);
1020 assert(args);
1021
1022 dbus_error_init(&error);
1023
1024 show_properties = !strstr(args[0], "status");
1025
1026 if (show_properties)
1027 pager_open_if_enabled();
1028
1029 if (show_properties && n <= 1) {
1030 /* If not argument is specified inspect the manager
1031 * itself */
1032
1033 ret = show_one(args[0], bus, "/org/freedesktop/login1", show_properties, &new_line);
1034 goto finish;
1035 }
1036
1037 for (i = 1; i < n; i++) {
1038 const char *path = NULL;
1039
1040 if (strstr(args[0], "session")) {
1041
1042 m = dbus_message_new_method_call(
1043 "org.freedesktop.login1",
1044 "/org/freedesktop/login1",
1045 "org.freedesktop.login1.Manager",
1046 "GetSession");
1047 if (!m) {
1048 log_error("Could not allocate message.");
1049 ret = -ENOMEM;
1050 goto finish;
1051 }
1052
1053 if (!dbus_message_append_args(m,
1054 DBUS_TYPE_STRING, &args[i],
1055 DBUS_TYPE_INVALID)) {
1056 log_error("Could not append arguments to message.");
1057 ret = -ENOMEM;
1058 goto finish;
1059 }
1060
1061 } else if (strstr(args[0], "user")) {
1062 uint32_t uid;
1063
1064 if (safe_atou(args[i], &uid) < 0) {
1065 struct passwd *pw;
1066
1067 pw = getpwnam(args[i]);
1068 if (!pw) {
1069 log_error("User %s unknown.", args[i]);
1070 ret = -ENOENT;
1071 goto finish;
1072 }
1073
1074 uid = pw->pw_uid;
1075 }
1076
1077 m = dbus_message_new_method_call(
1078 "org.freedesktop.login1",
1079 "/org/freedesktop/login1",
1080 "org.freedesktop.login1.Manager",
1081 "GetUser");
1082 if (!m) {
1083 log_error("Could not allocate message.");
1084 ret = -ENOMEM;
1085 goto finish;
1086 }
1087
1088 if (!dbus_message_append_args(m,
1089 DBUS_TYPE_UINT32, &uid,
1090 DBUS_TYPE_INVALID)) {
1091 log_error("Could not append arguments to message.");
1092 ret = -ENOMEM;
1093 goto finish;
1094 }
1095 } else {
1096
1097 m = dbus_message_new_method_call(
1098 "org.freedesktop.login1",
1099 "/org/freedesktop/login1",
1100 "org.freedesktop.login1.Manager",
1101 "GetSeat");
1102 if (!m) {
1103 log_error("Could not allocate message.");
1104 ret = -ENOMEM;
1105 goto finish;
1106 }
1107
1108 if (!dbus_message_append_args(m,
1109 DBUS_TYPE_STRING, &args[i],
1110 DBUS_TYPE_INVALID)) {
1111 log_error("Could not append arguments to message.");
1112 ret = -ENOMEM;
1113 goto finish;
1114 }
1115 }
1116
1117 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
1118 if (!reply) {
1119 log_error("Failed to issue method call: %s", bus_error_message(&error));
1120 ret = -EIO;
1121 goto finish;
1122 }
1123
1124 if (!dbus_message_get_args(reply, &error,
1125 DBUS_TYPE_OBJECT_PATH, &path,
1126 DBUS_TYPE_INVALID)) {
1127 log_error("Failed to parse reply: %s", bus_error_message(&error));
1128 ret = -EIO;
1129 goto finish;
1130 }
1131
1132 r = show_one(args[0], bus, path, show_properties, &new_line);
1133 if (r != 0)
1134 ret = r;
1135
1136 dbus_message_unref(m);
1137 dbus_message_unref(reply);
1138 m = reply = NULL;
1139 }
1140
1141 finish:
1142 if (m)
1143 dbus_message_unref(m);
1144
1145 if (reply)
1146 dbus_message_unref(reply);
1147
1148 dbus_error_free(&error);
1149
1150 return ret;
1151 }
1152
1153 static int activate(DBusConnection *bus, char **args, unsigned n) {
1154 return 0;
1155 }
1156
1157 static int kill_session(DBusConnection *bus, char **args, unsigned n) {
1158 return 0;
1159 }
1160
1161 static int enable_linger(DBusConnection *bus, char **args, unsigned n) {
1162 return 0;
1163 }
1164
1165 static int attach(DBusConnection *bus, char **args, unsigned n) {
1166 return 0;
1167 }
1168
1169 static int flush_devices(DBusConnection *bus, char **args, unsigned n) {
1170 return 0;
1171 }
1172
1173 static int terminate_seat(DBusConnection *bus, char **args, unsigned n) {
1174 return 0;
1175 }
1176
1177 static int help(void) {
1178
1179 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1180 "Send control commands to or query the login manager.\n\n"
1181 " -h --help Show this help\n"
1182 " --version Show package version\n"
1183 " -p --property=NAME Show only properties by this name\n"
1184 " -a --all Show all properties, including empty ones\n"
1185 " --kill-who=WHO Who to send signal to\n"
1186 " -s --signal=SIGNAL Which signal to send\n"
1187 " -H --host=[USER@]HOST\n"
1188 " Show information for remote host\n"
1189 " -P --privileged Acquire privileges before execution\n"
1190 " --no-pager Do not pipe output into a pager\n\n"
1191 "Commands:\n"
1192 " list-sessions List sessions\n"
1193 " session-status [ID...] Show session status\n"
1194 " show-session [ID...] Show property of one or more sessions\n"
1195 " activate [ID] Activate a session\n"
1196 " lock-session [ID...] Screen lock one or more sessions\n"
1197 " terminate-session [ID...] Terminate one or more sessions\n"
1198 " kill-session [ID...] Send signal to processes of a session\n"
1199 " list-users List users\n"
1200 " user-status [USER...] Show user status\n"
1201 " show-user [USER...] Show property of one or more users\n"
1202 " enable-linger [USER...] Enable linger state of one or more users\n"
1203 " disable-linger [USER...] Disable linger state of one or more users\n"
1204 " terminate-user [USER...] Terminate all sessions of one or more users\n"
1205 " kill-user [USER...] Send signal to processes of a user\n"
1206 " list-seats List seats\n"
1207 " seat-status [NAME...] Show seat status\n"
1208 " show-seat [NAME...] Show property of one or more seats\n"
1209 " attach [NAME] [DEVICE...] Attach one or more devices to a seat\n"
1210 " flush-devices Flush all device associations\n"
1211 " terminate-seat [NAME...] Terminate all sessions on one or more seats\n"
1212 " kill-seat [NAME...] Send signal to processes of sessions on a seat\n",
1213 program_invocation_short_name);
1214
1215 return 0;
1216 }
1217
1218 static int parse_argv(int argc, char *argv[]) {
1219
1220 enum {
1221 ARG_VERSION = 0x100,
1222 ARG_NO_PAGER,
1223 ARG_KILL_WHO
1224 };
1225
1226 static const struct option options[] = {
1227 { "help", no_argument, NULL, 'h' },
1228 { "version", no_argument, NULL, ARG_VERSION },
1229 { "property", required_argument, NULL, 'p' },
1230 { "all", no_argument, NULL, 'a' },
1231 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1232 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
1233 { "signal", required_argument, NULL, 's' },
1234 { "host", required_argument, NULL, 'H' },
1235 { "privileged",no_argument, NULL, 'P' },
1236 { NULL, 0, NULL, 0 }
1237 };
1238
1239 int c;
1240
1241 assert(argc >= 0);
1242 assert(argv);
1243
1244 while ((c = getopt_long(argc, argv, "hp:as:H:P", options, NULL)) >= 0) {
1245
1246 switch (c) {
1247
1248 case 'h':
1249 help();
1250 return 0;
1251
1252 case ARG_VERSION:
1253 puts(PACKAGE_STRING);
1254 puts(DISTRIBUTION);
1255 puts(SYSTEMD_FEATURES);
1256 return 0;
1257
1258 case 'p': {
1259 char **l;
1260
1261 l = strv_append(arg_property, optarg);
1262 if (!l)
1263 return -ENOMEM;
1264
1265 strv_free(arg_property);
1266 arg_property = l;
1267
1268 /* If the user asked for a particular
1269 * property, show it to him, even if it is
1270 * empty. */
1271 arg_all = true;
1272 break;
1273 }
1274
1275 case 'a':
1276 arg_all = true;
1277 break;
1278
1279 case ARG_NO_PAGER:
1280 arg_no_pager = true;
1281 break;
1282
1283 case ARG_KILL_WHO:
1284 arg_kill_who = optarg;
1285 break;
1286
1287 case 's':
1288 arg_signal = signal_from_string_try_harder(optarg);
1289 if (arg_signal < 0) {
1290 log_error("Failed to parse signal string %s.", optarg);
1291 return -EINVAL;
1292 }
1293 break;
1294
1295 case 'P':
1296 arg_transport = TRANSPORT_POLKIT;
1297 break;
1298
1299 case 'H':
1300 arg_transport = TRANSPORT_SSH;
1301 arg_host = optarg;
1302 break;
1303
1304 case '?':
1305 return -EINVAL;
1306
1307 default:
1308 log_error("Unknown option code %c", c);
1309 return -EINVAL;
1310 }
1311 }
1312
1313 return 1;
1314 }
1315
1316 static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
1317
1318 static const struct {
1319 const char* verb;
1320 const enum {
1321 MORE,
1322 LESS,
1323 EQUAL
1324 } argc_cmp;
1325 const int argc;
1326 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
1327 } verbs[] = {
1328 { "list-sessions", LESS, 1, list_sessions },
1329 { "session-status", MORE, 2, show },
1330 { "show-session", MORE, 2, show },
1331 { "activate", EQUAL, 2, activate },
1332 { "lock-session", MORE, 2, activate },
1333 { "terminate-session", MORE, 2, activate },
1334 { "kill-session", MORE, 2, kill_session },
1335 { "list-users", EQUAL, 1, list_users },
1336 { "user-status", MORE, 2, show },
1337 { "show-user", MORE, 2, show },
1338 { "enable-linger", MORE, 2, enable_linger },
1339 { "disable-linger", MORE, 2, enable_linger },
1340 { "terminate-user", MORE, 2, enable_linger },
1341 { "kill-user", MORE, 2, kill_session },
1342 { "list-seats", EQUAL, 1, list_seats },
1343 { "seat-status", MORE, 2, show },
1344 { "show-seat", MORE, 2, show },
1345 { "attach", MORE, 3, attach },
1346 { "flush-devices", EQUAL, 1, flush_devices },
1347 { "terminate-seat", MORE, 2, terminate_seat },
1348 { "kill-seat", MORE, 2, kill_session },
1349 };
1350
1351 int left;
1352 unsigned i;
1353
1354 assert(argc >= 0);
1355 assert(argv);
1356 assert(error);
1357
1358 left = argc - optind;
1359
1360 if (left <= 0)
1361 /* Special rule: no arguments means "list-sessions" */
1362 i = 0;
1363 else {
1364 if (streq(argv[optind], "help")) {
1365 help();
1366 return 0;
1367 }
1368
1369 for (i = 0; i < ELEMENTSOF(verbs); i++)
1370 if (streq(argv[optind], verbs[i].verb))
1371 break;
1372
1373 if (i >= ELEMENTSOF(verbs)) {
1374 log_error("Unknown operation %s", argv[optind]);
1375 return -EINVAL;
1376 }
1377 }
1378
1379 switch (verbs[i].argc_cmp) {
1380
1381 case EQUAL:
1382 if (left != verbs[i].argc) {
1383 log_error("Invalid number of arguments.");
1384 return -EINVAL;
1385 }
1386
1387 break;
1388
1389 case MORE:
1390 if (left < verbs[i].argc) {
1391 log_error("Too few arguments.");
1392 return -EINVAL;
1393 }
1394
1395 break;
1396
1397 case LESS:
1398 if (left > verbs[i].argc) {
1399 log_error("Too many arguments.");
1400 return -EINVAL;
1401 }
1402
1403 break;
1404
1405 default:
1406 assert_not_reached("Unknown comparison operator.");
1407 }
1408
1409 if (!bus) {
1410 log_error("Failed to get D-Bus connection: %s", error->message);
1411 return -EIO;
1412 }
1413
1414 return verbs[i].dispatch(bus, argv + optind, left);
1415 }
1416
1417 int main(int argc, char*argv[]) {
1418 int r, retval = EXIT_FAILURE;
1419 DBusConnection *bus = NULL;
1420 DBusError error;
1421
1422 dbus_error_init(&error);
1423
1424 log_parse_environment();
1425 log_open();
1426
1427 r = parse_argv(argc, argv);
1428 if (r < 0)
1429 goto finish;
1430 else if (r == 0) {
1431 retval = EXIT_SUCCESS;
1432 goto finish;
1433 }
1434
1435 if (arg_transport == TRANSPORT_NORMAL)
1436 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1437 else if (arg_transport == TRANSPORT_POLKIT)
1438 bus_connect_system_polkit(&bus, &error);
1439 else if (arg_transport == TRANSPORT_SSH)
1440 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
1441 else
1442 assert_not_reached("Uh, invalid transport...");
1443
1444 r = loginctl_main(bus, argc, argv, &error);
1445 retval = r < 0 ? EXIT_FAILURE : r;
1446
1447 finish:
1448 if (bus) {
1449 dbus_connection_flush(bus);
1450 dbus_connection_close(bus);
1451 dbus_connection_unref(bus);
1452 }
1453
1454 dbus_error_free(&error);
1455 dbus_shutdown();
1456
1457 strv_free(arg_property);
1458
1459 pager_close();
1460
1461 return retval;
1462 }