chiark / gitweb /
test: make it easier to override kernel version
[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         _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
74         _cleanup_fclose_ FILE *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         if (!noauto && !nofail) {
217                 int r;
218                 free(p);
219                 p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf", NULL);
220                 if (!p)
221                         return log_oom();
222
223                 mkdir_parents_label(p, 0755);
224
225                 r = write_string_file(p,
226                                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
227                                 "[Unit]\n"
228                                 "JobTimeoutSec=0\n"); /* the binary handles timeouts anyway */
229                 if (r)
230                         return r;
231         }
232
233         return 0;
234 }
235
236 static int parse_proc_cmdline(char ***arg_proc_cmdline_disks, char **arg_proc_cmdline_keyfile) {
237         _cleanup_free_ char *line = NULL;
238         char *w = NULL, *state = NULL;
239         int r;
240         size_t l;
241
242         if (detect_container(NULL) > 0)
243                 return 0;
244
245         r = read_one_line_file("/proc/cmdline", &line);
246         if (r < 0) {
247                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
248                 return 0;
249         }
250
251         FOREACH_WORD_QUOTED(w, l, line, state) {
252                 _cleanup_free_ char *word = NULL;
253
254                 word = strndup(w, l);
255                 if (!word)
256                         return log_oom();
257
258                 if (startswith(word, "luks=")) {
259                         r = parse_boolean(word + 5);
260                         if (r < 0)
261                                 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
262                         else
263                                 arg_enabled = r;
264
265                 } else if (startswith(word, "rd.luks=")) {
266
267                         if (in_initrd()) {
268                                 r = parse_boolean(word + 8);
269                                 if (r < 0)
270                                         log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
271                                 else
272                                         arg_enabled = r;
273                         }
274
275                 } else if (startswith(word, "luks.crypttab=")) {
276                         r = parse_boolean(word + 14);
277                         if (r < 0)
278                                 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
279                         else
280                                 arg_read_crypttab = r;
281
282                 } else if (startswith(word, "rd.luks.crypttab=")) {
283
284                         if (in_initrd()) {
285                                 r = parse_boolean(word + 17);
286                                 if (r < 0)
287                                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
288                                 else
289                                         arg_read_crypttab = r;
290                         }
291
292                 } else if (startswith(word, "luks.uuid=")) {
293                         if (strv_extend(arg_proc_cmdline_disks, word + 10) < 0)
294                                 return log_oom();
295
296                 } else if (startswith(word, "rd.luks.uuid=")) {
297
298                         if (in_initrd()) {
299                                 if (strv_extend(arg_proc_cmdline_disks, word + 13) < 0)
300                                         return log_oom();
301                         }
302
303                 } else if (startswith(word, "luks.key=")) {
304                         *arg_proc_cmdline_keyfile = strdup(word + 9);
305                         if (!*arg_proc_cmdline_keyfile)
306                                 return log_oom();
307
308                 } else if (startswith(word, "rd.luks.key=")) {
309
310                         if (in_initrd()) {
311                                 if (*arg_proc_cmdline_keyfile)
312                                         free(*arg_proc_cmdline_keyfile);
313                                 *arg_proc_cmdline_keyfile = strdup(word + 12);
314                                 if (!*arg_proc_cmdline_keyfile)
315                                         return log_oom();
316                         }
317
318                 } else if (startswith(word, "luks.") ||
319                            (in_initrd() && startswith(word, "rd.luks."))) {
320
321                         log_warning("Unknown kernel switch %s. Ignoring.", word);
322                 }
323         }
324
325         strv_uniq(*arg_proc_cmdline_disks);
326
327         return 0;
328 }
329
330 int main(int argc, char *argv[]) {
331         _cleanup_fclose_ FILE *f = NULL;
332         unsigned n = 0;
333         int r = EXIT_SUCCESS;
334         char **i;
335         _cleanup_strv_free_ char **arg_proc_cmdline_disks_done = NULL;
336         _cleanup_strv_free_ char **arg_proc_cmdline_disks = NULL;
337         _cleanup_free_ char *arg_proc_cmdline_keyfile = NULL;
338
339         if (argc > 1 && argc != 4) {
340                 log_error("This program takes three or no arguments.");
341                 return EXIT_FAILURE;
342         }
343
344         if (argc > 1)
345                 arg_dest = argv[1];
346
347         log_set_target(LOG_TARGET_SAFE);
348         log_parse_environment();
349         log_open();
350
351         umask(0022);
352
353         if (parse_proc_cmdline(&arg_proc_cmdline_disks, &arg_proc_cmdline_keyfile) < 0)
354                 return EXIT_FAILURE;
355
356         if (!arg_enabled)
357                 return EXIT_SUCCESS;
358
359         if (arg_read_crypttab) {
360                 f = fopen("/etc/crypttab", "re");
361
362                 if (!f) {
363                         if (errno == ENOENT)
364                                 r = EXIT_SUCCESS;
365                         else {
366                                 r = EXIT_FAILURE;
367                                 log_error("Failed to open /etc/crypttab: %m");
368                         }
369                 } else for (;;) {
370                         char line[LINE_MAX], *l;
371                         _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
372                         int k;
373
374                         if (!fgets(line, sizeof(line), f))
375                                 break;
376
377                         n++;
378
379                         l = strstrip(line);
380                         if (*l == '#' || *l == 0)
381                                 continue;
382
383                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
384                         if (k < 2 || k > 4) {
385                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
386                                 r = EXIT_FAILURE;
387                                 continue;
388                         }
389
390                         if (arg_proc_cmdline_disks) {
391                                 /*
392                                   If luks UUIDs are specified on the kernel command line, use them as a filter
393                                   for /etc/crypttab and only generate units for those.
394                                 */
395                                 STRV_FOREACH(i, arg_proc_cmdline_disks) {
396                                         _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
397                                         const char *p = *i;
398
399                                         if (startswith(p, "luks-"))
400                                                 p += 5;
401
402                                         proc_name = strappend("luks-", p);
403                                         proc_device = strappend("UUID=", p);
404
405                                         if (!proc_name || !proc_device)
406                                                 return log_oom();
407
408                                         if (streq(proc_device, device) || streq(proc_name, name)) {
409                                                 if (create_disk(name, device, password, options) < 0)
410                                                         r = EXIT_FAILURE;
411
412                                                 if (strv_extend(&arg_proc_cmdline_disks_done, p) < 0)
413                                                         return log_oom();
414                                         }
415                                 }
416                         } else {
417                                 if (create_disk(name, device, password, options) < 0)
418                                         r = EXIT_FAILURE;
419                         }
420                 }
421         }
422
423         STRV_FOREACH(i, arg_proc_cmdline_disks) {
424                 /*
425                   Generate units for those UUIDs, which were specified
426                   on the kernel command line and not yet written.
427                 */
428
429                 _cleanup_free_ char *name = NULL, *device = NULL;
430                 const char *p = *i;
431
432                 if (startswith(p, "luks-"))
433                         p += 5;
434
435                 if (strv_contains(arg_proc_cmdline_disks_done, p))
436                         continue;
437
438                 name = strappend("luks-", p);
439                 device = strappend("UUID=", p);
440
441                 if (!name || !device)
442                         return log_oom();
443
444                 if (create_disk(name, device, arg_proc_cmdline_keyfile, "timeout=0") < 0)
445                         r = EXIT_FAILURE;
446         }
447
448         return r;
449 }