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