1#include <asm.h>
2#include <cmdline.h>
3#include <console/panic.h>
4#include <console/printf.h>
5#include <errno.h>
6#include <fs/vfs.h>
7#include <global.h>
8#include <kassert.h>
9#include <math/fixed.h>
10#include <mem/alloc.h>
11#include <mem/alloc_or_die.h>
12#include <string.h>
13
14#define MAX_VAR_LEN 128
15#define MAX_VAL_LEN 256
16
17static enum errno cmdline_parse_long(const char *text, long *out) {
18 char *end;
19 long v = strtol(nptr: text, endptr: &end, base: 0);
20 if (end == text || *end != '\0')
21 return ERR_INVAL;
22 *out = v;
23 return ERR_OK;
24}
25
26static enum errno cmdline_parse_ulong(const char *text, uint64_t *out) {
27 /* strtoull would silently wrap a leading '-'; reject it for unsigned. */
28 const char *p = text;
29 while (*p == ' ' || *p == '\t')
30 p++;
31 if (*p == '-')
32 return ERR_INVAL;
33
34 char *end;
35 unsigned long long v = strtoull(nptr: text, endptr: &end, base: 0);
36 if (end == text || *end != '\0')
37 return ERR_INVAL;
38 *out = (uint64_t) v;
39 return ERR_OK;
40}
41
42#define CMDLINE_DEFINE_UINT_PARSER(fn, ctype, cmax) \
43 static enum errno fn(void *write_to, const char *text) { \
44 uint64_t v; \
45 enum errno err = cmdline_parse_ulong(text, &v); \
46 if (err != ERR_OK) \
47 return err; \
48 if (v > (cmax)) \
49 return ERR_OVERFLOW; \
50 *(ctype *) write_to = (ctype) v; \
51 return ERR_OK; \
52 }
53
54#define CMDLINE_DEFINE_INT_PARSER(fn, ctype, cmin, cmax) \
55 static enum errno fn(void *write_to, const char *text) { \
56 long v; \
57 enum errno err = cmdline_parse_long(text, &v); \
58 if (err != ERR_OK) \
59 return err; \
60 if (v < (cmin) || v > (cmax)) \
61 return ERR_OVERFLOW; \
62 *(ctype *) write_to = (ctype) v; \
63 return ERR_OK; \
64 }
65
66CMDLINE_DEFINE_UINT_PARSER(cmdline_parse_u8, uint8_t, UINT8_MAX)
67CMDLINE_DEFINE_UINT_PARSER(cmdline_parse_u16, uint16_t, UINT16_MAX)
68CMDLINE_DEFINE_UINT_PARSER(cmdline_parse_u32, uint32_t, UINT32_MAX)
69CMDLINE_DEFINE_INT_PARSER(cmdline_parse_i8, int8_t, INT8_MIN, INT8_MAX)
70CMDLINE_DEFINE_INT_PARSER(cmdline_parse_i16, int16_t, INT16_MIN, INT16_MAX)
71CMDLINE_DEFINE_INT_PARSER(cmdline_parse_i32, int32_t, INT32_MIN, INT32_MAX)
72
73static enum errno cmdline_parse_i64(void *write_to, const char *text) {
74 return cmdline_parse_long(text, out: (long *) write_to);
75}
76
77/* u64 uses strtoull, so a full 64-bit value (e.g. a hex seed above INT64_MAX)
78 * round-trips correctly. */
79static enum errno cmdline_parse_u64(void *write_to, const char *text) {
80 return cmdline_parse_ulong(text, out: (uint64_t *) write_to);
81}
82
83static enum errno cmdline_parse_bool(void *write_to, const char *text) {
84 *(bool *) write_to = cmdline_is_enabled(str: text);
85 return ERR_OK;
86}
87
88static enum errno cmdline_parse_float(void *write_to, const char *text) {
89 (void) write_to;
90 panic("cmdline: floating-point values are unsupported (kernel builds "
91 "without FP), got '%s'",
92 text);
93}
94
95static enum errno cmdline_parse_unsupported(void *write_to, const char *text) {
96 (void) write_to;
97 panic("cmdline: no parser for the requested type (value '%s')", text);
98}
99
100enum errno cmdline_parse_fx(void *write_to, const char *text) {
101 char *end;
102 fx32_32_t v = fx_parse(str: text, endptr: &end);
103 if (end == text || *end != '\0')
104 return ERR_INVAL;
105 *(fx32_32_t *) write_to = v;
106 return ERR_OK;
107}
108
109enum errno (*cmdline_parse_table[TYPE_MAX])(void *write_to,
110 const char *text) = {
111 [TYPE_NONE] = cmdline_parse_unsupported,
112 [TYPE_INT8] = cmdline_parse_i8,
113 [TYPE_UINT8] = cmdline_parse_u8,
114 [TYPE_INT16] = cmdline_parse_i16,
115 [TYPE_UINT16] = cmdline_parse_u16,
116 [TYPE_INT32] = cmdline_parse_i32,
117 [TYPE_UINT32] = cmdline_parse_u32,
118 [TYPE_INT64] = cmdline_parse_i64,
119 [TYPE_UINT64] = cmdline_parse_u64,
120 [TYPE_FLOAT32] = cmdline_parse_float,
121 [TYPE_FLOAT64] = cmdline_parse_float,
122 [TYPE_BOOL] = cmdline_parse_bool,
123 [TYPE_POINTER] = cmdline_parse_unsupported,
124 [TYPE_UNKNOWN] = cmdline_parse_unsupported,
125};
126
127CMDLINE_ENTRY_DECLARE(root,
128 .desc = "Root filesystem partition to mount at boot",
129 .arg = "<device>", .default_val = NULL,
130 .value = &global.root_partition,
131 .flags = CMDLINE_ENTRY_REQUIRED);
132
133static void get_functional_name(struct cmdline_entry *ent,
134 char name_out[CMDLINE_ENTRY_NAME_LEN_MAX]) {
135 /* Find the root, then the next one down,
136 * then the next, until we get to this */
137 struct cmdline_entry *stop_at = NULL;
138 size_t idx = 0;
139
140 while (true) {
141 struct cmdline_entry *curr = ent;
142 struct cmdline_entry *prev = curr;
143 while ((curr = prev->parent) != stop_at)
144 prev = curr;
145
146 const char *name = prev->name;
147 size_t len = strlen(str: name);
148 memcpy(name_out + idx, name, len);
149 idx += len;
150
151 if (prev == ent) {
152 /* prev == ent, we are at the root, break */
153 name_out[idx] = '\0';
154 return;
155 } else {
156 /* append a period */
157 name_out[idx++] = '.';
158 stop_at = prev;
159 }
160 }
161}
162
163static void cmdline_check_for_duplicates(void) {
164 size_t dupes = 0;
165 for (struct cmdline_entry *a = __skernel_cmdline_entries;
166 a < __ekernel_cmdline_entries; a++) {
167 char name_a[CMDLINE_ENTRY_NAME_LEN_MAX];
168 get_functional_name(ent: a, name_out: name_a);
169
170 for (struct cmdline_entry *b = a + 1; b < __ekernel_cmdline_entries;
171 b++) {
172 char name_b[CMDLINE_ENTRY_NAME_LEN_MAX];
173 get_functional_name(ent: b, name_out: name_b);
174 if (strcmp(str1: name_a, str2: name_b) == 0) {
175 log_msg(LOG_ERROR, "duplicate command line entry: %s", name_a);
176 dupes++;
177 }
178 }
179 }
180 if (dupes)
181 panic("%zu duplicate command line entries", dupes);
182}
183
184static void cmdline_check_for_unfilled(void) {
185 size_t found = 0;
186 for (struct cmdline_entry *e = __skernel_cmdline_entries;
187 e < __ekernel_cmdline_entries; e++) {
188 if ((e->flags & CMDLINE_ENTRY_REQUIRED) &&
189 e->status == CMDLINE_ENTRY_NOT_FOUND) {
190 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
191 get_functional_name(ent: e, name_out: name);
192 log_msg(LOG_ERROR, "Required command line entry %s not present",
193 name);
194 found++;
195 }
196 }
197 if (found)
198 panic("%zu required command line entries not present", found);
199}
200
201static void cmdline_apply_defaults(void) {
202 for (struct cmdline_entry *e = __skernel_cmdline_entries;
203 e < __ekernel_cmdline_entries; e++) {
204 if (e->status == CMDLINE_ENTRY_NOT_FOUND && e->default_val) {
205 if (e->value)
206 *e->value = (char *) e->default_val;
207 else if (e->callback)
208 e->callback(e->default_val, e);
209 e->status = CMDLINE_ENTRY_DEFAULTED;
210 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
211 get_functional_name(ent: e, name_out: name);
212 log_msg(LOG_INFO, "command line entry '%s' defaulted to '%s'", name,
213 e->default_val);
214 }
215 }
216}
217
218static void cmdline_dispatch(const char *var, const char *val) {
219 for (struct cmdline_entry *e = __skernel_cmdline_entries;
220 e < __ekernel_cmdline_entries; e++) {
221 kassert(e->name);
222
223 if (e->flags & CMDLINE_ENTRY_SYMBOLIC)
224 continue;
225
226 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
227 get_functional_name(ent: e, name_out: name);
228
229 if (strcmp(str1: name, str2: var) != 0)
230 continue;
231
232 if (e->status == CMDLINE_ENTRY_FOUND)
233 panic("duplicate cmdline entry: %s", var);
234
235 e->status = CMDLINE_ENTRY_FOUND;
236 if (e->callback) {
237 e->callback(val, e);
238 } else if (e->value) {
239 char *copy = kmalloc_or_die(strlen(val) + 1);
240 memcpy(copy, val, strlen(val) + 1);
241 *e->value = copy;
242 log_msg(LOG_INFO, "command line entry '%s' set to '%s'", name,
243 copy);
244 }
245
246 return;
247 }
248
249 panic("unknown command line key '%s'", var);
250}
251
252static void cmdline_print_all() {
253#ifdef DEBUG_CMDLINE
254 for (struct cmdline_entry *e = __skernel_cmdline_entries;
255 e < __ekernel_cmdline_entries; e++) {
256 if (e->flags & CMDLINE_ENTRY_SYMBOLIC)
257 continue;
258
259 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
260 get_functional_name(e, name);
261 log_msg(LOG_INFO, "command line entry %s = %s", name,
262 e->value ? *e->value : "(null)");
263 }
264#endif
265}
266
267bool cmdline_is_enabled(const char *str) {
268 kassert(str);
269
270 size_t len = strlen(str);
271 char *lower_str = kmalloc_or_die(len + 1);
272
273 for (size_t i = 0; i < len; i++)
274 lower_str[i] = tolower(c: (unsigned char) str[i]);
275
276 lower_str[len] = '\0';
277
278 const char *enabled_terms[] = {
279 "true", "enabled", "y", "yes", "yeah", "yup", "on",
280 "positive", "1", "active", "allow", "ok", "open"};
281
282 int num_enabled = sizeof(enabled_terms) / sizeof(enabled_terms[0]);
283
284 const char *disabled_terms[] = {
285 "false", "disabled", "n", "no", "nope", "off",
286 "negative", "0", "inactive", "deny", "blocked", "closed"};
287 int num_disabled = sizeof(disabled_terms) / sizeof(disabled_terms[0]);
288
289 for (int i = 0; i < num_enabled; i++) {
290 if (strcmp(str1: lower_str, str2: enabled_terms[i]) == 0) {
291 kfree(lower_str);
292 return true;
293 }
294 }
295
296 for (int i = 0; i < num_disabled; i++) {
297 if (strcmp(str1: lower_str, str2: disabled_terms[i]) == 0) {
298 kfree(lower_str);
299 return false;
300 }
301 }
302
303 panic("invalid value '%s'", str);
304}
305
306static void cmdline_assign_all_parsers() {
307 for (struct cmdline_entry *ent = __skernel_cmdline_entries;
308 ent < __ekernel_cmdline_entries; ent++) {
309 if (ent->type != TYPE_NONE) {
310 ent->parse = cmdline_parse_table[ent->type];
311 }
312 }
313}
314
315bool cmdline_wants_help(const char *input) {
316 if (!input)
317 return false;
318
319 while (*input) {
320 while (*input == ' ')
321 input++;
322
323 const char *tok = input;
324 while (*input && *input != ' ' && *input != '=')
325 input++;
326
327 size_t len = (size_t) (input - tok);
328 if (len == 4 && memcmp(tok, "help", 4) == 0 && *input != '=')
329 return true;
330
331 /* skip the rest of this token (any =value and trailing chars) */
332 while (*input && *input != ' ')
333 input++;
334 }
335 return false;
336}
337
338__noreturn void cmdline_dump_help(void) {
339 printf(format: "charmos kernel command-line options:\n\n");
340 for (struct cmdline_entry *e = __skernel_cmdline_entries;
341 e < __ekernel_cmdline_entries; e++) {
342 char name[CMDLINE_ENTRY_NAME_LEN_MAX];
343 get_functional_name(ent: e, name_out: name);
344 printf(format: " %s%s%s\n", name, e->arg ? "=" : "", e->arg ? e->arg : "");
345 if (e->desc)
346 printf(format: " %s\n", e->desc);
347 printf(format: " %s",
348 (e->flags & CMDLINE_ENTRY_REQUIRED) ? "required" : "optional");
349 if (e->default_val)
350 printf(format: ", default: %s", e->default_val);
351 printf(format: "\n\n");
352 }
353 printf(format: "(kernel halted after `help`)\n");
354
355 disable_interrupts();
356 for (;;)
357 hcf();
358}
359
360void cmdline_parse(const char *input) {
361 char var_buf[MAX_VAR_LEN];
362 char val_buf[MAX_VAL_LEN];
363
364 cmdline_check_for_duplicates();
365 cmdline_assign_all_parsers();
366
367 while (*input) {
368 while (*input == ' ')
369 input++;
370
371 const char *var_start = input;
372 while (*input && *input != '=' && *input != ' ')
373 input++;
374
375 const char *var_end = input;
376
377 while (var_end > var_start && *(var_end - 1) == ' ')
378 var_end--;
379
380 while (*input && *input != '=')
381 input++;
382
383 if (*input != '=')
384 break;
385
386 input++;
387
388 while (*input == ' ')
389 input++;
390
391 const char *val_start = input;
392 while (*input && *input != ' ')
393 input++;
394
395 const char *val_end = input;
396
397 uint64_t var_len = var_end - var_start;
398 if (var_len >= MAX_VAR_LEN)
399 var_len = MAX_VAR_LEN - 1;
400
401 memcpy(var_buf, var_start, var_len);
402 var_buf[var_len] = '\0';
403
404 uint64_t val_len = val_end - val_start;
405 if (val_len >= MAX_VAL_LEN)
406 val_len = MAX_VAL_LEN - 1;
407
408 memcpy(val_buf, val_start, val_len);
409 val_buf[val_len] = '\0';
410
411 cmdline_dispatch(var: var_buf, val: val_buf);
412 }
413
414 cmdline_apply_defaults();
415 cmdline_check_for_unfilled();
416 cmdline_print_all();
417}
418