chiark / gitweb /
analyze: various cleanups
[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         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) {
237         char _cleanup_free_ *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                 char _cleanup_free_ *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.") ||
304                            (in_initrd() && startswith(word, "rd.luks."))) {
305
306                         log_warning("Unknown kernel switch %s. Ignoring.", word);
307                 }
308         }
309
310         strv_uniq(*arg_proc_cmdline_disks);
311
312         return 0;
313 }
314
315 int main(int argc, char *argv[]) {
316         FILE _cleanup_fclose_ *f = NULL;
317         unsigned n = 0;
318         int r = EXIT_SUCCESS;
319         char **i;
320         char _cleanup_strv_free_ **arg_proc_cmdline_disks_done = NULL;
321         char _cleanup_strv_free_ **arg_proc_cmdline_disks = NULL;
322
323         if (argc > 1 && argc != 4) {
324                 log_error("This program takes three or no arguments.");
325                 return EXIT_FAILURE;
326         }
327
328         if (argc > 1)
329                 arg_dest = argv[1];
330
331         log_set_target(LOG_TARGET_SAFE);
332         log_parse_environment();
333         log_open();
334
335         umask(0022);
336
337         if (parse_proc_cmdline(&arg_proc_cmdline_disks) < 0)
338                 return EXIT_FAILURE;
339
340         if (!arg_enabled)
341                 return EXIT_SUCCESS;
342
343         if (arg_read_crypttab) {
344                 f = fopen("/etc/crypttab", "re");
345
346                 if (!f) {
347                         if (errno == ENOENT)
348                                 r = EXIT_SUCCESS;
349                         else {
350                                 r = EXIT_FAILURE;
351                                 log_error("Failed to open /etc/crypttab: %m");
352                         }
353                 } else for (;;) {
354                         char line[LINE_MAX], *l;
355                         char _cleanup_free_ *name = NULL, *device = NULL, *password = NULL, *options = NULL;
356                         int k;
357
358                         if (!fgets(line, sizeof(line), f))
359                                 break;
360
361                         n++;
362
363                         l = strstrip(line);
364                         if (*l == '#' || *l == 0)
365                                 continue;
366
367                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
368                         if (k < 2 || k > 4) {
369                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
370                                 r = EXIT_FAILURE;
371                                 continue;
372                         }
373
374                         if (arg_proc_cmdline_disks) {
375                                 /*
376                                   If luks UUIDs are specified on the kernel command line, use them as a filter
377                                   for /etc/crypttab and only generate units for those.
378                                 */
379                                 STRV_FOREACH(i, arg_proc_cmdline_disks) {
380                                         char _cleanup_free_ *proc_device = NULL, *proc_name = NULL;
381                                         const char *p = *i;
382
383                                         if (startswith(p, "luks-"))
384                                                 p += 5;
385
386                                         proc_name = strappend("luks-", p);
387                                         proc_device = strappend("UUID=", p);
388
389                                         if (!proc_name || !proc_device)
390                                                 return log_oom();
391
392                                         if (streq(proc_device, device) || streq(proc_name, name)) {
393                                                 if (create_disk(name, device, password, options) < 0)
394                                                         r = EXIT_FAILURE;
395
396                                                 if (strv_extend(&arg_proc_cmdline_disks_done, p) < 0)
397                                                         return log_oom();
398                                         }
399                                 }
400                         } else {
401                                 if (create_disk(name, device, password, options) < 0)
402                                         r = EXIT_FAILURE;
403                         }
404                 }
405         }
406
407         STRV_FOREACH(i, arg_proc_cmdline_disks) {
408                 /*
409                   Generate units for those UUIDs, which were specified
410                   on the kernel command line and not yet written.
411                 */
412
413                 char _cleanup_free_ *name = NULL, *device = NULL;
414                 const char *p = *i;
415
416                 if (startswith(p, "luks-"))
417                         p += 5;
418
419                 if (strv_contains(arg_proc_cmdline_disks_done, p))
420                         continue;
421
422                 name = strappend("luks-", p);
423                 device = strappend("UUID=", p);
424
425                 if (!name || !device)
426                         return log_oom();
427
428                 if (create_disk(name, device, NULL, "timeout=0") < 0)
429                         r = EXIT_FAILURE;
430         }
431
432         return r;
433 }