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