chiark / gitweb /
manager: expose a few special units via SIGRTMIN+x signals
[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 (strchr(name, '/'))
185                 return false;
186
187         if (!*name)
188                 return false;
189
190         if (ignore_file(name))
191                 return false;
192
193         return true;
194 }
195
196 static int install_info_add(const char *name) {
197         InstallInfo *i;
198         int r;
199
200         if (!unit_name_valid(name))
201                 return -EINVAL;
202
203         if (hashmap_get(have_installed, name) ||
204             hashmap_get(will_install, name))
205                 return 0;
206
207         if (!(i = new0(InstallInfo, 1))) {
208                 r = -ENOMEM;
209                 goto fail;
210         }
211
212         if (!(i->name = strdup(name))) {
213                 r = -ENOMEM;
214                 goto fail;
215         }
216
217         if ((r = hashmap_put(will_install, i->name, i)) < 0)
218                 goto fail;
219
220         return 0;
221
222 fail:
223         if (i)
224                 install_info_free(i);
225
226         return r;
227 }
228
229 static int config_parse_also(
230                 const char *filename,
231                 unsigned line,
232                 const char *section,
233                 const char *lvalue,
234                 const char *rvalue,
235                 void *data,
236                 void *userdata) {
237
238         char *w;
239         size_t l;
240         char *state;
241
242         assert(filename);
243         assert(lvalue);
244         assert(rvalue);
245
246         FOREACH_WORD_QUOTED(w, l, rvalue, state) {
247                 char *n;
248                 int r;
249
250                 if (!(n = strndup(w, l)))
251                         return -ENOMEM;
252
253                 r = install_info_add(n);
254                 free(n);
255
256                 if (r < 0)
257                         return r;
258         }
259
260         return 0;
261 }
262
263 static int create_symlink(const char *old_path, const char *new_path) {
264         int r;
265
266         assert(old_path);
267         assert(new_path);
268
269         if (arg_action == ACTION_ENABLE) {
270                 char *dest;
271
272                 mkdir_parents(new_path, 0755);
273
274                 if (symlink(old_path, new_path) >= 0)
275                         return 0;
276
277                 if (errno != EEXIST) {
278                         log_error("Cannot link %s to %s: %m", old_path, new_path);
279                         return -errno;
280                 }
281
282                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
283
284                         if (errno == EINVAL) {
285                                 log_error("Cannot link %s to %s, file exists already and is not a symlink.", old_path, new_path);
286                                 return -EEXIST;
287                         }
288
289                         log_error("readlink() failed: %s", strerror(-r));
290                         return r;
291                 }
292
293                 if (streq(dest, old_path)) {
294                         free(dest);
295                         return 0;
296                 }
297
298                 if (!arg_force) {
299                         log_error("Cannot link %s to %s, symlink exists already and points to %s.", old_path, new_path, dest);
300                         free(dest);
301                         return -EEXIST;
302                 }
303
304                 free(dest);
305                 unlink(new_path);
306
307                 if (symlink(old_path, new_path) >= 0)
308                         return 0;
309
310                 log_error("Cannot link %s to %s: %m", old_path, new_path);
311                 return -errno;
312
313         } else if (arg_action == ACTION_DISABLE) {
314                 char *dest;
315
316                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
317                         if (errno == ENOENT)
318                                 return 0;
319
320                         if (errno == EINVAL) {
321                                 log_warning("File %s not a symlink, ignoring.", old_path);
322                                 return 0;
323                         }
324
325                         log_error("readlink() failed: %s", strerror(-r));
326                         return r;
327                 }
328
329                 if (!streq(dest, old_path)) {
330                         log_warning("File %s not a symlink to %s but points to %s, ignoring.", new_path, old_path, dest);
331                         free(dest);
332                         return 0;
333                 }
334
335                 free(dest);
336                 if (unlink(new_path) >= 0)
337                         return 0;
338
339                 log_error("Cannot unlink %s: %m", new_path);
340                 return -errno;
341
342         } else if (arg_action == ACTION_TEST) {
343                 char *dest;
344
345                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
346
347                         if (errno == ENOENT || errno == EINVAL)
348                                 return 0;
349
350                         log_error("readlink() failed: %s", strerror(-r));
351                         return r;
352                 }
353
354                 if (streq(dest, old_path)) {
355                         free(dest);
356                         return 1;
357                 }
358
359                 return 0;
360         }
361
362         assert_not_reached("Unknown action.");
363 }
364
365 static int install_info_symlink_alias(InstallInfo *i, const char *config_path) {
366         char **s;
367         char *alias_path = NULL;
368         int r;
369
370         assert(i);
371
372         STRV_FOREACH(s, i->aliases) {
373
374                 if (!unit_name_valid(*s)) {
375                         log_error("Invalid name %s.", *s);
376                         r = -EINVAL;
377                         goto finish;
378                 }
379
380                 free(alias_path);
381                 if (!(alias_path = path_make_absolute(*s, config_path))) {
382                         log_error("Out of memory");
383                         r = -ENOMEM;
384                         goto finish;
385                 }
386
387                 if ((r = create_symlink(i->path, alias_path)) != 0)
388                         goto finish;
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                         char *t;
427
428                         /* Try to remove .wants dir if we don't need it anymore */
429                         if (asprintf(&t, "%s/%s.wants", config_path, *s) >= 0) {
430                                 rmdir(t);
431                                 free(t);
432                         }
433                 }
434         }
435
436         r = 0;
437
438 finish:
439         free(alias_path);
440
441         return r;
442 }
443
444 static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *config_path) {
445
446         const ConfigItem items[] = {
447                 { "Alias",    config_parse_strv, &i->aliases,   "Install" },
448                 { "WantedBy", config_parse_strv, &i->wanted_by, "Install" },
449                 { "Also",     config_parse_also, NULL,          "Install" },
450
451                 { NULL, NULL, NULL, NULL }
452         };
453
454         char **p;
455         char *filename = NULL;
456         FILE *f = NULL;
457         int r;
458
459         assert(paths);
460         assert(i);
461
462         STRV_FOREACH(p, paths->unit_path) {
463
464                 if (!(filename = path_make_absolute(i->name, *p))) {
465                         log_error("Out of memory");
466                         return -ENOMEM;
467                 }
468
469                 if ((f = fopen(filename, "re")))
470                         break;
471
472                 free(filename);
473                 filename = NULL;
474
475                 if (errno != ENOENT) {
476                         log_error("Failed to open %s: %m", filename);
477                         return -errno;
478                 }
479         }
480
481         if (!f) {
482                 log_error("Couldn't find %s.", i->name);
483                 return -ENOENT;
484         }
485
486         i->path = filename;
487
488         if ((r = config_parse(filename, f, NULL, items, true, i)) < 0) {
489                 fclose(f);
490                 return r;
491         }
492
493         fclose(f);
494
495         if ((r = install_info_symlink_alias(i, config_path)) != 0)
496                 return r;
497
498         if ((r = install_info_symlink_wants(i, config_path)) != 0)
499                 return r;
500
501         return 0;
502 }
503
504 static char *get_config_path(void) {
505
506         switch (arg_where) {
507
508         case WHERE_SYSTEM:
509                 return strdup(SYSTEM_CONFIG_UNIT_PATH);
510
511         case WHERE_GLOBAL:
512                 return strdup(SESSION_CONFIG_UNIT_PATH);
513
514         case WHERE_SESSION: {
515                 char *p;
516
517                 if (session_config_home(&p) < 0)
518                         return NULL;
519
520                 return p;
521         }
522
523         default:
524                 assert_not_reached("Unknown config path.");
525         }
526 }
527
528 int main(int argc, char *argv[]) {
529         int r, retval = 1, j;
530         LookupPaths paths;
531         InstallInfo *i;
532         char *config_path = NULL;
533
534         zero(paths);
535
536         log_parse_environment();
537
538         if ((r = parse_argv(argc, argv)) < 0)
539                 goto finish;
540         else if (r == 0) {
541                 retval = 0;
542                 goto finish;
543         }
544
545         if ((r = lookup_paths_init(&paths, arg_where == WHERE_SYSTEM ? MANAGER_INIT : MANAGER_SESSION)) < 0) {
546                 log_error("Failed to determine lookup paths: %s", strerror(-r));
547                 goto finish;
548         }
549
550         if (!(config_path = get_config_path())) {
551                 log_error("Failed to determine config path");
552                 goto finish;
553         }
554
555         will_install = hashmap_new(string_hash_func, string_compare_func);
556         have_installed = hashmap_new(string_hash_func, string_compare_func);
557
558         if (!will_install || !have_installed) {
559                 log_error("Failed to allocate unit sets.");
560                 goto finish;
561         }
562
563         for (j = optind; j < argc; j++)
564                 if ((r = install_info_add(argv[j])) < 0)
565                         goto finish;
566
567         while ((i = hashmap_first(will_install))) {
568                 assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0);
569
570                 if ((r = install_info_apply(&paths, i, config_path)) != 0) {
571
572                         if (r < 0)
573                                 goto finish;
574
575                         /* In test mode and found something */
576                         retval = 0;
577                         goto finish;
578                 }
579         }
580
581         retval = arg_action == ACTION_TEST ? 1 : 0;
582
583 finish:
584         install_info_hashmap_free(will_install);
585         install_info_hashmap_free(have_installed);
586
587         lookup_paths_free(&paths);
588
589         free(config_path);
590
591         return retval;
592 }