]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/proc-cmdline.c
Merge pull request #25608 from poettering/dissect-moar
[thirdparty/systemd.git] / src / basic / proc-cmdline.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <stdbool.h>
4 #include <stddef.h>
5
6 #include "alloc-util.h"
7 #include "efivars.h"
8 #include "extract-word.h"
9 #include "fileio.h"
10 #include "getopt-defs.h"
11 #include "initrd-util.h"
12 #include "macro.h"
13 #include "parse-util.h"
14 #include "proc-cmdline.h"
15 #include "process-util.h"
16 #include "string-util.h"
17 #include "strv.h"
18 #include "virt.h"
19
20 int proc_cmdline_filter_pid1_args(
21 char **argv, /* input, may be reordered by this function. */
22 char ***ret) {
23
24 enum {
25 COMMON_GETOPT_ARGS,
26 SYSTEMD_GETOPT_ARGS,
27 SHUTDOWN_GETOPT_ARGS,
28 };
29
30 static const struct option options[] = {
31 COMMON_GETOPT_OPTIONS,
32 SYSTEMD_GETOPT_OPTIONS,
33 SHUTDOWN_GETOPT_OPTIONS,
34 {}
35 };
36
37 int saved_optind, saved_opterr, saved_optopt, argc;
38 char *saved_optarg;
39 char **filtered;
40 size_t idx;
41
42 assert(argv);
43 assert(ret);
44
45 /* Backup global variables. */
46 saved_optind = optind;
47 saved_opterr = opterr;
48 saved_optopt = optopt;
49 saved_optarg = optarg;
50
51 /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
52 * that checks for GNU extensions in optstring ('-' or '+' at the beginning). Here, we do not use
53 * the GNU extensions, but might be used previously. Hence, we need to always reset it. */
54 optind = 0;
55
56 /* Do not print an error message. */
57 opterr = 0;
58
59 /* Filter out all known options. */
60 argc = strv_length(argv);
61 while (getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL) >= 0)
62 ;
63
64 idx = optind;
65
66 /* Restore global variables. */
67 optind = saved_optind;
68 opterr = saved_opterr;
69 optopt = saved_optopt;
70 optarg = saved_optarg;
71
72 filtered = strv_copy(strv_skip(argv, idx));
73 if (!filtered)
74 return -ENOMEM;
75
76 *ret = filtered;
77 return 0;
78 }
79
80 int proc_cmdline(char **ret) {
81 const char *e;
82
83 assert(ret);
84
85 /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
86 e = secure_getenv("SYSTEMD_PROC_CMDLINE");
87 if (e) {
88 char *m;
89
90 m = strdup(e);
91 if (!m)
92 return -ENOMEM;
93
94 *ret = m;
95 return 0;
96 }
97
98 if (detect_container() > 0)
99 return get_process_cmdline(1, SIZE_MAX, 0, ret);
100 else
101 return read_one_line_file("/proc/cmdline", ret);
102 }
103
104 static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
105 const char *e;
106 int r;
107
108 assert(ret);
109
110 /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
111 e = secure_getenv("SYSTEMD_PROC_CMDLINE");
112 if (e)
113 return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
114
115 if (detect_container() > 0) {
116 _cleanup_strv_free_ char **args = NULL;
117
118 r = get_process_cmdline_strv(1, /* flags = */ 0, &args);
119 if (r < 0)
120 return r;
121
122 if (filter_pid1_args)
123 return proc_cmdline_filter_pid1_args(args, ret);
124
125 *ret = TAKE_PTR(args);
126 return 0;
127
128 } else {
129 _cleanup_free_ char *s = NULL;
130
131 r = read_one_line_file("/proc/cmdline", &s);
132 if (r < 0)
133 return r;
134
135 return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
136 }
137 }
138
139 int proc_cmdline_strv(char ***ret) {
140 return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
141 }
142
143 static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
144 char *c;
145
146 c = startswith(word, "rd.");
147 if (c) {
148 /* Filter out arguments that are intended only for the initrd */
149
150 if (!in_initrd())
151 return NULL;
152
153 if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
154 return c;
155
156 } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
157 /* And optionally filter out arguments that are intended only for the host */
158 return NULL;
159
160 return (char*) word;
161 }
162
163 static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
164 int r;
165
166 assert(parse_item);
167
168 /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's
169 * make this clear. */
170 assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
171
172 STRV_FOREACH(word, args) {
173 char *key, *value;
174
175 key = mangle_word(*word, flags);
176 if (!key)
177 continue;
178
179 value = strchr(key, '=');
180 if (value)
181 *(value++) = '\0';
182
183 r = parse_item(key, value, data);
184 if (r < 0)
185 return r;
186 }
187
188 return 0;
189 }
190
191 int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
192 _cleanup_strv_free_ char **args = NULL;
193 int r;
194
195 assert(parse_item);
196
197 /* We parse the EFI variable first, because later settings have higher priority. */
198
199 if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
200 _cleanup_free_ char *line = NULL;
201
202 r = systemd_efi_options_variable(&line);
203 if (r < 0) {
204 if (r != -ENODATA)
205 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
206 } else {
207 r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
208 if (r < 0)
209 return r;
210
211 r = proc_cmdline_parse_strv(args, parse_item, data, flags);
212 if (r < 0)
213 return r;
214
215 args = strv_free(args);
216 }
217 }
218
219 r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
220 if (r < 0)
221 return r;
222
223 return proc_cmdline_parse_strv(args, parse_item, data, flags);
224 }
225
226 static bool relaxed_equal_char(char a, char b) {
227 return a == b ||
228 (a == '_' && b == '-') ||
229 (a == '-' && b == '_');
230 }
231
232 char *proc_cmdline_key_startswith(const char *s, const char *prefix) {
233 assert(s);
234 assert(prefix);
235
236 /* Much like startswith(), but considers "-" and "_" the same */
237
238 for (; *prefix != 0; s++, prefix++)
239 if (!relaxed_equal_char(*s, *prefix))
240 return NULL;
241
242 return (char*) s;
243 }
244
245 bool proc_cmdline_key_streq(const char *x, const char *y) {
246 assert(x);
247 assert(y);
248
249 /* Much like streq(), but considers "-" and "_" the same */
250
251 for (; *x != 0 || *y != 0; x++, y++)
252 if (!relaxed_equal_char(*x, *y))
253 return false;
254
255 return true;
256 }
257
258 static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
259 _cleanup_free_ char *v = NULL;
260 bool found = false;
261 int r;
262
263 assert(key);
264
265 STRV_FOREACH(p, args) {
266 const char *word;
267
268 word = mangle_word(*p, flags);
269 if (!word)
270 continue;
271
272 if (ret_value) {
273 const char *e;
274
275 e = proc_cmdline_key_startswith(word, key);
276 if (!e)
277 continue;
278
279 if (*e == '=') {
280 r = free_and_strdup(&v, e+1);
281 if (r < 0)
282 return r;
283
284 found = true;
285
286 } else if (*e == 0 && FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL))
287 found = true;
288
289 } else {
290 if (proc_cmdline_key_streq(word, key)) {
291 found = true;
292 break; /* we found what we were looking for */
293 }
294 }
295 }
296
297 if (ret_value)
298 *ret_value = TAKE_PTR(v);
299
300 return found;
301 }
302
303 int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
304 _cleanup_strv_free_ char **args = NULL;
305 _cleanup_free_ char *line = NULL, *v = NULL;
306 int r;
307
308 /* Looks for a specific key on the kernel command line and (with lower priority) the EFI variable.
309 * Supports three modes:
310 *
311 * a) The "ret_value" parameter is used. In this case a parameter beginning with the "key" string followed by
312 * "=" is searched for, and the value following it is returned in "ret_value".
313 *
314 * b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the key is found as a separate
315 * word (i.e. not followed by "=" but instead by whitespace or the end of the command line), then this is
316 * also accepted, and "value" is returned as NULL.
317 *
318 * c) The "ret_value" parameter is NULL. In this case a search for the exact "key" parameter is performed.
319 *
320 * In all three cases, > 0 is returned if the key is found, 0 if not. */
321
322 if (isempty(key))
323 return -EINVAL;
324
325 if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
326 return -EINVAL;
327
328 r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
329 if (r < 0)
330 return r;
331
332 if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
333 return cmdline_get_key(args, key, flags, ret_value);
334
335 r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL);
336 if (r < 0)
337 return r;
338 if (r > 0) {
339 if (ret_value)
340 *ret_value = TAKE_PTR(v);
341
342 return r;
343 }
344
345 r = systemd_efi_options_variable(&line);
346 if (r == -ENODATA) {
347 if (ret_value)
348 *ret_value = NULL;
349
350 return false; /* Not found */
351 }
352 if (r < 0)
353 return r;
354
355 args = strv_free(args);
356 r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
357 if (r < 0)
358 return r;
359
360 return cmdline_get_key(args, key, flags, ret_value);
361 }
362
363 int proc_cmdline_get_bool(const char *key, bool *ret) {
364 _cleanup_free_ char *v = NULL;
365 int r;
366
367 assert(ret);
368
369 r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v);
370 if (r < 0)
371 return r;
372 if (r == 0) { /* key not specified at all */
373 *ret = false;
374 return 0;
375 }
376
377 if (v) { /* key with parameter passed */
378 r = parse_boolean(v);
379 if (r < 0)
380 return r;
381 *ret = r;
382 } else /* key without parameter passed */
383 *ret = true;
384
385 return 1;
386 }
387
388 static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
389 int r, ret = 0;
390
391 for (;;) {
392 char **v;
393 const char *k, *e;
394
395 k = va_arg(ap, const char*);
396 if (!k)
397 break;
398
399 assert_se(v = va_arg(ap, char**));
400
401 STRV_FOREACH(p, args) {
402 const char *word;
403
404 word = mangle_word(*p, flags);
405 if (!word)
406 continue;
407
408 e = proc_cmdline_key_startswith(word, k);
409 if (e && *e == '=') {
410 r = free_and_strdup(v, e + 1);
411 if (r < 0)
412 return r;
413
414 ret++;
415 }
416 }
417 }
418
419 return ret;
420 }
421
422 int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
423 _cleanup_strv_free_ char **args = NULL;
424 int r, ret = 0;
425 va_list ap;
426
427 /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
428 * this clear. */
429 assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
430
431 /* This call may clobber arguments on failure! */
432
433 if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
434 _cleanup_free_ char *line = NULL;
435
436 r = systemd_efi_options_variable(&line);
437 if (r < 0 && r != -ENODATA)
438 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
439 if (r >= 0) {
440 r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
441 if (r < 0)
442 return r;
443
444 va_start(ap, flags);
445 r = cmdline_get_key_ap(flags, args, ap);
446 va_end(ap);
447 if (r < 0)
448 return r;
449
450 ret = r;
451 args = strv_free(args);
452 }
453 }
454
455 r = proc_cmdline_strv(&args);
456 if (r < 0)
457 return r;
458
459 va_start(ap, flags);
460 r = cmdline_get_key_ap(flags, args, ap);
461 va_end(ap);
462 if (r < 0)
463 return r;
464
465 return ret + r;
466 }