/src/systemd/src/shared/generator.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <stdlib.h> |
4 | | #include <sys/stat.h> |
5 | | #include <unistd.h> |
6 | | |
7 | | #include "alloc-util.h" |
8 | | #include "argv-util.h" |
9 | | #include "dropin.h" |
10 | | #include "escape.h" |
11 | | #include "fd-util.h" |
12 | | #include "fileio.h" |
13 | | #include "fstab-util.h" |
14 | | #include "generator.h" |
15 | | #include "initrd-util.h" |
16 | | #include "log.h" |
17 | | #include "mkdir.h" |
18 | | #include "mountpoint-util.h" |
19 | | #include "parse-util.h" |
20 | | #include "path-util.h" |
21 | | #include "special.h" |
22 | | #include "specifier.h" |
23 | | #include "string-util.h" |
24 | | #include "strv.h" |
25 | | #include "time-util.h" |
26 | | #include "tmpfile-util.h" |
27 | | #include "unit-name.h" |
28 | | #include "verbs.h" |
29 | | |
30 | 784 | static int symlink_unless_exists(const char *target, const char *linkpath) { |
31 | 784 | (void) mkdir_parents(linkpath, 0755); |
32 | | |
33 | 784 | if (symlink(target, linkpath) < 0 && errno != EEXIST) |
34 | 784 | return log_error_errno(errno, "Failed to create symlink %s: %m", linkpath); |
35 | 784 | return 0; |
36 | 784 | } |
37 | | |
38 | | int generator_open_unit_file_full( |
39 | | const char *dir, |
40 | | const char *source, |
41 | | const char *filename, |
42 | | FILE **ret_file, |
43 | | char **ret_final_path, |
44 | 784 | char **ret_temp_path) { |
45 | | |
46 | 784 | _cleanup_free_ char *p = NULL; |
47 | 784 | FILE *f; |
48 | 784 | int r; |
49 | | |
50 | 784 | assert(dir); |
51 | 784 | assert(ret_file); |
52 | | |
53 | | /* If <ret_temp_path> is specified, it creates a temporary unit file and also returns its |
54 | | * temporary path. */ |
55 | | |
56 | 784 | if (ret_temp_path) { |
57 | 0 | r = fopen_temporary(dir, &f, &p); |
58 | 0 | if (r < 0) |
59 | 0 | return log_error_errno(r, "Failed to create temporary unit file in '%s': %m", dir); |
60 | | |
61 | 0 | (void) fchmod(fileno(f), 0644); |
62 | |
|
63 | 0 | *ret_temp_path = TAKE_PTR(p); |
64 | 784 | } else { |
65 | 784 | assert(filename); |
66 | | |
67 | 784 | p = path_join(dir, filename); |
68 | 784 | if (!p) |
69 | 0 | return log_oom(); |
70 | | |
71 | 784 | r = fopen_unlocked(p, "wxe", &f); |
72 | 784 | if (r < 0) { |
73 | 0 | if (source && r == -EEXIST) |
74 | 0 | return log_error_errno(r, |
75 | 0 | "Failed to create unit file '%s', as it already exists. Duplicate entry in '%s'?", |
76 | 0 | p, source); |
77 | | |
78 | 0 | return log_error_errno(r, "Failed to create unit file '%s': %m", p); |
79 | 0 | } |
80 | 784 | } |
81 | | |
82 | 784 | fprintf(f, |
83 | 784 | "# Automatically generated by %s\n\n", |
84 | 784 | program_invocation_short_name); |
85 | | |
86 | 784 | *ret_file = f; |
87 | | |
88 | 784 | if (ret_final_path) |
89 | 0 | *ret_final_path = TAKE_PTR(p); |
90 | | |
91 | 784 | return 0; |
92 | 784 | } |
93 | | |
94 | | int generator_add_symlink_full( |
95 | | const char *dir, |
96 | | const char *dst, |
97 | | const char *dep_type, |
98 | | const char *src, |
99 | 784 | const char *instance) { |
100 | | |
101 | 784 | _cleanup_free_ char *dn = NULL, *fn = NULL, *instantiated = NULL, *to = NULL, *from = NULL; |
102 | 784 | int r; |
103 | | |
104 | 784 | assert(dir); |
105 | 784 | assert(dst); |
106 | 784 | assert(src); |
107 | | |
108 | | /* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). |
109 | | * |
110 | | * If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias. |
111 | | * |
112 | | * If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */ |
113 | | |
114 | 784 | r = path_split_prefix_filename(src, &dn, &fn); |
115 | 784 | if (r < 0) |
116 | 784 | return log_error_errno(r, "Failed to split '%s' into directory prefix and filename: %m", src); |
117 | 784 | if (r == O_DIRECTORY) |
118 | 784 | return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Expected path to regular file name, but got '%s', refusing.", src); |
119 | | |
120 | 784 | if (instance) { |
121 | 0 | r = unit_name_replace_instance(fn, instance, &instantiated); |
122 | 0 | if (r < 0) |
123 | 0 | return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance); |
124 | 0 | } |
125 | | |
126 | 784 | if (dep_type) { /* Create a .wants/ style dep */ |
127 | 784 | from = path_join(dn ?: "..", fn); |
128 | 784 | if (!from) |
129 | 0 | return log_oom(); |
130 | | |
131 | 784 | to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn); |
132 | 784 | } else { /* or create an alias */ |
133 | 0 | from = dn ? path_join(dn, fn) : strdup(fn); |
134 | 0 | if (!from) |
135 | 0 | return log_oom(); |
136 | | |
137 | 0 | to = strjoin(dir, "/", dst); |
138 | 0 | } |
139 | 784 | if (!to) |
140 | 0 | return log_oom(); |
141 | | |
142 | 784 | return symlink_unless_exists(from, to); |
143 | 784 | } |
144 | | |
145 | | static int generator_add_ordering( |
146 | | const char *dir, |
147 | | const char *src, |
148 | | const char *order, |
149 | | const char *dst, |
150 | 0 | const char *instance) { |
151 | |
|
152 | 0 | _cleanup_free_ char *instantiated = NULL, *p = NULL, *fn = NULL; |
153 | 0 | _cleanup_fclose_ FILE *f = NULL; |
154 | 0 | const char *to; |
155 | 0 | int r; |
156 | |
|
157 | 0 | assert(dir); |
158 | 0 | assert(src); |
159 | 0 | assert(order); |
160 | 0 | assert(dst); |
161 | | |
162 | | /* Adds an explicit ordering dependency of type <order> from <src> to <dst>. If <instance> is |
163 | | * specified, it is inserted into <dst>. */ |
164 | |
|
165 | 0 | if (instance) { |
166 | 0 | r = unit_name_replace_instance(dst, instance, &instantiated); |
167 | 0 | if (r < 0) |
168 | 0 | return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", dst, instance); |
169 | | |
170 | 0 | to = instantiated; |
171 | 0 | } else |
172 | 0 | to = dst; |
173 | | |
174 | 0 | fn = strjoin(src, ".d/50-order-", to, ".conf"); |
175 | 0 | if (!fn) |
176 | 0 | return log_oom(); |
177 | | |
178 | 0 | p = path_join(dir, fn); |
179 | 0 | if (!p) |
180 | 0 | return log_oom(); |
181 | | |
182 | 0 | (void) mkdir_parents_label(p, 0755); |
183 | |
|
184 | 0 | r = fopen_unlocked(p, "wxe", &f); |
185 | 0 | if (r < 0) |
186 | 0 | return log_error_errno(r, "Failed to create '%s': %m", p); |
187 | | |
188 | 0 | fprintf(f, |
189 | 0 | "# Automatically generated by %s\n\n" |
190 | 0 | "[Unit]\n" |
191 | 0 | "%s=%s\n", |
192 | 0 | program_invocation_short_name, |
193 | 0 | order, |
194 | 0 | to); |
195 | |
|
196 | 0 | r = fflush_and_check(f); |
197 | 0 | if (r < 0) |
198 | 0 | return log_error_errno(r, "Failed to write drop-in '%s': %m", p); |
199 | | |
200 | 0 | return 0; |
201 | 0 | } |
202 | | |
203 | | static int write_fsck_sysroot_service( |
204 | | const char *unit, /* Either SPECIAL_FSCK_ROOT_SERVICE or SPECIAL_FSCK_USR_SERVICE */ |
205 | | const char *dir, |
206 | | const char *what, |
207 | 0 | const char *extra_after) { |
208 | |
|
209 | 0 | _cleanup_free_ char *device = NULL, *escaped = NULL, *escaped2 = NULL; |
210 | 0 | _cleanup_fclose_ FILE *f = NULL; |
211 | 0 | int r; |
212 | |
|
213 | 0 | assert(unit); |
214 | 0 | assert(dir); |
215 | 0 | assert(what); |
216 | | |
217 | | /* Writes out special versions of systemd-fsck-root.service and systemd-fsck-usr.service for use in |
218 | | * the initrd. The regular statically shipped versions of these unit files use / and /usr for as |
219 | | * paths, which doesn't match what we need for the initrd (where the dirs are /sysroot/ + |
220 | | * /sysusr/usr/), hence we overwrite those versions here. */ |
221 | |
|
222 | 0 | escaped = specifier_escape(what); |
223 | 0 | if (!escaped) |
224 | 0 | return log_oom(); |
225 | | |
226 | 0 | escaped2 = cescape(escaped); |
227 | 0 | if (!escaped2) |
228 | 0 | return log_oom(); |
229 | | |
230 | 0 | r = unit_name_from_path(what, ".device", &device); |
231 | 0 | if (r < 0) |
232 | 0 | return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what); |
233 | | |
234 | 0 | r = generator_open_unit_file(dir, /* source= */ NULL, unit, &f); |
235 | 0 | if (r < 0) |
236 | 0 | return r; |
237 | | |
238 | 0 | fprintf(f, |
239 | 0 | "[Unit]\n" |
240 | 0 | "Description=File System Check on %1$s\n" |
241 | 0 | "Documentation=man:%2$s(8)\n" |
242 | 0 | "\n" |
243 | 0 | "DefaultDependencies=no\n" |
244 | 0 | "BindsTo=%3$s\n" |
245 | 0 | "Conflicts=shutdown.target\n" |
246 | 0 | "After=%4$s%5$slocal-fs-pre.target %3$s\n" |
247 | 0 | "Before=shutdown.target\n" |
248 | 0 | "\n" |
249 | 0 | "[Service]\n" |
250 | 0 | "Type=oneshot\n" |
251 | 0 | "RemainAfterExit=yes\n" |
252 | 0 | "ExecStart=" SYSTEMD_FSCK_PATH " %6$s\n" |
253 | 0 | "TimeoutSec=infinity\n" |
254 | 0 | "ImportCredential=fsck.*\n", |
255 | 0 | escaped, |
256 | 0 | unit, |
257 | 0 | device, |
258 | 0 | strempty(extra_after), |
259 | 0 | isempty(extra_after) ? "" : " ", |
260 | 0 | escaped2); |
261 | |
|
262 | 0 | r = fflush_and_check(f); |
263 | 0 | if (r < 0) |
264 | 0 | return log_error_errno(r, "Failed to write unit %s: %m", unit); |
265 | | |
266 | 0 | return 0; |
267 | 0 | } |
268 | | |
269 | | int generator_write_fsck_deps( |
270 | | FILE *f, |
271 | | const char *dir, |
272 | | const char *what, |
273 | | const char *where, |
274 | | const char *fstype, |
275 | 0 | const char *options) { |
276 | |
|
277 | 0 | int r; |
278 | |
|
279 | 0 | assert(f); |
280 | 0 | assert(dir); |
281 | 0 | assert(what); |
282 | 0 | assert(where); |
283 | | |
284 | | /* Let's do an early exit if we are invoked for the root and /usr/ trees in the initrd, to avoid |
285 | | * generating confusing log messages. */ |
286 | 0 | if (in_initrd() && PATH_IN_SET(where, "/", "/usr")) { |
287 | 0 | log_debug("Skipping fsck for %s in initrd.", where); |
288 | 0 | return 0; |
289 | 0 | } |
290 | | |
291 | 0 | if (fstype) { |
292 | 0 | if (!fstype_is_blockdev_backed(fstype)) { |
293 | 0 | log_debug("Skipping file system check for non-block based file system '%s'.", what); |
294 | 0 | return 0; |
295 | 0 | } |
296 | | |
297 | 0 | if (fstype_is_ro(fstype)) { |
298 | 0 | log_debug("Skipping file system check for read-only file system '%s'.", what); |
299 | 0 | return 0; |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | 0 | if (fstab_test_option(options, "bind\0rbind\0")) { |
304 | 0 | log_debug("Skipping file system check for bind mount of '%s'.", what); |
305 | 0 | return 0; |
306 | 0 | } |
307 | | |
308 | 0 | if (!is_device_path(what)) { |
309 | 0 | log_debug("Checking was requested for \"%s\", but it is not a device.", what); |
310 | 0 | return 0; |
311 | 0 | } |
312 | | |
313 | 0 | if (!isempty(fstype) && !streq(fstype, "auto")) { |
314 | 0 | r = fsck_exists_for_fstype(fstype); |
315 | 0 | if (r < 0) |
316 | 0 | log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype); |
317 | 0 | else if (r == 0) { |
318 | | /* treat missing check as essentially OK */ |
319 | 0 | log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype); |
320 | 0 | return 0; |
321 | 0 | } |
322 | 0 | } else { |
323 | 0 | r = fsck_exists(); |
324 | 0 | if (r < 0) |
325 | 0 | log_warning_errno(r, "Checking was requested for %s, but couldn't detect if the fsck command may be used, proceeding: %m", what); |
326 | 0 | else if (r == 0) { |
327 | | /* treat missing fsck as essentially OK */ |
328 | 0 | log_debug("Checking was requested for %s, but the fsck command does not exist.", what); |
329 | 0 | return 0; |
330 | 0 | } |
331 | 0 | } |
332 | | |
333 | 0 | if (path_equal(where, "/")) { |
334 | | /* We support running the fsck instance for the root fs while it is already mounted, for |
335 | | * compatibility with non-initrd boots. It's ugly, but it is how it is. Since – unlike for |
336 | | * regular file systems – this means the ordering is reversed (i.e. mount *before* fsck) we |
337 | | * have a separate fsck unit for this, independent of systemd-fsck@.service. */ |
338 | |
|
339 | 0 | const char *lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/" SPECIAL_FSCK_ROOT_SERVICE); |
340 | |
|
341 | 0 | r = symlink_unless_exists(SYSTEM_DATA_UNIT_DIR "/" SPECIAL_FSCK_ROOT_SERVICE, lnk); |
342 | 0 | if (r < 0) |
343 | 0 | return r; |
344 | 0 | } else { |
345 | 0 | _cleanup_free_ char *_fsck = NULL; |
346 | 0 | const char *fsck, *dep; |
347 | |
|
348 | 0 | if (in_initrd() && path_equal(where, "/sysroot")) { |
349 | 0 | r = write_fsck_sysroot_service(SPECIAL_FSCK_ROOT_SERVICE, dir, what, SPECIAL_INITRD_ROOT_DEVICE_TARGET); |
350 | 0 | if (r < 0) |
351 | 0 | return r; |
352 | | |
353 | 0 | fsck = SPECIAL_FSCK_ROOT_SERVICE; |
354 | 0 | dep = "Requires"; |
355 | |
|
356 | 0 | } else if (in_initrd() && path_equal(where, "/sysusr/usr")) { |
357 | 0 | r = write_fsck_sysroot_service(SPECIAL_FSCK_USR_SERVICE, dir, what, NULL); |
358 | 0 | if (r < 0) |
359 | 0 | return r; |
360 | | |
361 | 0 | fsck = SPECIAL_FSCK_USR_SERVICE; |
362 | 0 | dep = "Requires"; |
363 | 0 | } else { |
364 | | /* When this is /usr, then let's add a Wants= dependency, otherwise a Requires= |
365 | | * dependency. Why? We can't possibly unmount /usr during shutdown, but if we have a |
366 | | * Requires= from /usr onto a fsck@.service unit and that unit is shut down, then |
367 | | * we'd have to unmount /usr too. */ |
368 | |
|
369 | 0 | dep = path_equal(where, "/usr") ? "Wants" : "Requires"; |
370 | |
|
371 | 0 | r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck); |
372 | 0 | if (r < 0) |
373 | 0 | return log_error_errno(r, "Failed to create fsck service name: %m"); |
374 | | |
375 | 0 | fsck = _fsck; |
376 | 0 | } |
377 | | |
378 | 0 | fprintf(f, |
379 | 0 | "%1$s=%2$s\n" |
380 | 0 | "After=%2$s\n", |
381 | 0 | dep, fsck); |
382 | 0 | } |
383 | | |
384 | 0 | return 0; |
385 | 0 | } |
386 | | |
387 | | int generator_write_device_timeout( |
388 | | const char *dir, |
389 | | const char *what, |
390 | | const char *opts, |
391 | 0 | char **filtered) { |
392 | | |
393 | | /* Configure how long we wait for a device that backs a mount point or a |
394 | | * swap partition to show up. This is useful to support endless device timeouts |
395 | | * for devices that show up only after user input, like crypto devices. */ |
396 | |
|
397 | 0 | _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL; |
398 | 0 | usec_t u; |
399 | 0 | int r; |
400 | |
|
401 | 0 | assert(dir); |
402 | 0 | assert(what); |
403 | |
|
404 | 0 | r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" |
405 | 0 | "x-systemd.device-timeout\0", |
406 | 0 | NULL, &timeout, NULL, filtered); |
407 | 0 | if (r < 0) { |
408 | 0 | log_warning_errno(r, "Failed to parse fstab options, ignoring: %m"); |
409 | 0 | return 0; |
410 | 0 | } |
411 | 0 | if (r == 0) |
412 | 0 | return 0; |
413 | | |
414 | 0 | r = parse_sec_fix_0(timeout, &u); |
415 | 0 | if (r < 0) { |
416 | 0 | log_warning("Failed to parse timeout for device '%s', ignoring: %s", what, timeout); |
417 | 0 | return 0; |
418 | 0 | } |
419 | | |
420 | 0 | node = fstab_node_to_udev_node(what); |
421 | 0 | if (!node) |
422 | 0 | return log_oom(); |
423 | 0 | if (!is_device_path(node)) { |
424 | 0 | log_warning("'%s' is not a device path, ignoring x-systemd.device-timeout= option.", node); |
425 | 0 | return 0; |
426 | 0 | } |
427 | | |
428 | 0 | r = unit_name_from_path(node, ".device", &unit); |
429 | 0 | if (r < 0) |
430 | 0 | return log_error_errno(r, "Failed to make unit name from device path '%s': %m", node); |
431 | | |
432 | 0 | r = write_drop_in_format(dir, unit, 50, "device-timeout", |
433 | 0 | "# Automatically generated by %s\n" |
434 | 0 | "# from supplied options \"%s\"\n\n" |
435 | 0 | "[Unit]\n" |
436 | 0 | "JobRunningTimeoutSec=%s", |
437 | 0 | program_invocation_short_name, |
438 | 0 | opts, |
439 | 0 | timeout); |
440 | 0 | if (r < 0) |
441 | 0 | return r; |
442 | | |
443 | 0 | return 1; |
444 | 0 | } |
445 | | |
446 | | int generator_write_unit_timeout( |
447 | | FILE *f, |
448 | | const char *where, |
449 | | const char *opts, |
450 | | const char *filter, |
451 | 0 | const char *unit_setting) { |
452 | |
|
453 | 0 | _cleanup_free_ char *timeout = NULL; |
454 | 0 | usec_t u; |
455 | 0 | int r; |
456 | |
|
457 | 0 | assert(f); |
458 | 0 | assert(where); |
459 | 0 | assert(filter); |
460 | 0 | assert(unit_setting); |
461 | |
|
462 | 0 | r = fstab_filter_options(opts, filter, NULL, &timeout, NULL, NULL); |
463 | 0 | if (r < 0) |
464 | 0 | return log_error_errno(r, "Failed to parse options for '%s': %m", where); |
465 | 0 | if (r == 0) |
466 | 0 | return 0; |
467 | | |
468 | 0 | r = parse_sec_fix_0(timeout, &u); |
469 | 0 | if (r < 0) { |
470 | 0 | log_warning_errno(r, "Failed to parse timeout '%s' for '%s', ignoring: %m", timeout, where); |
471 | 0 | return 0; |
472 | 0 | } |
473 | | |
474 | 0 | fprintf(f, "%s=%s\n", unit_setting, FORMAT_TIMESPAN(u, 0)); |
475 | |
|
476 | 0 | return 0; |
477 | 0 | } |
478 | | |
479 | | int generator_write_network_device_deps( |
480 | | const char *dir, |
481 | | const char *what, |
482 | | const char *where, |
483 | 0 | const char *opts) { |
484 | | |
485 | | /* fstab records that specify _netdev option should apply the network |
486 | | * ordering on the actual device depending on network connection. If we |
487 | | * are not mounting real device (NFS, CIFS), we rely on _netdev effect |
488 | | * on the mount unit itself. */ |
489 | |
|
490 | 0 | _cleanup_free_ char *node = NULL, *unit = NULL; |
491 | 0 | int r; |
492 | |
|
493 | 0 | assert(dir); |
494 | 0 | assert(what); |
495 | |
|
496 | 0 | if (where && fstab_is_extrinsic(where, opts)) |
497 | 0 | return 0; |
498 | | |
499 | 0 | if (!fstab_test_option(opts, "_netdev\0")) |
500 | 0 | return 0; |
501 | | |
502 | 0 | node = fstab_node_to_udev_node(what); |
503 | 0 | if (!node) |
504 | 0 | return log_oom(); |
505 | | |
506 | | /* Nothing to apply dependencies to. */ |
507 | 0 | if (!is_device_path(node)) |
508 | 0 | return 0; |
509 | | |
510 | 0 | r = unit_name_from_path(node, ".device", &unit); |
511 | 0 | if (r < 0) |
512 | 0 | return log_error_errno(r, "Failed to make unit name from path \"%s\": %m", node); |
513 | | |
514 | | /* See mount_add_default_dependencies for explanation why we create such |
515 | | * dependencies. */ |
516 | 0 | return write_drop_in_format(dir, unit, 50, "netdev-dependencies", |
517 | 0 | "# Automatically generated by %s\n\n" |
518 | 0 | "[Unit]\n" |
519 | 0 | "After=" SPECIAL_NETWORK_ONLINE_TARGET " " SPECIAL_NETWORK_TARGET "\n" |
520 | 0 | "Wants=" SPECIAL_NETWORK_ONLINE_TARGET "\n", |
521 | 0 | program_invocation_short_name); |
522 | 0 | } |
523 | | |
524 | 0 | int generator_write_initrd_root_device_deps(const char *dir, const char *what) { |
525 | 0 | _cleanup_free_ char *unit = NULL; |
526 | 0 | int r; |
527 | |
|
528 | 0 | r = unit_name_from_path(what, ".device", &unit); |
529 | 0 | if (r < 0) |
530 | 0 | return log_error_errno(r, "Failed to make unit name from path \"%s\": %m", |
531 | 0 | what); |
532 | | |
533 | 0 | return write_drop_in_format(dir, SPECIAL_INITRD_ROOT_DEVICE_TARGET, 50, "root-device", |
534 | 0 | "# Automatically generated by %s\n\n" |
535 | 0 | "[Unit]\n" |
536 | 0 | "Requires=%s\n" |
537 | 0 | "After=%s", |
538 | 0 | program_invocation_short_name, |
539 | 0 | unit, |
540 | 0 | unit); |
541 | 0 | } |
542 | | |
543 | | int generator_hook_up_mkswap( |
544 | | const char *dir, |
545 | 0 | const char *what) { |
546 | |
|
547 | 0 | _cleanup_free_ char *node = NULL, *unit = NULL, *escaped = NULL, *where_unit = NULL; |
548 | 0 | _cleanup_fclose_ FILE *f = NULL; |
549 | 0 | int r; |
550 | |
|
551 | 0 | assert(dir); |
552 | 0 | assert(what); |
553 | |
|
554 | 0 | node = fstab_node_to_udev_node(what); |
555 | 0 | if (!node) |
556 | 0 | return log_oom(); |
557 | | |
558 | | /* Nothing to work on. */ |
559 | 0 | if (!is_device_path(node)) |
560 | 0 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
561 | 0 | "Cannot format something that is not a device node: %s", |
562 | 0 | node); |
563 | | |
564 | 0 | r = unit_name_from_path_instance("systemd-mkswap", node, ".service", &unit); |
565 | 0 | if (r < 0) |
566 | 0 | return log_error_errno(r, "Failed to make unit instance name from path \"%s\": %m", |
567 | 0 | node); |
568 | | |
569 | 0 | escaped = cescape(node); |
570 | 0 | if (!escaped) |
571 | 0 | return log_oom(); |
572 | | |
573 | 0 | r = unit_name_from_path(what, ".swap", &where_unit); |
574 | 0 | if (r < 0) |
575 | 0 | return log_error_errno(r, "Failed to make unit name from path \"%s\": %m", |
576 | 0 | what); |
577 | | |
578 | 0 | r = generator_open_unit_file(dir, /* source= */ NULL, unit, &f); |
579 | 0 | if (r < 0) |
580 | 0 | return r; |
581 | | |
582 | 0 | fprintf(f, |
583 | 0 | "[Unit]\n" |
584 | 0 | "Description=Make Swap on %%f\n" |
585 | 0 | "Documentation=man:systemd-mkswap@.service(8)\n" |
586 | 0 | "\n" |
587 | 0 | "DefaultDependencies=no\n" |
588 | 0 | "BindsTo=%%i.device\n" |
589 | 0 | "After=%%i.device\n" |
590 | 0 | "Before=%s\n" |
591 | 0 | "Conflicts=shutdown.target\n" |
592 | 0 | "Before=shutdown.target\n" |
593 | 0 | "\n" |
594 | 0 | "[Service]\n" |
595 | 0 | "Type=oneshot\n" |
596 | 0 | "RemainAfterExit=yes\n" |
597 | 0 | "ExecStart="SYSTEMD_MAKEFS_PATH " swap %s\n" |
598 | 0 | "TimeoutSec=infinity\n", |
599 | 0 | where_unit, |
600 | 0 | escaped); |
601 | |
|
602 | 0 | r = fflush_and_check(f); |
603 | 0 | if (r < 0) |
604 | 0 | return log_error_errno(r, "Failed to write unit %s: %m", unit); |
605 | | |
606 | 0 | return generator_add_symlink(dir, where_unit, "requires", unit); |
607 | 0 | } |
608 | | |
609 | | int generator_hook_up_mkfs( |
610 | | const char *dir, |
611 | | const char *what, |
612 | | const char *where, |
613 | 0 | const char *type) { |
614 | |
|
615 | 0 | _cleanup_free_ char *node = NULL, *unit = NULL, *escaped = NULL, *where_unit = NULL; |
616 | 0 | _cleanup_fclose_ FILE *f = NULL; |
617 | 0 | const char *fsck_unit; |
618 | 0 | int r; |
619 | |
|
620 | 0 | assert(dir); |
621 | 0 | assert(what); |
622 | 0 | assert(where); |
623 | |
|
624 | 0 | node = fstab_node_to_udev_node(what); |
625 | 0 | if (!node) |
626 | 0 | return log_oom(); |
627 | | |
628 | | /* Nothing to work on. */ |
629 | 0 | if (!is_device_path(node)) |
630 | 0 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
631 | 0 | "Cannot format something that is not a device node: %s", |
632 | 0 | node); |
633 | | |
634 | 0 | if (!type || streq(type, "auto")) |
635 | 0 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
636 | 0 | "Cannot format partition %s, filesystem type is not specified", |
637 | 0 | node); |
638 | | |
639 | 0 | r = unit_name_from_path_instance("systemd-makefs", node, ".service", &unit); |
640 | 0 | if (r < 0) |
641 | 0 | return log_error_errno(r, "Failed to make unit instance name from path \"%s\": %m", |
642 | 0 | node); |
643 | | |
644 | 0 | if (in_initrd() && path_equal(where, "/sysroot")) |
645 | 0 | fsck_unit = SPECIAL_FSCK_ROOT_SERVICE; |
646 | 0 | else if (in_initrd() && path_equal(where, "/sysusr/usr")) |
647 | 0 | fsck_unit = SPECIAL_FSCK_USR_SERVICE; |
648 | 0 | else |
649 | 0 | fsck_unit = "systemd-fsck@%i.service"; |
650 | |
|
651 | 0 | escaped = cescape(node); |
652 | 0 | if (!escaped) |
653 | 0 | return log_oom(); |
654 | | |
655 | 0 | r = unit_name_from_path(where, ".mount", &where_unit); |
656 | 0 | if (r < 0) |
657 | 0 | return log_error_errno(r, "Failed to make unit name from path \"%s\": %m", |
658 | 0 | where); |
659 | | |
660 | 0 | r = generator_open_unit_file(dir, /* source= */ NULL, unit, &f); |
661 | 0 | if (r < 0) |
662 | 0 | return r; |
663 | | |
664 | 0 | fprintf(f, |
665 | 0 | "[Unit]\n" |
666 | 0 | "Description=Make File System on %%f\n" |
667 | 0 | "Documentation=man:systemd-makefs@.service(8)\n" |
668 | 0 | "\n" |
669 | 0 | "DefaultDependencies=no\n" |
670 | 0 | "BindsTo=%%i.device\n" |
671 | 0 | "After=%%i.device\n" |
672 | | /* fsck might or might not be used, so let's be safe and order |
673 | | * ourselves before both systemd-fsck@.service and the mount unit. */ |
674 | 0 | "Before=%s %s\n" |
675 | 0 | "Conflicts=shutdown.target\n" |
676 | 0 | "Before=shutdown.target\n" |
677 | 0 | "\n" |
678 | 0 | "[Service]\n" |
679 | 0 | "Type=oneshot\n" |
680 | 0 | "RemainAfterExit=yes\n" |
681 | 0 | "ExecStart="SYSTEMD_MAKEFS_PATH " %s %s\n" |
682 | 0 | "TimeoutSec=infinity\n", |
683 | 0 | fsck_unit, |
684 | 0 | where_unit, |
685 | 0 | type, |
686 | 0 | escaped); |
687 | | // XXX: what about local-fs-pre.target? |
688 | |
|
689 | 0 | r = fflush_and_check(f); |
690 | 0 | if (r < 0) |
691 | 0 | return log_error_errno(r, "Failed to write unit %s: %m", unit); |
692 | | |
693 | 0 | return generator_add_symlink(dir, where_unit, "requires", unit); |
694 | 0 | } |
695 | | |
696 | | int generator_hook_up_growfs( |
697 | | const char *dir, |
698 | | const char *where, |
699 | 0 | const char *target) { |
700 | |
|
701 | 0 | const char *growfs_unit, *growfs_unit_path; |
702 | 0 | _cleanup_free_ char *where_unit = NULL, *instance = NULL; |
703 | 0 | int r; |
704 | |
|
705 | 0 | assert(dir); |
706 | 0 | assert(where); |
707 | |
|
708 | 0 | r = unit_name_from_path(where, ".mount", &where_unit); |
709 | 0 | if (r < 0) |
710 | 0 | return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); |
711 | | |
712 | 0 | if (empty_or_root(where)) { |
713 | 0 | growfs_unit = SPECIAL_GROWFS_ROOT_SERVICE; |
714 | 0 | growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_ROOT_SERVICE; |
715 | 0 | } else { |
716 | 0 | growfs_unit = SPECIAL_GROWFS_SERVICE; |
717 | 0 | growfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_GROWFS_SERVICE; |
718 | |
|
719 | 0 | r = unit_name_path_escape(where, &instance); |
720 | 0 | if (r < 0) |
721 | 0 | return log_error_errno(r, "Failed to escape path '%s': %m", where); |
722 | 0 | } |
723 | | |
724 | 0 | if (target) { |
725 | 0 | r = generator_add_ordering(dir, target, "After", growfs_unit, instance); |
726 | 0 | if (r < 0) |
727 | 0 | return r; |
728 | 0 | } |
729 | | |
730 | 0 | return generator_add_symlink_full(dir, where_unit, "wants", growfs_unit_path, instance); |
731 | 0 | } |
732 | | |
733 | | int generator_hook_up_pcrfs( |
734 | | const char *dir, |
735 | | const char *where, |
736 | 0 | const char *target) { |
737 | |
|
738 | 0 | const char *pcrfs_unit, *pcrfs_unit_path; |
739 | 0 | _cleanup_free_ char *where_unit = NULL, *instance = NULL; |
740 | 0 | int r; |
741 | |
|
742 | 0 | assert(dir); |
743 | 0 | assert(where); |
744 | |
|
745 | 0 | r = unit_name_from_path(where, ".mount", &where_unit); |
746 | 0 | if (r < 0) |
747 | 0 | return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); |
748 | | |
749 | 0 | if (empty_or_root(where)) { |
750 | 0 | pcrfs_unit = SPECIAL_PCRFS_ROOT_SERVICE; |
751 | 0 | pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_ROOT_SERVICE; |
752 | 0 | } else { |
753 | 0 | pcrfs_unit = SPECIAL_PCRFS_SERVICE; |
754 | 0 | pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_SERVICE; |
755 | |
|
756 | 0 | r = unit_name_path_escape(where, &instance); |
757 | 0 | if (r < 0) |
758 | 0 | return log_error_errno(r, "Failed to escape path '%s': %m", where); |
759 | 0 | } |
760 | | |
761 | 0 | if (target) { |
762 | 0 | r = generator_add_ordering(dir, target, "After", pcrfs_unit, instance); |
763 | 0 | if (r < 0) |
764 | 0 | return r; |
765 | 0 | } |
766 | | |
767 | 0 | return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance); |
768 | 0 | } |
769 | | |
770 | | int generator_hook_up_validatefs( |
771 | | const char *dir, |
772 | | const char *where, |
773 | 0 | const char *target) { |
774 | |
|
775 | 0 | _cleanup_free_ char *where_unit = NULL, *instance = NULL; |
776 | 0 | const char *validatefs_unit, *validatefs_unit_path; |
777 | 0 | int r; |
778 | |
|
779 | 0 | assert(dir); |
780 | 0 | assert(where); |
781 | | |
782 | | /* never hook this in for the actual root fs, because it's too late then, we already are running from |
783 | | * the root fs, it makes no sense to validate it anymore */ |
784 | 0 | if (empty_or_root(where)) |
785 | 0 | return 0; |
786 | | |
787 | 0 | r = unit_name_from_path(where, ".mount", &where_unit); |
788 | 0 | if (r < 0) |
789 | 0 | return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); |
790 | | |
791 | 0 | validatefs_unit = SPECIAL_VALIDATEFS_SERVICE; |
792 | 0 | validatefs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_VALIDATEFS_SERVICE; |
793 | |
|
794 | 0 | r = unit_name_path_escape(where, &instance); |
795 | 0 | if (r < 0) |
796 | 0 | return log_error_errno(r, "Failed to escape path '%s': %m", where); |
797 | | |
798 | 0 | if (target) { |
799 | 0 | r = generator_add_ordering(dir, target, "After", validatefs_unit, instance); |
800 | 0 | if (r < 0) |
801 | 0 | return r; |
802 | 0 | } |
803 | | |
804 | 0 | return generator_add_symlink_full(dir, where_unit, "wants", validatefs_unit_path, instance); |
805 | 0 | } |
806 | | |
807 | | int generator_hook_up_quotacheck( |
808 | | const char *dir, |
809 | | const char *what, |
810 | | const char *where, |
811 | | const char *target, |
812 | 0 | const char *fstype) { |
813 | |
|
814 | 0 | _cleanup_free_ char *where_unit = NULL, *instance = NULL; |
815 | 0 | int r; |
816 | |
|
817 | 0 | assert(dir); |
818 | 0 | assert(where); |
819 | |
|
820 | 0 | if (isempty(fstype) || streq(fstype, "auto")) |
821 | 0 | return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what); |
822 | 0 | if (fstype_has_internal_quota(fstype)) { |
823 | 0 | log_debug("%s handles quotas internally, skipping quotacheck/quotaon setup for %s", fstype, what); |
824 | 0 | return 0; |
825 | 0 | } |
826 | 0 | if (!fstype_needs_quota(fstype)) |
827 | 0 | return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype); |
828 | | |
829 | | /* quotacheck unit for system root */ |
830 | 0 | if (path_equal(where, "/")) { |
831 | 0 | r = generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); |
832 | 0 | return r < 0 ? r : 1; |
833 | 0 | } |
834 | | |
835 | 0 | r = unit_name_path_escape(where, &instance); |
836 | 0 | if (r < 0) |
837 | 0 | return log_error_errno(r, "Failed to escape path '%s': %m", where); |
838 | | |
839 | 0 | if (target) { |
840 | 0 | r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTACHECK_SERVICE, instance); |
841 | 0 | if (r < 0) |
842 | 0 | return r; |
843 | 0 | } |
844 | | |
845 | 0 | r = unit_name_from_path(where, ".mount", &where_unit); |
846 | 0 | if (r < 0) |
847 | 0 | return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); |
848 | | |
849 | 0 | r = generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); |
850 | 0 | return r < 0 ? r : 1; |
851 | 0 | } |
852 | | |
853 | | int generator_hook_up_quotaon( |
854 | | const char *dir, |
855 | | const char *where, |
856 | 0 | const char *target) { |
857 | |
|
858 | 0 | _cleanup_free_ char *where_unit = NULL, *instance = NULL; |
859 | 0 | int r; |
860 | |
|
861 | 0 | assert(dir); |
862 | 0 | assert(where); |
863 | | |
864 | | /* quotaon unit for system root is not instantiated */ |
865 | 0 | if (path_equal(where, "/")) |
866 | 0 | return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_ROOT_SERVICE); |
867 | | |
868 | 0 | r = unit_name_path_escape(where, &instance); |
869 | 0 | if (r < 0) |
870 | 0 | return log_error_errno(r, "Failed to escape path '%s': %m", where); |
871 | | |
872 | 0 | if (target) { |
873 | 0 | r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTAON_SERVICE, instance); |
874 | 0 | if (r < 0) |
875 | 0 | return r; |
876 | 0 | } |
877 | | |
878 | 0 | r = unit_name_from_path(where, ".mount", &where_unit); |
879 | 0 | if (r < 0) |
880 | 0 | return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); |
881 | | |
882 | 0 | return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_SERVICE, instance); |
883 | 0 | } |
884 | | |
885 | 0 | int generator_enable_remount_fs_service(const char *dir) { |
886 | | /* Pull in systemd-remount-fs.service */ |
887 | 0 | return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", |
888 | 0 | SYSTEM_DATA_UNIT_DIR "/" SPECIAL_REMOUNT_FS_SERVICE); |
889 | 0 | } |
890 | | |
891 | 0 | int generator_write_blockdev_dependency(FILE *f, const char *what) { |
892 | 0 | _cleanup_free_ char *escaped = NULL; |
893 | 0 | int r; |
894 | |
|
895 | 0 | assert(f); |
896 | 0 | assert(what); |
897 | |
|
898 | 0 | if (!path_startswith(what, "/dev/")) |
899 | 0 | return 0; |
900 | | |
901 | 0 | r = unit_name_path_escape(what, &escaped); |
902 | 0 | if (r < 0) |
903 | 0 | return log_error_errno(r, "Failed to escape device node path %s: %m", what); |
904 | | |
905 | 0 | fprintf(f, |
906 | 0 | "After=blockdev@%s.target\n", |
907 | 0 | escaped); |
908 | |
|
909 | 0 | return 0; |
910 | 0 | } |
911 | | |
912 | 0 | int generator_write_cryptsetup_unit_section(FILE *f, const char *source) { |
913 | 0 | assert(f); |
914 | |
|
915 | 0 | fprintf(f, |
916 | 0 | "[Unit]\n" |
917 | 0 | "Description=Cryptography Setup for %%I\n" |
918 | 0 | "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n"); |
919 | |
|
920 | 0 | if (source) |
921 | 0 | fprintf(f, "SourcePath=%s\n", source); |
922 | |
|
923 | 0 | fprintf(f, |
924 | 0 | "\n" |
925 | 0 | "DefaultDependencies=no\n" |
926 | | /* The ordering against systemd-udevd.service is mostly about shutdown, not startup: on stop |
927 | | * the device is detached via a device-mapper ioctl that carries a udev cookie, and we block in |
928 | | * dm_udev_wait() until udev releases it (see 95-dm-notify.rules). Ordering After= it means we |
929 | | * are stopped first, while udevd is still around to service that cookie; otherwise during |
930 | | * soft-reboot/shutdown udevd may exit first and the detach hangs until the job timeout. */ |
931 | 0 | "After=cryptsetup-pre.target systemd-udevd-kernel.socket systemd-udevd.service systemd-tpm2-setup-early.service\n" |
932 | 0 | "Before=blockdev@dev-mapper-%%i.target\n" |
933 | 0 | "Wants=blockdev@dev-mapper-%%i.target\n" |
934 | 0 | "IgnoreOnIsolate=true\n"); |
935 | |
|
936 | 0 | return 0; |
937 | 0 | } |
938 | | |
939 | | int generator_write_cryptsetup_service_section( |
940 | | FILE *f, |
941 | | const char *name, |
942 | | const char *what, |
943 | | const char *key_file, |
944 | 0 | const char *options) { |
945 | |
|
946 | 0 | _cleanup_free_ char *name_escaped = NULL, *what_escaped = NULL, *key_file_escaped = NULL, *options_escaped = NULL; |
947 | |
|
948 | 0 | assert(f); |
949 | 0 | assert(name); |
950 | 0 | assert(what); |
951 | |
|
952 | 0 | name_escaped = specifier_escape(name); |
953 | 0 | if (!name_escaped) |
954 | 0 | return log_oom(); |
955 | | |
956 | 0 | what_escaped = specifier_escape(what); |
957 | 0 | if (!what_escaped) |
958 | 0 | return log_oom(); |
959 | | |
960 | 0 | if (key_file) { |
961 | 0 | key_file_escaped = specifier_escape(key_file); |
962 | 0 | if (!key_file_escaped) |
963 | 0 | return log_oom(); |
964 | 0 | } |
965 | | |
966 | 0 | if (options) { |
967 | 0 | options_escaped = specifier_escape(options); |
968 | 0 | if (!options_escaped) |
969 | 0 | return log_oom(); |
970 | 0 | } |
971 | | |
972 | 0 | fprintf(f, |
973 | 0 | "\n" |
974 | 0 | "[Service]\n" |
975 | 0 | "Type=oneshot\n" |
976 | 0 | "RemainAfterExit=yes\n" |
977 | 0 | "TimeoutSec=infinity\n" /* The binary handles timeouts on its own */ |
978 | 0 | "KeyringMode=shared\n" /* Make sure we can share cached keys among instances */ |
979 | 0 | "OOMScoreAdjust=500\n" /* Unlocking can allocate a lot of memory if Argon2 is used */ |
980 | 0 | "ImportCredential=cryptsetup.*\n" |
981 | 0 | "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" |
982 | 0 | "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", |
983 | 0 | name_escaped, what_escaped, strempty(key_file_escaped), strempty(options_escaped), |
984 | 0 | name_escaped); |
985 | |
|
986 | 0 | return 0; |
987 | 0 | } |
988 | | |
989 | 0 | int generator_write_veritysetup_unit_section(FILE *f, const char *source) { |
990 | 0 | assert(f); |
991 | |
|
992 | 0 | fprintf(f, |
993 | 0 | "[Unit]\n" |
994 | 0 | "Description=Integrity Protection Setup for %%I\n" |
995 | 0 | "Documentation=man:veritytab(5) man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n"); |
996 | |
|
997 | 0 | if (source) |
998 | 0 | fprintf(f, "SourcePath=%s\n", source); |
999 | |
|
1000 | 0 | fprintf(f, |
1001 | 0 | "DefaultDependencies=no\n" |
1002 | 0 | "IgnoreOnIsolate=true\n" |
1003 | | /* The systemd-udevd.service ordering is mostly about shutdown, not startup: on stop the dm |
1004 | | * device is detached via an ioctl carrying a udev cookie that blocks in dm_udev_wait() until |
1005 | | * udev releases it (95-dm-notify.rules). Stopping before udevd keeps it around to service the |
1006 | | * cookie; otherwise during soft-reboot/shutdown udevd may exit first and the detach hangs. */ |
1007 | 0 | "After=veritysetup-pre.target systemd-udevd-kernel.socket systemd-udevd.service\n" |
1008 | 0 | "Before=blockdev@dev-mapper-%%i.target\n" |
1009 | 0 | "Wants=blockdev@dev-mapper-%%i.target\n"); |
1010 | |
|
1011 | 0 | return 0; |
1012 | 0 | } |
1013 | | |
1014 | | int generator_write_veritysetup_service_section( |
1015 | | FILE *f, |
1016 | | const char *name, |
1017 | | const char *data_what, |
1018 | | const char *hash_what, |
1019 | | const char *roothash, |
1020 | 0 | const char *options) { |
1021 | |
|
1022 | 0 | _cleanup_free_ char *name_escaped = NULL, *data_what_escaped = NULL, *hash_what_escaped = NULL, |
1023 | 0 | *roothash_escaped = NULL, *options_escaped = NULL; |
1024 | |
|
1025 | 0 | assert(f); |
1026 | 0 | assert(name); |
1027 | 0 | assert(data_what); |
1028 | 0 | assert(hash_what); |
1029 | |
|
1030 | 0 | name_escaped = specifier_escape(name); |
1031 | 0 | if (!name_escaped) |
1032 | 0 | return log_oom(); |
1033 | | |
1034 | 0 | data_what_escaped = specifier_escape(data_what); |
1035 | 0 | if (!data_what_escaped) |
1036 | 0 | return log_oom(); |
1037 | | |
1038 | 0 | hash_what_escaped = specifier_escape(hash_what); |
1039 | 0 | if (!hash_what_escaped) |
1040 | 0 | return log_oom(); |
1041 | | |
1042 | 0 | if (roothash) { |
1043 | 0 | roothash_escaped = specifier_escape(roothash); |
1044 | 0 | if (!roothash_escaped) |
1045 | 0 | return log_oom(); |
1046 | 0 | } |
1047 | | |
1048 | 0 | if (options) { |
1049 | 0 | options_escaped = specifier_escape(options); |
1050 | 0 | if (!options_escaped) |
1051 | 0 | return log_oom(); |
1052 | 0 | } |
1053 | | |
1054 | 0 | fprintf(f, |
1055 | 0 | "\n" |
1056 | 0 | "[Service]\n" |
1057 | 0 | "Type=oneshot\n" |
1058 | 0 | "RemainAfterExit=yes\n" |
1059 | 0 | "ExecStart=" SYSTEMD_VERITYSETUP_PATH " attach '%s' '%s' '%s' '%s' '%s'\n" |
1060 | 0 | "ExecStop=" SYSTEMD_VERITYSETUP_PATH " detach '%s'\n", |
1061 | 0 | name_escaped, data_what_escaped, hash_what_escaped, empty_to_dash(roothash_escaped), strempty(options_escaped), |
1062 | 0 | name_escaped); |
1063 | |
|
1064 | 0 | return 0; |
1065 | 0 | } |
1066 | | |
1067 | 0 | void log_setup_generator(void) { |
1068 | 0 | if (invoked_by_systemd()) { |
1069 | | /* Disable talking to syslog/journal (i.e. the two IPC-based loggers) if we run in system context. */ |
1070 | 0 | if (streq_ptr(getenv("SYSTEMD_SCOPE"), "system")) |
1071 | 0 | log_set_prohibit_ipc(true); |
1072 | | |
1073 | | /* This effectively means: journal for per-user service manager generators, kmsg for per-system service manager generators */ |
1074 | 0 | log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); |
1075 | 0 | } else |
1076 | 0 | log_set_target(LOG_TARGET_AUTO); |
1077 | |
|
1078 | 0 | log_parse_environment(); |
1079 | 0 | log_open(); |
1080 | 0 | } |
1081 | | |
1082 | 0 | bool generator_soft_rebooted(void) { |
1083 | 0 | static int cached = -1; |
1084 | 0 | int r; |
1085 | |
|
1086 | 0 | if (cached >= 0) |
1087 | 0 | return cached; |
1088 | | |
1089 | 0 | const char *e = secure_getenv("SYSTEMD_SOFT_REBOOTS_COUNT"); |
1090 | 0 | if (!e) |
1091 | 0 | return (cached = false); |
1092 | | |
1093 | 0 | unsigned u; |
1094 | |
|
1095 | 0 | r = safe_atou(e, &u); |
1096 | 0 | if (r < 0) { |
1097 | 0 | log_debug_errno(r, "Failed to parse $SYSTEMD_SOFT_REBOOTS_COUNT, assuming the system hasn't soft-rebooted: %m"); |
1098 | 0 | return (cached = false); |
1099 | 0 | } |
1100 | | |
1101 | 0 | return (cached = (u > 0)); |
1102 | 0 | } |
1103 | | |
1104 | | int _generator_main( |
1105 | | const Verb verbs[], |
1106 | | const Verb verbs_end[], |
1107 | | const Option options[], |
1108 | | const Option options_end[], |
1109 | | int argc, char *argv[], |
1110 | 0 | generator_main_func_t impl) { |
1111 | 0 | int r; |
1112 | |
|
1113 | 0 | assert(argc >= 0); |
1114 | 0 | assert(argv); |
1115 | 0 | assert(impl); |
1116 | |
|
1117 | 0 | char **args = NULL; |
1118 | 0 | r = _common_parse_argv(verbs, verbs_end, options, options_end, argc, argv, |
1119 | 0 | OPTION_PARSER_NORMAL, |
1120 | 0 | /* min_args= */ 0, /* max_args= */ SIZE_MAX, &args); |
1121 | 0 | if (r <= 0) |
1122 | 0 | return r; |
1123 | | |
1124 | | /* The count is checked here rather than through min_args/max_args so that every wrong count |
1125 | | * is reported with the same message. */ |
1126 | 0 | size_t n = strv_length(args); |
1127 | 0 | if (!IN_SET(n, 1, 3)) |
1128 | 0 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
1129 | 0 | "This program takes one or three arguments."); |
1130 | | |
1131 | 0 | return impl(args[0], args[n == 3 ? 1 : 0], args[n == 3 ? 2 : 0]); |
1132 | 0 | } |
1133 | | |
1134 | 0 | GptAutoRoot parse_gpt_auto_root(const char *switch_name, const char *value) { |
1135 | 0 | assert(switch_name); |
1136 | 0 | assert(value); |
1137 | | |
1138 | | /* Parses the 'gpt-auto'/'gpt-auto-root'/'dissect'/'dissect-force' parameters to root= |
1139 | | * |
1140 | | * note that we are not using a regular string table here, because the mode names don't fully match |
1141 | | * the parameter names. And root= being something else is not an error. */ |
1142 | |
|
1143 | 0 | if (streq(value, "gpt-auto")) { |
1144 | 0 | log_debug("Enabling partition auto-detection (respecting factory reset mode), %s is explicitly set to 'gpt-auto'.", switch_name); |
1145 | 0 | return GPT_AUTO_ROOT_ON; |
1146 | 0 | } |
1147 | | |
1148 | 0 | if (streq(value, "gpt-auto-force")) { |
1149 | 0 | log_debug("Enabling partition auto-detection (ignoring factory reset mode), %s is explicitly set to 'gpt-auto-force'.", switch_name); |
1150 | 0 | return GPT_AUTO_ROOT_FORCE; |
1151 | 0 | } |
1152 | | |
1153 | 0 | if (streq(value, "dissect")) { |
1154 | 0 | log_debug("Enabling partition auto-detection via full image dissection (respecting factory reset mode), %s is explicitly set to 'dissect'.", switch_name); |
1155 | 0 | return GPT_AUTO_ROOT_DISSECT; |
1156 | 0 | } |
1157 | | |
1158 | 0 | if (streq(value, "dissect-force")) { |
1159 | 0 | log_debug("Enabling partition auto-detection via full image dissection (ignoring factory reset mode), %s is explicitly set to 'dissect-force'.", switch_name); |
1160 | 0 | return GPT_AUTO_ROOT_DISSECT_FORCE; |
1161 | 0 | } |
1162 | | |
1163 | 0 | if (streq(value, "off")) |
1164 | 0 | log_debug("Disabling partition auto-detection, %s handling is explicitly turned off.", switch_name); |
1165 | 0 | else |
1166 | 0 | log_debug("Disabling partition auto-detection, %s is neither unset, nor set to 'gpt-auto', 'gpt-auto-force', 'dissect' or 'dissect-force'.", switch_name); |
1167 | |
|
1168 | 0 | return GPT_AUTO_ROOT_OFF; |
1169 | 0 | } |