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