/* * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009 * The President and Fellows of Harvard College. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <types.h> #include <kern/errno.h> #include <kern/reboot.h> #include <kern/unistd.h> #include <kern/wait.h> #include <limits.h> #include <lib.h> #include <uio.h> #include <clock.h> #include <thread.h> #include <proc.h> #include <vfs.h> #include <sfs.h> #include <syscall.h> #include <test.h> #include "opt-sfs.h" #include "opt-net.h" #include <proc_syscalls.h> /* * In-kernel menu and command dispatcher. */ #define _PATH_SHELL "/bin/sh" #define MAXMENUARGS 16 //////////////////////////////////////////////////////////// // // Command menu functions /* * Function for a thread that runs an arbitrary userlevel program by * name. * * Note: this cannot pass arguments to the program. You may wish to * change it so it can, because that will make testing much easier * in the future. * * It copies the program name because runprogram destroys the copy * it gets by passing it to vfs_open(). */ static void cmd_progthread(void *ptr, unsigned long nargs) { char **args = ptr; char progname[128]; int result; KASSERT(nargs >= 1); if (nargs > 2) { kprintf("Warning: argument passing from menu not supported\n"); } /* Hope we fit. */ KASSERT(strlen(args[0]) < sizeof(progname)); strcpy(progname, args[0]); result = runprogram(progname); if (result) { kprintf("Running program %s failed: %s\n", args[0], strerror(result)); return; } /* NOTREACHED: runprogram only returns on error. */ } /* * Common code for cmd_prog and cmd_shell. * * Note that this does not wait for the subprogram to finish, but * returns immediately to the menu. This is usually not what you want, * so you should have it call your system-calls-assignment waitpid * code after forking. * * Also note that because the subprogram's thread uses the "args" * array and strings, until you do this a race condition exists * between that code and the menu input code. */ static int common_prog(int nargs, char **args) { struct proc *proc; int result; /* Create a process for the new program to run in. */ proc = proc_create_runprogram(args[0] /* name */); if (proc == NULL) { return ENOMEM; } result = thread_fork(args[0] /* thread name */, proc /* new process */, cmd_progthread /* thread function */, args /* thread arg */, nargs /* thread arg */); if (result) { kprintf("thread_fork failed: %s\n", strerror(result)); proc_destroy(proc); return result; } int status; wait_pid(proc->pid, &status, 0); (void)status; //TODO: Interpret status /* * The new process will be destroyed when the program exits... * once you write t`he code for handling that. */ return 0; } /* * Command for running an arbitrary userlevel program. */ static int cmd_prog(int nargs, char **args) { if (nargs < 2) { kprintf("Usage: p program [arguments]\n"); return EINVAL; } /* drop the leading "p" */ args++; nargs--; return common_prog(nargs, args); } /* * Command for starting the system shell. */ static int cmd_shell(int nargs, char **args) { (void)args; if (nargs != 1) { kprintf("Usage: s\n"); return EINVAL; } args[0] = (char *)_PATH_SHELL; return common_prog(nargs, args); } /* * Command for changing directory. */ static int cmd_chdir(int nargs, char **args) { if (nargs != 2) { kprintf("Usage: cd directory\n"); return EINVAL; } return vfs_chdir(args[1]); } /* * Command for printing the current directory. */ static int cmd_pwd(int nargs, char **args) { char buf[PATH_MAX+1]; int result; struct iovec iov; struct uio ku; (void)nargs; (void)args; uio_kinit(&iov, &ku, buf, sizeof(buf)-1, 0, UIO_READ); result = vfs_getcwd(&ku); if (result) { kprintf("vfs_getcwd failed (%s)\n", strerror(result)); return result; } /* null terminate */ buf[sizeof(buf)-1-ku.uio_resid] = 0; /* print it */ kprintf("%s\n", buf); return 0; } /* * Command for running sync. */ static int cmd_sync(int nargs, char **args) { (void)nargs; (void)args; vfs_sync(); return 0; } /* * Command for doing an intentional panic. */ static int cmd_panic(int nargs, char **args) { (void)nargs; (void)args; panic("User requested panic\n"); return 0; } /* * Command for shutting down. */ static int cmd_quit(int nargs, char **args) { (void)nargs; (void)args; vfs_sync(); sys_reboot(RB_POWEROFF); thread_exit(); return 0; } /* * Command for mounting a filesystem. */ /* Table of mountable filesystem types. */ static const struct { const char *name; int (*func)(const char *device); } mounttable[] = { #if OPT_SFS { "sfs", sfs_mount }, #endif }; static int cmd_mount(int nargs, char **args) { char *fstype; char *device; unsigned i; if (nargs != 3) { kprintf("Usage: mount fstype device:\n"); return EINVAL; } fstype = args[1]; device = args[2]; /* Allow (but do not require) colon after device name */ if (device[strlen(device)-1]==':') { device[strlen(device)-1] = 0; } for (i=0; i<ARRAYCOUNT(mounttable); i++) { if (!strcmp(mounttable[i].name, fstype)) { return mounttable[i].func(device); } } kprintf("Unknown filesystem type %s\n", fstype); return EINVAL; } static int cmd_unmount(int nargs, char **args) { char *device; if (nargs != 2) { kprintf("Usage: unmount device:\n"); return EINVAL; } device = args[1]; /* Allow (but do not require) colon after device name */ if (device[strlen(device)-1]==':') { device[strlen(device)-1] = 0; } return vfs_unmount(device); } /* * Command to set the "boot fs". * * The boot filesystem is the one that pathnames like /bin/sh with * leading slashes refer to. * * The default bootfs is "emu0". */ static int cmd_bootfs(int nargs, char **args) { char *device; if (nargs != 2) { kprintf("Usage: bootfs device\n"); return EINVAL; } device = args[1]; /* Allow (but do not require) colon after device name */ if (device[strlen(device)-1]==':') { device[strlen(device)-1] = 0; } return vfs_setbootfs(device); } static int cmd_kheapstats(int nargs, char **args) { (void)nargs; (void)args; kheap_printstats(); return 0; } static int cmd_kheapgeneration(int nargs, char **args) { (void)nargs; (void)args; kheap_nextgeneration(); return 0; } static int cmd_kheapdump(int nargs, char **args) { if (nargs == 1) { kheap_dump(); } else if (nargs == 2 && !strcmp(args[1], "all")) { kheap_dumpall(); } else { kprintf("Usage: khdump [all]\n"); } return 0; } //////////////////////////////////////// // // Menus. static void showmenu(const char *name, const char *x[]) { int ct, half, i; kprintf("\n"); kprintf("%s\n", name); for (i=ct=0; x[i]; i++) { ct++; } half = (ct+1)/2; for (i=0; i<half; i++) { kprintf(" %-36s", x[i]); if (i+half < ct) { kprintf("%s", x[i+half]); } kprintf("\n"); } kprintf("\n"); } static const char *opsmenu[] = { "[s] Shell ", "[p] Other program ", "[mount] Mount a filesystem ", "[unmount] Unmount a filesystem ", "[bootfs] Set \"boot\" filesystem ", "[pf] Print a file ", "[cd] Change directory ", "[pwd] Print current directory ", "[sync] Sync filesystems ", "[panic] Intentional panic ", "[q] Quit and shut down ", NULL }; static int cmd_opsmenu(int n, char **a) { (void)n; (void)a; showmenu("OS/161 operations menu", opsmenu); return 0; } static const char *testmenu[] = { "[at] Array test ", "[bt] Bitmap test ", "[tlt] Threadlist test ", "[km1] Kernel malloc test ", "[km2] kmalloc stress test ", "[km3] Large kmalloc test ", "[km4] Multipage kmalloc test ", "[tt1] Thread test 1 ", "[tt2] Thread test 2 ", "[tt3] Thread test 3 ", #if OPT_NET "[net] Network test ", #endif "[sy1] Semaphore test ", "[sy2] Lock test (1) ", "[sy3] CV test (1) ", "[sy4] CV test #2 (1) ", "[fs1] Filesystem test ", "[fs2] FS read stress ", "[fs3] FS write stress ", "[fs4] FS write stress 2 ", "[fs5] FS long stress ", "[fs6] FS create stress ", NULL }; static int cmd_testmenu(int n, char **a) { (void)n; (void)a; showmenu("OS/161 tests menu", testmenu); kprintf(" (1) These tests will fail until you finish the " "synch assignment.\n"); kprintf("\n"); return 0; } static const char *mainmenu[] = { "[?o] Operations menu ", "[?t] Tests menu ", "[kh] Kernel heap stats ", "[khgen] Next kernel heap generation ", "[khdump] Dump kernel heap ", "[q] Quit and shut down ", NULL }; static int cmd_mainmenu(int n, char **a) { (void)n; (void)a; showmenu("OS/161 kernel menu", mainmenu); return 0; } //////////////////////////////////////// // // Command table. static struct { const char *name; int (*func)(int nargs, char **args); } cmdtable[] = { /* menus */ { "?", cmd_mainmenu }, { "h", cmd_mainmenu }, { "help", cmd_mainmenu }, { "?o", cmd_opsmenu }, { "?t", cmd_testmenu }, /* operations */ { "s", cmd_shell }, { "p", cmd_prog }, { "mount", cmd_mount }, { "unmount", cmd_unmount }, { "bootfs", cmd_bootfs }, { "pf", printfile }, { "cd", cmd_chdir }, { "pwd", cmd_pwd }, { "sync", cmd_sync }, { "panic", cmd_panic }, { "q", cmd_quit }, { "exit", cmd_quit }, { "halt", cmd_quit }, /* stats */ { "kh", cmd_kheapstats }, { "khgen", cmd_kheapgeneration }, { "khdump", cmd_kheapdump }, /* base system tests */ { "at", arraytest }, { "bt", bitmaptest }, { "tlt", threadlisttest }, { "km1", malloctest }, { "km2", mallocstress }, { "km3", malloctest3 }, { "km4", malloctest4 }, #if OPT_NET { "net", nettest }, #endif { "tt1", threadtest }, { "tt2", threadtest2 }, { "tt3", threadtest3 }, { "sy1", semtest }, /* synchronization assignment tests */ { "sy2", locktest }, { "sy3", cvtest }, { "sy4", cvtest2 }, /* file system assignment tests */ { "fs1", fstest }, { "fs2", readstress }, { "fs3", writestress }, { "fs4", writestress2 }, { "fs5", longstress }, { "fs6", createstress }, { NULL, NULL } }; /* * Process a single command. */ static int cmd_dispatch(char *cmd) { struct timespec before, after, duration; char *args[MAXMENUARGS]; int nargs=0; char *word; char *context; int i, result; for (word = strtok_r(cmd, " \t", &context); word != NULL; word = strtok_r(NULL, " \t", &context)) { if (nargs >= MAXMENUARGS) { kprintf("Command line has too many words\n"); return E2BIG; } args[nargs++] = word; } if (nargs==0) { return 0; } for (i=0; cmdtable[i].name; i++) { if (*cmdtable[i].name && !strcmp(args[0], cmdtable[i].name)) { KASSERT(cmdtable[i].func!=NULL); gettime(&before); result = cmdtable[i].func(nargs, args); gettime(&after); timespec_sub(&after, &before, &duration); kprintf("Operation took %llu.%09lu seconds\n", (unsigned long long) duration.tv_sec, (unsigned long) duration.tv_nsec); return result; } } kprintf("%s: Command not found\n", args[0]); return EINVAL; } /* * Evaluate a command line that may contain multiple semicolon-delimited * commands. * * If "isargs" is set, we're doing command-line processing; print the * comamnds as we execute them and panic if the command is invalid or fails. */ static void menu_execute(char *line, int isargs) { char *command; char *context; int result; for (command = strtok_r(line, ";", &context); command != NULL; command = strtok_r(NULL, ";", &context)) { if (isargs) { kprintf("OS/161 kernel: %s\n", command); } result = cmd_dispatch(command); if (result) { kprintf("Menu command failed: %s\n", strerror(result)); if (isargs) { panic("Failure processing kernel arguments\n"); } } } } /* * Command menu main loop. * * First, handle arguments passed on the kernel's command line from * the bootloader. Then loop prompting for commands. * * The line passed in from the bootloader is treated as if it had been * typed at the prompt. Semicolons separate commands; spaces and tabs * separate words (command names and arguments). * * So, for instance, to mount an SFS on lhd0 and make it the boot * filesystem, and then boot directly into the shell, one would use * the kernel command line * * "mount sfs lhd0; bootfs lhd0; s" */ void menu(char *args) { char buf[64]; menu_execute(args, 1); while (1) { kprintf("OS/161 kernel [? for menu]: "); kgets(buf, sizeof(buf)); menu_execute(buf, 0); } }