chiark / gitweb /
e30f623627eddccd5c4a94efc28446d37e9bb990
[elogind.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
35 static bool arg_force = false;
36
37 static enum {
38         WHERE_SYSTEM,
39         WHERE_SESSION,
40         WHERE_GLOBAL,
41 } arg_where = WHERE_SYSTEM;
42
43 static enum {
44         ACTION_INVALID,
45         ACTION_ENABLE,
46         ACTION_DISABLE,
47         ACTION_TEST
48 } arg_action = ACTION_INVALID;
49
50 typedef struct {
51         char *name;
52         char *path;
53
54         char **aliases;
55         char **wanted_by;
56 } InstallInfo;
57
58 Hashmap *will_install = NULL, *have_installed = NULL;
59
60 static int help(void) {
61
62         printf("%s [options]\n\n"
63                "Install init system units.\n\n"
64                "  -h --help        Show this help\n"
65                "     --force       Override existing links\n"
66                "     --system      Install into system\n"
67                "     --session     Install into session\n"
68                "     --global      Install into all sessions\n"
69                "Commands:\n"
70                "  enable [NAME...]    Enable one or more units\n"
71                "  disable [NAME...]   Disable one or more units\n"
72                "  test [NAME...]      Test whether any of the specified units are enabled\n",
73                program_invocation_short_name);
74
75         return 0;
76 }
77
78 static int parse_argv(int argc, char *argv[]) {
79
80         enum {
81                 ARG_SESSION = 0x100,
82                 ARG_SYSTEM,
83                 ARG_GLOBAL,
84                 ARG_FORCE
85         };
86
87         static const struct option options[] = {
88                 { "help",      no_argument,       NULL, 'h'         },
89                 { "session",   no_argument,       NULL, ARG_SESSION },
90                 { "system",    no_argument,       NULL, ARG_SYSTEM  },
91                 { "global",    no_argument,       NULL, ARG_GLOBAL  },
92                 { "force",     no_argument,       NULL, ARG_FORCE   },
93                 { NULL,        0,                 NULL, 0           }
94         };
95
96         int c;
97
98         assert(argc >= 1);
99         assert(argv);
100
101         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
102
103                 switch (c) {
104
105                 case 'h':
106                         help();
107                         return 0;
108
109                 case ARG_SESSION:
110                         arg_where = WHERE_SESSION;
111                         break;
112
113                 case ARG_SYSTEM:
114                         arg_where = WHERE_SYSTEM;
115                         break;
116
117                 case ARG_GLOBAL:
118                         arg_where = WHERE_GLOBAL;
119                         break;
120
121                 case ARG_FORCE:
122                         arg_force = true;
123                         break;
124
125                 case '?':
126                         return -EINVAL;
127
128                 default:
129                         log_error("Unknown option code %c", c);
130                         return -EINVAL;
131                 }
132         }
133
134         if (optind >= argc) {
135                 log_error("Missing verb.");
136                 return -EINVAL;
137         }
138
139         if (streq(argv[optind], "enable"))
140                 arg_action = ACTION_ENABLE;
141         else if (streq(argv[optind], "disable"))
142                 arg_action = ACTION_DISABLE;
143         else if (streq(argv[optind], "test"))
144                 arg_action = ACTION_TEST;
145         else {
146                 log_error("Unknown verb %s", argv[optind]);
147                 return -EINVAL;
148         }
149
150         optind++;
151
152         if (optind >= argc) {
153                 log_error("Missing unit name.");
154                 return -EINVAL;
155         }
156
157         return 1;
158 }
159
160 static void install_info_free(InstallInfo *i) {
161         assert(i);
162
163         free(i->name);
164         free(i->path);
165         strv_free(i->aliases);
166         strv_free(i->wanted_by);
167         free(i);
168 }
169
170 static void install_info_hashmap_free(Hashmap *m) {
171         InstallInfo *i;
172
173         while ((i = hashmap_steal_first(m)))
174                 install_info_free(i);
175
176         hashmap_free(m);
177 }
178
179 static bool unit_name_valid(const char *name) {
180
181         /* This is a minimal version of unit_name_valid() from
182          * unit-name.c */
183
184         if (!*name)
185                 return false;
186
187         if (ignore_file(name))
188                 return false;
189
190         return true;
191 }
192
193 static int install_info_add(const char *name) {
194         InstallInfo *i;
195         int r;
196
197         if (!unit_name_valid(name))
198                 return -EINVAL;
199
200         if (hashmap_get(have_installed, name) ||
201             hashmap_get(will_install, name))
202                 return 0;
203
204         if (!(i = new0(InstallInfo, 1))) {
205                 r = -ENOMEM;
206                 goto fail;
207         }
208
209         if (!(i->name = strdup(name))) {
210                 r = -ENOMEM;
211                 goto fail;
212         }
213
214         if ((r = hashmap_put(will_install, i->name, i)) < 0)
215                 goto fail;
216
217         return 0;
218
219 fail:
220         if (i)
221                 install_info_free(i);
222
223         return r;
224 }
225
226 static int config_parse_also(
227                 const char *filename,
228                 unsigned line,
229                 const char *section,
230                 const char *lvalue,
231                 const char *rvalue,
232                 void *data,
233                 void *userdata) {
234
235         char *w;
236         size_t l;
237         char *state;
238
239         assert(filename);
240         assert(lvalue);
241         assert(rvalue);
242
243         FOREACH_WORD_QUOTED(w, l, rvalue, state) {
244                 char *n;
245                 int r;
246
247                 if (!(n = strndup(w, l)))
248                         return -ENOMEM;
249
250                 r = install_info_add(n);
251                 free(n);
252
253                 if (r < 0)
254                         return r;
255         }
256
257         return 0;
258 }
259
260 static int create_symlink(const char *old_path, const char *new_path) {
261         int r;
262
263         assert(old_path);
264         assert(new_path);
265
266         if (arg_action == ACTION_ENABLE) {
267                 char *dest;
268
269                 mkdir_parents(new_path, 0755);
270
271                 if (symlink(old_path, new_path) >= 0)
272                         return 0;
273
274                 if (errno != EEXIST) {
275                         log_error("Cannot link %s to %s: %m", old_path, new_path);
276                         return -errno;
277                 }
278
279                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
280
281                         if (errno == EINVAL) {
282                                 log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path);
283                                 return -EEXIST;
284                         }
285
286                         log_error("readlink() failed: %s", strerror(-r));
287                         return r;
288                 }
289
290                 if (streq(dest, old_path)) {
291                         free(dest);
292                         return 0;
293                 }
294
295                 if (!arg_force) {
296                         log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest);
297                         free(dest);
298                         return -EEXIST;
299                 }
300
301                 free(dest);
302                 unlink(new_path);
303
304                 if (symlink(old_path, new_path) >= 0)
305                         return 0;
306
307                 log_error("Cannot link %s to %s: %m", old_path, new_path);
308                 return -errno;
309
310         } else if (arg_action == ACTION_DISABLE) {
311                 char *dest;
312
313                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
314                         if (errno == ENOENT)
315                                 return 0;
316
317                         if (errno == EINVAL) {
318                                 log_warning("File %s not a symlink, ignoring.", old_path);
319                                 return 0;
320                         }
321
322                         log_error("readlink() failed: %s", strerror(-r));
323                         return r;
324                 }
325
326                 if (!streq(dest, old_path)) {
327                         log_warning("File %s not a symlink to %s but points to %s, ignoring.", new_path, old_path, dest);
328                         free(dest);
329                         return 0;
330                 }
331
332                 free(dest);
333                 if (unlink(new_path) >= 0)
334                         return 0;
335
336                 log_error("Cannot unlink %s: %m", new_path);
337                 return -errno;
338
339         } else if (arg_action == ACTION_TEST) {
340                 char *dest;
341
342                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
343
344                         if (errno == ENOENT || errno == EINVAL)
345                                 return 0;
346
347                         log_error("readlink() failed: %s", strerror(-r));
348                         return r;
349                 }
350
351                 if (streq(dest, old_path)) {
352                         free(dest);
353                         return 1;
354                 }
355
356                 return 0;
357         }
358
359         assert_not_reached("Unknown action.");
360 }
361
362 static int install_info_symlink_alias(InstallInfo *i, const char *config_path) {
363         char **s;
364         char *alias_path = NULL;
365         int r;
366
367         assert(i);
368
369         STRV_FOREACH(s, i->aliases) {
370
371                 if (!unit_name_valid(*s)) {
372                         log_error("Invalid name %s.", *s);
373                         r = -EINVAL;
374                         goto finish;
375                 }
376
377                 free(alias_path);
378                 if (!(alias_path = path_make_absolute(*s, config_path))) {
379                         log_error("Out of memory");
380                         r = -ENOMEM;
381                         goto finish;
382                 }
383
384                 if ((r = create_symlink(i->path, alias_path)) != 0)
385                         goto finish;
386
387                 if (arg_action == ACTION_DISABLE)
388                         rmdir_parents(alias_path, config_path);
389         }
390
391         r = 0;
392
393 finish:
394         free(alias_path);
395
396         return r;
397 }
398
399 static int install_info_symlink_wants(InstallInfo *i, const char *config_path) {
400         char **s;
401         char *alias_path = NULL;
402         int r;
403
404         assert(i);
405
406         STRV_FOREACH(s, i->wanted_by) {
407                 if (!unit_name_valid(*s)) {
408                         log_error("Invalid name %s.", *s);
409                         r = -EINVAL;
410                         goto finish;
411                 }
412
413                 free(alias_path);
414                 alias_path = NULL;
415
416                 if (asprintf(&alias_path, "%s/%s.wants/%s", config_path, *s, i->name) < 0) {
417                         log_error("Out of memory");
418                         r = -ENOMEM;
419                         goto finish;
420                 }
421
422                 if ((r = create_symlink(i->path, alias_path)) != 0)
423                         goto finish;
424
425                 if (arg_action == ACTION_DISABLE)
426                         rmdir_parents(alias_path, config_path);
427         }
428
429         r = 0;
430
431 finish:
432         free(alias_path);
433
434         return r;
435 }
436
437 static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) {
438
439         const ConfigItem items[] = {
440                 { "Alias",    config_parse_strv, &i->aliases,   "Install" },
441                 { "WantedBy", config_parse_strv, &i->wanted_by, "Install" },
442                 { "Also",     config_parse_also, NULL,          "Install" },
443
444                 { NULL, NULL, NULL, NULL }
445         };
446
447         char **p;
448         char *filename = NULL;
449         FILE *f = NULL;
450         int r;
451
452         assert(paths);
453         assert(i);
454
455         STRV_FOREACH(p, paths->unit_path) {
456
457                 if (!(filename = path_make_absolute(i->name, *p))) {
458                         log_error("Out of memory");
459                         return -ENOMEM;
460                 }
461
462                 if ((f = fopen(filename, "re")))
463                         break;
464
465                 free(filename);
466                 filename = NULL;
467
468                 if (errno != ENOENT) {
469                         log_error("Failed to open %s: %m", filename);
470                         return -errno;
471                 }
472         }
473
474         if (!f) {
475                 log_error("Couldn't find %s.", i->name);
476                 return -ENOENT;
477         }
478
479         i->path = filename;
480
481         if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) {
482                 fclose(f);
483                 return r;
484         }
485
486         fclose(f);
487
488         if ((r = install_info_symlink_alias(i, config_path)) != 0)
489                 return r;
490
491         if ((r = install_info_symlink_wants(i, config_path)) != 0)
492                 return r;
493
494         return 0;
495 }
496
497 static char *get_config_path(void) {
498
499         switch (arg_where) {
500
501         case WHERE_SYSTEM:
502                 return strdup(SYSTEM_CONFIG_UNIT_PATH);
503
504         case WHERE_GLOBAL:
505                 return strdup(SESSION_CONFIG_UNIT_PATH);
506
507         case WHERE_SESSION: {
508                 char *p;
509
510                 if (session_config_home(&p) < 0)
511                         return NULL;
512
513                 return p;
514         }
515
516         default:
517                 assert_not_reached("Unknown config path.");
518         }
519 }
520
521 int main(int argc, char *argv[]) {
522         int r, retval = 1, j;
523         LookupPaths paths;
524         InstallInfo *i;
525         char *config_path = NULL;
526
527         zero(paths);
528
529         log_parse_environment();
530
531         if ((r = parse_argv(argc, argv)) < 0)
532                 goto finish;
533         else if (r == 0) {
534                 retval = 0;
535                 goto finish;
536         }
537
538         if ((r = lookup_paths_init(&paths, arg_where == WHERE_SYSTEM ? MANAGER_INIT : MANAGER_SESSION)) < 0) {
539                 log_error("Failed to determine lookup paths: %s", strerror(-r));
540                 goto finish;
541         }
542
543         if (!(config_path = get_config_path())) {
544                 log_error("Failed to determine config path");
545                 goto finish;
546         }
547
548         will_install = hashmap_new(string_hash_func, string_compare_func);
549         have_installed = hashmap_new(string_hash_func, string_compare_func);
550
551         if (!will_install || !have_installed) {
552                 log_error("Failed to allocate unit sets.");
553                 goto finish;
554         }
555
556         for (j = optind; j < argc; j++)
557                 if ((r = install_info_add(argv[j])) < 0)
558                         goto finish;
559
560         while ((i = hashmap_first(will_install))) {
561                 assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0);
562
563                 if ((r = install_info_apply(&paths, i, config_path)) != 0) {
564
565                         if (r < 0)
566                                 goto finish;
567
568                         /* In test mode and found something */
569                         retval = 0;
570                         goto finish;
571                 }
572         }
573
574         retval = arg_action == ACTION_TEST ? 1 : 0;
575
576 finish:
577         install_info_hashmap_free(will_install);
578         install_info_hashmap_free(have_installed);
579
580         lookup_paths_free(&paths);
581
582         free(config_path);
583
584         return retval;
585 }