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