1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
7 #include "creds-util.h"
9 #include "errno-util.h"
10 #include "extract-word.h"
13 #include "generator.h"
14 #include "initrd-util.h"
15 #include "parse-util.h"
16 #include "path-util.h"
17 #include "proc-cmdline.h"
18 #include "recurse-dir.h"
20 #include "string-util.h"
22 #include "unit-file.h"
23 #include "unit-name.h"
25 static const char *arg_dest
= NULL
;
26 static char *arg_default_unit
= NULL
;
27 static char **arg_mask
= NULL
;
28 static char **arg_wants
= NULL
;
29 static bool arg_debug_shell
= false;
30 static char *arg_debug_tty
= NULL
;
31 static char *arg_default_debug_tty
= NULL
;
32 static uint32_t arg_breakpoints
= 0;
34 STATIC_DESTRUCTOR_REGISTER(arg_default_unit
, freep
);
35 STATIC_DESTRUCTOR_REGISTER(arg_mask
, strv_freep
);
36 STATIC_DESTRUCTOR_REGISTER(arg_wants
, strv_freep
);
37 STATIC_DESTRUCTOR_REGISTER(arg_debug_tty
, freep
);
38 STATIC_DESTRUCTOR_REGISTER(arg_default_debug_tty
, freep
);
40 typedef enum BreakpointType
{
43 BREAKPOINT_PRE_SYSROOT_MOUNT
,
44 BREAKPOINT_PRE_SWITCH_ROOT
,
46 _BREAKPOINT_TYPE_INVALID
= -EINVAL
,
49 typedef enum BreakpointValidity
{
50 BREAKPOINT_DEFAULT
= 1 << 0,
51 BREAKPOINT_IN_INITRD
= 1 << 1,
52 BREAKPOINT_ON_HOST
= 1 << 2,
55 typedef struct BreakpointInfo
{
59 BreakpointValidity validity
;
62 static const struct BreakpointInfo breakpoint_info_table
[_BREAKPOINT_TYPE_MAX
] = {
63 { BREAKPOINT_PRE_UDEV
, "pre-udev", "breakpoint-pre-udev.service", BREAKPOINT_IN_INITRD
| BREAKPOINT_ON_HOST
},
64 { BREAKPOINT_PRE_BASIC
, "pre-basic", "breakpoint-pre-basic.service", BREAKPOINT_IN_INITRD
| BREAKPOINT_ON_HOST
},
65 { BREAKPOINT_PRE_SYSROOT_MOUNT
, "pre-mount", "breakpoint-pre-mount.service", BREAKPOINT_IN_INITRD
},
66 { BREAKPOINT_PRE_SWITCH_ROOT
, "pre-switch-root", "breakpoint-pre-switch-root.service", BREAKPOINT_IN_INITRD
| BREAKPOINT_DEFAULT
},
69 static bool breakpoint_applies(const BreakpointInfo
*info
, int log_level
) {
72 if (in_initrd() && !FLAGS_SET(info
->validity
, BREAKPOINT_IN_INITRD
))
73 log_full(log_level
, "Breakpoint '%s' not valid in the initrd, ignoring.", info
->name
);
74 else if (!in_initrd() && !FLAGS_SET(info
->validity
, BREAKPOINT_ON_HOST
))
75 log_full(log_level
, "Breakpoint '%s' not valid on the host, ignoring.", info
->name
);
82 static BreakpointType
parse_breakpoint_from_string_one(const char *s
) {
85 FOREACH_ELEMENT(i
, breakpoint_info_table
)
86 if (streq(i
->name
, s
))
89 return _BREAKPOINT_TYPE_INVALID
;
92 static int parse_breakpoint_from_string(const char *s
, uint32_t *ret_breakpoints
) {
93 uint32_t breakpoints
= 0;
96 assert(ret_breakpoints
);
98 /* Empty value? set default breakpoint */
100 bool found_default
= false;
102 FOREACH_ELEMENT(i
, breakpoint_info_table
)
103 if (FLAGS_SET(i
->validity
, BREAKPOINT_DEFAULT
) && breakpoint_applies(i
, INT_MAX
)) {
104 breakpoints
|= 1 << i
->type
;
105 found_default
= true;
110 log_warning("No default breakpoint defined %s, ignoring.",
111 in_initrd() ? "in the initrd" : "on the host");
114 _cleanup_free_
char *t
= NULL
;
117 r
= extract_first_word(&s
, &t
, ",", EXTRACT_DONT_COALESCE_SEPARATORS
);
123 tt
= parse_breakpoint_from_string_one(t
);
125 log_warning("Invalid breakpoint value '%s', ignoring.", t
);
129 if (breakpoint_applies(&breakpoint_info_table
[tt
], LOG_WARNING
))
130 breakpoints
|= 1 << tt
;
133 *ret_breakpoints
= breakpoints
;
138 static int parse_proc_cmdline_item(const char *key
, const char *value
, void *data
) {
143 if (streq(key
, "systemd.mask")) {
146 if (proc_cmdline_value_missing(key
, value
))
149 r
= unit_name_mangle(value
, UNIT_NAME_MANGLE_WARN
, &n
);
151 return log_error_errno(r
, "Failed to glob unit name: %m");
153 if (strv_consume(&arg_mask
, n
) < 0)
156 } else if (streq(key
, "systemd.wants")) {
159 if (proc_cmdline_value_missing(key
, value
))
162 r
= unit_name_mangle(value
, UNIT_NAME_MANGLE_WARN
, &n
);
164 return log_error_errno(r
, "Failed to glob unit name: %m");
166 if (strv_consume(&arg_wants
, n
) < 0)
169 } else if (proc_cmdline_key_streq(key
, "systemd.debug_shell")) {
171 r
= value
? parse_boolean(value
) : 1;
172 arg_debug_shell
= r
!= 0;
176 return free_and_strdup_warn(&arg_debug_tty
, skip_dev_prefix(value
));
178 } else if (proc_cmdline_key_streq(key
, "systemd.default_debug_tty")) {
180 if (proc_cmdline_value_missing(key
, value
))
183 return free_and_strdup_warn(&arg_default_debug_tty
, skip_dev_prefix(value
));
185 } else if (streq(key
, "systemd.unit")) {
187 if (proc_cmdline_value_missing(key
, value
))
190 return free_and_strdup_warn(&arg_default_unit
, value
);
192 } else if (streq(key
, "systemd.break")) {
193 uint32_t breakpoints
= 0;
195 r
= parse_breakpoint_from_string(value
, &breakpoints
);
197 return log_warning_errno(r
, "Failed to parse breakpoint value '%s': %m", value
);
199 arg_breakpoints
|= breakpoints
;
204 target
= runlevel_to_target(key
);
206 return free_and_strdup_warn(&arg_default_unit
, target
);
212 static int generate_mask_symlinks(void) {
215 STRV_FOREACH(u
, arg_mask
) {
216 _cleanup_free_
char *p
= NULL
;
218 p
= path_join(arg_dest
, *u
);
222 if (symlink("/dev/null", p
) < 0)
223 RET_GATHER(r
, log_error_errno(errno
, "Failed to create mask symlink '%s': %m", p
));
229 static int generate_wants_symlinks(void) {
232 STRV_FOREACH(u
, arg_wants
) {
233 _cleanup_free_
char *f
= NULL
;
236 /* This should match what do_queue_default_job() in core/main.c does. */
237 if (arg_default_unit
)
238 target
= arg_default_unit
;
239 else if (in_initrd())
240 target
= SPECIAL_INITRD_TARGET
;
242 target
= SPECIAL_DEFAULT_TARGET
;
244 f
= path_join(SYSTEM_DATA_UNIT_DIR
, *u
);
248 RET_GATHER(r
, generator_add_symlink(arg_dest
, target
, "wants", f
));
254 static int install_debug_shell_dropin(void) {
255 const char *tty
= arg_debug_tty
?: arg_default_debug_tty
;
258 if (!tty
|| path_equal(tty
, skip_dev_prefix(DEBUGTTY
)))
261 r
= write_drop_in_format(arg_dest
, "debug-shell.service", 50, "tty",
262 "# Automatically generated by systemd-debug-generator\n\n"
264 "Description=Early root shell on /dev/%s FOR DEBUGGING ONLY\n"
265 "ConditionPathExists=\n"
270 return log_warning_errno(r
, "Failed to write drop-in for debug-shell.service: %m");
275 static int process_unit_credentials(const char *credentials_dir
) {
276 _cleanup_free_ DirectoryEntries
*des
= NULL
;
279 assert(credentials_dir
);
281 r
= readdir_all_at(AT_FDCWD
, credentials_dir
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
|RECURSE_DIR_ENSURE_TYPE
, &des
);
283 return log_error_errno(r
, "Failed to enumerate credentials from credentials directory '%s': %m", credentials_dir
);
285 FOREACH_ARRAY(i
, des
->entries
, des
->n_entries
) {
286 struct dirent
*de
= *i
;
287 const char *unit
, *dropin
;
289 if (de
->d_type
!= DT_REG
)
292 unit
= startswith(de
->d_name
, "systemd.extra-unit.");
293 dropin
= startswith(de
->d_name
, "systemd.unit-dropin.");
295 if (!unit
&& !dropin
)
298 _cleanup_free_
char *d
= NULL
;
300 r
= read_credential_with_decryption(de
->d_name
, (void**) &d
, NULL
);
302 log_warning_errno(r
, "Failed to read credential '%s', ignoring: %m", de
->d_name
);
307 _cleanup_free_
char *p
= NULL
;
309 if (!unit_name_is_valid(unit
, UNIT_NAME_ANY
)) {
310 log_warning("Invalid unit name '%s' in credential '%s', ignoring.",
315 p
= path_join(arg_dest
, unit
);
319 r
= write_string_file(p
, d
, WRITE_STRING_FILE_CREATE
|WRITE_STRING_FILE_ATOMIC
|WRITE_STRING_FILE_MKDIR_0755
|WRITE_STRING_FILE_LABEL
);
321 log_warning_errno(r
, "Failed to write unit file '%s' from credential '%s', ignoring: %m",
326 log_debug("Wrote unit file '%s' from credential '%s'", unit
, de
->d_name
);
329 _cleanup_free_
char *dropin_unit
= NULL
;
330 const char *tilde
, *dropin_name
;
332 tilde
= strchrnul(dropin
, '~');
333 dropin_unit
= strndup(dropin
, tilde
- dropin
);
337 if (!unit_name_is_valid(dropin_unit
, UNIT_NAME_ANY
)) {
338 log_warning("Invalid unit name '%s' in credential '%s', ignoring.",
339 dropin_unit
, de
->d_name
);
343 dropin_name
= isempty(tilde
) ? "50-credential" : tilde
+ 1;
344 if (isempty(dropin_name
)) {
345 log_warning("Empty drop-in name for unit '%s' in credential '%s', ignoring.",
346 dropin_unit
, de
->d_name
);
350 r
= write_drop_in(arg_dest
, dropin_unit
, /* level = */ UINT_MAX
, dropin_name
, d
);
352 log_warning_errno(r
, "Failed to write drop-in '%s' for unit '%s' from credential '%s', ignoring: %m",
353 dropin_name
, dropin_unit
, de
->d_name
);
357 log_debug("Wrote drop-in '%s' for unit '%s' from credential '%s'", dropin_name
, dropin_unit
, de
->d_name
);
359 assert_not_reached();
365 static int run(const char *dest
, const char *dest_early
, const char *dest_late
) {
366 const char *credentials_dir
;
369 assert_se(arg_dest
= dest_early
);
371 r
= proc_cmdline_parse(parse_proc_cmdline_item
, NULL
, PROC_CMDLINE_RD_STRICT
| PROC_CMDLINE_STRIP_RD_PREFIX
);
373 log_warning_errno(r
, "Failed to parse kernel command line, ignoring: %m");
375 if (arg_debug_shell
) {
376 if (strv_extend(&arg_wants
, "debug-shell.service") < 0)
379 RET_GATHER(r
, install_debug_shell_dropin());
382 BIT_FOREACH(i
, arg_breakpoints
)
383 if (strv_extend(&arg_wants
, breakpoint_info_table
[i
].unit
) < 0)
386 if (get_credentials_dir(&credentials_dir
) >= 0)
387 RET_GATHER(r
, process_unit_credentials(credentials_dir
));
389 if (get_encrypted_credentials_dir(&credentials_dir
) >= 0)
390 RET_GATHER(r
, process_unit_credentials(credentials_dir
));
392 RET_GATHER(r
, generate_mask_symlinks());
393 RET_GATHER(r
, generate_wants_symlinks());
398 DEFINE_MAIN_GENERATOR_FUNCTION(run
);