/* * Kernel Debug Shell */ #include #include #include #include #include #include /* * This is basically the same as a userspace buffered/unbuffered * termio call. These are the same sorts of things I would use in * a text editor in userspace, but with the internal kernel calls * rather than system calls. */ static struct termios old; void set_unbuffered(fs_node_t * dev) { ioctl_fs(dev, TCGETS, &old); struct termios new = old; new.c_lflag &= (~ICANON & ~ECHO); ioctl_fs(dev, TCSETSF, &new); } void set_buffered(fs_node_t * dev) { ioctl_fs(dev, TCSETSF, &old); } /* * TODO move this to the printf module */ void fs_printf(fs_node_t * device, char *fmt, ...) { va_list args; va_start(args, fmt); char buffer[1024]; vasprintf(buffer, fmt, args); va_end(args); write_fs(device, 0, strlen(buffer), (uint8_t *)buffer); } /* * Quick readline implementation. * * Most of these TODOs are things I've done already in older code: * TODO tabcompletion would be nice * TODO history is also nice */ int debug_shell_readline(fs_node_t * dev, char * linebuf, int max) { int read = 0; set_unbuffered(dev); while (read < max) { uint8_t buf[1]; int r = read_fs(dev, 0, 1, (unsigned char *)buf); if (!r) { debug_print(WARNING, "Read nothing?"); continue; } linebuf[read] = buf[0]; if (buf[0] == '\n') { fs_printf(dev, "\n"); linebuf[read] = 0; break; } else if (buf[0] == 0x08) { if (read > 0) { fs_printf(dev, "\010 \010"); read--; linebuf[read] = 0; } continue; } fs_printf(dev, "%c", buf[0]); read += r; } set_buffered(dev); return read; } /* * Tasklet for running a userspace application. */ void debug_shell_run_sh(void * data, char * name) { fs_node_t * tty = (fs_node_t *)data; current_process->fds->entries[0] = tty; current_process->fds->entries[1] = tty; current_process->fds->entries[2] = tty; char * argv[] = { "/bin/sh", NULL }; int argc = 0; while (argv[argc]) { argc++; } system(argv[0], argc, argv); /* Run shell */ task_exit(42); } /* * We're going to have a list of shell commands. * We'll search through it linearly because I don't * care to write a hashmap right now. Maybe later. */ struct shell_command { char * name; int (*function) (fs_node_t * tty, int argc, char * argv[]); char * description; }; #define NUM_COMMANDS 4 static struct shell_command shell_commands[NUM_COMMANDS]; /* * Shell commands */ static int shell_create_userspace_shell(fs_node_t * tty, int argc, char * argv[]) { int pid = create_kernel_tasklet(debug_shell_run_sh, "[[k-sh]]", tty); fs_printf(tty, "Shell started with pid = %d\n", pid); process_t * child_task = process_from_pid(pid); sleep_on(child_task->wait_queue); return child_task->status; } static int shell_echo(fs_node_t * tty, int argc, char * argv[]) { for (int i = 1; i < argc; ++i) { fs_printf(tty, "%s ", argv[i]); } fs_printf(tty, "\n"); return 0; } static int shell_help(fs_node_t * tty, int argc, char * argv[]) { struct shell_command * sh = &shell_commands[0]; while (sh->name) { fs_printf(tty, "%s - %s\n", sh->name, sh->description); sh++; } return 0; } static struct shell_command shell_commands[NUM_COMMANDS] = { {"shell", &shell_create_userspace_shell, "Runs a userspace shell on this tty."}, {"echo", &shell_echo, "Prints arguments."}, {"help", &shell_help, "Prints a list of possible shell commands and their descriptions."}, {NULL, NULL, NULL} }; /* * A TTY object to pass to the tasklets for handling * serial-tty interaction. This probably shouldn't * be done as tasklets - TTYs should just be able * to wrap existing fs_nodes themselves, but that's * a problem for another day. */ struct tty_o { fs_node_t * node; fs_node_t * tty; }; /* * These tasklets handle tty-serial interaction. */ void debug_shell_handle_in(void * data, char * name) { struct tty_o * tty = (struct tty_o *)data; while (1) { uint8_t buf[1]; int r = read_fs(tty->tty, 0, 1, (unsigned char *)buf); write_fs(tty->node, 0, r, buf); } } void debug_shell_handle_out(void * data, char * name) { struct tty_o * tty = (struct tty_o *)data; while (1) { uint8_t buf[1]; int r = read_fs(tty->node, 0, 1, (unsigned char *)buf); write_fs(tty->tty, 0, r, buf); } } /* * Determine the size of a smart terminal that we don't have direct * termios access to. This is done by sending a cursor-move command * that will put the cursor into the lower right corner and then * requesting the cursor position report. We then read and parse * the position report. In the case where the terminal on the other * end is actually dumb, we end up waiting for some input and * then timing out. * TODO with asyncio support, the timeout should actually work. * consider also using an alarm (which I also don't have) */ void divine_size(fs_node_t * dev, int * width, int * height) { char tmp[100]; int read = 0; unsigned long start_tick = timer_ticks; /* Move cursor, Request position, Reset cursor */ fs_printf(dev, "\033[1000;1000H\033[6n\033[H"); while (1) { char buf[1]; int r = read_fs(dev, 0, 1, (unsigned char *)buf); if (r > 0) { if (buf[0] != 'R') { if (read > 1) { tmp[read-2] = buf[0]; } read++; } else { break; } } if (timer_ticks - start_tick >= 2) { /* * We've timed out. This will only be triggered * when we eventually receive something, though */ *width = 80; *height = 23; /* Clear and return */ fs_printf(dev, "\033[J"); return; } } /* Clear */ fs_printf(dev, "\033[J"); /* Break up the result into two strings */ for (unsigned int i = 0; i < strlen(tmp); i++) { if (tmp[i] == ';') { tmp[i] = '\0'; break; } } char * h = (char *)((uintptr_t)tmp + strlen(tmp)+1); /* And then parse it into numbers */ *height = atoi(tmp); *width = atoi(h); } /* * Tasklet for managing the kernel serial console. * This is basically a very simple shell, with access * to some internal kernel commands, and (eventually) * debugging routines. */ void debug_shell_run(void * data, char * name) { /* * We will run on the first serial port. * TODO detect that this failed */ fs_node_t * tty = kopen("/dev/ttyS0", 0); /* Our prompt will include the version number of the current kernel */ char version_number[1024]; sprintf(version_number, __kernel_version_format, __kernel_version_major, __kernel_version_minor, __kernel_version_lower, __kernel_version_suffix); /* We will convert the serial interface into an actual TTY */ int master, slave; struct winsize size = {0,0,0,0}; /* Attempt to divine the terminal size. Changing the window size after this will do bad things */ int width, height; divine_size(tty, &width, &height); size.ws_row = height; size.ws_col = width; /* Convert the serial line into a TTY */ openpty(&master, &slave, NULL, NULL, &size); /* Attach the serial to the TTY interface */ struct tty_o _tty = {.node = current_process->fds->entries[master], .tty = tty}; create_kernel_tasklet(debug_shell_handle_in, "[kttydebug-in]", (void *)&_tty); create_kernel_tasklet(debug_shell_handle_out, "[kttydebug-out]", (void *)&_tty); /* Set the device to be the actual TTY slave */ tty = current_process->fds->entries[slave]; int retval = 0; while (1) { char command[512]; /* Print out the prompt */ if (retval) { fs_printf(tty, "%s-%s %d %s# ", __kernel_name, version_number, retval, current_process->wd_name); } else { fs_printf(tty, "%s-%s %s# ", __kernel_name, version_number, current_process->wd_name); } /* Read a line */ debug_shell_readline(tty, command, 511); char * arg = strdup(command); char * pch; /* Tokenizer pointer */ char * save; /* We use the reentrant form of strtok */ char * argv[1024]; /* Command tokens (space-separated elements) */ int tokenid = 0; /* argc, basically */ /* Tokenize the arguments, splitting at spaces */ pch = strtok_r(arg," ",&save); if (!pch) { free(arg); continue; } while (pch != NULL) { argv[tokenid] = (char *)pch; ++tokenid; pch = strtok_r(NULL," ",&save); } argv[tokenid] = NULL; /* Tokens are now stored in argv. */ /* Parse the command string */ struct shell_command * sh = &shell_commands[0]; while (sh->name) { if (!strcmp(sh->name, argv[0])) { retval = sh->function(tty, tokenid, argv); break; } sh++; } if (sh->name == NULL) { fs_printf(tty, "Unrecognized command: %s\n", argv[0]); } free(arg); } } int debug_shell_start(void) { int i = create_kernel_tasklet(debug_shell_run, "[kttydebug]", NULL); debug_print(NOTICE, "Started tasklet with pid=%d", i); return 0; }