chiark / gitweb /
8959bf51c8c72fb757ac93c14d003d20c5caf170
[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                         if (strv_extend(arg_proc_cmdline_disks, word + 10) < 0)
262                                 return log_oom();
263
264                 } else if (startswith(word, "rd.luks.uuid=")) {
265
266                         if (in_initrd()) {
267                                 if (strv_extend(arg_proc_cmdline_disks, word + 13) < 0)
268                                         return log_oom();
269                         }
270
271                 } else if (startswith(word, "luks.") ||
272                            (in_initrd() && startswith(word, "rd.luks."))) {
273
274                         log_warning("Unknown kernel switch %s. Ignoring.", word);
275                 }
276         }
277
278         strv_uniq(*arg_proc_cmdline_disks);
279
280         return 0;
281 }
282
283 int main(int argc, char *argv[]) {
284         FILE _cleanup_fclose_ *f = NULL;
285         unsigned n = 0;
286         int r = EXIT_SUCCESS;
287         char **i;
288         char _cleanup_strv_free_ **arg_proc_cmdline_disks_done = NULL;
289         char _cleanup_strv_free_ **arg_proc_cmdline_disks = NULL;
290
291         if (argc > 1 && argc != 4) {
292                 log_error("This program takes three or no arguments.");
293                 return EXIT_FAILURE;
294         }
295
296         if (argc > 1)
297                 arg_dest = argv[1];
298
299         log_set_target(LOG_TARGET_SAFE);
300         log_parse_environment();
301         log_open();
302
303         umask(0022);
304
305         if (parse_proc_cmdline(&arg_proc_cmdline_disks) < 0)
306                 return EXIT_FAILURE;
307
308         if (!arg_enabled)
309                 return EXIT_SUCCESS;
310
311         if (arg_read_crypttab) {
312                 f = fopen("/etc/crypttab", "re");
313
314                 if (!f) {
315                         if (errno == ENOENT)
316                                 r = EXIT_SUCCESS;
317                         else {
318                                 r = EXIT_FAILURE;
319                                 log_error("Failed to open /etc/crypttab: %m");
320                         }
321                 } else for (;;) {
322                         char line[LINE_MAX], *l;
323                         char _cleanup_free_ *name = NULL, *device = NULL, *password = NULL, *options = NULL;
324                         int k;
325
326                         if (!fgets(line, sizeof(line), f))
327                                 break;
328
329                         n++;
330
331                         l = strstrip(line);
332                         if (*l == '#' || *l == 0)
333                                 continue;
334
335                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
336                         if (k < 2 || k > 4) {
337                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
338                                 r = EXIT_FAILURE;
339                                 continue;
340                         }
341
342                         if (arg_proc_cmdline_disks) {
343                                 /*
344                                   If luks UUIDs are specified on the kernel command line, use them as a filter
345                                   for /etc/crypttab and only generate units for those.
346                                 */
347                                 STRV_FOREACH(i, arg_proc_cmdline_disks) {
348                                         char _cleanup_free_ *proc_device = NULL, *proc_name = NULL;
349                                         const char *p = *i;
350
351                                         if (startswith(p, "luks-"))
352                                                 p += 5;
353
354                                         proc_name = strappend("luks-", p);
355                                         proc_device = strappend("UUID=", p);
356
357                                         if (!proc_name || !proc_device)
358                                                 return log_oom();
359
360                                         if (streq(proc_device, device) || streq(proc_name, name)) {
361                                                 if (create_disk(name, device, password, options) < 0)
362                                                         r = EXIT_FAILURE;
363
364                                                 if (strv_extend(&arg_proc_cmdline_disks_done, p) < 0)
365                                                         return log_oom();
366                                         }
367                                 }
368                         } else {
369                                 if (create_disk(name, device, password, options) < 0)
370                                         r = EXIT_FAILURE;
371                         }
372                 }
373         }
374
375         STRV_FOREACH(i, arg_proc_cmdline_disks) {
376                 /*
377                   Generate units for those UUIDs, which were specified
378                   on the kernel command line and not yet written.
379                 */
380
381                 char _cleanup_free_ *name = NULL, *device = NULL;
382                 const char *p = *i;
383
384                 if (startswith(p, "luks-"))
385                         p += 5;
386
387                 if (strv_contains(arg_proc_cmdline_disks_done, p))
388                         continue;
389
390                 name = strappend("luks-", p);
391                 device = strappend("UUID=", p);
392
393                 if (!name || !device)
394                         return log_oom();
395
396                 if (create_disk(name, device, NULL, "timeout=0") < 0)
397                         r = EXIT_FAILURE;
398         }
399
400         return r;
401 }