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