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