chiark / gitweb /
8a7a2de4031c6d1cd22563f9b1bcebdedd580516
[elogind.git] / src / systemctl.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
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 <stdio.h>
23 #include <getopt.h>
24 #include <stdbool.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <sys/ioctl.h>
28 #include <termios.h>
29 #include <unistd.h>
30
31 #include <dbus/dbus.h>
32
33 #include "log.h"
34 #include "util.h"
35 #include "macro.h"
36 #include "set.h"
37
38 static const char *arg_type = NULL;
39 static bool arg_all = false;
40 static bool arg_replace = false;
41 static bool arg_session = false;
42 static bool arg_block = false;
43
44 static int bus_iter_get_basic_and_next(DBusMessageIter *iter, int type, void *data, bool next) {
45
46         if (dbus_message_iter_get_arg_type(iter) != type)
47                 return -EIO;
48
49         dbus_message_iter_get_basic(iter, data);
50
51         if (!dbus_message_iter_next(iter) != !next)
52                 return -EIO;
53
54         return 0;
55 }
56
57 static int columns(void) {
58         static int parsed_columns = 0;
59         const char *e;
60
61         if (parsed_columns > 0)
62                 return parsed_columns;
63
64         if ((e = getenv("COLUMNS")))
65                 parsed_columns = atoi(e);
66
67         if (parsed_columns <= 0) {
68                 struct winsize ws;
69                 zero(ws);
70
71                 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
72                         parsed_columns = ws.ws_col;
73         }
74
75         if (parsed_columns <= 0)
76                 parsed_columns = 80;
77
78         return parsed_columns;
79 }
80
81 static int list_units(DBusConnection *bus, char **args, unsigned n) {
82         DBusMessage *m = NULL, *reply = NULL;
83         DBusError error;
84         int r;
85         DBusMessageIter iter, sub, sub2;
86         unsigned k = 0;
87
88         dbus_error_init(&error);
89
90         if (!(m = dbus_message_new_method_call(
91                               "org.freedesktop.systemd1",
92                               "/org/freedesktop/systemd1",
93                               "org.freedesktop.systemd1.Manager",
94                               "ListUnits"))) {
95                 log_error("Could not allocate message.");
96                 return -ENOMEM;
97         }
98
99         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
100                 log_error("Failed to issue method call: %s", error.message);
101                 r = -EIO;
102                 goto finish;
103         }
104
105         if (!dbus_message_iter_init(reply, &iter) ||
106             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
107             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
108                 log_error("Failed to parse reply.");
109                 r = -EIO;
110                 goto finish;
111         }
112
113         dbus_message_iter_recurse(&iter, &sub);
114
115         printf("%-45s %-6s %-12s %-12s %-15s %s\n", "UNIT", "LOAD", "ACTIVE", "SUB", "JOB", "DESCRIPTION");
116
117         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
118                 const char *id, *description, *load_state, *active_state, *sub_state, *unit_state, *job_type, *job_path, *dot;
119                 uint32_t job_id;
120
121                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
122                         log_error("Failed to parse reply.");
123                         r = -EIO;
124                         goto finish;
125                 }
126
127                 dbus_message_iter_recurse(&sub, &sub2);
128
129                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 ||
130                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &description, true) < 0 ||
131                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &load_state, true) < 0 ||
132                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &active_state, true) < 0 ||
133                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &sub_state, true) < 0 ||
134                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_state, true) < 0 ||
135                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &job_id, true) < 0 ||
136                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &job_type, true) < 0 ||
137                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &job_path, false) < 0) {
138                         log_error("Failed to parse reply.");
139                         r = -EIO;
140                         goto finish;
141                 }
142
143                 if ((!arg_type || ((dot = strrchr(id, '.')) &&
144                                    streq(dot+1, arg_type))) &&
145                     (arg_all || !streq(active_state, "inactive"))) {
146
147                         int a = 0, b = 0;
148
149                         printf("%-45s %-6s %-12s %-12s%n", id, load_state, active_state, sub_state, &a);
150
151                         if (job_id != 0)
152                                 printf(" %-15s%n", job_type, &b);
153                         else
154                                 b = 1 + 15;
155
156                         if (a + b + 2 < columns()) {
157                                 if (job_id == 0)
158                                         printf("                ");
159
160                                 printf("%.*s", columns() - a - b - 2, description);
161                         }
162
163                         fputs("\n", stdout);
164                         k++;
165                 }
166
167                 dbus_message_iter_next(&sub);
168         }
169
170         if (arg_all)
171                 printf("\n%u units listed.\n", k);
172         else
173                 printf("\n%u live units listed. Pass --all to see dead units, too.\n", k);
174
175         r = 0;
176
177 finish:
178         if (m)
179                 dbus_message_unref(m);
180
181         if (reply)
182                 dbus_message_unref(reply);
183
184         dbus_error_free(&error);
185
186         return r;
187 }
188
189 static int list_jobs(DBusConnection *bus, char **args, unsigned n) {
190         DBusMessage *m = NULL, *reply = NULL;
191         DBusError error;
192         int r;
193         DBusMessageIter iter, sub, sub2;
194         unsigned k = 0;
195
196         dbus_error_init(&error);
197
198         if (!(m = dbus_message_new_method_call(
199                               "org.freedesktop.systemd1",
200                               "/org/freedesktop/systemd1",
201                               "org.freedesktop.systemd1.Manager",
202                               "ListJobs"))) {
203                 log_error("Could not allocate message.");
204                 return -ENOMEM;
205         }
206
207         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
208                 log_error("Failed to issue method call: %s", error.message);
209                 r = -EIO;
210                 goto finish;
211         }
212
213         if (!dbus_message_iter_init(reply, &iter) ||
214             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
215             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
216                 log_error("Failed to parse reply.");
217                 r = -EIO;
218                 goto finish;
219         }
220
221         dbus_message_iter_recurse(&iter, &sub);
222
223         printf("%4s %-45s %-17s %-7s\n", "JOB", "UNIT", "TYPE", "STATE");
224
225         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
226                 const char *name, *type, *state, *job_path, *unit_path;
227                 uint32_t id;
228
229                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
230                         log_error("Failed to parse reply.");
231                         r = -EIO;
232                         goto finish;
233                 }
234
235                 dbus_message_iter_recurse(&sub, &sub2);
236
237                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &id, true) < 0 ||
238                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 ||
239                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &type, true) < 0 ||
240                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &state, true) < 0 ||
241                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &job_path, true) < 0 ||
242                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_path, false) < 0) {
243                         log_error("Failed to parse reply.");
244                         r = -EIO;
245                         goto finish;
246                 }
247
248                 printf("%4u %-45s %-17s %-7s\n", id, name, type, state);
249                 k++;
250
251                 dbus_message_iter_next(&sub);
252         }
253
254         printf("\n%u jobs listed.\n", k);
255         r = 0;
256
257 finish:
258         if (m)
259                 dbus_message_unref(m);
260
261         if (reply)
262                 dbus_message_unref(reply);
263
264         dbus_error_free(&error);
265
266         return r;
267 }
268
269 static int load_unit(DBusConnection *bus, char **args, unsigned n) {
270         DBusMessage *m = NULL, *reply = NULL;
271         DBusError error;
272         int r;
273         unsigned i;
274
275         dbus_error_init(&error);
276
277         for (i = 1; i < n; i++) {
278
279                 if (!(m = dbus_message_new_method_call(
280                                       "org.freedesktop.systemd1",
281                                       "/org/freedesktop/systemd1",
282                                       "org.freedesktop.systemd1.Manager",
283                                       "LoadUnit"))) {
284                         log_error("Could not allocate message.");
285                         r = -ENOMEM;
286                         goto finish;
287                 }
288
289                 if (!dbus_message_append_args(m,
290                                               DBUS_TYPE_STRING, &args[i],
291                                               DBUS_TYPE_INVALID)) {
292                         log_error("Could not append arguments to message.");
293                         r = -ENOMEM;
294                         goto finish;
295                 }
296
297                 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
298                         log_error("Failed to issue method call: %s", error.message);
299                         r = -EIO;
300                         goto finish;
301                 }
302
303                 dbus_message_unref(m);
304                 dbus_message_unref(reply);
305
306                 m = reply = NULL;
307         }
308
309         r = 0;
310
311 finish:
312         if (m)
313                 dbus_message_unref(m);
314
315         if (reply)
316                 dbus_message_unref(reply);
317
318         dbus_error_free(&error);
319
320         return r;
321 }
322
323 static int cancel_job(DBusConnection *bus, char **args, unsigned n) {
324         DBusMessage *m = NULL, *reply = NULL;
325         DBusError error;
326         int r;
327         unsigned i;
328
329         dbus_error_init(&error);
330
331         for (i = 1; i < n; i++) {
332                 unsigned id;
333                 const char *path;
334
335                 if (!(m = dbus_message_new_method_call(
336                                       "org.freedesktop.systemd1",
337                                       "/org/freedesktop/systemd1",
338                                       "org.freedesktop.systemd1.Manager",
339                                       "GetJob"))) {
340                         log_error("Could not allocate message.");
341                         r = -ENOMEM;
342                         goto finish;
343                 }
344
345                 if ((r = safe_atou(args[i], &id)) < 0) {
346                         log_error("Failed to parse job id: %s", strerror(-r));
347                         goto finish;
348                 }
349
350                 assert_cc(sizeof(uint32_t) == sizeof(id));
351                 if (!dbus_message_append_args(m,
352                                               DBUS_TYPE_UINT32, &id,
353                                               DBUS_TYPE_INVALID)) {
354                         log_error("Could not append arguments to message.");
355                         r = -ENOMEM;
356                         goto finish;
357                 }
358
359                 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
360                         log_error("Failed to issue method call: %s", error.message);
361                         r = -EIO;
362                         goto finish;
363                 }
364
365                 if (!dbus_message_get_args(reply, &error,
366                                            DBUS_TYPE_OBJECT_PATH, &path,
367                                            DBUS_TYPE_INVALID)) {
368                         log_error("Failed to parse reply: %s", error.message);
369                         r = -EIO;
370                         goto finish;
371                 }
372
373                 dbus_message_unref(m);
374                 if (!(m = dbus_message_new_method_call(
375                                       "org.freedesktop.systemd1",
376                                       path,
377                                       "org.freedesktop.systemd1.Job",
378                                       "Cancel"))) {
379                         log_error("Could not allocate message.");
380                         r = -ENOMEM;
381                         goto finish;
382                 }
383
384                 dbus_message_unref(reply);
385                 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
386                         log_error("Failed to issue method call: %s", error.message);
387                         r = -EIO;
388                         goto finish;
389                 }
390
391                 dbus_message_unref(m);
392                 dbus_message_unref(reply);
393                 m = reply = NULL;
394         }
395
396         r = 0;
397
398 finish:
399         if (m)
400                 dbus_message_unref(m);
401
402         if (reply)
403                 dbus_message_unref(reply);
404
405         dbus_error_free(&error);
406
407         return r;
408 }
409
410 static DBusHandlerResult wait_filter(DBusConnection *connection, DBusMessage *message, void *data) {
411         DBusError error;
412         Set *s = data;
413
414         assert(connection);
415         assert(message);
416         assert(s);
417
418         dbus_error_init(&error);
419
420         /* log_debug("Got D-Bus request: %s.%s() on %s", */
421         /*           dbus_message_get_interface(message), */
422         /*           dbus_message_get_member(message), */
423         /*           dbus_message_get_path(message)); */
424
425         if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
426                 log_error("Warning! D-Bus connection terminated.");
427                 dbus_connection_close(connection);
428
429         } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
430                 uint32_t id;
431                 const char *path;
432
433                 if (!dbus_message_get_args(message, &error,
434                                            DBUS_TYPE_UINT32, &id,
435                                            DBUS_TYPE_OBJECT_PATH, &path,
436                                            DBUS_TYPE_INVALID))
437                         log_error("Failed to parse message: %s", error.message);
438                 else {
439                         char *p;
440
441                         if ((p = set_remove(s, (char*) path)))
442                                 free(p);
443                 }
444         }
445
446         dbus_error_free(&error);
447         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
448 }
449
450 static int wait_for_jobs(DBusConnection *bus, Set *s) {
451         DBusError error;
452         DBusMessage *m = NULL, *reply = NULL;
453         int r;
454
455         assert(bus);
456         assert(s);
457
458         dbus_error_init(&error);
459
460         dbus_bus_add_match(bus,
461                            "type='signal',"
462                            "sender='org.freedesktop.systemd1',"
463                            "interface='org.freedesktop.systemd1.Manager',"
464                            "member='JobRemoved',"
465                            "path='/org/freedesktop/systemd1'",
466                            &error);
467
468         if (dbus_error_is_set(&error)) {
469                 log_error("Failed to add match: %s", error.message);
470                 r = -EIO;
471                 goto finish;
472         }
473
474         if (!dbus_connection_add_filter(bus, wait_filter, s, NULL)) {
475                 log_error("Failed to add filter.");
476                 r = -ENOMEM;
477                 goto finish;
478         }
479
480         if (!(m = dbus_message_new_method_call(
481                               "org.freedesktop.systemd1",
482                               "/org/freedesktop/systemd1",
483                               "org.freedesktop.systemd1.Manager",
484                               "Subscribe"))) {
485                 log_error("Could not allocate message.");
486                 r = -ENOMEM;
487                 goto finish;
488         }
489
490         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
491                 log_error("Failed to issue method call: %s", error.message);
492                 r = -EIO;
493                 goto finish;
494         }
495
496         while (!set_isempty(s) &&
497                dbus_connection_read_write_dispatch(bus, -1))
498                 ;
499
500         r = 0;
501
502 finish:
503         /* This is slightly dirty, since we don't undo the filter or the matches. */
504
505         if (m)
506                 dbus_message_unref(m);
507
508         if (reply)
509                 dbus_message_unref(reply);
510
511         dbus_error_free(&error);
512
513         return r;
514 }
515
516 static int start_unit(DBusConnection *bus, char **args, unsigned n) {
517         DBusMessage *m = NULL, *reply = NULL;
518         DBusError error;
519         int r;
520         unsigned i;
521         const char *method, *mode;
522         char *p = NULL;
523         Set *s = NULL;
524
525         dbus_error_init(&error);
526
527         method =
528                 streq(args[0], "start")  ? "StartUnit" :
529                 streq(args[0], "stop")   ? "StopUnit" :
530                 streq(args[0], "reload") ? "ReloadUnit" :
531                                            "RestartUnit";
532
533         mode = arg_replace ? "replace" : "fail";
534
535         for (i = 1; i < n; i++) {
536
537                 if (!(m = dbus_message_new_method_call(
538                                       "org.freedesktop.systemd1",
539                                       "/org/freedesktop/systemd1",
540                                       "org.freedesktop.systemd1.Manager",
541                                       method))) {
542                         log_error("Could not allocate message.");
543                         r = -ENOMEM;
544                         goto finish;
545                 }
546
547                 if (!dbus_message_append_args(m,
548                                               DBUS_TYPE_STRING, &args[i],
549                                               DBUS_TYPE_STRING, &mode,
550                                               DBUS_TYPE_INVALID)) {
551                         log_error("Could not append arguments to message.");
552                         r = -ENOMEM;
553                         goto finish;
554                 }
555
556                 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
557                         log_error("Failed to issue method call: %s", error.message);
558                         r = -EIO;
559                         goto finish;
560                 }
561
562                 if (arg_block) {
563                         const char *path;
564
565                         if (!dbus_message_get_args(reply, &error,
566                                                    DBUS_TYPE_OBJECT_PATH, &path,
567                                                    DBUS_TYPE_INVALID)) {
568                                 log_error("Failed to parse reply: %s", error.message);
569                                 r = -EIO;
570                                 goto finish;
571                         }
572
573                         if (!s)
574                                 if (!(s = set_new(string_hash_func, string_compare_func))) {
575                                         log_error("Failed to allocate set.");
576                                         r = -ENOMEM;
577                                         goto finish;
578                                 }
579
580                         if (!(p = strdup(path))) {
581                                 log_error("Failed to duplicate path.");
582                                 r = -ENOMEM;
583                                 goto finish;
584                         }
585
586                         if ((r = set_put(s, p)) < 0) {
587                                 log_error("Failed to add path to set.");
588                                 goto finish;
589                         }
590                         p = NULL;
591                 }
592
593                 dbus_message_unref(m);
594                 dbus_message_unref(reply);
595
596                 m = reply = NULL;
597         }
598
599         if (arg_block)
600                 r = wait_for_jobs(bus, s);
601         else
602                 r = 0;
603
604 finish:
605         free(p);
606
607         if (s)
608                 set_free_free(s);
609
610         if (m)
611                 dbus_message_unref(m);
612
613         if (reply)
614                 dbus_message_unref(reply);
615
616         dbus_error_free(&error);
617
618         return r;
619 }
620
621 static int isolate_unit(DBusConnection *bus, char **args, unsigned n) {
622         DBusMessage *m = NULL, *reply = NULL;
623         DBusError error;
624         int r;
625         const char *mode = "isolate";
626         char *p = NULL;
627         Set *s = NULL;
628
629         dbus_error_init(&error);
630
631         if (!(m = dbus_message_new_method_call(
632                               "org.freedesktop.systemd1",
633                               "/org/freedesktop/systemd1",
634                               "org.freedesktop.systemd1.Manager",
635                               "StartUnit"))) {
636                 log_error("Could not allocate message.");
637                 r = -ENOMEM;
638                 goto finish;
639         }
640
641         if (!dbus_message_append_args(m,
642                                       DBUS_TYPE_STRING, &args[1],
643                                       DBUS_TYPE_STRING, &mode,
644                                       DBUS_TYPE_INVALID)) {
645                 log_error("Could not append arguments to message.");
646                 r = -ENOMEM;
647                 goto finish;
648         }
649
650         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
651                 log_error("Failed to issue method call: %s", error.message);
652                 r = -EIO;
653                 goto finish;
654         }
655
656         if (arg_block) {
657                 const char *path;
658
659                 if (!dbus_message_get_args(reply, &error,
660                                            DBUS_TYPE_OBJECT_PATH, &path,
661                                            DBUS_TYPE_INVALID)) {
662                         log_error("Failed to parse reply: %s", error.message);
663                         r = -EIO;
664                         goto finish;
665                 }
666
667                 if (!(s = set_new(string_hash_func, string_compare_func))) {
668                         log_error("Failed to allocate set.");
669                         r = -ENOMEM;
670                         goto finish;
671                 }
672
673                 if (!(p = strdup(path))) {
674                         log_error("Failed to duplicate path.");
675                         r = -ENOMEM;
676                         goto finish;
677                 }
678
679                 if ((r = set_put(s, p)) < 0) {
680                         log_error("Failed to add path to set.");
681                         goto finish;
682                 }
683                 p = NULL;
684
685                 r = wait_for_jobs(bus, s);
686
687         } else
688                 r = 0;
689
690 finish:
691         free(p);
692
693         if (s)
694                 set_free_free(s);
695
696         if (m)
697                 dbus_message_unref(m);
698
699         if (reply)
700                 dbus_message_unref(reply);
701
702         dbus_error_free(&error);
703
704         return r;
705 }
706
707 static DBusHandlerResult monitor_filter(DBusConnection *connection, DBusMessage *message, void *data) {
708         DBusError error;
709         DBusMessage *m = NULL, *reply = NULL;
710
711         assert(connection);
712         assert(message);
713
714         dbus_error_init(&error);
715
716         /* log_debug("Got D-Bus request: %s.%s() on %s", */
717         /*           dbus_message_get_interface(message), */
718         /*           dbus_message_get_member(message), */
719         /*           dbus_message_get_path(message)); */
720
721         if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
722                 log_error("Warning! D-Bus connection terminated.");
723                 dbus_connection_close(connection);
724
725         } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "UnitNew") ||
726                    dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "UnitRemoved")) {
727                 const char *id, *path;
728
729                 if (!dbus_message_get_args(message, &error,
730                                            DBUS_TYPE_STRING, &id,
731                                            DBUS_TYPE_OBJECT_PATH, &path,
732                                            DBUS_TYPE_INVALID))
733                         log_error("Failed to parse message: %s", error.message);
734                 else if (streq(dbus_message_get_member(message), "UnitNew"))
735                         printf("Unit %s added.\n", id);
736                 else
737                         printf("Unit %s removed.\n", id);
738
739         } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobNew") ||
740                    dbus_message_is_signal(message, "org.freedesktop.systemd1.Manager", "JobRemoved")) {
741                 uint32_t id;
742                 const char *path;
743
744                 if (!dbus_message_get_args(message, &error,
745                                            DBUS_TYPE_UINT32, &id,
746                                            DBUS_TYPE_OBJECT_PATH, &path,
747                                            DBUS_TYPE_INVALID))
748                         log_error("Failed to parse message: %s", error.message);
749                 else if (streq(dbus_message_get_member(message), "JobNew"))
750                         printf("Job %u added.\n", id);
751                 else
752                         printf("Job %u removed.\n", id);
753
754
755         } else if (dbus_message_is_signal(message, "org.freedesktop.systemd1.Unit", "Changed") ||
756                    dbus_message_is_signal(message, "org.freedesktop.systemd1.Job", "Changed")) {
757
758                 const char *path, *interface, *property = "Id";
759                 DBusMessageIter iter, sub;
760
761                 path = dbus_message_get_path(message);
762                 interface = dbus_message_get_interface(message);
763
764                 if (!(m = dbus_message_new_method_call(
765                               "org.freedesktop.systemd1",
766                               path,
767                               "org.freedesktop.DBus.Properties",
768                               "Get"))) {
769                         log_error("Could not allocate message.");
770                         goto oom;
771                 }
772
773                 if (!dbus_message_append_args(m,
774                                               DBUS_TYPE_STRING, &interface,
775                                               DBUS_TYPE_STRING, &property,
776                                               DBUS_TYPE_INVALID)) {
777                         log_error("Could not append arguments to message.");
778                         goto finish;
779                 }
780
781                 if (!(reply = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) {
782                         log_error("Failed to issue method call: %s", error.message);
783                         goto finish;
784                 }
785
786                 if (!dbus_message_iter_init(reply, &iter) ||
787                     dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
788                         log_error("Failed to parse reply.");
789                         goto finish;
790                 }
791
792                 dbus_message_iter_recurse(&iter, &sub);
793
794                 if (streq(interface, "org.freedesktop.systemd1.Unit")) {
795                         const char *id;
796
797                         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
798                                 log_error("Failed to parse reply.");
799                                 goto finish;
800                         }
801
802                         dbus_message_iter_get_basic(&sub, &id);
803                         printf("Unit %s changed.\n", id);
804                 } else {
805                         uint32_t id;
806
807                         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)  {
808                                 log_error("Failed to parse reply.");
809                                 goto finish;
810                         }
811
812                         dbus_message_iter_get_basic(&sub, &id);
813                         printf("Job %u changed.\n", id);
814                 }
815         }
816
817 finish:
818         if (m)
819                 dbus_message_unref(m);
820
821         if (reply)
822                 dbus_message_unref(reply);
823
824         dbus_error_free(&error);
825         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
826
827 oom:
828         if (m)
829                 dbus_message_unref(m);
830
831         if (reply)
832                 dbus_message_unref(reply);
833
834         dbus_error_free(&error);
835         return DBUS_HANDLER_RESULT_NEED_MEMORY;
836 }
837
838 static int monitor(DBusConnection *bus, char **args, unsigned n) {
839         DBusMessage *m = NULL, *reply = NULL;
840         DBusError error;
841         int r;
842
843         dbus_error_init(&error);
844
845         dbus_bus_add_match(bus,
846                            "type='signal',"
847                            "sender='org.freedesktop.systemd1',"
848                            "interface='org.freedesktop.systemd1.Manager',"
849                            "path='/org/freedesktop/systemd1'",
850                            &error);
851
852         if (dbus_error_is_set(&error)) {
853                 log_error("Failed to add match: %s", error.message);
854                 r = -EIO;
855                 goto finish;
856         }
857
858         dbus_bus_add_match(bus,
859                            "type='signal',"
860                            "sender='org.freedesktop.systemd1',"
861                            "interface='org.freedesktop.systemd1.Unit',"
862                            "member='Changed'",
863                            &error);
864
865         if (dbus_error_is_set(&error)) {
866                 log_error("Failed to add match: %s", error.message);
867                 r = -EIO;
868                 goto finish;
869         }
870
871         dbus_bus_add_match(bus,
872                            "type='signal',"
873                            "sender='org.freedesktop.systemd1',"
874                            "interface='org.freedesktop.systemd1.Job',"
875                            "member='Changed'",
876                            &error);
877
878         if (dbus_error_is_set(&error)) {
879                 log_error("Failed to add match: %s", error.message);
880                 r = -EIO;
881                 goto finish;
882         }
883
884         if (!dbus_connection_add_filter(bus, monitor_filter, NULL, NULL)) {
885                 log_error("Failed to add filter.");
886                 r = -ENOMEM;
887                 goto finish;
888         }
889
890         if (!(m = dbus_message_new_method_call(
891                               "org.freedesktop.systemd1",
892                               "/org/freedesktop/systemd1",
893                               "org.freedesktop.systemd1.Manager",
894                               "Subscribe"))) {
895                 log_error("Could not allocate message.");
896                 r = -ENOMEM;
897                 goto finish;
898         }
899
900         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
901                 log_error("Failed to issue method call: %s", error.message);
902                 r = -EIO;
903                 goto finish;
904         }
905
906         while (dbus_connection_read_write_dispatch(bus, -1))
907                 ;
908
909         r = 0;
910
911 finish:
912
913         /* This is slightly dirty, since we don't undo the filter or the matches. */
914
915         if (m)
916                 dbus_message_unref(m);
917
918         if (reply)
919                 dbus_message_unref(reply);
920
921         dbus_error_free(&error);
922
923         return r;
924 }
925
926 static int dump(DBusConnection *bus, char **args, unsigned n) {
927         DBusMessage *m = NULL, *reply = NULL;
928         DBusError error;
929         int r;
930         const char *text;
931
932         dbus_error_init(&error);
933
934         if (!(m = dbus_message_new_method_call(
935                               "org.freedesktop.systemd1",
936                               "/org/freedesktop/systemd1",
937                               "org.freedesktop.systemd1.Manager",
938                               "Dump"))) {
939                 log_error("Could not allocate message.");
940                 return -ENOMEM;
941         }
942
943         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
944                 log_error("Failed to issue method call: %s", error.message);
945                 r = -EIO;
946                 goto finish;
947         }
948
949         if (!dbus_message_get_args(reply, &error,
950                                    DBUS_TYPE_STRING, &text,
951                                    DBUS_TYPE_INVALID)) {
952                 log_error("Failed to parse reply: %s", error.message);
953                 r = -EIO;
954                 goto finish;
955         }
956
957         fputs(text, stdout);
958
959         r = 0;
960
961 finish:
962         if (m)
963                 dbus_message_unref(m);
964
965         if (reply)
966                 dbus_message_unref(reply);
967
968         dbus_error_free(&error);
969
970         return r;
971 }
972
973 static int snapshot(DBusConnection *bus, char **args, unsigned n) {
974         DBusMessage *m = NULL, *reply = NULL;
975         DBusError error;
976         int r;
977         const char *name = "", *path, *id;
978         dbus_bool_t cleanup = FALSE;
979         DBusMessageIter iter, sub;
980         const char
981                 *interface = "org.freedesktop.systemd1.Unit",
982                 *property = "Id";
983
984         dbus_error_init(&error);
985
986         if (!(m = dbus_message_new_method_call(
987                               "org.freedesktop.systemd1",
988                               "/org/freedesktop/systemd1",
989                               "org.freedesktop.systemd1.Manager",
990                               "CreateSnapshot"))) {
991                 log_error("Could not allocate message.");
992                 return -ENOMEM;
993         }
994
995         if (n > 1)
996                 name = args[1];
997
998         if (!dbus_message_append_args(m,
999                                       DBUS_TYPE_STRING, &name,
1000                                       DBUS_TYPE_BOOLEAN, &cleanup,
1001                                       DBUS_TYPE_INVALID)) {
1002                 log_error("Could not append arguments to message.");
1003                 r = -ENOMEM;
1004                 goto finish;
1005         }
1006
1007         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
1008                 log_error("Failed to issue method call: %s", error.message);
1009                 r = -EIO;
1010                 goto finish;
1011         }
1012
1013         if (!dbus_message_get_args(reply, &error,
1014                                    DBUS_TYPE_OBJECT_PATH, &path,
1015                                    DBUS_TYPE_INVALID)) {
1016                 log_error("Failed to parse reply: %s", error.message);
1017                 r = -EIO;
1018                 goto finish;
1019         }
1020
1021         dbus_message_unref(m);
1022         if (!(m = dbus_message_new_method_call(
1023                               "org.freedesktop.systemd1",
1024                               path,
1025                               "org.freedesktop.DBus.Properties",
1026                               "Get"))) {
1027                 log_error("Could not allocate message.");
1028                 return -ENOMEM;
1029         }
1030
1031         if (!dbus_message_append_args(m,
1032                                       DBUS_TYPE_STRING, &interface,
1033                                       DBUS_TYPE_STRING, &property,
1034                                       DBUS_TYPE_INVALID)) {
1035                 log_error("Could not append arguments to message.");
1036                 r = -ENOMEM;
1037                 goto finish;
1038         }
1039
1040         dbus_message_unref(reply);
1041         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
1042                 log_error("Failed to issue method call: %s", error.message);
1043                 r = -EIO;
1044                 goto finish;
1045         }
1046
1047         if (!dbus_message_iter_init(reply, &iter) ||
1048             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
1049                 log_error("Failed to parse reply.");
1050                 r = -EIO;
1051                 goto finish;
1052         }
1053
1054         dbus_message_iter_recurse(&iter, &sub);
1055
1056         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
1057                 log_error("Failed to parse reply.");
1058                 r = -EIO;
1059                 goto finish;
1060         }
1061
1062         dbus_message_iter_get_basic(&sub, &id);
1063         puts(id);
1064         r = 0;
1065
1066 finish:
1067         if (m)
1068                 dbus_message_unref(m);
1069
1070         if (reply)
1071                 dbus_message_unref(reply);
1072
1073         dbus_error_free(&error);
1074
1075         return r;
1076 }
1077
1078 static int clear_jobs(DBusConnection *bus, char **args, unsigned n) {
1079         DBusMessage *m = NULL, *reply = NULL;
1080         DBusError error;
1081         int r;
1082         const char *method;
1083
1084         dbus_error_init(&error);
1085
1086         method =
1087                 streq(args[0], "clear-jobs")    ? "ClearJobs" :
1088                 streq(args[0], "daemon-reload") ? "Reload" :
1089                 streq(args[0], "daemon-reexec") ? "Reexecute" :
1090                                                   "Exit";
1091
1092         if (!(m = dbus_message_new_method_call(
1093                               "org.freedesktop.systemd1",
1094                               "/org/freedesktop/systemd1",
1095                               "org.freedesktop.systemd1.Manager",
1096                               method))) {
1097                 log_error("Could not allocate message.");
1098                 return -ENOMEM;
1099         }
1100
1101         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
1102                 log_error("Failed to issue method call: %s", error.message);
1103                 r = -EIO;
1104                 goto finish;
1105         }
1106
1107         r = 0;
1108
1109 finish:
1110         if (m)
1111                 dbus_message_unref(m);
1112
1113         if (reply)
1114                 dbus_message_unref(reply);
1115
1116         dbus_error_free(&error);
1117
1118         return r;
1119 }
1120
1121 static int show_enviroment(DBusConnection *bus, char **args, unsigned n) {
1122         DBusMessage *m = NULL, *reply = NULL;
1123         DBusError error;
1124         DBusMessageIter iter, sub, sub2;
1125         int r;
1126         const char
1127                 *interface = "org.freedesktop.systemd1.Manager",
1128                 *property = "Environment";
1129
1130         dbus_error_init(&error);
1131
1132         if (!(m = dbus_message_new_method_call(
1133                               "org.freedesktop.systemd1",
1134                               "/org/freedesktop/systemd1",
1135                               "org.freedesktop.DBus.Properties",
1136                               "Get"))) {
1137                 log_error("Could not allocate message.");
1138                 return -ENOMEM;
1139         }
1140
1141         if (!dbus_message_append_args(m,
1142                                       DBUS_TYPE_STRING, &interface,
1143                                       DBUS_TYPE_STRING, &property,
1144                                       DBUS_TYPE_INVALID)) {
1145                 log_error("Could not append arguments to message.");
1146                 r = -ENOMEM;
1147                 goto finish;
1148         }
1149
1150         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
1151                 log_error("Failed to issue method call: %s", error.message);
1152                 r = -EIO;
1153                 goto finish;
1154         }
1155
1156         if (!dbus_message_iter_init(reply, &iter) ||
1157             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
1158                 log_error("Failed to parse reply.");
1159                 r = -EIO;
1160                 goto finish;
1161         }
1162
1163         dbus_message_iter_recurse(&iter, &sub);
1164
1165         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_ARRAY ||
1166             dbus_message_iter_get_element_type(&sub) != DBUS_TYPE_STRING)  {
1167                 log_error("Failed to parse reply.");
1168                 r = -EIO;
1169                 goto finish;
1170         }
1171
1172         dbus_message_iter_recurse(&sub, &sub2);
1173
1174         while (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_INVALID) {
1175                 const char *text;
1176
1177                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
1178                         log_error("Failed to parse reply.");
1179                         r = -EIO;
1180                         goto finish;
1181                 }
1182
1183                 dbus_message_iter_get_basic(&sub2, &text);
1184                 printf("%s\n", text);
1185
1186                 dbus_message_iter_next(&sub2);
1187         }
1188
1189         r = 0;
1190
1191 finish:
1192         if (m)
1193                 dbus_message_unref(m);
1194
1195         if (reply)
1196                 dbus_message_unref(reply);
1197
1198         dbus_error_free(&error);
1199
1200         return r;
1201 }
1202
1203 static int set_environment(DBusConnection *bus, char **args, unsigned n) {
1204         DBusMessage *m = NULL, *reply = NULL;
1205         DBusError error;
1206         int r;
1207         const char *method;
1208         DBusMessageIter iter, sub;
1209         unsigned i;
1210
1211         dbus_error_init(&error);
1212
1213         method = streq(args[0], "set-environment")
1214                 ? "SetEnvironment"
1215                 : "UnsetEnvironment";
1216
1217         if (!(m = dbus_message_new_method_call(
1218                               "org.freedesktop.systemd1",
1219                               "/org/freedesktop/systemd1",
1220                               "org.freedesktop.systemd1.Manager",
1221                               method))) {
1222
1223                 log_error("Could not allocate message.");
1224                 return -ENOMEM;
1225         }
1226
1227         dbus_message_iter_init_append(m, &iter);
1228
1229         if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
1230                 log_error("Could not append arguments to message.");
1231                 r = -ENOMEM;
1232                 goto finish;
1233         }
1234
1235         for (i = 1; i < n; i++)
1236                 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &args[i])) {
1237                         log_error("Could not append arguments to message.");
1238                         r = -ENOMEM;
1239                         goto finish;
1240                 }
1241
1242         if (!dbus_message_iter_close_container(&iter, &sub)) {
1243                 log_error("Could not append arguments to message.");
1244                 r = -ENOMEM;
1245                 goto finish;
1246         }
1247
1248         if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
1249                 log_error("Failed to issue method call: %s", error.message);
1250                 r = -EIO;
1251                 goto finish;
1252         }
1253
1254         r = 0;
1255
1256 finish:
1257         if (m)
1258                 dbus_message_unref(m);
1259
1260         if (reply)
1261                 dbus_message_unref(reply);
1262
1263         dbus_error_free(&error);
1264
1265         return r;
1266 }
1267
1268 static int help(void) {
1269
1270         printf("%s [options]\n\n"
1271                "  -h --help      Show this help\n"
1272                "  -t --type=TYPE List only units of a particular type\n"
1273                "  -a --all       Show all units, including dead ones\n"
1274                "     --replace   When installing a new job, replace existing conflicting ones\n"
1275                "     --system    Connect to system bus\n"
1276                "     --session   Connect to session bus\n"
1277                "     --block     Wait until operation finished\n\n"
1278                "Commands:\n"
1279                "  list-units                      List units\n"
1280                "  list-jobs                       List jobs\n"
1281                "  clear-jobs                      Cancel all jobs\n"
1282                "  load [NAME...]                  Load one or more units\n"
1283                "  cancel [JOB...]                 Cancel one or more jobs\n"
1284                "  start [NAME...]                 Start one or more units\n"
1285                "  stop [NAME...]                  Stop one or more units\n"
1286                "  restart [NAME...]               Restart one or more units\n"
1287                "  reload [NAME...]                Reload one or more units\n"
1288                "  isolate [NAME]                  Start one unit and stop all others\n"
1289                "  monitor                         Monitor unit/job changes\n"
1290                "  dump                            Dump server status\n"
1291                "  snapshot [NAME]                 Create a snapshot\n"
1292                "  daemon-reload                   Reload daemon configuration\n"
1293                "  daemon-reexecute                Reexecute daemon\n"
1294                "  daemon-exit                     Ask the daemon to quit\n"
1295                "  show-environment                Dump environment\n"
1296                "  set-environment [NAME=VALUE...] Set one or more environment variables\n"
1297                "  unset-environment [NAME...]     Unset one or more environment variables\n",
1298                __progname);
1299
1300         return 0;
1301 }
1302
1303 static int parse_argv(int argc, char *argv[]) {
1304
1305         enum {
1306                 ARG_REPLACE = 0x100,
1307                 ARG_SESSION,
1308                 ARG_SYSTEM,
1309                 ARG_BLOCK,
1310         };
1311
1312         static const struct option options[] = {
1313                 { "help",      no_argument,       NULL, 'h'         },
1314                 { "type",      required_argument, NULL, 't'         },
1315                 { "all",       no_argument,       NULL, 'a'         },
1316                 { "replace",   no_argument,       NULL, ARG_REPLACE },
1317                 { "session",   no_argument,       NULL, ARG_SESSION },
1318                 { "system",    no_argument,       NULL, ARG_SYSTEM  },
1319                 { "block",     no_argument,       NULL, ARG_BLOCK   }
1320         };
1321
1322         int c;
1323
1324         assert(argc >= 1);
1325         assert(argv);
1326
1327         while ((c = getopt_long(argc, argv, "hta", options, NULL)) >= 0) {
1328
1329                 switch (c) {
1330
1331                 case 'h':
1332                         help();
1333                         return 0;
1334
1335                 case 't':
1336                         arg_type = optarg;
1337                         break;
1338
1339                 case 'a':
1340                         arg_all = true;
1341                         break;
1342
1343                 case ARG_REPLACE:
1344                         arg_replace = true;
1345                         break;
1346
1347                 case ARG_SESSION:
1348                         arg_session = true;
1349                         break;
1350
1351                 case ARG_SYSTEM:
1352                         arg_session = false;
1353                         break;
1354
1355                 case ARG_BLOCK:
1356                         arg_block = true;
1357                         break;
1358
1359                 case '?':
1360                         return -EINVAL;
1361
1362                 default:
1363                         log_error("Unknown option code %c", c);
1364                         return -EINVAL;
1365                 }
1366         }
1367
1368         return 1;
1369 }
1370
1371 int main(int argc, char*argv[]) {
1372
1373
1374         static const struct {
1375                 const char* verb;
1376                 const enum {
1377                         MORE,
1378                         LESS,
1379                         EQUAL
1380                 } argc_cmp;
1381                 const int argc;
1382                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
1383         } verbs[] = {
1384                 { "list-units",        LESS,  1, list_units      },
1385                 { "list-jobs",         EQUAL, 1, list_jobs       },
1386                 { "clear-jobs",        EQUAL, 1, clear_jobs      },
1387                 { "load",              MORE,  2, load_unit       },
1388                 { "cancel",            MORE,  2, cancel_job      },
1389                 { "start",             MORE,  2, start_unit      },
1390                 { "stop",              MORE,  2, start_unit      },
1391                 { "reload",            MORE,  2, start_unit      },
1392                 { "restart",           MORE,  2, start_unit      },
1393                 { "isolate",           EQUAL, 2, isolate_unit    },
1394                 { "monitor",           EQUAL, 1, monitor         },
1395                 { "dump",              EQUAL, 1, dump            },
1396                 { "snapshot",          LESS,  2, snapshot        },
1397                 { "daemon-reload",     EQUAL, 1, clear_jobs      },
1398                 { "daemon-reexec",     EQUAL, 1, clear_jobs      },
1399                 { "daemon-exit",       EQUAL, 1, clear_jobs      },
1400                 { "show-environment",  EQUAL, 1, show_enviroment },
1401                 { "set-environment",   MORE,  2, set_environment },
1402                 { "unset-environment", MORE,  2, set_environment },
1403         };
1404
1405         int r, retval = 1, left;
1406         unsigned i;
1407         DBusConnection *bus = NULL;
1408         DBusError error;
1409
1410         dbus_error_init(&error);
1411
1412         log_set_target(LOG_TARGET_CONSOLE);
1413         log_parse_environment();
1414
1415         if ((r = parse_argv(argc, argv)) < 0)
1416                 goto finish;
1417         else if (r == 0) {
1418                 retval = 0;
1419                 goto finish;
1420         }
1421
1422         left = argc - optind;
1423
1424         if (left <= 0)
1425                 /* Special rule: no arguments means "list-units" */
1426                 i = 0;
1427         else {
1428                 for (i = 0; i < ELEMENTSOF(verbs); i++)
1429                         if (streq(argv[optind], verbs[i].verb))
1430                                 break;
1431
1432                 if (i >= ELEMENTSOF(verbs)) {
1433                         log_error("Unknown operation %s", argv[optind]);
1434                         goto finish;
1435                 }
1436         }
1437
1438         switch (verbs[i].argc_cmp) {
1439
1440         case EQUAL:
1441                 if (left != verbs[i].argc) {
1442                         log_error("Invalid number of arguments.");
1443                         goto finish;
1444                 }
1445
1446                 break;
1447
1448         case MORE:
1449                 if (left < verbs[i].argc) {
1450                         log_error("Too few arguments.");
1451                         goto finish;
1452                 }
1453
1454                 break;
1455
1456         case LESS:
1457                 if (left > verbs[i].argc) {
1458                         log_error("Too many arguments.");
1459                         goto finish;
1460                 }
1461
1462                 break;
1463
1464         default:
1465                 assert_not_reached("Unknown comparison operator.");
1466         }
1467
1468         if (!(bus = dbus_bus_get(arg_session ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &error))) {
1469                 log_error("Failed to get D-Bus connection: %s", error.message);
1470                 goto finish;
1471         }
1472
1473         dbus_connection_set_exit_on_disconnect(bus, FALSE);
1474
1475         retval = verbs[i].dispatch(bus, argv + optind, left) < 0;
1476
1477 finish:
1478
1479         if (bus)
1480                 dbus_connection_unref(bus);
1481
1482         dbus_shutdown();
1483
1484         return retval;
1485 }