chiark / gitweb /
log.h: new log_oom() -> int -ENOMEM, use it
[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
33 static const char *arg_dest = "/tmp";
34 static bool arg_enabled = true;
35 static bool arg_read_crypttab = true;
36 static char **arg_proc_cmdline_disks = NULL;
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 *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
74         int r;
75         FILE *f = NULL;
76         bool noauto, nofail;
77
78         assert(name);
79         assert(device);
80
81         noauto = has_option(options, "noauto");
82         nofail = has_option(options, "nofail");
83
84         n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service");
85         if (!n) {
86                 r = -ENOMEM;
87                 log_error("Failed to allocate unit name.");
88                 goto fail;
89         }
90
91         p = strjoin(arg_dest, "/", n, NULL);
92         if (!p) {
93                 r = -ENOMEM;
94                 log_error("Failed to allocate unit file name.");
95                 goto fail;
96         }
97
98         u = fstab_node_to_udev_node(device);
99         if (!u) {
100                 r = -ENOMEM;
101                 log_error("Failed to allocate device node.");
102                 goto fail;
103         }
104
105         d = unit_name_from_path(u, ".device");
106         if (!d) {
107                 r = -ENOMEM;
108                 log_error("Failed to allocate device name.");
109                 goto fail;
110         }
111
112         f = fopen(p, "wxe");
113         if (!f) {
114                 r = -errno;
115                 log_error("Failed to create unit file: %m");
116                 goto fail;
117         }
118
119         fprintf(f,
120                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
121                 "[Unit]\n"
122                 "Description=Cryptography Setup for %%I\n"
123                 "Documentation=man:systemd-cryptsetup@.service(8) man:crypttab(5)\n"
124                 "SourcePath=/etc/crypttab\n"
125                 "Conflicts=umount.target\n"
126                 "DefaultDependencies=no\n"
127                 "BindsTo=%s dev-mapper-%%i.device\n"
128                 "After=systemd-readahead-collect.service systemd-readahead-replay.service %s\n"
129                 "Before=umount.target\n",
130                 d, d);
131
132         if (!nofail)
133                 fprintf(f,
134                         "Before=cryptsetup.target\n");
135
136         if (password && (streq(password, "/dev/urandom") ||
137                          streq(password, "/dev/random") ||
138                          streq(password, "/dev/hw_random")))
139                 fputs("After=systemd-random-seed-load.service\n", f);
140         else
141                 fputs("Before=local-fs.target\n", f);
142
143         fprintf(f,
144                 "\n[Service]\n"
145                 "Type=oneshot\n"
146                 "RemainAfterExit=yes\n"
147                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
148                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
149                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
150                 name, u, strempty(password), strempty(options),
151                 name);
152
153         if (has_option(options, "tmp"))
154                 fprintf(f,
155                         "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
156                         name);
157
158         if (has_option(options, "swap"))
159                 fprintf(f,
160                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
161                         name);
162
163         fflush(f);
164
165         if (ferror(f)) {
166                 r = -errno;
167                 log_error("Failed to write file: %m");
168                 goto fail;
169         }
170
171         if (asprintf(&from, "../%s", n) < 0) {
172                 r = -ENOMEM;
173                 goto fail;
174         }
175
176         if (!noauto) {
177
178                 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
179                 if (!to) {
180                         r = -ENOMEM;
181                         goto fail;
182                 }
183
184                 mkdir_parents_label(to, 0755);
185                 if (symlink(from, to) < 0) {
186                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
187                         r = -errno;
188                         goto fail;
189                 }
190
191                 free(to);
192
193                 if (!nofail)
194                         to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
195                 else
196                         to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
197                 if (!to) {
198                         r = -ENOMEM;
199                         goto fail;
200                 }
201
202                 mkdir_parents_label(to, 0755);
203                 if (symlink(from, to) < 0) {
204                         log_error("Failed to create symlink '%s' to '%s': %m", from, to);
205                         r = -errno;
206                         goto fail;
207                 }
208
209                 free(to);
210                 to = NULL;
211         }
212
213         e = unit_name_escape(name);
214         to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
215         if (!to) {
216                 r = -ENOMEM;
217                 goto fail;
218         }
219
220         mkdir_parents_label(to, 0755);
221         if (symlink(from, to) < 0) {
222                 log_error("Failed to create symlink '%s' to '%s': %m", from, to);
223                 r = -errno;
224                 goto fail;
225         }
226
227         r = 0;
228
229 fail:
230         free(p);
231         free(n);
232         free(d);
233         free(e);
234
235         free(from);
236         free(to);
237
238         if (f)
239                 fclose(f);
240
241         return r;
242 }
243
244 static int parse_proc_cmdline(void) {
245         char *line, *w, *state;
246         int r;
247         size_t l;
248
249         if (detect_container(NULL) > 0)
250                 return 0;
251
252         r = read_one_line_file("/proc/cmdline", &line);
253         if (r < 0) {
254                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
255                 return 0;
256         }
257
258         FOREACH_WORD_QUOTED(w, l, line, state) {
259                 char *word;
260
261                 word = strndup(w, l);
262                 if (!word) {
263                         r = -ENOMEM;
264                         goto finish;
265                 }
266
267                 if (startswith(word, "luks=")) {
268                         r = parse_boolean(word + 5);
269                         if (r < 0)
270                                 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
271                         else
272                                 arg_enabled = r;
273
274                 } else if (startswith(word, "rd.luks=")) {
275
276                         if (in_initrd()) {
277                                 r = parse_boolean(word + 8);
278                                 if (r < 0)
279                                         log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
280                                 else
281                                         arg_enabled = r;
282                         }
283
284                 } else if (startswith(word, "luks.crypttab=")) {
285                         r = parse_boolean(word + 14);
286                         if (r < 0)
287                                 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
288                         else
289                                 arg_read_crypttab = r;
290
291                 } else if (startswith(word, "rd.luks.crypttab=")) {
292
293                         if (in_initrd()) {
294                                 r = parse_boolean(word + 17);
295                                 if (r < 0)
296                                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
297                                 else
298                                         arg_read_crypttab = r;
299                         }
300
301                 } else if (startswith(word, "luks.uuid=")) {
302                         char **t;
303
304                         t = strv_append(arg_proc_cmdline_disks, word + 10);
305                         if (!t) {
306                                 r = log_oom();
307                                 goto finish;
308                         }
309                         strv_free(arg_proc_cmdline_disks);
310                         arg_proc_cmdline_disks = t;
311
312                 } else if (startswith(word, "rd.luks.uuid=")) {
313
314                         if (in_initrd()) {
315                                 char **t;
316
317                                 t = strv_append(arg_proc_cmdline_disks, word + 13);
318                                 if (!t) {
319                                         r = log_oom();
320                                         goto finish;
321                                 }
322                                 strv_free(arg_proc_cmdline_disks);
323                                 arg_proc_cmdline_disks = t;
324                         }
325
326                 } else if (startswith(word, "luks.") ||
327                            (in_initrd() && startswith(word, "rd.luks."))) {
328
329                         log_warning("Unknown kernel switch %s. Ignoring.", word);
330                 }
331
332                 free(word);
333         }
334
335         r = 0;
336
337 finish:
338         free(line);
339         return r;
340 }
341
342 int main(int argc, char *argv[]) {
343         FILE *f = NULL;
344         int r = EXIT_SUCCESS;
345         unsigned n = 0;
346         char **i;
347
348         if (argc > 1 && argc != 4) {
349                 log_error("This program takes three or no arguments.");
350                 return EXIT_FAILURE;
351         }
352
353         if (argc > 1)
354                 arg_dest = argv[1];
355
356         log_set_target(LOG_TARGET_SAFE);
357         log_parse_environment();
358         log_open();
359
360         umask(0022);
361
362         if (parse_proc_cmdline() < 0)
363                 return EXIT_FAILURE;
364
365         if (!arg_enabled) {
366                 r = EXIT_SUCCESS;
367                 goto finish;
368         }
369
370         STRV_FOREACH(i, arg_proc_cmdline_disks) {
371                 char *name, *device;
372                 const char *p = *i;
373
374                 if (startswith(p, "luks-"))
375                         p += 5;
376
377                 name = strappend("luks-", *i);
378                 device = strappend("UUID=", *i);
379
380                 if (!name || !device) {
381                         log_oom();
382                         r = EXIT_FAILURE;
383                         free(name);
384                         free(device);
385                         goto finish;
386                 }
387
388                 if (create_disk(name, device, NULL, NULL) < 0)
389                         r = EXIT_FAILURE;
390
391                 free(name);
392                 free(device);
393         }
394
395         if (!arg_read_crypttab)
396                 return r;
397
398         f = fopen("/etc/crypttab", "re");
399         if (!f) {
400
401                 if (errno == ENOENT)
402                         r = EXIT_SUCCESS;
403                 else {
404                         r = EXIT_FAILURE;
405                         log_error("Failed to open /etc/crypttab: %m");
406                 }
407
408                 goto finish;
409         }
410
411         for (;;) {
412                 char line[LINE_MAX], *l;
413                 char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
414                 int k;
415
416                 if (!fgets(line, sizeof(line), f))
417                         break;
418
419                 n++;
420
421                 l = strstrip(line);
422                 if (*l == '#' || *l == 0)
423                         continue;
424
425                 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
426                 if (k < 2 || k > 4) {
427                         log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
428                         r = EXIT_FAILURE;
429                         goto next;
430                 }
431
432                 if (create_disk(name, device, password, options) < 0)
433                         r = EXIT_FAILURE;
434
435         next:
436                 free(name);
437                 free(device);
438                 free(password);
439                 free(options);
440         }
441
442 finish:
443         if (f)
444                 fclose(f);
445
446         strv_free(arg_proc_cmdline_disks);
447
448         return r;
449 }