]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cryptsetup/cryptsetup-generator.c
Extract looping over /proc/cmdline into a shared function
[thirdparty/systemd.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 "strv.h"
31 #include "fileio.h"
32
33 static const char *arg_dest = "/tmp";
34 static bool arg_enabled = true;
35 static bool arg_read_crypttab = true;
36
37 static char **arg_disks;
38 static char **arg_options;
39 static char *arg_keyfile;
40
41
42 static bool has_option(const char *haystack, const char *needle) {
43 const char *f = haystack;
44 size_t l;
45
46 assert(needle);
47
48 if (!haystack)
49 return false;
50
51 l = strlen(needle);
52
53 while ((f = strstr(f, needle))) {
54
55 if (f > haystack && f[-1] != ',') {
56 f++;
57 continue;
58 }
59
60 if (f[l] != 0 && f[l] != ',') {
61 f++;
62 continue;
63 }
64
65 return true;
66 }
67
68 return false;
69 }
70
71 static int create_disk(
72 const char *name,
73 const char *device,
74 const char *password,
75 const char *options) {
76
77 _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
78 _cleanup_fclose_ FILE *f = NULL;
79 bool noauto, nofail, tmp, swap;
80
81 assert(name);
82 assert(device);
83
84 noauto = has_option(options, "noauto");
85 nofail = has_option(options, "nofail");
86 tmp = has_option(options, "tmp");
87 swap = has_option(options, "swap");
88
89 if (tmp && swap) {
90 log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name);
91 return -EINVAL;
92 }
93
94 n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service");
95 if (!n)
96 return log_oom();
97
98 p = strjoin(arg_dest, "/", n, NULL);
99 if (!p)
100 return log_oom();
101
102 u = fstab_node_to_udev_node(device);
103 if (!u)
104 return log_oom();
105
106 d = unit_name_from_path(u, ".device");
107 if (!d)
108 return log_oom();
109
110 f = fopen(p, "wxe");
111 if (!f) {
112 log_error("Failed to create unit file %s: %m", p);
113 return -errno;
114 }
115
116 fputs(
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=dev-mapper-%i.device\n"
125 "IgnoreOnIsolate=true\n"
126 "After=systemd-readahead-collect.service systemd-readahead-replay.service\n",
127 f);
128
129 if (!nofail)
130 fprintf(f,
131 "Before=cryptsetup.target\n");
132
133 if (password) {
134 if (streq(password, "/dev/urandom") ||
135 streq(password, "/dev/random") ||
136 streq(password, "/dev/hw_random"))
137 fputs("After=systemd-random-seed.service\n", f);
138
139 else if (!streq(password, "-") && !streq(password, "none")) {
140 _cleanup_free_ char *uu = fstab_node_to_udev_node(password);
141 if (uu == NULL)
142 return log_oom();
143
144 if (is_device_path(uu)) {
145 _cleanup_free_ char *dd = unit_name_from_path(uu, ".device");
146 if (dd == NULL)
147 return log_oom();
148
149 fprintf(f, "After=%1$s\nRequires=%1$s\n", dd);
150 } else
151 fprintf(f, "RequiresMountsFor=%s\n", password);
152 }
153 }
154
155 if (is_device_path(u))
156 fprintf(f,
157 "BindsTo=%s\n"
158 "After=%s\n"
159 "Before=umount.target\n",
160 d, d);
161 else
162 fprintf(f,
163 "RequiresMountsFor=%s\n",
164 u);
165
166 fprintf(f,
167 "\n[Service]\n"
168 "Type=oneshot\n"
169 "RemainAfterExit=yes\n"
170 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
171 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
172 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
173 name, u, strempty(password), strempty(options),
174 name);
175
176 if (tmp)
177 fprintf(f,
178 "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
179 name);
180
181 if (swap)
182 fprintf(f,
183 "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
184 name);
185
186 fflush(f);
187
188 if (ferror(f)) {
189 log_error("Failed to write file %s: %m", p);
190 return -errno;
191 }
192
193 if (asprintf(&from, "../%s", n) < 0)
194 return log_oom();
195
196 if (!noauto) {
197
198 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
199 if (!to)
200 return log_oom();
201
202 mkdir_parents_label(to, 0755);
203 if (symlink(from, to) < 0) {
204 log_error("Failed to create symlink %s: %m", to);
205 return -errno;
206 }
207
208 free(to);
209 if (!nofail)
210 to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
211 else
212 to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
213 if (!to)
214 return log_oom();
215
216 mkdir_parents_label(to, 0755);
217 if (symlink(from, to) < 0) {
218 log_error("Failed to create symlink %s: %m", to);
219 return -errno;
220 }
221 }
222
223 e = unit_name_escape(name);
224 if (!e)
225 return log_oom();
226
227 free(to);
228 to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
229 if (!to)
230 return log_oom();
231
232 mkdir_parents_label(to, 0755);
233 if (symlink(from, to) < 0) {
234 log_error("Failed to create symlink %s: %m", to);
235 return -errno;
236 }
237
238 if (!noauto && !nofail) {
239 int r;
240 free(p);
241 p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf", NULL);
242 if (!p)
243 return log_oom();
244
245 mkdir_parents_label(p, 0755);
246
247 r = write_string_file(p,
248 "# Automatically generated by systemd-cryptsetup-generator\n\n"
249 "[Unit]\n"
250 "JobTimeoutSec=0\n"); /* the binary handles timeouts anyway */
251 if (r)
252 return r;
253 }
254
255 return 0;
256 }
257
258 static int parse_proc_cmdline_word(const char *word) {
259 int r;
260
261 if (startswith(word, "luks=")) {
262 r = parse_boolean(word + 5);
263 if (r < 0)
264 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
265 else
266 arg_enabled = r;
267
268 } else if (startswith(word, "rd.luks=")) {
269
270 if (in_initrd()) {
271 r = parse_boolean(word + 8);
272 if (r < 0)
273 log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
274 else
275 arg_enabled = r;
276 }
277
278 } else if (startswith(word, "luks.crypttab=")) {
279 r = parse_boolean(word + 14);
280 if (r < 0)
281 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
282 else
283 arg_read_crypttab = r;
284
285 } else if (startswith(word, "rd.luks.crypttab=")) {
286
287 if (in_initrd()) {
288 r = parse_boolean(word + 17);
289 if (r < 0)
290 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
291 else
292 arg_read_crypttab = r;
293 }
294
295 } else if (startswith(word, "luks.uuid=")) {
296 if (strv_extend(&arg_disks, word + 10) < 0)
297 return log_oom();
298
299 } else if (startswith(word, "rd.luks.uuid=")) {
300
301 if (in_initrd()) {
302 if (strv_extend(&arg_disks, word + 13) < 0)
303 return log_oom();
304 }
305
306 } else if (startswith(word, "luks.options=")) {
307 if (strv_extend(&arg_options, word + 13) < 0)
308 return log_oom();
309
310 } else if (startswith(word, "rd.luks.options=")) {
311
312 if (in_initrd()) {
313 if (strv_extend(&arg_options, word + 16) < 0)
314 return log_oom();
315 }
316
317 } else if (startswith(word, "luks.key=")) {
318 free(arg_keyfile);
319 arg_keyfile = strdup(word + 9);
320 if (!arg_keyfile)
321 return log_oom();
322
323 } else if (startswith(word, "rd.luks.key=")) {
324
325 if (in_initrd()) {
326 free(arg_keyfile);
327 arg_keyfile = strdup(word + 12);
328 if (!arg_keyfile)
329 return log_oom();
330 }
331
332 } else if (startswith(word, "luks.") ||
333 (in_initrd() && startswith(word, "rd.luks."))) {
334
335 log_warning("Unknown kernel switch %s. Ignoring.", word);
336 }
337
338 return 0;
339 }
340
341 int main(int argc, char *argv[]) {
342 _cleanup_strv_free_ char **disks_done = NULL;
343 _cleanup_fclose_ FILE *f = NULL;
344 unsigned n = 0;
345 int r = EXIT_FAILURE, r2 = EXIT_FAILURE;
346 char **i;
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(parse_proc_cmdline_word) < 0)
363 goto cleanup;
364
365 if (!arg_enabled) {
366 r = r2 = EXIT_SUCCESS;
367 goto cleanup;
368 }
369
370 strv_uniq(arg_disks);
371
372 if (arg_read_crypttab) {
373 struct stat st;
374
375 f = fopen("/etc/crypttab", "re");
376 if (!f) {
377 if (errno == ENOENT)
378 r = EXIT_SUCCESS;
379 else
380 log_error("Failed to open /etc/crypttab: %m");
381
382 goto next;
383 }
384
385 if (fstat(fileno(f), &st) < 0) {
386 log_error("Failed to stat /etc/crypttab: %m");
387 goto next;
388 }
389
390 /* If we readd support for specifying passphrases
391 * directly in crypttabe we should upgrade the warning
392 * below, though possibly only if a passphrase is
393 * specified directly. */
394 if (st.st_mode & 0005)
395 log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
396
397 for (;;) {
398 char line[LINE_MAX], *l;
399 _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
400 int k;
401
402 if (!fgets(line, sizeof(line), f))
403 break;
404
405 n++;
406
407 l = strstrip(line);
408 if (*l == '#' || *l == 0)
409 continue;
410
411 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
412 if (k < 2 || k > 4) {
413 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
414 continue;
415 }
416
417 /*
418 If options are specified on the kernel commandline, let them override
419 the ones from crypttab.
420 */
421 STRV_FOREACH(i, arg_options) {
422 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
423 const char *p = *i;
424
425 k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
426 if (k == 2 && streq(proc_uuid, device + 5)) {
427 free(options);
428 options = strdup(p);
429 if (!proc_options) {
430 log_oom();
431 goto cleanup;
432 }
433 }
434 }
435
436 if (arg_disks) {
437 /*
438 If luks UUIDs are specified on the kernel command line, use them as a filter
439 for /etc/crypttab and only generate units for those.
440 */
441 STRV_FOREACH(i, arg_disks) {
442 _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
443 const char *p = *i;
444
445 if (startswith(p, "luks-"))
446 p += 5;
447
448 proc_name = strappend("luks-", p);
449 proc_device = strappend("UUID=", p);
450
451 if (!proc_name || !proc_device) {
452 log_oom();
453 goto cleanup;
454 }
455
456 if (streq(proc_device, device) || streq(proc_name, name)) {
457 if (create_disk(name, device, password, options) < 0)
458 goto cleanup;
459
460 if (strv_extend(&disks_done, p) < 0) {
461 log_oom();
462 goto cleanup;
463 }
464 }
465 }
466 } else if (create_disk(name, device, password, options) < 0)
467 goto cleanup;
468
469 }
470 }
471
472 r = EXIT_SUCCESS;
473
474 next:
475 STRV_FOREACH(i, arg_disks) {
476 /*
477 Generate units for those UUIDs, which were specified
478 on the kernel command line and not yet written.
479 */
480
481 _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL;
482 const char *p = *i;
483
484 if (startswith(p, "luks-"))
485 p += 5;
486
487 if (strv_contains(disks_done, p))
488 continue;
489
490 name = strappend("luks-", p);
491 device = strappend("UUID=", p);
492
493 if (!name || !device) {
494 log_oom();
495 goto cleanup;
496 }
497
498 if (arg_options) {
499 /*
500 If options are specified on the kernel commandline, use them.
501 */
502 char **j;
503
504 STRV_FOREACH(j, arg_options) {
505 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
506 const char *s = *j;
507 int k;
508
509 k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
510 if (k == 2) {
511 if (streq(proc_uuid, device + 5)) {
512 if (options)
513 free(options);
514 options = strdup(proc_options);
515 if (!options) {
516 log_oom();
517 goto cleanup;
518 }
519 }
520 } else if (!options) {
521 /*
522 Fall back to options without a specified UUID
523 */
524 options = strdup(s);
525 if (!options) {
526 log_oom();
527 goto cleanup;
528 };
529 }
530 }
531 }
532
533 if (!options) {
534 options = strdup("timeout=0");
535 if (!options) {
536 log_oom();
537 goto cleanup;
538 }
539 }
540
541 if (create_disk(name, device, arg_keyfile, options) < 0)
542 goto cleanup;
543 }
544
545 r2 = EXIT_SUCCESS;
546
547 cleanup:
548 strv_free(arg_disks);
549 strv_free(arg_options);
550 free(arg_keyfile);
551
552 return r != EXIT_SUCCESS ? r : r2;
553 }