chiark / gitweb /
cryptsetup: add RequiresMountsFor for keyfile
[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
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 _cleanup_free_ *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
74         FILE _cleanup_fclose_ *f = NULL;
75         bool noauto, nofail;
76
77         assert(name);
78         assert(device);
79
80         noauto = has_option(options, "noauto");
81         nofail = has_option(options, "nofail");
82
83         n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service");
84         if (!n)
85                 return log_oom();
86
87         p = strjoin(arg_dest, "/", n, NULL);
88         if (!p)
89                 return log_oom();
90
91         u = fstab_node_to_udev_node(device);
92         if (!u)
93                 return log_oom();
94
95         d = unit_name_from_path(u, ".device");
96         if (!d)
97                 return log_oom();
98
99         f = fopen(p, "wxe");
100         if (!f) {
101                 log_error("Failed to create unit file %s: %m", p);
102                 return -errno;
103         }
104
105         fputs(
106                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
107                 "[Unit]\n"
108                 "Description=Cryptography Setup for %I\n"
109                 "Documentation=man:systemd-cryptsetup@.service(8) man:crypttab(5)\n"
110                 "SourcePath=/etc/crypttab\n"
111                 "Conflicts=umount.target\n"
112                 "DefaultDependencies=no\n"
113                 "BindsTo=dev-mapper-%i.device\n"
114                 "After=systemd-readahead-collect.service systemd-readahead-replay.service\n",
115                 f);
116
117         if (!nofail)
118                 fprintf(f,
119                         "Before=cryptsetup.target\n");
120
121         if (password) {
122                 if (streq(password, "/dev/urandom") ||
123                     streq(password, "/dev/random") ||
124                     streq(password, "/dev/hw_random"))
125                         fputs("After=systemd-random-seed-load.service\n", f);
126                 else if (!streq(password, "-") &&
127                          !streq(password, "none"))
128                         fprintf(f,
129                                 "RequiresMountsFor=%s\n",
130                                 password);
131         }
132
133         if (is_device_path(u))
134                 fprintf(f,
135                         "BindsTo=%s\n"
136                         "After=%s\n"
137                         "Before=umount.target\n",
138                         d, d);
139         else
140                 fprintf(f,
141                         "RequiresMountsFor=%s\n",
142                         u);
143
144         fprintf(f,
145                 "\n[Service]\n"
146                 "Type=oneshot\n"
147                 "RemainAfterExit=yes\n"
148                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
149                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
150                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
151                 name, u, strempty(password), strempty(options),
152                 name);
153
154         if (has_option(options, "tmp"))
155                 fprintf(f,
156                         "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
157                         name);
158
159         if (has_option(options, "swap"))
160                 fprintf(f,
161                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
162                         name);
163
164         fflush(f);
165
166         if (ferror(f)) {
167                 log_error("Failed to write file %s: %m", p);
168                 return -errno;
169         }
170
171         if (asprintf(&from, "../%s", n) < 0)
172                 return log_oom();
173
174         if (!noauto) {
175
176                 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
177                 if (!to)
178                         return log_oom();
179
180                 mkdir_parents_label(to, 0755);
181                 if (symlink(from, to) < 0) {
182                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
183                         return -errno;
184                 }
185
186                 free(to);
187                 if (!nofail)
188                         to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
189                 else
190                         to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
191                 if (!to)
192                         return log_oom();
193
194                 mkdir_parents_label(to, 0755);
195                 if (symlink(from, to) < 0) {
196                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
197                         return -errno;
198                 }
199         }
200
201         e = unit_name_escape(name);
202         if (!e)
203                 return log_oom();
204
205         free(to);
206         to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
207         if (!to)
208                 return log_oom();
209
210         mkdir_parents_label(to, 0755);
211         if (symlink(from, to) < 0) {
212                 log_error("Failed to create symlink '%s' to '%s': %m", from, to);
213                 return -errno;
214         }
215
216         return 0;
217 }
218
219 static int parse_proc_cmdline(char ***arg_proc_cmdline_disks) {
220         char _cleanup_free_ *line = NULL;
221         char *w = NULL, *state = NULL;
222         int r;
223         size_t l;
224
225         if (detect_container(NULL) > 0)
226                 return 0;
227
228         r = read_one_line_file("/proc/cmdline", &line);
229         if (r < 0) {
230                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
231                 return 0;
232         }
233
234         FOREACH_WORD_QUOTED(w, l, line, state) {
235                 char _cleanup_free_ *word = NULL;
236
237                 word = strndup(w, l);
238                 if (!word)
239                         return log_oom();
240
241                 if (startswith(word, "luks=")) {
242                         r = parse_boolean(word + 5);
243                         if (r < 0)
244                                 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
245                         else
246                                 arg_enabled = r;
247
248                 } else if (startswith(word, "rd.luks=")) {
249
250                         if (in_initrd()) {
251                                 r = parse_boolean(word + 8);
252                                 if (r < 0)
253                                         log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
254                                 else
255                                         arg_enabled = r;
256                         }
257
258                 } else if (startswith(word, "luks.crypttab=")) {
259                         r = parse_boolean(word + 14);
260                         if (r < 0)
261                                 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
262                         else
263                                 arg_read_crypttab = r;
264
265                 } else if (startswith(word, "rd.luks.crypttab=")) {
266
267                         if (in_initrd()) {
268                                 r = parse_boolean(word + 17);
269                                 if (r < 0)
270                                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
271                                 else
272                                         arg_read_crypttab = r;
273                         }
274
275                 } else if (startswith(word, "luks.uuid=")) {
276                         if (strv_extend(arg_proc_cmdline_disks, word + 10) < 0)
277                                 return log_oom();
278
279                 } else if (startswith(word, "rd.luks.uuid=")) {
280
281                         if (in_initrd()) {
282                                 if (strv_extend(arg_proc_cmdline_disks, word + 13) < 0)
283                                         return log_oom();
284                         }
285
286                 } else if (startswith(word, "luks.") ||
287                            (in_initrd() && startswith(word, "rd.luks."))) {
288
289                         log_warning("Unknown kernel switch %s. Ignoring.", word);
290                 }
291         }
292
293         strv_uniq(*arg_proc_cmdline_disks);
294
295         return 0;
296 }
297
298 int main(int argc, char *argv[]) {
299         FILE _cleanup_fclose_ *f = NULL;
300         unsigned n = 0;
301         int r = EXIT_SUCCESS;
302         char **i;
303         char _cleanup_strv_free_ **arg_proc_cmdline_disks_done = NULL;
304         char _cleanup_strv_free_ **arg_proc_cmdline_disks = NULL;
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(&arg_proc_cmdline_disks) < 0)
321                 return EXIT_FAILURE;
322
323         if (!arg_enabled)
324                 return EXIT_SUCCESS;
325
326         if (arg_read_crypttab) {
327                 f = fopen("/etc/crypttab", "re");
328
329                 if (!f) {
330                         if (errno == ENOENT)
331                                 r = EXIT_SUCCESS;
332                         else {
333                                 r = EXIT_FAILURE;
334                                 log_error("Failed to open /etc/crypttab: %m");
335                         }
336                 } else for (;;) {
337                         char line[LINE_MAX], *l;
338                         char _cleanup_free_ *name = NULL, *device = NULL, *password = NULL, *options = NULL;
339                         int k;
340
341                         if (!fgets(line, sizeof(line), f))
342                                 break;
343
344                         n++;
345
346                         l = strstrip(line);
347                         if (*l == '#' || *l == 0)
348                                 continue;
349
350                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
351                         if (k < 2 || k > 4) {
352                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
353                                 r = EXIT_FAILURE;
354                                 continue;
355                         }
356
357                         if (arg_proc_cmdline_disks) {
358                                 /*
359                                   If luks UUIDs are specified on the kernel command line, use them as a filter
360                                   for /etc/crypttab and only generate units for those.
361                                 */
362                                 STRV_FOREACH(i, arg_proc_cmdline_disks) {
363                                         char _cleanup_free_ *proc_device = NULL, *proc_name = NULL;
364                                         const char *p = *i;
365
366                                         if (startswith(p, "luks-"))
367                                                 p += 5;
368
369                                         proc_name = strappend("luks-", p);
370                                         proc_device = strappend("UUID=", p);
371
372                                         if (!proc_name || !proc_device)
373                                                 return log_oom();
374
375                                         if (streq(proc_device, device) || streq(proc_name, name)) {
376                                                 if (create_disk(name, device, password, options) < 0)
377                                                         r = EXIT_FAILURE;
378
379                                                 if (strv_extend(&arg_proc_cmdline_disks_done, p) < 0)
380                                                         return log_oom();
381                                         }
382                                 }
383                         } else {
384                                 if (create_disk(name, device, password, options) < 0)
385                                         r = EXIT_FAILURE;
386                         }
387                 }
388         }
389
390         STRV_FOREACH(i, arg_proc_cmdline_disks) {
391                 /*
392                   Generate units for those UUIDs, which were specified
393                   on the kernel command line and not yet written.
394                 */
395
396                 char _cleanup_free_ *name = NULL, *device = NULL;
397                 const char *p = *i;
398
399                 if (startswith(p, "luks-"))
400                         p += 5;
401
402                 if (strv_contains(arg_proc_cmdline_disks_done, p))
403                         continue;
404
405                 name = strappend("luks-", p);
406                 device = strappend("UUID=", p);
407
408                 if (!name || !device)
409                         return log_oom();
410
411                 if (create_disk(name, device, NULL, "timeout=0") < 0)
412                         r = EXIT_FAILURE;
413         }
414
415         return r;
416 }