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 = kmalloc_or_die(sizeof(*list));
36 list->count = plist.count;
37 list->items = kmalloc_or_die(plist.count * sizeof(*list->items));
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 kfree(list->items[k].data);
46 }
47 kfree(list->items);
48 kfree(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 = kmalloc_or_die(sizeof(*mask));
70 if (p->parse(mask, value) == ERR_OK)
71 return mask;
72 kfree(mask);
73 } else if (p->type == CMDLINE_TYPE_RANGE) {
74 struct cmdline_range *range = kmalloc_or_die(sizeof(*range));
75 if (p->parse(range, value) == ERR_OK)
76 return range;
77 kfree(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
202void dispatch_parse_value(struct cmdline_entry *e, const char *name,
203 const char *var, const char *val) {
204 if (e->mappings) {
205 uint64_t mapped_val = 0;
206 if (!cmdline_lookup_mapping(mappings: e->mappings, val, out: &mapped_val))
207 panic("cmdline entry '%s' received invalid mapping choice '%s'",
208 name, val);
209 if (cmdline_value_is_typed(val: &e->value)) {
210 cmdline_write_typed_uint(val: &e->value, raw_val: mapped_val);
211 } else {
212 e->value = (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
213 .type = CMDLINE_TYPE_UINT,
214 .u64 = mapped_val};
215 }
216 return;
217 }
218
219 if (e->flags_table) {
220 uint64_t accumulated_mask = 0;
221 const char *err_tok = NULL;
222 size_t err_len = 0;
223 if (!cmdline_parse_flags(table: e->flags_table, val, out_mask: &accumulated_mask,
224 err_token: &err_tok, err_len: &err_len))
225 panic("cmdline entry '%s' received invalid flag '%.*s'", name,
226 (int) err_len, err_tok);
227 if (cmdline_value_is_typed(val: &e->value)) {
228 cmdline_write_typed_uint(val: &e->value, raw_val: accumulated_mask);
229 } else {
230 e->value = (struct cmdline_value){.mode = CMDLINE_MODE_POLYMORPHIC,
231 .type = CMDLINE_TYPE_UINT,
232 .u64 = accumulated_mask};
233 }
234 return;
235 }
236
237 if (cmdline_value_is_typed(val: &e->value)) {
238 if (e->value.parse) {
239 enum errno err = e->value.parse(e->value.write_to, val);
240 if (err != ERR_OK)
241 panic("cmdline entry '%s' failed to parse value '%s' (err: %d)",
242 name, val, err);
243 return;
244 }
245
246 struct cmdline_value vtmp =
247 cmdline_parse_value_for(value: val, accepted: cmdline_entry_get_accepted_mask(e));
248 if (vtmp.type == CMDLINE_TYPE_ERR)
249 panic("cmdline variable %s received incompatible value '%s'", var,
250 val);
251 if (e->value.write_to)
252 cmdline_extract_value_into(dst: e->value.write_to, c_type: e->value.c_type,
253 v: &vtmp);
254 return;
255 }
256
257 struct cmdline_value vtmp =
258 cmdline_parse_value_for(value: val, accepted: cmdline_entry_get_accepted_mask(e));
259 if (vtmp.type == CMDLINE_TYPE_ERR)
260 panic("cmdline variable %s received incompatible value '%s'", var, val);
261 e->value = vtmp;
262}
263
264static void validate_scalar_range(enum type_enum c_type, const void *ptr,
265 uint64_t low, uint64_t hi,
266 const char *var_name, const char *val) {
267 switch (c_type) {
268 case TYPE_INT8: {
269 int64_t v = *(const int8_t *) ptr;
270 if (v < (int64_t) low || v > (int64_t) hi)
271 panic("cmdline entry '%s' integer '%s' out of range", var_name,
272 val);
273 break;
274 }
275 case TYPE_INT16: {
276 int64_t v = *(const int16_t *) ptr;
277 if (v < (int64_t) low || v > (int64_t) hi)
278 panic("cmdline entry '%s' integer '%s' out of range", var_name,
279 val);
280 break;
281 }
282 case TYPE_INT32: {
283 int64_t v = *(const int32_t *) ptr;
284 if (v < (int64_t) low || v > (int64_t) hi)
285 panic("cmdline entry '%s' integer '%s' out of range", var_name,
286 val);
287 break;
288 }
289 case TYPE_INT64: {
290 int64_t v = *(const int64_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_UINT8: {
297 uint64_t v = *(const uint8_t *) ptr;
298 if (v < low || v > hi)
299 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
300 break;
301 }
302 case TYPE_UINT16: {
303 uint64_t v = *(const uint16_t *) ptr;
304 if (v < low || v > hi)
305 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
306 break;
307 }
308 case TYPE_UINT32: {
309 uint64_t v = *(const uint32_t *) ptr;
310 if (v < low || v > hi)
311 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
312 break;
313 }
314 case TYPE_UINT64: {
315 uint64_t v = *(const uint64_t *) ptr;
316 if (v < low || v > hi)
317 panic("cmdline entry '%s' value '%s' out of range", var_name, val);
318 break;
319 }
320 default: break;
321 }
322}
323
324static void dispatch_validate_range(const struct cmdline_entry *e,
325 const char *name, const char *val) {
326 if (!cmdline_entry_has_range(e))
327 return;
328
329 if (e->value.mode == CMDLINE_MODE_POLYMORPHIC) {
330 if (e->value.type == CMDLINE_TYPE_INT) {
331 if (e->value.i64 < (int64_t) e->range.low ||
332 e->value.i64 > (int64_t) e->range.hi)
333 panic("cmdline entry '%s' integer '%s' out of range", name,
334 val);
335 } else if (e->value.type == CMDLINE_TYPE_UINT ||
336 e->value.type == CMDLINE_TYPE_DATA_SIZE) {
337 if (!RANGE_CONTAINS(e->range, e->value.u64))
338 panic("cmdline entry '%s' value '%s' out of range", name, val);
339 } else if (e->value.type == CMDLINE_TYPE_DURATION) {
340 if (!RANGE_CONTAINS(e->range, e->value.duration))
341 panic("cmdline entry '%s' duration '%s' out of range", name,
342 val);
343 } else if (e->value.type == CMDLINE_TYPE_FX) {
344 if (e->value.fx < (fx32_32_t) e->range.low ||
345 e->value.fx > (fx32_32_t) e->range.hi)
346 panic("cmdline entry '%s' value '%s' out of range", name, val);
347 }
348 return;
349 }
350
351 if (cmdline_value_is_typed(val: &e->value) && e->value.write_to) {
352 if ((e->types & (1ULL << CMDLINE_TYPE_FX)) != 0) {
353 fx32_32_t v = *(const fx32_32_t *) e->value.write_to;
354 if (v < (fx32_32_t) e->range.low || v > (fx32_32_t) e->range.hi)
355 panic("cmdline entry '%s' value '%s' out of range", name, val);
356 return;
357 }
358 if ((e->types & (1ULL << CMDLINE_TYPE_DURATION)) != 0) {
359 uint64_t v = *(const uint64_t *) e->value.write_to;
360 if (!RANGE_CONTAINS(e->range, v))
361 panic("cmdline entry '%s' duration '%s' out of range", name,
362 val);
363 return;
364 }
365 validate_scalar_range(c_type: e->value.c_type, ptr: e->value.write_to, low: e->range.low,
366 hi: e->range.hi, var_name: name, val);
367 }
368}
369
370static void dispatch_validate_choices(const struct cmdline_entry *e,
371 const char *name, const char *val) {
372 if (e->choices && !cmdline_has_choice(choices: e->choices, val))
373 panic("cmdline entry '%s' received invalid choice '%s'", name, val);
374}
375
376static void schema_validate_range(const struct cmdline_schema_prop *p,
377 const char *var, const char *val,
378 const void *ptr) {
379 if (!RANGE_VALID(p->range))
380 return; /* Uninitialized / sentinel range */
381
382 if ((p->types & (1ULL << CMDLINE_TYPE_FX)) != 0 ||
383 p->parse == cmdline_parse_fx) {
384 fx32_32_t v = *(const fx32_32_t *) ptr;
385 if (v < (fx32_32_t) p->range.low || v > (fx32_32_t) p->range.hi)
386 panic("cmdline entry '%s' value '%s' out of range", var, val);
387 return;
388 }
389
390 if ((p->types & (1ULL << CMDLINE_TYPE_DURATION)) != 0 ||
391 p->parse == cmdline_parse_duration) {
392 time_ns_t v = *(const time_ns_t *) ptr;
393 if (!RANGE_CONTAINS(p->range, v))
394 panic("cmdline entry '%s' duration '%s' out of range", var, val);
395 return;
396 }
397
398 validate_scalar_range(c_type: p->c_type, ptr, low: p->range.low, hi: p->range.hi, var_name: var, val);
399}
400
401static bool schema_dispatch(const char *var, const char *val,
402 const struct cmdline_schema **near_miss_out) {
403 const struct cmdline_schema *near_miss = NULL;
404
405 for (struct cmdline_schema *s = __skernel_cmdline_schemas;
406 s < __ekernel_cmdline_schemas; s++) {
407 size_t prefix_len = strlen(str: s->prefix);
408 if (strncmp(s1: var, s2: s->prefix, n: prefix_len) != 0 || var[prefix_len] != '.')
409 continue;
410
411 const char *subpath = var + prefix_len + 1;
412 const char *last_dot = strrchr(s: subpath, c: '.');
413 if (!last_dot)
414 continue;
415
416 size_t instance_path_len = (size_t) (last_dot - subpath);
417 const char *prop_name = last_dot + 1;
418
419 const struct cmdline_schema_prop *matched_prop = NULL;
420 for (size_t i = 0; i < s->prop_count; i++) {
421 if (strcmp(str1: s->props[i].name, str2: prop_name) == 0) {
422 matched_prop = &s->props[i];
423 break;
424 }
425 }
426 if (!matched_prop)
427 continue;
428
429 void *instance =
430 s->resolve ? s->resolve(subpath, instance_path_len) : NULL;
431 if (!instance) {
432 near_miss = s;
433 continue;
434 }
435
436 void *target_ptr = ((uint8_t *) instance) + matched_prop->offset;
437
438 if (matched_prop->mappings) {
439 uint64_t mval = 0;
440 if (!cmdline_lookup_mapping(mappings: matched_prop->mappings, val, out: &mval))
441 panic("cmdline '%s': invalid mapping '%s'", var, val);
442 struct cmdline_value tmp_val = {
443 .mode = CMDLINE_MODE_TYPED,
444 .c_type = matched_prop->c_type,
445 .write_to = target_ptr,
446 };
447 cmdline_write_typed_uint(val: &tmp_val, raw_val: mval);
448 } else if (matched_prop->flags_table) {
449 uint64_t mask = 0;
450 const char *err_tok = NULL;
451 size_t err_len = 0;
452 if (!cmdline_parse_flags(table: matched_prop->flags_table, val, out_mask: &mask,
453 err_token: &err_tok, err_len: &err_len))
454 panic("cmdline '%s': invalid flag '%.*s'", var, (int) err_len,
455 err_tok);
456 struct cmdline_value tmp_val = {
457 .mode = CMDLINE_MODE_TYPED,
458 .c_type = matched_prop->c_type,
459 .write_to = target_ptr,
460 };
461 cmdline_write_typed_uint(val: &tmp_val, raw_val: mask);
462 } else if (matched_prop->parse) {
463 enum errno err = matched_prop->parse(target_ptr, val);
464 if (err != ERR_OK)
465 panic("cmdline '%s': parse failed for '%s' (err %d)", var, val,
466 err);
467 } else if (matched_prop->types != 0 ||
468 matched_prop->c_type != TYPE_NONE) {
469 uint64_t mask = UINT64_MAX;
470 if (matched_prop->types >= (1ULL << CMDLINE_TYPE_OFFSET)) {
471 mask = matched_prop->types;
472 } else if (matched_prop->types >= CMDLINE_TYPE_OFFSET &&
473 matched_prop->types < CMDLINE_TYPE_NONE) {
474 mask = (1ULL << matched_prop->types);
475 } else if (matched_prop->c_type != TYPE_NONE) {
476 switch (matched_prop->c_type) {
477 case TYPE_BOOL: mask = (1ULL << CMDLINE_TYPE_BOOL); break;
478 case TYPE_INT8:
479 case TYPE_INT16:
480 case TYPE_INT32:
481 case TYPE_INT64:
482 mask = (1ULL << CMDLINE_TYPE_INT) |
483 (1ULL << CMDLINE_TYPE_UINT);
484 break;
485 case TYPE_UINT8:
486 case TYPE_UINT16:
487 case TYPE_UINT32:
488 case TYPE_UINT64: mask = (1ULL << CMDLINE_TYPE_UINT); break;
489 case TYPE_POINTER: mask = (1ULL << CMDLINE_TYPE_STRING); break;
490 default: break;
491 }
492 }
493 struct cmdline_value vtmp = cmdline_parse_value_for(value: val, accepted: mask);
494 if (vtmp.type == CMDLINE_TYPE_ERR)
495 panic("cmdline '%s': parse failed for '%s'", var, val);
496 cmdline_extract_value_into(dst: target_ptr, c_type: matched_prop->c_type, v: &vtmp);
497 } else {
498 panic("cmdline schema '%s': property '%s' has no parser configured",
499 s->prefix, prop_name);
500 }
501
502 if (matched_prop->choices &&
503 !cmdline_has_choice(choices: matched_prop->choices, val))
504 panic("cmdline entry '%s' received invalid choice '%s'", var, val);
505
506 schema_validate_range(p: matched_prop, var, val, ptr: target_ptr);
507 log_msg(LOG_INFO, "command line entry '%s' set to '%s'", var, val);
508 return true;
509 }
510 *near_miss_out = near_miss;
511 return false;
512}
513
514void cmdline_dispatch(const char *var, const char *val) {
515 for (struct cmdline_entry *e = __skernel_cmdline_entries;
516 e < __ekernel_cmdline_entries; e++) {
517 kassert(e->name);
518
519 if (e->flags & CMDLINE_ENTRY_SYMBOLIC)
520 continue;
521
522 if (!entry_matches_key(e, key: var))
523 continue;
524
525 if (e->status == CMDLINE_ENTRY_FOUND)
526 panic("duplicate cmdline entry: %s", var);
527
528 e->status = CMDLINE_ENTRY_FOUND;
529
530 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
531 cmdline_functional_name(ent: e, name_out: name);
532
533 dispatch_parse_value(e, name, var, val);
534 dispatch_validate_range(e, name, val);
535 dispatch_validate_choices(e, name, val);
536 log_msg(LOG_INFO, "command line entry '%s' set to '%s'", name, val);
537 return;
538 }
539
540 const struct cmdline_schema *near_miss = NULL;
541 if (schema_dispatch(var, val, near_miss_out: &near_miss))
542 return;
543
544 if (near_miss) {
545 panic("unknown command line key '%s' (schema '%s' <%s> has the "
546 "property but no matching instance)",
547 var, near_miss->prefix, near_miss->path_hint);
548 }
549
550 panic("unknown command line key '%s'", var);
551}
552