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 "macro.h"
11 #include "parse-util.h"
12 #include "proc-cmdline.h"
13 #include "process-util.h"
14 #include "special.h"
15 #include "string-util.h"
16 #include "util.h"
17 #include "virt.h"
18
proc_cmdline(char ** ret)19 int proc_cmdline(char **ret) {
20 const char *e;
21 assert(ret);
22
23 /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
24 e = secure_getenv("SYSTEMD_PROC_CMDLINE");
25 if (e) {
26 char *m;
27
28 m = strdup(e);
29 if (!m)
30 return -ENOMEM;
31
32 *ret = m;
33 return 0;
34 }
35
36 if (detect_container() > 0)
37 return get_process_cmdline(1, SIZE_MAX, 0, ret);
38 else
39 return read_one_line_file("/proc/cmdline", ret);
40 }
41
proc_cmdline_extract_first(const char ** p,char ** ret_word,ProcCmdlineFlags flags)42 static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
43 const char *q = *p;
44 int r;
45
46 for (;;) {
47 _cleanup_free_ char *word = NULL;
48 const char *c;
49
50 r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
51 if (r < 0)
52 return r;
53 if (r == 0)
54 break;
55
56 /* Filter out arguments that are intended only for the initrd */
57 c = startswith(word, "rd.");
58 if (c) {
59 if (!in_initrd())
60 continue;
61
62 if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) {
63 r = free_and_strdup(&word, c);
64 if (r < 0)
65 return r;
66 }
67
68 } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
69 continue; /* And optionally filter out arguments that are intended only for the host */
70
71 *p = q;
72 *ret_word = TAKE_PTR(word);
73 return 1;
74 }
75
76 *p = q;
77 *ret_word = NULL;
78 return 0;
79 }
80
proc_cmdline_parse_given(const char * line,proc_cmdline_parse_t parse_item,void * data,ProcCmdlineFlags flags)81 int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
82 const char *p;
83 int r;
84
85 assert(parse_item);
86
87 /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this
88 * clear. */
89 assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
90
91 p = line;
92 for (;;) {
93 _cleanup_free_ char *word = NULL;
94 char *value;
95
96 r = proc_cmdline_extract_first(&p, &word, flags);
97 if (r < 0)
98 return r;
99 if (r == 0)
100 break;
101
102 value = strchr(word, '=');
103 if (value)
104 *(value++) = 0;
105
106 r = parse_item(word, value, data);
107 if (r < 0)
108 return r;
109 }
110
111 return 0;
112 }
113
proc_cmdline_parse(proc_cmdline_parse_t parse_item,void * data,ProcCmdlineFlags flags)114 int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
115 _cleanup_free_ char *line = NULL;
116 int r;
117
118 assert(parse_item);
119
120 /* We parse the EFI variable first, because later settings have higher priority. */
121
122 if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
123 r = systemd_efi_options_variable(&line);
124 if (r < 0) {
125 if (r != -ENODATA)
126 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
127 } else {
128 r = proc_cmdline_parse_given(line, parse_item, data, flags);
129 if (r < 0)
130 return r;
131
132 line = mfree(line);
133 }
134 }
135
136 r = proc_cmdline(&line);
137 if (r < 0)
138 return r;
139
140 return proc_cmdline_parse_given(line, parse_item, data, flags);
141 }
142
relaxed_equal_char(char a,char b)143 static bool relaxed_equal_char(char a, char b) {
144 return a == b ||
145 (a == '_' && b == '-') ||
146 (a == '-' && b == '_');
147 }
148
proc_cmdline_key_startswith(const char * s,const char * prefix)149 char *proc_cmdline_key_startswith(const char *s, const char *prefix) {
150 assert(s);
151 assert(prefix);
152
153 /* Much like startswith(), but considers "-" and "_" the same */
154
155 for (; *prefix != 0; s++, prefix++)
156 if (!relaxed_equal_char(*s, *prefix))
157 return NULL;
158
159 return (char*) s;
160 }
161
proc_cmdline_key_streq(const char * x,const char * y)162 bool proc_cmdline_key_streq(const char *x, const char *y) {
163 assert(x);
164 assert(y);
165
166 /* Much like streq(), but considers "-" and "_" the same */
167
168 for (; *x != 0 || *y != 0; x++, y++)
169 if (!relaxed_equal_char(*x, *y))
170 return false;
171
172 return true;
173 }
174
cmdline_get_key(const char * line,const char * key,ProcCmdlineFlags flags,char ** ret_value)175 static int cmdline_get_key(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) {
176 _cleanup_free_ char *ret = NULL;
177 bool found = false;
178 const char *p;
179 int r;
180
181 assert(line);
182 assert(key);
183
184 p = line;
185 for (;;) {
186 _cleanup_free_ char *word = NULL;
187
188 r = proc_cmdline_extract_first(&p, &word, flags);
189 if (r < 0)
190 return r;
191 if (r == 0)
192 break;
193
194 if (ret_value) {
195 const char *e;
196
197 e = proc_cmdline_key_startswith(word, key);
198 if (!e)
199 continue;
200
201 if (*e == '=') {
202 r = free_and_strdup(&ret, e+1);
203 if (r < 0)
204 return r;
205
206 found = true;
207
208 } else if (*e == 0 && FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL))
209 found = true;
210
211 } else {
212 if (streq(word, key)) {
213 found = true;
214 break; /* we found what we were looking for */
215 }
216 }
217 }
218
219 if (ret_value)
220 *ret_value = TAKE_PTR(ret);
221
222 return found;
223 }
224
proc_cmdline_get_key(const char * key,ProcCmdlineFlags flags,char ** ret_value)225 int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
226 _cleanup_free_ char *line = NULL, *v = NULL;
227 int r;
228
229 /* Looks for a specific key on the kernel command line and (with lower priority) the EFI variable.
230 * Supports three modes:
231 *
232 * a) The "ret_value" parameter is used. In this case a parameter beginning with the "key" string followed by
233 * "=" is searched for, and the value following it is returned in "ret_value".
234 *
235 * b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the key is found as a separate
236 * word (i.e. not followed by "=" but instead by whitespace or the end of the command line), then this is
237 * also accepted, and "value" is returned as NULL.
238 *
239 * c) The "ret_value" parameter is NULL. In this case a search for the exact "key" parameter is performed.
240 *
241 * In all three cases, > 0 is returned if the key is found, 0 if not. */
242
243 if (isempty(key))
244 return -EINVAL;
245
246 if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
247 return -EINVAL;
248
249 r = proc_cmdline(&line);
250 if (r < 0)
251 return r;
252
253 if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */
254 return cmdline_get_key(line, key, flags, ret_value);
255
256 r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL);
257 if (r < 0)
258 return r;
259 if (r > 0) {
260 if (ret_value)
261 *ret_value = TAKE_PTR(v);
262
263 return r;
264 }
265
266 line = mfree(line);
267 r = systemd_efi_options_variable(&line);
268 if (r == -ENODATA) {
269 if (ret_value)
270 *ret_value = NULL;
271
272 return false; /* Not found */
273 }
274 if (r < 0)
275 return r;
276
277 return cmdline_get_key(line, key, flags, ret_value);
278 }
279
proc_cmdline_get_bool(const char * key,bool * ret)280 int proc_cmdline_get_bool(const char *key, bool *ret) {
281 _cleanup_free_ char *v = NULL;
282 int r;
283
284 assert(ret);
285
286 r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v);
287 if (r < 0)
288 return r;
289 if (r == 0) { /* key not specified at all */
290 *ret = false;
291 return 0;
292 }
293
294 if (v) { /* key with parameter passed */
295 r = parse_boolean(v);
296 if (r < 0)
297 return r;
298 *ret = r;
299 } else /* key without parameter passed */
300 *ret = true;
301
302 return 1;
303 }
304
proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags,...)305 int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
306 _cleanup_free_ char *line = NULL;
307 bool processing_efi = true;
308 const char *p;
309 va_list ap;
310 int r, ret = 0;
311
312 /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make
313 * this clear. */
314 assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL));
315
316 /* This call may clobber arguments on failure! */
317
318 if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) {
319 r = systemd_efi_options_variable(&line);
320 if (r < 0 && r != -ENODATA)
321 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
322 }
323
324 p = line;
325 for (;;) {
326 _cleanup_free_ char *word = NULL;
327
328 r = proc_cmdline_extract_first(&p, &word, flags);
329 if (r < 0)
330 return r;
331 if (r == 0) {
332 /* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */
333 if (processing_efi) {
334 processing_efi = false;
335
336 line = mfree(line);
337 r = proc_cmdline(&line);
338 if (r < 0)
339 return r;
340
341 p = line;
342 continue;
343 }
344
345 break;
346 }
347
348 va_start(ap, flags);
349
350 for (;;) {
351 char **v;
352 const char *k, *e;
353
354 k = va_arg(ap, const char*);
355 if (!k)
356 break;
357
358 assert_se(v = va_arg(ap, char**));
359
360 e = proc_cmdline_key_startswith(word, k);
361 if (e && *e == '=') {
362 r = free_and_strdup(v, e + 1);
363 if (r < 0) {
364 va_end(ap);
365 return r;
366 }
367
368 ret++;
369 }
370 }
371
372 va_end(ap);
373 }
374
375 return ret;
376 }
377