/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt-sfs.h" #include "opt-net.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; } /* * The new process will be destroyed when the program exits... * once you write the 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 dropping to the debugger. */ static int cmd_debug(int nargs, char **args) { (void)nargs; (void)args; mainbus_debugger(); 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; } /* * Subthread for intentially deadlocking. */ struct deadlock { struct lock *lock1; struct lock *lock2; }; static void cmd_deadlockthread(void *ptr, unsigned long num) { struct deadlock *dl = ptr; (void)num; /* If it doesn't wedge right away, keep trying... */ while (1) { lock_acquire(dl->lock2); lock_acquire(dl->lock1); kprintf("+"); lock_release(dl->lock1); lock_release(dl->lock2); } } /* * Command for doing an intentional deadlock. */ static int cmd_deadlock(int nargs, char **args) { struct deadlock dl; int result; (void)nargs; (void)args; dl.lock1 = lock_create("deadlock1"); if (dl.lock1 == NULL) { kprintf("lock_create failed\n"); return ENOMEM; } dl.lock2 = lock_create("deadlock2"); if (dl.lock2 == NULL) { lock_destroy(dl.lock1); kprintf("lock_create failed\n"); return ENOMEM; } result = thread_fork(args[0] /* thread name */, NULL /* kernel thread */, cmd_deadlockthread /* thread function */, &dl /* thread arg */, 0 /* thread arg */); if (result) { kprintf("thread_fork failed: %s\n", strerror(result)); lock_release(dl.lock1); lock_destroy(dl.lock2); lock_destroy(dl.lock1); return result; } /* If it doesn't wedge right away, keep trying... */ while (1) { lock_acquire(dl.lock1); lock_acquire(dl.lock2); kprintf("."); lock_release(dl.lock2); lock_release(dl.lock1); } /* NOTREACHED */ 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 ", "[debug] Drop to debugger ", "[panic] Intentional panic ", "[deadlock] Intentional deadlock ", "[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 ", "[at2] Large 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) ", "[semu1-22] Semaphore unit tests ", "[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}, {"debug", cmd_debug}, {"panic", cmd_panic}, {"deadlock", cmd_deadlock}, {"q", cmd_quit}, {"exit", cmd_quit}, {"halt", cmd_quit}, /* stats */ {"kh", cmd_kheapstats}, {"khgen", cmd_kheapgeneration}, {"khdump", cmd_kheapdump}, /* base system tests */ {"at", arraytest}, {"at2", arraytest2}, {"bt", bitmaptest}, {"tlt", threadlisttest}, {"km1", kmalloctest}, {"km2", kmallocstress}, {"km3", kmalloctest3}, {"km4", kmalloctest4}, #if OPT_NET {"net", nettest}, #endif {"tt1", threadtest}, {"tt2", threadtest2}, {"tt3", threadtest3}, {"sy1", semtest}, /* synchronization assignment tests */ {"sy2", locktest}, {"sy3", cvtest}, {"sy4", cvtest2}, /* semaphore unit tests */ {"semu1", semu1}, {"semu2", semu2}, {"semu3", semu3}, {"semu4", semu4}, {"semu5", semu5}, {"semu6", semu6}, {"semu7", semu7}, {"semu8", semu8}, {"semu9", semu9}, {"semu10", semu10}, {"semu11", semu11}, {"semu12", semu12}, {"semu13", semu13}, {"semu14", semu14}, {"semu15", semu15}, {"semu16", semu16}, {"semu17", semu17}, {"semu18", semu18}, {"semu19", semu19}, {"semu20", semu20}, {"semu21", semu21}, {"semu22", semu22}, /* 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); } }