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