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