1#include "internal.h"
2
3bool cmdline_has_list_separator(const char *value) {
4 bool quoted = false;
5 for (const char *p = value; *p; p++) {
6 if (*p == '\\' && p[1]) {
7 p++;
8 continue;
9 }
10 if (*p == '"')
11 quoted = !quoted;
12 else if (*p == ',' && !quoted)
13 return true;
14 }
15 return false;
16}
17
18static inline bool cmdline_type_is_heap_allocated(enum cmdline_type t) {
19 return t == CMDLINE_TYPE_STRING || t == CMDLINE_TYPE_CPU_MASK ||
20 t == CMDLINE_TYPE_RANGE || t == CMDLINE_TYPE_LIST;
21}
22
23struct cmdline_value cmdline_parse_list(const char *value, uint64_t accepted) {
24 if (accepted == 0)
25 accepted = UINT64_MAX;
26
27 uint64_t item_mask = accepted & ~CMDLINE_TYPES(CMDLINE_TYPE_LIST);
28 if (item_mask == 0)
29 item_mask = UINT64_MAX;
30
31 struct parse_list plist = {0};
32 if (!parse_is_list(str: value, out: &plist))
33 panic("cmdline: invalid list format in '%s'", value);
34
35 struct cmdline_list *list = nofail_vnew(list);
36 list->count = plist.count;
37 list->items = nofail_vnews(list->items, plist.count);
38
39 for (size_t i = 0; i < plist.count; i++) {
40 list->items[i] = cmdline_parse_value_for(value: plist.items[i], accepted: item_mask);
41 if (list->items[i].type == CMDLINE_TYPE_ERR) {
42 for (size_t k = 0; k < i; k++) {
43 if (cmdline_type_is_heap_allocated(t: list->items[k].type) &&
44 list->items[k].data)
45 vfree(list->items[k].data);
46 }
47 vfree(list->items);
48 vfree(list);
49 parse_list_free(list: &plist);
50 return (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
51 .type = CMDLINE_TYPE_ERR};
52 }
53 }
54
55 parse_list_free(list: &plist);
56 return (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
57 .type = CMDLINE_TYPE_LIST,
58 .data = list};
59}
60
61static void *allocate_parsed_value_data(const struct cmdline_type_parser *p,
62 const char *value,
63 enum cmdline_type *out_type) {
64 if (p->type == CMDLINE_TYPE_STRING) {
65 char *str_copy = NULL;
66 if (p->parse(&str_copy, value) == ERR_OK)
67 return str_copy;
68 } else if (p->type == CMDLINE_TYPE_CPU_MASK) {
69 struct cpu_mask *mask = nofail_vnew(mask);
70 if (p->parse(mask, value) == ERR_OK)
71 return mask;
72 vfree(mask);
73 } else if (p->type == CMDLINE_TYPE_RANGE) {
74 struct cmdline_range *range = nofail_vnew(range);
75 if (p->parse(range, value) == ERR_OK)
76 return range;
77 vfree(range);
78 }
79
80 *out_type = CMDLINE_TYPE_ERR;
81 return NULL;
82}
83
84struct cmdline_value cmdline_parse_value_for(const char *value,
85 uint64_t accepted) {
86 struct cmdline_value val = {
87 .mode = CMDLINE_MODE_POLYMORPHIC,
88 .type = CMDLINE_TYPE_NONE,
89 .u64 = 0,
90 };
91
92 if (!value)
93 return val;
94
95 if (accepted == 0)
96 accepted = UINT64_MAX;
97
98 if ((accepted & CMDLINE_TYPES(CMDLINE_TYPE_LIST)) &&
99 cmdline_has_list_separator(value))
100 return cmdline_parse_list(value, accepted);
101
102 uint64_t candidate_mask = 0;
103 for (size_t i = 0; i < cmdline_parsers_count; i++) {
104 const struct cmdline_type_parser *p = &cmdline_parsers[i];
105 if (p->detect(value, NULL))
106 candidate_mask |= CMDLINE_TYPES(p->type);
107 }
108
109 uint64_t valid_mask = candidate_mask & accepted;
110 if (valid_mask == 0)
111 return (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
112 .type = CMDLINE_TYPE_ERR};
113
114 for (size_t i = 0; i < cmdline_parsers_count; i++) {
115 const struct cmdline_type_parser *p = &cmdline_parsers[i];
116 if (valid_mask & CMDLINE_TYPES(p->type)) {
117 val.type = p->type;
118 if (!p->uses_allocated_ptr) {
119 if (p->parse(&val.u64, value) == ERR_OK)
120 return val;
121 } else {
122 val.data = allocate_parsed_value_data(p, value, out_type: &val.type);
123 if (val.type != CMDLINE_TYPE_ERR)
124 return val;
125 }
126 }
127 }
128
129 return (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
130 .type = CMDLINE_TYPE_ERR};
131}
132
133struct cmdline_value cmdline_parse_value(struct cmdline_entry *ent,
134 const char *value) {
135 return cmdline_parse_value_for(value, accepted: cmdline_entry_get_accepted_mask(e: ent));
136}
137
138static bool entry_matches_key(const struct cmdline_entry *e, const char *key) {
139 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
140 cmdline_functional_name(ent: e, name_out: name);
141 return strcmp(str1: name, str2: key) == 0;
142}
143
144struct cmdline_entry *cmdline_lookup(const char *key) {
145 if (!key)
146 return NULL;
147
148 for (struct cmdline_entry *ent = __skernel_cmdline_entries;
149 ent < __ekernel_cmdline_entries; ent++) {
150 if (entry_matches_key(e: ent, key))
151 return ent;
152 }
153
154 return NULL;
155}
156
157static void cmdline_extract_value_into(void *dst, enum type_enum c_type,
158 const struct cmdline_value *v) {
159 if (!dst || !v)
160 return;
161
162 switch (c_type) {
163 case TYPE_BOOL:
164 *(bool *) dst = (v->type == CMDLINE_TYPE_BOOL) ? v->b : (v->u64 != 0);
165 return;
166 case TYPE_INT8: *(int8_t *) dst = (int8_t) v->i64; return;
167 case TYPE_INT16: *(int16_t *) dst = (int16_t) v->i64; return;
168 case TYPE_INT32: *(int32_t *) dst = (int32_t) v->i64; return;
169 case TYPE_INT64:
170 *(int64_t *) dst =
171 (v->type == CMDLINE_TYPE_FX) ? (int64_t) v->fx : v->i64;
172 return;
173 case TYPE_UINT8: *(uint8_t *) dst = (uint8_t) v->u64; return;
174 case TYPE_UINT16: *(uint16_t *) dst = (uint16_t) v->u64; return;
175 case TYPE_UINT32: *(uint32_t *) dst = (uint32_t) v->u64; return;
176 case TYPE_UINT64:
177 *(uint64_t *) dst =
178 (v->type == CMDLINE_TYPE_DURATION) ? v->duration : v->u64;
179 return;
180 case TYPE_POINTER:
181 if (v->type == CMDLINE_TYPE_STRING)
182 *(const char **) dst = (const char *) v->data;
183 else if (v->type == CMDLINE_TYPE_LIST)
184 *(const struct cmdline_list **) dst =
185 (const struct cmdline_list *) v->data;
186 else
187 *(void **) dst = v->data;
188 return;
189 default:
190 if (v->type == CMDLINE_TYPE_RANGE) {
191 *(struct cmdline_range *) dst =
192 *(const struct cmdline_range *) v->data;
193 } else if (v->type == CMDLINE_TYPE_CPU_MASK) {
194 memcpy(dst, v->data, sizeof(struct cpu_mask));
195 } else {
196 *(uint64_t *) dst = v->u64;
197 }
198 return;
199 }
200}
201
202static void cmdline_value_free(struct cmdline_value *v) {
203 if (!cmdline_type_is_heap_allocated(t: v->type) || !v->data)
204 return;
205
206 if (v->type == CMDLINE_TYPE_LIST) {
207 struct cmdline_list *list = v->data;
208 for (size_t i = 0; i < list->count; i++)
209 cmdline_value_free(v: &list->items[i]);
210 vfree(list->items);
211 }
212
213 vfree(v->data);
214 v->data = NULL;
215}
216
217static void cmdline_consume_value_into(void *dst, enum type_enum c_type,
218 struct cmdline_value *v) {
219 cmdline_extract_value_into(dst, c_type, v);
220 if (!dst || c_type != TYPE_POINTER)
221 cmdline_value_free(v);
222}
223
224void dispatch_parse_value(struct cmdline_entry *e, const char *name,
225 const char *var, const char *val) {
226 if (e->mappings) {
227 uint64_t mapped_val = 0;
228 if (!cmdline_lookup_mapping(mappings: e->mappings, val, out: &mapped_val))
229 panic("cmdline entry '%s' received invalid mapping choice '%s'",
230 name, val);
231 if (cmdline_value_is_typed(val: &e->value)) {
232 cmdline_write_typed_uint(val: &e->value, raw_val: mapped_val);
233 } else {
234 e->value = (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
235 .type = CMDLINE_TYPE_UINT,
236 .u64 = mapped_val};
237 }
238 return;
239 }
240
241 if (e->flags_table) {
242 uint64_t accumulated_mask = 0;
243 const char *err_tok = NULL;
244 size_t err_len = 0;
245 if (!cmdline_parse_flags(table: e->flags_table, val, out_mask: &accumulated_mask,
246 err_token: &err_tok, err_len: &err_len))
247 panic("cmdline entry '%s' received invalid flag '%.*s'", name,
248 (int) err_len, err_tok);
249 if (cmdline_value_is_typed(val: &e->value)) {
250 cmdline_write_typed_uint(val: &e->value, raw_val: accumulated_mask);
251 } else {
252 e->value = (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
253 .type = CMDLINE_TYPE_UINT,
254 .u64 = accumulated_mask};
255 }
256 return;
257 }
258
259 if (cmdline_value_is_typed(val: &e->value)) {
260 if (e->value.parse) {
261 enum err err = e->value.parse(e->value.write_to, val);
262 if (err != ERR_OK)
263 panic("cmdline entry '%s' failed to parse value '%s' (err: %d)",
264 name, val, err);
265 return;
266 }
267
268 struct cmdline_value vtmp =
269 cmdline_parse_value_for(value: val, accepted: cmdline_entry_get_accepted_mask(e));
270 if (vtmp.type == CMDLINE_TYPE_ERR)
271 panic("cmdline variable %s received incompatible value '%s'", var,
272 val);
273 // NOLINTNEXTLINE(*Malloc)
274 cmdline_consume_value_into(dst: e->value.write_to, c_type: e->value.c_type, v: &vtmp);
275 return;
276 }
277
278 struct cmdline_value vtmp =
279 cmdline_parse_value_for(value: val, accepted: cmdline_entry_get_accepted_mask(e));
280 if (vtmp.type == CMDLINE_TYPE_ERR)
281 panic("cmdline variable %s received incompatible value '%s'", var, val);
282 e->value = vtmp; // NOLINT(*Malloc)
283}
284
285static void validate_scalar_range(enum type_enum c_type, const void *ptr,
286 uint64_t low, uint64_t hi,
287 const char *var_name, const char *val) {
288 switch (c_type) {
289 case TYPE_INT8: {
290 int64_t v = *(const int8_t *) ptr;
291 if (v < (int64_t) low || v > (int64_t) hi)
292 panic("cmdline entry '%s' integer '%s' out of range", var_name,
293 val);
294 break;
295 }
296 case TYPE_INT16: {
297 int64_t v = *(const int16_t *) ptr;
298 if (v < (int64_t) low || v > (int64_t) hi)
299 panic("cmdline entry '%s' integer '%s' out of range", var_name,
300 val);
301 break;
302 }
303 case TYPE_INT32: {
304 int64_t v = *(const int32_t *) ptr;
305 if (v < (int64_t) low || v > (int64_t) hi)
306 panic("cmdline entry '%s' integer '%s' out of range", var_name,
307 val);
308 break;
309 }
310 case TYPE_INT64: {
311 int64_t v = *(const int64_t *) ptr;
312 if (v < (int64_t) low || v > (int64_t) hi)
313 panic("cmdline entry '%s' integer '%s' out of range", var_name,
314 val);
315 break;
316 }
317 case TYPE_UINT8: {
318 uint64_t v = *(const uint8_t *) ptr;
319 if (v < low || v > hi)
320 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
321 break;
322 }
323 case TYPE_UINT16: {
324 uint64_t v = *(const uint16_t *) ptr;
325 if (v < low || v > hi)
326 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
327 break;
328 }
329 case TYPE_UINT32: {
330 uint64_t v = *(const uint32_t *) ptr;
331 if (v < low || v > hi)
332 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
333 break;
334 }
335 case TYPE_UINT64: {
336 uint64_t v = *(const uint64_t *) ptr;
337 if (v < low || v > hi)
338 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
339 break;
340 }
341 default: break;
342 }
343}
344
345static void dispatch_validate_range(const struct cmdline_entry *e,
346 const char *name, const char *val) {
347 if (!cmdline_entry_has_range(e))
348 return;
349
350 if (e->value.mode == CMDLINE_MODE_POLYMORPHIC) {
351 if (e->value.type == CMDLINE_TYPE_INT) {
352 if (e->value.i64 < (int64_t) e->range.low ||
353 e->value.i64 > (int64_t) e->range.hi)
354 panic("cmdline entry '%s' integer '%s' out of range", name,
355 val);
356 } else if (e->value.type == CMDLINE_TYPE_UINT ||
357 e->value.type == CMDLINE_TYPE_DATA_SIZE) {
358 if (!RANGE_CONTAINS(e->range, e->value.u64))
359 panic("cmdline entry '%s' value '%s' out of range", name, val);
360 } else if (e->value.type == CMDLINE_TYPE_DURATION) {
361 if (!RANGE_CONTAINS(e->range, e->value.duration))
362 panic("cmdline entry '%s' duration '%s' out of range", name,
363 val);
364 } else if (e->value.type == CMDLINE_TYPE_FX) {
365 if (e->value.fx < (fx32_32_t) e->range.low ||
366 e->value.fx > (fx32_32_t) e->range.hi)
367 panic("cmdline entry '%s' value '%s' out of range", name, val);
368 }
369 return;
370 }
371
372 if (cmdline_value_is_typed(val: &e->value) && e->value.write_to) {
373 if (BIT_TEST(e->types, CMDLINE_TYPE_FX)) {
374 fx32_32_t v = *(const fx32_32_t *) e->value.write_to;
375 if (v < (fx32_32_t) e->range.low || v > (fx32_32_t) e->range.hi)
376 panic("cmdline entry '%s' value '%s' out of range", name, val);
377 return;
378 }
379 if (BIT_TEST(e->types, CMDLINE_TYPE_DURATION)) {
380 uint64_t v = *(const uint64_t *) e->value.write_to;
381 if (!RANGE_CONTAINS(e->range, v))
382 panic("cmdline entry '%s' duration '%s' out of range", name,
383 val);
384 return;
385 }
386 validate_scalar_range(c_type: e->value.c_type, ptr: e->value.write_to, low: e->range.low,
387 hi: e->range.hi, var_name: name, val);
388 }
389}
390
391static void dispatch_validate_choices(const struct cmdline_entry *e,
392 const char *name, const char *val) {
393 if (e->choices && !cmdline_has_choice(choices: e->choices, val))
394 panic("cmdline entry '%s' received invalid choice '%s'", name, val);
395}
396
397static void schema_validate_range(const struct cmdline_schema_prop *p,
398 const char *var, const char *val,
399 const void *ptr) {
400 if (!RANGE_VALID(p->range))
401 return; /* Uninitialized / sentinel range */
402
403 if (BIT_TEST(p->types, CMDLINE_TYPE_FX) || p->parse == cmdline_parse_fx) {
404 fx32_32_t v = *(const fx32_32_t *) ptr;
405 if (v < (fx32_32_t) p->range.low || v > (fx32_32_t) p->range.hi)
406 panic("cmdline entry '%s' value '%s' out of range", var, val);
407 return;
408 }
409
410 if (BIT_TEST(p->types, CMDLINE_TYPE_DURATION) ||
411 p->parse == cmdline_parse_duration) {
412 time_ns_t v = *(const time_ns_t *) ptr;
413 if (!RANGE_CONTAINS(p->range, v))
414 panic("cmdline entry '%s' duration '%s' out of range", var, val);
415 return;
416 }
417
418 validate_scalar_range(c_type: p->c_type, ptr, low: p->range.low, hi: p->range.hi, var_name: var, val);
419}
420
421static bool schema_dispatch(const char *var, const char *val,
422 const struct cmdline_schema **near_miss_out) {
423 const struct cmdline_schema *near_miss = NULL;
424
425 for (struct cmdline_schema *s = __skernel_cmdline_schemas;
426 s < __ekernel_cmdline_schemas; s++) {
427 size_t prefix_len = strlen(str: s->prefix);
428 if (strncmp(s1: var, s2: s->prefix, n: prefix_len) != 0 || var[prefix_len] != '.')
429 continue;
430
431 const char *subpath = var + prefix_len + 1;
432 const char *last_dot = strrchr(s: subpath, c: '.');
433 if (!last_dot)
434 continue;
435
436 size_t instance_path_len = (size_t) (last_dot - subpath);
437 const char *prop_name = last_dot + 1;
438
439 const struct cmdline_schema_prop *matched_prop = NULL;
440 for (size_t i = 0; i < s->prop_count; i++) {
441 if (strcmp(str1: s->props[i].name, str2: prop_name) == 0) {
442 matched_prop = &s->props[i];
443 break;
444 }
445 }
446 if (!matched_prop)
447 continue;
448
449 void *instance =
450 s->resolve ? s->resolve(subpath, instance_path_len) : NULL;
451 if (!instance) {
452 near_miss = s;
453 continue;
454 }
455
456 void *target_ptr = ((uint8_t *) instance) + matched_prop->offset;
457
458 if (matched_prop->mappings) {
459 uint64_t mval = 0;
460 if (!cmdline_lookup_mapping(mappings: matched_prop->mappings, val, out: &mval))
461 panic("cmdline '%s': invalid mapping '%s'", var, val);
462 struct cmdline_value tmp_val = {
463 .mode = CMDLINE_MODE_TYPED,
464 .c_type = matched_prop->c_type,
465 .write_to = target_ptr,
466 };
467 cmdline_write_typed_uint(val: &tmp_val, raw_val: mval);
468 } else if (matched_prop->flags_table) {
469 uint64_t mask = 0;
470 const char *err_tok = NULL;
471 size_t err_len = 0;
472 if (!cmdline_parse_flags(table: matched_prop->flags_table, val, out_mask: &mask,
473 err_token: &err_tok, err_len: &err_len))
474 panic("cmdline '%s': invalid flag '%.*s'", var, (int) err_len,
475 err_tok);
476 struct cmdline_value tmp_val = {
477 .mode = CMDLINE_MODE_TYPED,
478 .c_type = matched_prop->c_type,
479 .write_to = target_ptr,
480 };
481 cmdline_write_typed_uint(val: &tmp_val, raw_val: mask);
482 } else if (matched_prop->parse) {
483 enum err err = matched_prop->parse(target_ptr, val);
484 if (err != ERR_OK)
485 panic("cmdline '%s': parse failed for '%s' (err %d)", var, val,
486 err);
487 } else if (matched_prop->types != 0 ||
488 matched_prop->c_type != TYPE_NONE) {
489 uint64_t mask = UINT64_MAX;
490 if (matched_prop->types >= BIT(CMDLINE_TYPE_OFFSET)) {
491 mask = matched_prop->types;
492 } else if (matched_prop->types >= CMDLINE_TYPE_OFFSET &&
493 matched_prop->types < CMDLINE_TYPE_NONE) {
494 mask = BIT(matched_prop->types);
495 } else if (matched_prop->c_type != TYPE_NONE) {
496 switch (matched_prop->c_type) {
497 case TYPE_BOOL: mask = BIT(CMDLINE_TYPE_BOOL); break;
498 case TYPE_INT8:
499 case TYPE_INT16:
500 case TYPE_INT32:
501 case TYPE_INT64:
502 mask = BIT(CMDLINE_TYPE_INT) | BIT(CMDLINE_TYPE_UINT);
503 break;
504 case TYPE_UINT8:
505 case TYPE_UINT16:
506 case TYPE_UINT32:
507 case TYPE_UINT64: mask = BIT(CMDLINE_TYPE_UINT); break;
508 case TYPE_POINTER: mask = BIT(CMDLINE_TYPE_STRING); break;
509 default: break;
510 }
511 }
512 struct cmdline_value vtmp = cmdline_parse_value_for(value: val, accepted: mask);
513 if (vtmp.type == CMDLINE_TYPE_ERR)
514 panic("cmdline '%s': parse failed for '%s'", var, val);
515 // NOLINTNEXTLINE(*Malloc)
516 cmdline_consume_value_into(dst: target_ptr, c_type: matched_prop->c_type, v: &vtmp);
517 } else {
518 panic("cmdline schema '%s': property '%s' has no parser configured",
519 s->prefix, prop_name);
520 }
521
522 if (matched_prop->choices &&
523 !cmdline_has_choice(choices: matched_prop->choices, val))
524 panic("cmdline entry '%s' received invalid choice '%s'", var, val);
525
526 schema_validate_range(p: matched_prop, var, val, ptr: target_ptr);
527 log_msg(LOG_INFO, "command line entry '%s' set to '%s'", var, val);
528 return true;
529 }
530 *near_miss_out = near_miss;
531 return false;
532}
533
534void cmdline_dispatch(const char *var, const char *val) {
535 for (struct cmdline_entry *e = __skernel_cmdline_entries;
536 e < __ekernel_cmdline_entries; e++) {
537 enforce(e->name);
538
539 if (e->flags & CMDLINE_ENTRY_SYMBOLIC)
540 continue;
541
542 if (!entry_matches_key(e, key: var))
543 continue;
544
545 if (e->status == CMDLINE_ENTRY_FOUND)
546 panic("duplicate cmdline entry: %s", var);
547
548 e->status = CMDLINE_ENTRY_FOUND;
549
550 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
551 cmdline_functional_name(ent: e, name_out: name);
552
553 dispatch_parse_value(e, name, var, val);
554 dispatch_validate_range(e, name, val);
555 dispatch_validate_choices(e, name, val);
556 log_msg(LOG_INFO, "command line entry '%s' set to '%s'", name, val);
557 return;
558 }
559
560 const struct cmdline_schema *near_miss = NULL;
561 if (schema_dispatch(var, val, near_miss_out: &near_miss))
562 return;
563
564 if (near_miss) {
565 panic("unknown command line key '%s' (schema '%s' <%s> has the "
566 "property but no matching instance)",
567 var, near_miss->prefix, near_miss->path_hint);
568 }
569
570 panic("unknown command line key '%s'", var);
571}
572