]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/install.c
install: implement --start option
[thirdparty/systemd.git] / src / install.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 <sys/stat.h>
23 #include <stdio.h>
24 #include <getopt.h>
25 #include <errno.h>
26 #include <unistd.h>
27
28 #include "log.h"
29 #include "path-lookup.h"
30 #include "util.h"
31 #include "macro.h"
32 #include "strv.h"
33 #include "conf-parser.h"
34 #include "dbus-common.h"
35
36 static bool arg_force = false;
37
38 static enum {
39 WHERE_SYSTEM,
40 WHERE_SESSION,
41 WHERE_GLOBAL,
42 } arg_where = WHERE_SYSTEM;
43
44 static enum {
45 ACTION_INVALID,
46 ACTION_ENABLE,
47 ACTION_DISABLE,
48 ACTION_TEST
49 } arg_action = ACTION_INVALID;
50
51 static enum {
52 START_NO, /* Don't start/stop or anything */
53 START_MINIMAL, /* Only shutdown/restart if running. */
54 START_MAYBE, /* Start if WantedBy= suggests */
55 START_YES /* Start unconditionally */
56 } arg_start = START_NO;
57
58 typedef struct {
59 char *name;
60 char *path;
61
62 char **aliases;
63 char **wanted_by;
64 } InstallInfo;
65
66 Hashmap *will_install = NULL, *have_installed = NULL;
67
68 static int help(void) {
69
70 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
71 "Install init system units.\n\n"
72 " -h --help Show this help\n"
73 " --force Override existing links\n"
74 " --system Install into system\n"
75 " --session Install into session\n"
76 " --global Install into all sessions\n"
77 " --start[=MODE] Start/stop/restart unit after installation\n"
78 " Takes 'no', 'minimal', 'maybe' or 'yes'\n\n"
79 "Commands:\n"
80 " enable [NAME...] Enable one or more units\n"
81 " disable [NAME...] Disable one or more units\n"
82 " test [NAME...] Test whether any of the specified units are enabled\n",
83 program_invocation_short_name);
84
85 return 0;
86 }
87
88 static int parse_argv(int argc, char *argv[]) {
89
90 enum {
91 ARG_SESSION = 0x100,
92 ARG_SYSTEM,
93 ARG_GLOBAL,
94 ARG_FORCE,
95 ARG_START
96 };
97
98 static const struct option options[] = {
99 { "help", no_argument, NULL, 'h' },
100 { "session", no_argument, NULL, ARG_SESSION },
101 { "system", no_argument, NULL, ARG_SYSTEM },
102 { "global", no_argument, NULL, ARG_GLOBAL },
103 { "force", no_argument, NULL, ARG_FORCE },
104 { "start", optional_argument, NULL, ARG_START },
105 { NULL, 0, NULL, 0 }
106 };
107
108 int c;
109
110 assert(argc >= 1);
111 assert(argv);
112
113 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
114
115 switch (c) {
116
117 case 'h':
118 help();
119 return 0;
120
121 case ARG_SESSION:
122 arg_where = WHERE_SESSION;
123 break;
124
125 case ARG_SYSTEM:
126 arg_where = WHERE_SYSTEM;
127 break;
128
129 case ARG_GLOBAL:
130 arg_where = WHERE_GLOBAL;
131 break;
132
133 case ARG_FORCE:
134 arg_force = true;
135 break;
136
137 case ARG_START:
138
139 if (!optarg)
140 arg_start = START_MAYBE;
141 else if (streq(optarg, "no"))
142 arg_start = START_NO;
143 else if (streq(optarg, "minimal"))
144 arg_start = START_MINIMAL;
145 else if (streq(optarg, "maybe"))
146 arg_start = START_MAYBE;
147 else if (streq(optarg, "yes"))
148 arg_start = START_YES;
149 else {
150 log_error("Invalid --start argument %s", optarg);
151 return -EINVAL;
152 }
153
154 break;
155
156 case '?':
157 return -EINVAL;
158
159 default:
160 log_error("Unknown option code %c", c);
161 return -EINVAL;
162 }
163 }
164
165 if (optind >= argc) {
166 help();
167 return -EINVAL;
168 }
169
170 if (streq(argv[optind], "enable"))
171 arg_action = ACTION_ENABLE;
172 else if (streq(argv[optind], "disable"))
173 arg_action = ACTION_DISABLE;
174 else if (streq(argv[optind], "test"))
175 arg_action = ACTION_TEST;
176 else {
177 log_error("Unknown verb %s.", argv[optind]);
178 return -EINVAL;
179 }
180
181 optind++;
182
183 if (optind >= argc) {
184 log_error("Missing unit name.");
185 return -EINVAL;
186 }
187
188
189 return 1;
190 }
191
192 static void install_info_free(InstallInfo *i) {
193 assert(i);
194
195 free(i->name);
196 free(i->path);
197 strv_free(i->aliases);
198 strv_free(i->wanted_by);
199 free(i);
200 }
201
202 static void install_info_hashmap_free(Hashmap *m) {
203 InstallInfo *i;
204
205 while ((i = hashmap_steal_first(m)))
206 install_info_free(i);
207
208 hashmap_free(m);
209 }
210
211 static bool unit_name_valid(const char *name) {
212
213 /* This is a minimal version of unit_name_valid() from
214 * unit-name.c */
215
216 if (!*name)
217 return false;
218
219 if (ignore_file(name))
220 return false;
221
222 return true;
223 }
224
225 static int install_info_add(const char *name) {
226 InstallInfo *i;
227 int r;
228
229 if (!unit_name_valid(name))
230 return -EINVAL;
231
232 if (hashmap_get(have_installed, name) ||
233 hashmap_get(will_install, name))
234 return 0;
235
236 if (!(i = new0(InstallInfo, 1))) {
237 r = -ENOMEM;
238 goto fail;
239 }
240
241 if (!(i->name = strdup(name))) {
242 r = -ENOMEM;
243 goto fail;
244 }
245
246 if ((r = hashmap_put(will_install, i->name, i)) < 0)
247 goto fail;
248
249 return 0;
250
251 fail:
252 if (i)
253 install_info_free(i);
254
255 return r;
256 }
257
258 static int daemon_reload(DBusConnection *bus) {
259 DBusMessage *m = NULL, *reply = NULL;
260 DBusError error;
261 int r;
262
263 assert(bus);
264
265 dbus_error_init(&error);
266
267 if (!(m = dbus_message_new_method_call(
268 "org.freedesktop.systemd1",
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
271 "Reload"))) {
272 log_error("Could not allocate message.");
273 return -ENOMEM;
274 }
275
276 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
277 log_error("Failed to reload configuration: %s", error.message);
278 r = -EIO;
279 goto finish;
280 }
281
282 r = 0;
283
284 finish:
285 if (m)
286 dbus_message_unref(m);
287
288 if (reply)
289 dbus_message_unref(reply);
290
291 dbus_error_free(&error);
292
293 return r;
294 }
295
296 static int install_info_run(DBusConnection *bus, InstallInfo *i) {
297 DBusMessage *m = NULL, *reply = NULL;
298 DBusError error;
299 int r;
300 const char *mode = "replace";
301
302 assert(bus);
303 assert(i);
304
305 dbus_error_init(&error);
306
307 if (arg_action == ACTION_ENABLE) {
308
309 if (arg_start == START_MAYBE) {
310 char **k;
311 bool yes_please = false;
312
313 STRV_FOREACH(k, i->wanted_by) {
314 DBusMessageIter sub, iter;
315
316 const char *path, *state;
317 const char *interface = "org.freedesktop.systemd1.Unit";
318 const char *property = "ActiveState";
319
320 if (!(m = dbus_message_new_method_call(
321 "org.freedesktop.systemd1",
322 "/org/freedesktop/systemd1",
323 "org.freedesktop.systemd1.Manager",
324 "GetUnit"))) {
325 log_error("Could not allocate message.");
326 r = -ENOMEM;
327 goto finish;
328 }
329
330 if (!dbus_message_append_args(m,
331 DBUS_TYPE_STRING, k,
332 DBUS_TYPE_INVALID)) {
333 log_error("Could not append arguments to message.");
334 r = -ENOMEM;
335 goto finish;
336 }
337
338 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
339 /* Hmm, this unit doesn't exist, let's try the next one */
340 dbus_message_unref(m);
341 m = NULL;
342 continue;
343 }
344
345 if (!dbus_message_get_args(reply, &error,
346 DBUS_TYPE_OBJECT_PATH, &path,
347 DBUS_TYPE_INVALID)) {
348 log_error("Failed to parse reply: %s", error.message);
349 r = -EIO;
350 goto finish;
351 }
352
353 dbus_message_unref(m);
354 if (!(m = dbus_message_new_method_call(
355 "org.freedesktop.systemd1",
356 path,
357 "org.freedesktop.DBus.Properties",
358 "Get"))) {
359 log_error("Could not allocate message.");
360 r = -ENOMEM;
361 goto finish;
362 }
363
364 if (!dbus_message_append_args(m,
365 DBUS_TYPE_STRING, &interface,
366 DBUS_TYPE_STRING, &property,
367 DBUS_TYPE_INVALID)) {
368 log_error("Could not append arguments to message.");
369 r = -ENOMEM;
370 goto finish;
371 }
372
373 dbus_message_unref(reply);
374 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
375 log_error("Failed to issue method call: %s", error.message);
376 r = -EIO;
377 goto finish;
378 }
379
380 if (!dbus_message_iter_init(reply, &iter) ||
381 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
382 log_error("Failed to parse reply.");
383 r = -EIO;
384 goto finish;
385 }
386
387 dbus_message_iter_recurse(&iter, &sub);
388
389 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
390 log_error("Failed to parse reply.");
391 r = -EIO;
392 goto finish;
393 }
394
395 dbus_message_iter_get_basic(&sub, &state);
396
397 dbus_message_unref(m);
398 dbus_message_unref(reply);
399 m = reply = NULL;
400
401 if (streq(state, "active") ||
402 startswith(state, "reloading") ||
403 startswith(state, "activating")) {
404 yes_please = true;
405 break;
406 }
407 }
408
409 if (!yes_please) {
410 r = 0;
411 goto finish;
412 }
413 }
414
415 if (!(m = dbus_message_new_method_call(
416 "org.freedesktop.systemd1",
417 "/org/freedesktop/systemd1",
418 "org.freedesktop.systemd1.Manager",
419 arg_start == START_MINIMAL ? "TryRestartUnit" : "RestartUnit"))) {
420 log_error("Could not allocate message.");
421 r = -ENOMEM;
422 goto finish;
423 }
424
425 if (!dbus_message_append_args(m,
426 DBUS_TYPE_STRING, &i->name,
427 DBUS_TYPE_STRING, &mode,
428 DBUS_TYPE_INVALID)) {
429 log_error("Could not append arguments to message.");
430 r = -ENOMEM;
431 goto finish;
432 }
433
434
435 } else if (arg_action == ACTION_DISABLE) {
436
437 if (!(m = dbus_message_new_method_call(
438 "org.freedesktop.systemd1",
439 "/org/freedesktop/systemd1",
440 "org.freedesktop.systemd1.Manager",
441 "StopUnit"))) {
442 log_error("Could not allocate message.");
443 r = -ENOMEM;
444 goto finish;
445 }
446
447 if (!dbus_message_append_args(m,
448 DBUS_TYPE_STRING, &i->name,
449 DBUS_TYPE_STRING, &mode,
450 DBUS_TYPE_INVALID)) {
451 log_error("Could not append arguments to message.");
452 r = -ENOMEM;
453 goto finish;
454 }
455 }
456
457 if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
458 log_error("Failed to reload configuration: %s", error.message);
459 r = -EIO;
460 goto finish;
461 }
462
463 r = 0;
464
465 finish:
466 if (m)
467 dbus_message_unref(m);
468
469 if (reply)
470 dbus_message_unref(reply);
471
472 dbus_error_free(&error);
473
474 return r;
475 }
476
477 static int config_parse_also(
478 const char *filename,
479 unsigned line,
480 const char *section,
481 const char *lvalue,
482 const char *rvalue,
483 void *data,
484 void *userdata) {
485
486 char *w;
487 size_t l;
488 char *state;
489
490 assert(filename);
491 assert(lvalue);
492 assert(rvalue);
493
494 FOREACH_WORD_QUOTED(w, l, rvalue, state) {
495 char *n;
496 int r;
497
498 if (!(n = strndup(w, l)))
499 return -ENOMEM;
500
501 r = install_info_add(n);
502 free(n);
503
504 if (r < 0)
505 return r;
506 }
507
508 return 0;
509 }
510
511 static int create_symlink(const char *old_path, const char *new_path) {
512 int r;
513
514 assert(old_path);
515 assert(new_path);
516
517 if (arg_action == ACTION_ENABLE) {
518 char *dest;
519
520 mkdir_parents(new_path, 0755);
521
522 if (symlink(old_path, new_path) >= 0)
523 return 0;
524
525 if (errno != EEXIST) {
526 log_error("Cannot link %s to %s: %m", old_path, new_path);
527 return -errno;
528 }
529
530 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
531
532 if (errno == EINVAL) {
533 log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path);
534 return -EEXIST;
535 }
536
537 log_error("readlink() failed: %s", strerror(-r));
538 return r;
539 }
540
541 if (streq(dest, old_path)) {
542 free(dest);
543 return 0;
544 }
545
546 if (!arg_force) {
547 log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest);
548 free(dest);
549 return -EEXIST;
550 }
551
552 free(dest);
553 unlink(new_path);
554
555 if (symlink(old_path, new_path) >= 0)
556 return 0;
557
558 log_error("Cannot link %s to %s: %m", old_path, new_path);
559 return -errno;
560
561 } else if (arg_action == ACTION_DISABLE) {
562 char *dest;
563
564 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
565 if (errno == ENOENT)
566 return 0;
567
568 if (errno == EINVAL) {
569 log_warning("File %s not a symlink, ignoring.", old_path);
570 return 0;
571 }
572
573 log_error("readlink() failed: %s", strerror(-r));
574 return r;
575 }
576
577 if (!streq(dest, old_path)) {
578 log_warning("File %s not a symlink to %s but points to %s, ignoring.", new_path, old_path, dest);
579 free(dest);
580 return 0;
581 }
582
583 free(dest);
584 if (unlink(new_path) >= 0)
585 return 0;
586
587 log_error("Cannot unlink %s: %m", new_path);
588 return -errno;
589
590 } else if (arg_action == ACTION_TEST) {
591 char *dest;
592
593 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
594
595 if (errno == ENOENT || errno == EINVAL)
596 return 0;
597
598 log_error("readlink() failed: %s", strerror(-r));
599 return r;
600 }
601
602 if (streq(dest, old_path)) {
603 free(dest);
604 return 1;
605 }
606
607 return 0;
608 }
609
610 assert_not_reached("Unknown action.");
611 }
612
613 static int install_info_symlink_alias(InstallInfo *i, const char *config_path) {
614 char **s;
615 char *alias_path = NULL;
616 int r;
617
618 assert(i);
619
620 STRV_FOREACH(s, i->aliases) {
621
622 if (!unit_name_valid(*s)) {
623 log_error("Invalid name %s.", *s);
624 r = -EINVAL;
625 goto finish;
626 }
627
628 free(alias_path);
629 if (!(alias_path = path_make_absolute(*s, config_path))) {
630 log_error("Out of memory");
631 r = -ENOMEM;
632 goto finish;
633 }
634
635 if ((r = create_symlink(i->path, alias_path)) != 0)
636 goto finish;
637
638 if (arg_action == ACTION_DISABLE)
639 rmdir_parents(alias_path, config_path);
640 }
641
642 r = 0;
643
644 finish:
645 free(alias_path);
646
647 return r;
648 }
649
650 static int install_info_symlink_wants(InstallInfo *i, const char *config_path) {
651 char **s;
652 char *alias_path = NULL;
653 int r;
654
655 assert(i);
656
657 STRV_FOREACH(s, i->wanted_by) {
658 if (!unit_name_valid(*s)) {
659 log_error("Invalid name %s.", *s);
660 r = -EINVAL;
661 goto finish;
662 }
663
664 free(alias_path);
665 alias_path = NULL;
666
667 if (asprintf(&alias_path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) {
668 log_error("Out of memory");
669 r = -ENOMEM;
670 goto finish;
671 }
672
673 if ((r = create_symlink(i->path, alias_path)) != 0)
674 goto finish;
675
676 if (arg_action == ACTION_DISABLE)
677 rmdir_parents(alias_path, config_path);
678 }
679
680 r = 0;
681
682 finish:
683 free(alias_path);
684
685 return r;
686 }
687
688 static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) {
689
690 const ConfigItem items[] = {
691 { "Alias", config_parse_strv, &i->aliases, "Install" },
692 { "WantedBy", config_parse_strv, &i->wanted_by, "Install" },
693 { "Also", config_parse_also, NULL, "Install" },
694
695 { NULL, NULL, NULL, NULL }
696 };
697
698 char **p;
699 char *filename = NULL;
700 FILE *f = NULL;
701 int r;
702
703 assert(paths);
704 assert(i);
705
706 STRV_FOREACH(p, paths->unit_path) {
707
708 if (!(filename = path_make_absolute(i->name, *p))) {
709 log_error("Out of memory");
710 return -ENOMEM;
711 }
712
713 if ((f = fopen(filename, "re")))
714 break;
715
716 free(filename);
717 filename = NULL;
718
719 if (errno != ENOENT) {
720 log_error("Failed to open %s: %m", filename);
721 return -errno;
722 }
723 }
724
725 if (!f) {
726 log_error("Couldn't find %s.", i->name);
727 return -ENOENT;
728 }
729
730 i->path = filename;
731
732 if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) {
733 fclose(f);
734 return r;
735 }
736
737 fclose(f);
738
739 if ((r = install_info_symlink_alias(i, config_path)) != 0)
740 return r;
741
742 if ((r = install_info_symlink_wants(i, config_path)) != 0)
743 return r;
744
745 return 0;
746 }
747
748 static char *get_config_path(void) {
749
750 switch (arg_where) {
751
752 case WHERE_SYSTEM:
753 return strdup(SYSTEM_CONFIG_UNIT_PATH);
754
755 case WHERE_GLOBAL:
756 return strdup(SESSION_CONFIG_UNIT_PATH);
757
758 case WHERE_SESSION: {
759 char *p;
760
761 if (session_config_home(&p) < 0)
762 return NULL;
763
764 return p;
765 }
766
767 default:
768 assert_not_reached("Unknown config path.");
769 }
770 }
771
772 static int do_run(void) {
773 DBusConnection *bus = NULL;
774 DBusError error;
775 int r, q;
776 Iterator i;
777 InstallInfo *j;
778
779 dbus_error_init(&error);
780
781 if (arg_start == START_NO)
782 return 0;
783
784 if (arg_where == WHERE_GLOBAL) {
785 log_warning("Warning: --start has no effect with --global.");
786 return 0;
787 }
788
789 if (arg_action != ACTION_ENABLE && arg_action != ACTION_DISABLE) {
790 log_warning("Warning: --start has no effect with test.");
791 return 0;
792 }
793
794 if ((r = bus_connect(arg_where == WHERE_SESSION ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &error)) < 0) {
795 log_error("Failed to get D-Bus connection: %s", error.message);
796 goto finish;
797 }
798
799 r = 0;
800
801 if (arg_action == ACTION_ENABLE)
802 if ((r = daemon_reload(bus)) < 0)
803 goto finish;
804
805 HASHMAP_FOREACH(j, have_installed, i)
806 if ((q = install_info_run(bus, j)) < 0)
807 r = q;
808
809 if (arg_action == ACTION_DISABLE)
810 if ((q = daemon_reload(bus)) < 0)
811 r = q;
812
813 finish:
814 if (bus)
815 dbus_connection_unref(bus);
816
817 dbus_error_free(&error);
818
819 dbus_shutdown();
820 return r;
821 }
822
823 int main(int argc, char *argv[]) {
824 int r, retval = 1, j;
825 LookupPaths paths;
826 InstallInfo *i;
827 char *config_path = NULL;
828
829 zero(paths);
830
831 log_parse_environment();
832
833 if ((r = parse_argv(argc, argv)) < 0)
834 goto finish;
835 else if (r == 0) {
836 retval = 0;
837 goto finish;
838 }
839
840 if ((r = lookup_paths_init(&paths, arg_where == WHERE_SYSTEM ? MANAGER_SYSTEM : MANAGER_SESSION)) < 0) {
841 log_error("Failed to determine lookup paths: %s", strerror(-r));
842 goto finish;
843 }
844
845 if (!(config_path = get_config_path())) {
846 log_error("Failed to determine config path");
847 goto finish;
848 }
849
850 will_install = hashmap_new(string_hash_func, string_compare_func);
851 have_installed = hashmap_new(string_hash_func, string_compare_func);
852
853 if (!will_install || !have_installed) {
854 log_error("Failed to allocate unit sets.");
855 goto finish;
856 }
857
858 for (j = optind; j < argc; j++)
859 if ((r = install_info_add(argv[j])) < 0)
860 goto finish;
861
862 while ((i = hashmap_first(will_install))) {
863 assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0);
864
865 if ((r = install_info_apply(&paths, i, config_path)) != 0) {
866
867 if (r < 0)
868 goto finish;
869
870 /* In test mode and found something */
871 retval = 0;
872 goto finish;
873 }
874 }
875
876 if (do_run() < 0)
877 goto finish;
878
879 retval = arg_action == ACTION_TEST ? 1 : 0;
880
881 finish:
882 install_info_hashmap_free(will_install);
883 install_info_hashmap_free(have_installed);
884
885 lookup_paths_free(&paths);
886
887 free(config_path);
888
889 return retval;
890 }