chiark / gitweb /
unit: allow extension of unit files with .d/*.conf drop-ins
[elogind.git] / src / cryptsetup / cryptsetup-generator.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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 Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 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   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <string.h>
23 #include <errno.h>
24 #include <unistd.h>
25
26 #include "log.h"
27 #include "util.h"
28 #include "unit-name.h"
29 #include "mkdir.h"
30 #include "virt.h"
31 #include "strv.h"
32
33 static const char *arg_dest = "/tmp";
34 static bool arg_enabled = true;
35 static bool arg_read_crypttab = true;
36 static char **arg_proc_cmdline_disks = NULL;
37
38 static bool has_option(const char *haystack, const char *needle) {
39         const char *f = haystack;
40         size_t l;
41
42         assert(needle);
43
44         if (!haystack)
45                 return false;
46
47         l = strlen(needle);
48
49         while ((f = strstr(f, needle))) {
50
51                 if (f > haystack && f[-1] != ',') {
52                         f++;
53                         continue;
54                 }
55
56                 if (f[l] != 0 && f[l] != ',') {
57                         f++;
58                         continue;
59                 }
60
61                 return true;
62         }
63
64         return false;
65 }
66
67 static int create_disk(
68                 const char *name,
69                 const char *device,
70                 const char *password,
71                 const char *options) {
72
73         char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
74         int r;
75         FILE *f = NULL;
76         bool noauto, nofail;
77
78         assert(name);
79         assert(device);
80
81         noauto = has_option(options, "noauto");
82         nofail = has_option(options, "nofail");
83
84         n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service");
85         if (!n) {
86                 r = log_oom();
87                 goto fail;
88         }
89
90         p = strjoin(arg_dest, "/", n, NULL);
91         if (!p) {
92                 r = log_oom();
93                 goto fail;
94         }
95
96         u = fstab_node_to_udev_node(device);
97         if (!u) {
98                 r = log_oom();
99                 goto fail;
100         }
101
102         d = unit_name_from_path(u, ".device");
103         if (!d) {
104                 r = log_oom();
105                 goto fail;
106         }
107
108         f = fopen(p, "wxe");
109         if (!f) {
110                 r = -errno;
111                 log_error("Failed to create unit file %s: %m", p);
112                 goto fail;
113         }
114
115         fprintf(f,
116                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
117                 "[Unit]\n"
118                 "Description=Cryptography Setup for %%I\n"
119                 "Documentation=man:systemd-cryptsetup@.service(8) man:crypttab(5)\n"
120                 "SourcePath=/etc/crypttab\n"
121                 "Conflicts=umount.target\n"
122                 "DefaultDependencies=no\n"
123                 "BindsTo=%s dev-mapper-%%i.device\n"
124                 "After=systemd-readahead-collect.service systemd-readahead-replay.service %s\n"
125                 "Before=umount.target\n",
126                 d, d);
127
128         if (!nofail)
129                 fprintf(f,
130                         "Before=cryptsetup.target\n");
131
132         if (password && (streq(password, "/dev/urandom") ||
133                          streq(password, "/dev/random") ||
134                          streq(password, "/dev/hw_random")))
135                 fputs("After=systemd-random-seed-load.service\n", f);
136         else
137                 fputs("Before=local-fs.target\n", f);
138
139         fprintf(f,
140                 "\n[Service]\n"
141                 "Type=oneshot\n"
142                 "RemainAfterExit=yes\n"
143                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
144                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
145                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
146                 name, u, strempty(password), strempty(options),
147                 name);
148
149         if (has_option(options, "tmp"))
150                 fprintf(f,
151                         "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
152                         name);
153
154         if (has_option(options, "swap"))
155                 fprintf(f,
156                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
157                         name);
158
159         fflush(f);
160
161         if (ferror(f)) {
162                 r = -errno;
163                 log_error("Failed to write file %s: %m", p);
164                 goto fail;
165         }
166
167         if (asprintf(&from, "../%s", n) < 0) {
168                 r = log_oom();
169                 goto fail;
170         }
171
172         if (!noauto) {
173
174                 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
175                 if (!to) {
176                         r = log_oom();
177                         goto fail;
178                 }
179
180                 mkdir_parents_label(to, 0755);
181                 if (symlink(from, to) < 0) {
182                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
183                         r = -errno;
184                         goto fail;
185                 }
186
187                 free(to);
188
189                 if (!nofail)
190                         to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
191                 else
192                         to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
193                 if (!to) {
194                         r = log_oom();
195                         goto fail;
196                 }
197
198                 mkdir_parents_label(to, 0755);
199                 if (symlink(from, to) < 0) {
200                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
201                         r = -errno;
202                         goto fail;
203                 }
204
205                 free(to);
206                 to = NULL;
207         }
208
209         e = unit_name_escape(name);
210         to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
211         if (!to) {
212                 r = log_oom();
213                 goto fail;
214         }
215
216         mkdir_parents_label(to, 0755);
217         if (symlink(from, to) < 0) {
218                 log_error("Failed to create symlink '%s' to '%s': %m", from, to);
219                 r = -errno;
220                 goto fail;
221         }
222
223         r = 0;
224
225 fail:
226         free(p);
227         free(n);
228         free(d);
229         free(e);
230
231         free(from);
232         free(to);
233
234         if (f)
235                 fclose(f);
236
237         return r;
238 }
239
240 static int parse_proc_cmdline(void) {
241         char *line, *w, *state;
242         int r;
243         size_t l;
244
245         if (detect_container(NULL) > 0)
246                 return 0;
247
248         r = read_one_line_file("/proc/cmdline", &line);
249         if (r < 0) {
250                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
251                 return 0;
252         }
253
254         FOREACH_WORD_QUOTED(w, l, line, state) {
255                 char *word;
256
257                 word = strndup(w, l);
258                 if (!word) {
259                         r = log_oom();
260                         goto finish;
261                 }
262
263                 if (startswith(word, "luks=")) {
264                         r = parse_boolean(word + 5);
265                         if (r < 0)
266                                 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
267                         else
268                                 arg_enabled = r;
269
270                 } else if (startswith(word, "rd.luks=")) {
271
272                         if (in_initrd()) {
273                                 r = parse_boolean(word + 8);
274                                 if (r < 0)
275                                         log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
276                                 else
277                                         arg_enabled = r;
278                         }
279
280                 } else if (startswith(word, "luks.crypttab=")) {
281                         r = parse_boolean(word + 14);
282                         if (r < 0)
283                                 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
284                         else
285                                 arg_read_crypttab = r;
286
287                 } else if (startswith(word, "rd.luks.crypttab=")) {
288
289                         if (in_initrd()) {
290                                 r = parse_boolean(word + 17);
291                                 if (r < 0)
292                                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
293                                 else
294                                         arg_read_crypttab = r;
295                         }
296
297                 } else if (startswith(word, "luks.uuid=")) {
298                         char **t;
299
300                         t = strv_append(arg_proc_cmdline_disks, word + 10);
301                         if (!t) {
302                                 r = log_oom();
303                                 goto finish;
304                         }
305                         strv_free(arg_proc_cmdline_disks);
306                         arg_proc_cmdline_disks = t;
307
308                 } else if (startswith(word, "rd.luks.uuid=")) {
309
310                         if (in_initrd()) {
311                                 char **t;
312
313                                 t = strv_append(arg_proc_cmdline_disks, word + 13);
314                                 if (!t) {
315                                         r = log_oom();
316                                         goto finish;
317                                 }
318                                 strv_free(arg_proc_cmdline_disks);
319                                 arg_proc_cmdline_disks = t;
320                         }
321
322                 } else if (startswith(word, "luks.") ||
323                            (in_initrd() && startswith(word, "rd.luks."))) {
324
325                         log_warning("Unknown kernel switch %s. Ignoring.", word);
326                 }
327
328                 free(word);
329         }
330
331         r = 0;
332
333 finish:
334         free(line);
335         return r;
336 }
337
338 int main(int argc, char *argv[]) {
339         FILE *f = NULL;
340         int r = EXIT_SUCCESS;
341         unsigned n = 0;
342         char **i;
343
344         if (argc > 1 && argc != 4) {
345                 log_error("This program takes three or no arguments.");
346                 return EXIT_FAILURE;
347         }
348
349         if (argc > 1)
350                 arg_dest = argv[1];
351
352         log_set_target(LOG_TARGET_SAFE);
353         log_parse_environment();
354         log_open();
355
356         umask(0022);
357
358         if (parse_proc_cmdline() < 0)
359                 return EXIT_FAILURE;
360
361         if (!arg_enabled) {
362                 r = EXIT_SUCCESS;
363                 goto finish;
364         }
365
366         STRV_FOREACH(i, arg_proc_cmdline_disks) {
367                 char *name, *device;
368                 const char *p = *i;
369
370                 if (startswith(p, "luks-"))
371                         p += 5;
372
373                 name = strappend("luks-", p);
374                 device = strappend("UUID=", p);
375
376                 if (!name || !device) {
377                         log_oom();
378                         r = EXIT_FAILURE;
379                         free(name);
380                         free(device);
381                         goto finish;
382                 }
383
384                 if (create_disk(name, device, NULL, NULL) < 0)
385                         r = EXIT_FAILURE;
386
387                 free(name);
388                 free(device);
389         }
390
391         if (!arg_read_crypttab)
392                 return r;
393
394         f = fopen("/etc/crypttab", "re");
395         if (!f) {
396
397                 if (errno == ENOENT)
398                         r = EXIT_SUCCESS;
399                 else {
400                         r = EXIT_FAILURE;
401                         log_error("Failed to open /etc/crypttab: %m");
402                 }
403
404                 goto finish;
405         }
406
407         for (;;) {
408                 char line[LINE_MAX], *l;
409                 char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
410                 int k;
411
412                 if (!fgets(line, sizeof(line), f))
413                         break;
414
415                 n++;
416
417                 l = strstrip(line);
418                 if (*l == '#' || *l == 0)
419                         continue;
420
421                 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
422                 if (k < 2 || k > 4) {
423                         log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
424                         r = EXIT_FAILURE;
425                         goto next;
426                 }
427
428                 if (create_disk(name, device, password, options) < 0)
429                         r = EXIT_FAILURE;
430
431         next:
432                 free(name);
433                 free(device);
434                 free(password);
435                 free(options);
436         }
437
438 finish:
439         if (f)
440                 fclose(f);
441
442         strv_free(arg_proc_cmdline_disks);
443
444         return r;
445 }