chiark / gitweb /
05808df38041cc29bbaf225a01c2b7cf222d1e2d
[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_build_escape("cryptsetup", name, ".service");
85         if (!n) {
86                 r = -ENOMEM;
87                 log_error("Failed to allocate unit name.");
88                 goto fail;
89         }
90
91         p = join(arg_dest, "/", n, NULL);
92         if (!p) {
93                 r = -ENOMEM;
94                 log_error("Failed to allocate unit file name.");
95                 goto fail;
96         }
97
98         u = fstab_node_to_udev_node(device);
99         if (!u) {
100                 r = -ENOMEM;
101                 log_error("Failed to allocate device node.");
102                 goto fail;
103         }
104
105         d = unit_name_from_path(u, ".device");
106         if (!d) {
107                 r = -ENOMEM;
108                 log_error("Failed to allocate device name.");
109                 goto fail;
110         }
111
112         f = fopen(p, "wxe");
113         if (!f) {
114                 r = -errno;
115                 log_error("Failed to create unit file: %m");
116                 goto fail;
117         }
118
119         fprintf(f,
120                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
121                 "[Unit]\n"
122                 "Description=Cryptography Setup for %%I\n"
123                 "SourcePath=/etc/crypttab\n"
124                 "Conflicts=umount.target\n"
125                 "DefaultDependencies=no\n"
126                 "BindTo=%s dev-mapper-%%i.device\n"
127                 "After=systemd-readahead-collect.service systemd-readahead-replay.service %s\n"
128                 "Before=umount.target\n",
129                 d, d);
130
131         if (!nofail)
132                 fprintf(f,
133                         "Before=cryptsetup.target\n");
134
135         if (password && (streq(password, "/dev/urandom") ||
136                          streq(password, "/dev/random") ||
137                          streq(password, "/dev/hw_random")))
138                 fputs("After=systemd-random-seed-load.service\n", f);
139         else
140                 fputs("Before=local-fs.target\n", f);
141
142         fprintf(f,
143                 "\n[Service]\n"
144                 "Type=oneshot\n"
145                 "RemainAfterExit=yes\n"
146                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
147                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
148                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
149                 name, u, strempty(password), strempty(options),
150                 name);
151
152         if (has_option(options, "tmp"))
153                 fprintf(f,
154                         "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
155                         name);
156
157         if (has_option(options, "swap"))
158                 fprintf(f,
159                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
160                         name);
161
162         fflush(f);
163
164         if (ferror(f)) {
165                 r = -errno;
166                 log_error("Failed to write file: %m");
167                 goto fail;
168         }
169
170         if (asprintf(&from, "../%s", n) < 0) {
171                 r = -ENOMEM;
172                 goto fail;
173         }
174
175         if (!noauto) {
176
177                 to = join(arg_dest, "/", d, ".wants/", n, NULL);
178                 if (!to) {
179                         r = -ENOMEM;
180                         goto fail;
181                 }
182
183                 mkdir_parents_label(to, 0755);
184                 if (symlink(from, to) < 0) {
185                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
186                         r = -errno;
187                         goto fail;
188                 }
189
190                 free(to);
191
192                 if (!nofail)
193                         to = join(arg_dest, "/cryptsetup.target.requires/", n, NULL);
194                 else
195                         to = join(arg_dest, "/cryptsetup.target.wants/", n, NULL);
196                 if (!to) {
197                         r = -ENOMEM;
198                         goto fail;
199                 }
200
201                 mkdir_parents_label(to, 0755);
202                 if (symlink(from, to) < 0) {
203                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
204                         r = -errno;
205                         goto fail;
206                 }
207
208                 free(to);
209                 to = NULL;
210         }
211
212         e = unit_name_escape(name);
213         to = join(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
214         if (!to) {
215                 r = -ENOMEM;
216                 goto fail;
217         }
218
219         mkdir_parents_label(to, 0755);
220         if (symlink(from, to) < 0) {
221                 log_error("Failed to create symlink '%s' to '%s': %m", from, to);
222                 r = -errno;
223                 goto fail;
224         }
225
226         r = 0;
227
228 fail:
229         free(p);
230         free(n);
231         free(d);
232         free(e);
233
234         free(from);
235         free(to);
236
237         if (f)
238                 fclose(f);
239
240         return r;
241 }
242
243 static int parse_proc_cmdline(void) {
244         char *line, *w, *state;
245         int r;
246         size_t l;
247
248         if (detect_container(NULL) > 0)
249                 return 0;
250
251         r = read_one_line_file("/proc/cmdline", &line);
252         if (r < 0) {
253                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
254                 return 0;
255         }
256
257         FOREACH_WORD_QUOTED(w, l, line, state) {
258                 char *word;
259
260                 word = strndup(w, l);
261                 if (!word) {
262                         r = -ENOMEM;
263                         goto finish;
264                 }
265
266                 if (startswith(word, "luks=")) {
267                         r = parse_boolean(word + 5);
268                         if (r < 0)
269                                 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
270                         else
271                                 arg_enabled = r;
272
273                 } else if (startswith(word, "rd.luks=")) {
274
275                         if (in_initrd()) {
276                                 r = parse_boolean(word + 8);
277                                 if (r < 0)
278                                         log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
279                                 else
280                                         arg_enabled = r;
281                         }
282
283                 } else if (startswith(word, "luks.crypttab=")) {
284                         r = parse_boolean(word + 14);
285                         if (r < 0)
286                                 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
287                         else
288                                 arg_read_crypttab = r;
289
290                 } else if (startswith(word, "rd.luks.crypttab=")) {
291
292                         if (in_initrd()) {
293                                 r = parse_boolean(word + 17);
294                                 if (r < 0)
295                                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
296                                 else
297                                         arg_read_crypttab = r;
298                         }
299
300                 } else if (startswith(word, "luks.uuid=")) {
301                         char **t;
302
303                         t = strv_append(arg_proc_cmdline_disks, word + 10);
304                         if (!t) {
305                                 log_error("Out of memory");
306                                 r = -ENOMEM;
307                                 goto finish;
308                         }
309                         strv_free(arg_proc_cmdline_disks);
310                         arg_proc_cmdline_disks = t;
311
312                 } else if (startswith(word, "rd.luks.uuid=")) {
313
314                         if (in_initrd()) {
315                                 char **t;
316
317                                 t = strv_append(arg_proc_cmdline_disks, word + 13);
318                                 if (!t) {
319                                         log_error("Out of memory");
320                                         r = -ENOMEM;
321                                         goto finish;
322                                 }
323                                 strv_free(arg_proc_cmdline_disks);
324                                 arg_proc_cmdline_disks = t;
325                         }
326
327                 } else if (startswith(word, "luks.") ||
328                            (in_initrd() && startswith(word, "rd.luks."))) {
329
330                         log_warning("Unknown kernel switch %s. Ignoring.", word);
331                 }
332
333                 free(word);
334         }
335
336         r = 0;
337
338 finish:
339         free(line);
340         return r;
341 }
342
343 int main(int argc, char *argv[]) {
344         FILE *f = NULL;
345         int r = EXIT_SUCCESS;
346         unsigned n = 0;
347         char **i;
348
349         if (argc > 1 && argc != 4) {
350                 log_error("This program takes three or no arguments.");
351                 return EXIT_FAILURE;
352         }
353
354         if (argc > 1)
355                 arg_dest = argv[1];
356
357         log_set_target(LOG_TARGET_SAFE);
358         log_parse_environment();
359         log_open();
360
361         umask(0022);
362
363         if (parse_proc_cmdline() < 0)
364                 return EXIT_FAILURE;
365
366         if (!arg_enabled) {
367                 r = EXIT_SUCCESS;
368                 goto finish;
369         }
370
371         STRV_FOREACH(i, arg_proc_cmdline_disks) {
372                 char *name, *device;
373                 const char *p = *i;
374
375                 if (startswith(p, "luks-"))
376                         p += 5;
377
378                 name = strappend("luks-", *i);
379                 device = strappend("UUID=", *i);
380
381                 if (!name || !device) {
382                         log_error("Out of memory");
383                         r = EXIT_FAILURE;
384                         free(name);
385                         free(device);
386                         goto finish;
387                 }
388
389                 if (create_disk(name, device, NULL, NULL) < 0)
390                         r = EXIT_FAILURE;
391
392                 free(name);
393                 free(device);
394         }
395
396         if (!arg_read_crypttab)
397                 return r;
398
399         f = fopen("/etc/crypttab", "re");
400         if (!f) {
401
402                 if (errno == ENOENT)
403                         r = EXIT_SUCCESS;
404                 else {
405                         r = EXIT_FAILURE;
406                         log_error("Failed to open /etc/crypttab: %m");
407                 }
408
409                 goto finish;
410         }
411
412         for (;;) {
413                 char line[LINE_MAX], *l;
414                 char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
415                 int k;
416
417                 if (!fgets(line, sizeof(line), f))
418                         break;
419
420                 n++;
421
422                 l = strstrip(line);
423                 if (*l == '#' || *l == 0)
424                         continue;
425
426                 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
427                 if (k < 2 || k > 4) {
428                         log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
429                         r = EXIT_FAILURE;
430                         goto next;
431                 }
432
433                 if (create_disk(name, device, password, options) < 0)
434                         r = EXIT_FAILURE;
435
436         next:
437                 free(name);
438                 free(device);
439                 free(password);
440                 free(options);
441         }
442
443 finish:
444         if (f)
445                 fclose(f);
446
447         strv_free(arg_proc_cmdline_disks);
448
449         return r;
450 }