| 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 | |
| 17 | static 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 | |
| 26 | static 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 | |
| 66 | CMDLINE_DEFINE_UINT_PARSER(cmdline_parse_u8, uint8_t, UINT8_MAX) |
| 67 | CMDLINE_DEFINE_UINT_PARSER(cmdline_parse_u16, uint16_t, UINT16_MAX) |
| 68 | CMDLINE_DEFINE_UINT_PARSER(cmdline_parse_u32, uint32_t, UINT32_MAX) |
| 69 | CMDLINE_DEFINE_INT_PARSER(cmdline_parse_i8, int8_t, INT8_MIN, INT8_MAX) |
| 70 | CMDLINE_DEFINE_INT_PARSER(cmdline_parse_i16, int16_t, INT16_MIN, INT16_MAX) |
| 71 | CMDLINE_DEFINE_INT_PARSER(cmdline_parse_i32, int32_t, INT32_MIN, INT32_MAX) |
| 72 | |
| 73 | static 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. */ |
| 79 | static enum errno cmdline_parse_u64(void *write_to, const char *text) { |
| 80 | return cmdline_parse_ulong(text, out: (uint64_t *) write_to); |
| 81 | } |
| 82 | |
| 83 | static 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 | |
| 88 | static 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 | |
| 95 | static 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 | |
| 100 | enum 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 | |
| 109 | enum 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 | |
| 127 | CMDLINE_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 | |
| 133 | static 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 | |
| 163 | static 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 | |
| 184 | static 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 | |
| 201 | static 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 | |
| 218 | static 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 | |
| 252 | static 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 | |
| 267 | bool 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 | |
| 306 | static 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 | |
| 315 | bool 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 | |
| 360 | void 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 | |