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