#undef DEBUG_SH #ifdef DEBUG_SH #define dbg(x) fprintf x #else #define dbg(x) #endif #include #include #include #include #include #include #include #include #define ROOT "/" #define HOME ROOT "." #define TMP ROOT "tmp" #define ARGV_MAX 256 #define REDIR_MAX 10 typedef struct redirect { int r_sfd; int r_dfd; } redirect_t; typedef struct redirect_map { int rm_nfds; redirect_t rm_redir[REDIR_MAX]; } redirect_map_t; typedef struct ioenv { int io_map_fd[3]; #define io_map_file io_map_fd } ioenv_t; static char **my_envp; static void parse(char *line); static int execute(int argc, char *argv[], redirect_map_t *map); static void add_redirect(redirect_map_t *map, int sfd, int dfd); #define DECL_CMD(x) static int cmd_##x(int argc, char *argv[], ioenv_t *io) DECL_CMD(env); DECL_CMD(cd); DECL_CMD(help); DECL_CMD(exit); DECL_CMD(mkdir); DECL_CMD(rmdir); DECL_CMD(clear); DECL_CMD(ln); DECL_CMD(rm); DECL_CMD(mv); DECL_CMD(cat); DECL_CMD(echo); DECL_CMD(cp); DECL_CMD(sync); DECL_CMD(check); DECL_CMD(repeat); DECL_CMD(parallel); DECL_CMD(time); typedef struct { const char *cmd_name; int (*cmd_func)(int argc, char *argv[], ioenv_t *io); const char *cmd_helptext; } cmd_t; static cmd_t builtin_cmds[] = { {"?", cmd_help, "list shell commands"}, {"cat", cmd_cat, "display file"}, {"env", cmd_env, "display environment"}, {"cd", cmd_cd, "change directory"}, {"check", cmd_check, "test operating system"}, {"clear", cmd_clear, "clear screen"}, {"cp", cmd_cp, "copy file"}, {"echo", cmd_echo, "print arguments"}, {"exit", cmd_exit, "exit shell"}, {"help", cmd_help, "list shell commands"}, {"ln", cmd_ln, "link file"}, {"mkdir", cmd_mkdir, "create a directory"}, {"mv", cmd_mv, "move file"}, {"quit", cmd_exit, "exit shell"}, {"rm", cmd_rm, "remove file(s)"}, {"rmdir", cmd_rmdir, "remove a directory"}, {"sync", cmd_sync, "sync filesystems"}, {"repeat", cmd_repeat, "repeat a command"}, {"parallel", cmd_parallel, "run multiple commands in parallel"}, {"time", cmd_time, "time a command"}, {NULL, NULL, NULL}}; #define builtin_stdin (&io->io_map_file[0]) #define builtin_stdout (&io->io_map_file[1]) #define builtin_stderr (&io->io_map_file[2]) #define is_std_stream(fd) ((fd) >= 0 && (fd) <= 2) DECL_CMD(chk_sparse); DECL_CMD(chk_unlink); DECL_CMD(chk_wrnoent); DECL_CMD(chk_sbrk); DECL_CMD(chk_zero); DECL_CMD(chk_null); DECL_CMD(chk_priv); DECL_CMD(chk_malloc); static cmd_t check_cmds[] = { {"null", cmd_chk_null, "read and write /dev/null"}, {"sbrk", cmd_chk_sbrk, "memory allocation - sbrk"}, {"sparse", cmd_chk_sparse, "sparse mmap writes"}, {"unlink", cmd_chk_unlink, "create and unlink a file"}, {"wrnoent", cmd_chk_wrnoent, "write to an unlinked file"}, {"zero", cmd_chk_zero, "read and map /dev/zero"}, {"priv", cmd_chk_priv, "writes to MAP_PRIVATE mapping"}, {"malloc", cmd_chk_malloc, "memory allocation - malloc"}, {NULL, NULL, NULL}}; static int check_failed(const char *test, const char *cmd) { fprintf(stderr, "%s: %s failed: %s\n", test, cmd, strerror(errno)); /*fprintf(stderr, "%s: %s failed: errno %d\n", test, cmd, errno);*/ return 1; } static int check_exists(const char *file) { int fd; fd = open(file, O_RDONLY, 0); if (fd >= 0) { close(fd); return 1; } else { return 0; } } char *strdup(const char *); // darmanio: for some reason, gcc complains if I // don't re-forward declare it here... static char **copy_args(int argc, char *argv[]) { char **args = malloc(sizeof(char *) * (argc + 1)); args[argc] = NULL; while (--argc >= 0) args[argc] = strdup(argv[argc]); return args; } static void free_args(int argc, char *argv[]) { while (--argc >= 0) free(argv[argc]); free(argv); } DECL_CMD(chk_priv) { const char *test = argv[0]; const char *tmpfile = TMP "/priv"; int fd; void *addr; const char *str = "Hello there.\n"; int error; char *tmpstr; size_t len; uint32_t ii; char tmpbuf[256]; if (check_exists(tmpfile)) { fprintf(stderr, "%s: file exists: %s\n", test, tmpfile); return 1; } /* Create a file with some data. */ fd = open(tmpfile, O_CREAT | O_RDWR, 0); if (fd < 0) { return check_failed(test, "open"); } if (write(fd, str, strlen(str)) != (ssize_t)strlen(str)) { error = check_failed(test, "write"); goto err_close; } /* Map the file MAP_PRIVATE. */ len = strlen(str); addr = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { error = check_failed(test, "mmap"); goto err_close; } tmpstr = (char *)addr; /* Verify that the string is initially in the mapping. */ if (strncmp(tmpstr, str, strlen(str))) { fprintf(stderr, "%s: file doesn't have string\n", test); error = 1; goto err_unmap; } memset(addr, 0, strlen(str)); /* Verify that the string has been overwritten in the mapping. */ for (ii = 0; ii < len; ii++) { if (tmpstr[ii] != 0) { fprintf(stderr, "%s: didn't write to mapping\n", test); error = 1; goto err_unmap; } } /* Verify that the file still contains the original string. */ if (lseek(fd, 0, SEEK_SET) != 0) { error = check_failed(test, "lseek"); goto err_unmap; } if ((ii = read(fd, tmpbuf, sizeof(tmpbuf))) != len) { fprintf(stderr, "%s: read returned %d, expecting %lu.\n", test, ii, len); error = 1; goto err_unmap; } if (strncmp(tmpbuf, str, strlen(str))) { fprintf(stderr, "%s: file was changed by MAP_PRIVATE?!\n", test); error = 1; goto err_unmap; } error = 0; err_unmap: if (munmap(addr, len) < 0) { error = check_failed(test, "munmap"); } err_close: if (close(fd) < 0) { error = check_failed(test, "close"); } if (unlink(tmpfile) < 0) { error = check_failed(test, "unlink"); } return error; } DECL_CMD(chk_null) { const char *test = argv[0]; const char *null = "/dev/null"; int fd; int nbytes; char buf[256]; int error; fd = open(null, O_RDWR, 0600); if (fd < 0) { return check_failed(test, "open"); } memset(buf, 0xCC, sizeof(buf)); /* Try writing to /dev/null. Should return buffer size. */ nbytes = write(fd, buf, sizeof(buf)); if (nbytes != sizeof(buf)) { error = check_failed(test, "write"); goto err_close; } /* Try reading from /dev/null. Should return zero. */ nbytes = read(fd, buf, sizeof(buf)); if (nbytes != 0) { error = check_failed(test, "read"); goto err_close; } error = 0; err_close: if (close(fd) < 0) { error = check_failed(test, "close"); } return error; } DECL_CMD(chk_zero) { const char *test = argv[0]; void *addr; int fd; const char *zero = "/dev/zero"; char buf[256]; int nbytes; int error; uint32_t ii; size_t len; unsigned long *lp; unsigned char *cp; fd = open(zero, O_RDWR, 0600); if (fd < 0) { return check_failed(test, "open"); } /* Set buffer to a non-zero value, then read from /dev/zero * and make sure that the buffer is cleared. */ memset(buf, 0xCC, sizeof(buf)); nbytes = read(fd, buf, sizeof(buf)); if (nbytes != sizeof(buf)) { error = check_failed(test, "read"); goto err_close; } for (ii = 0; ii < sizeof(buf); ii++) { if (buf[ii] != 0) { error = check_failed(test, "verify read"); goto err_close; } } /* Map /dev/zero and make sure all pages are initially zero. */ len = 8192 * 5; addr = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { error = check_failed(test, "mmap"); goto err_close; } cp = (unsigned char *)addr; for (ii = 0; ii < len; ii++, cp++) { if (*cp != 0) { error = check_failed(test, "verify mmap zeros"); goto err_unmap; } } /* ... make sure writes are allowed. */ lp = (unsigned long *)addr; for (ii = 0; ii < (len / sizeof(*lp)); ii++, lp++) { *lp = ii; } lp = (unsigned long *)addr; for (ii = 0; ii < (len / sizeof(*lp)); ii++, lp++) { if (*lp != ii) { error = check_failed(test, "verify map write"); goto err_unmap; } } error = 0; err_unmap: if (munmap(addr, len) < 0) { error = check_failed(test, "munmap"); } err_close: if (close(fd) < 0) { error = check_failed(test, "close"); } return error; } DECL_CMD(chk_malloc) { const char *test = argv[0]; void *addr; int len; int *tmp; uint32_t ii; int error; len = 8192 + 128; addr = malloc(len); if (!addr) { return check_failed(test, "malloc"); } /* Try writing to the memory. */ tmp = (int *)addr; for (ii = 0; ii < (len / sizeof(int)); ii++) { *tmp++ = ii; } /* Verify what we've written. */ tmp = (int *)addr; for (ii = 0; ii < (len / sizeof(int)); ii++) { if (*tmp++ != (int)ii) { fprintf(stderr, "%s: verify failed at 0x%lx\n", test, (unsigned long)tmp); error = 1; goto out_free; } } error = 0; out_free: free(addr); return error; } DECL_CMD(chk_sbrk) { void *oldbrk1, *oldbrk2; const void *brk_failed = (void *)-1; const char *test = argv[0]; int len; int *tmp; uint32_t ii; /* A length which is not a page multiple, yet a multiple of 8. */ len = 8192 * 5 + 128; /* Try allocating some memory. */ oldbrk1 = sbrk(len); if (oldbrk1 == brk_failed) { return check_failed(test, "sbrk alloc"); } /* Try writing to the memory. */ tmp = (int *)oldbrk1; for (ii = 0; ii < (len / sizeof(int)); ii++) { *tmp++ = ii; } /* Try verifying what we wrote. */ tmp = (int *)oldbrk1; for (ii = 0; ii < (len / sizeof(int)); ii++) { if (*tmp++ != (int)ii) { fprintf(stderr, "%s: verify failed at 0x%lx\n", test, (unsigned long)tmp); return 1; } } /* Try freeing the memory. */ oldbrk2 = sbrk(-len); if (oldbrk2 == brk_failed) { return check_failed(test, "sbrk dealloc"); } /* oldbrk2 should be at least "len" greater than oldbrk1. */ if ((unsigned long)oldbrk2 < ((unsigned long)oldbrk1 + len)) { fprintf(stderr, "%s: sbrk didn't return old brk??\n", test); return 1; } return 0; } DECL_CMD(chk_wrnoent) { int fd; int error; int nfd; const char *tmpfile = TMP "/chk_wrnoent"; const char *test = argv[0]; const char *teststr = "Hello World!"; char buf[256]; /* Verify that file doesn't exist. */ if (check_exists(tmpfile)) { fprintf(stderr, "%s: tmpfile exists\n", test); return 1; } /* Create file. */ fd = open(tmpfile, O_CREAT | O_RDWR, 0600); if (fd < 0) { return check_failed(test, "create"); } /* Unlink file. */ error = unlink(tmpfile); if (error < 0) { error = check_failed(test, "unlink"); goto out_close; } /* Verify that file is gone. */ nfd = open(tmpfile, O_RDONLY, 0); if (nfd >= 0) { error = check_failed(test, "open nonexistent"); goto out_close; } /* Try writing a string to the file and reading it back. */ error = write(fd, teststr, strlen(teststr)); if (error != (ssize_t)strlen(teststr)) { error = check_failed(test, "write teststr"); goto out_close; } error = lseek(fd, 0, SEEK_SET); if (error != 0) { error = check_failed(test, "lseek begin"); goto out_close; } error = read(fd, buf, strlen(teststr)); if (error != (ssize_t)strlen(teststr)) { error = check_failed(test, "read teststr"); goto out_close; } /* Verify string. */ if (strncmp(buf, teststr, strlen(teststr))) { fprintf(stderr, "%s: verify string failed\n", test); error = 1; goto out_close; } error = 0; out_close: if (close(fd) < 0) { error = check_failed(test, "close"); } return error; } DECL_CMD(chk_unlink) { int fd; int error; int nfd; const char *tmpfile = TMP "/chk_unlink"; const char *test = argv[0]; /* Verify that file doesn't exist. */ if (check_exists(tmpfile)) { fprintf(stderr, "%s: tmpfile exists\n", test); return 1; } /* Create file. */ fd = open(tmpfile, O_CREAT | O_RDONLY, 0600); if (fd < 0) { return check_failed(test, "create"); } /* Unlink file. */ error = unlink(tmpfile); if (error < 0) { error = check_failed(test, "unlink"); goto out_close; } /* Verify that file is gone. */ nfd = open(tmpfile, O_RDONLY, 0); if (nfd >= 0) { error = check_failed(test, "open nonexistent"); goto out_close; } error = 0; out_close: if (close(fd) < 0) { error = check_failed(test, "close"); } return error; } DECL_CMD(chk_sparse) { int fd; const char *tmpfile = TMP "/chk_sparse"; const char *test = argv[0]; int error; int seek; int len = 5 * 8192; void *map; const char *teststr = "Hello there?"; char *map_ch; const char *tmpstr; char buf[256]; int ii; /* Verify that file doesn't exist. */ if (check_exists(tmpfile)) { fprintf(stderr, "%s: tmpfile exists\n", test); return 1; } /* Create file. */ fd = open(tmpfile, O_CREAT | O_RDWR, 0600); if (fd < 0) { return check_failed(test, "create"); } /* Extend length, so that there's a really big set of zero * blocks. */ seek = lseek(fd, len, SEEK_SET); if (seek != len) { error = check_failed(test, "lseek len"); goto out_close; } error = write(fd, &fd, 1); if (error < 0) { error = check_failed(test, "write"); goto out_close; } /* Verify that the first few bytes are zero. This should be * true, since the file should be sparse. */ seek = lseek(fd, 0, SEEK_SET); if (seek != 0) { error = check_failed(test, "lseek begin"); goto out_close; } error = read(fd, buf, strlen(teststr)); if (error != (ssize_t)strlen(teststr)) { error = check_failed(test, "read zeros"); goto out_close; } for (ii = strlen(teststr), tmpstr = buf; ii; ii--) { if (*tmpstr++) { fprintf(stderr, "%s: verify zeros failed\n", test); error = 1; goto out_close; } } /* Map file. */ map = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (map == MAP_FAILED) { error = check_failed(test, "mmap"); goto out_close; } /* Write to first page of the mapping, which should be a zero * block. */ map_ch = (char *)map; tmpstr = teststr; while (*tmpstr) *map_ch++ = *tmpstr++; /* Unmap the file. */ if (munmap(map, len) < 0) { error = check_failed(test, "munmap"); } /* Read from the first page. */ seek = lseek(fd, 0, SEEK_SET); if (seek != 0) { error = check_failed(test, "lseek begin"); goto out_close; } error = read(fd, buf, strlen(teststr)); if (error != (ssize_t)strlen(teststr)) { error = check_failed(test, "read teststr"); goto out_close; } buf[strlen(teststr)] = 0; /* Verify the data. */ if (strcmp(teststr, buf)) { fprintf(stderr, "%s: verify data failed\n", test); fprintf(stderr, "%s: teststr \"%s\", buf \"%s\"\n", test, teststr, buf); error = 1; goto out_close; } error = 0; out_close: if (close(fd) < 0) { error = check_failed(test, "close"); } if (unlink(tmpfile) < 0) { error = check_failed(test, "unlink"); } return error; } static int check_run_one(cmd_t *cmd, ioenv_t *io) { int retval; char *argv[2]; argv[0] = (char *)cmd->cmd_name; argv[1] = NULL; fprintf(stdout, "%10s ... ", cmd->cmd_name); fflush(NULL); retval = (*cmd->cmd_func)(1, argv, io); fprintf(stdout, "%s\n", retval ? "FAILED" : "SUCCESS"); return retval; } DECL_CMD(check) { int argn; cmd_t *cmd; int errors; if (argc < 2) { fprintf(stderr, "usage: check [...]\n\n"); fprintf(stderr, "Where is either \"all\" or one of:\n"); for (cmd = check_cmds; cmd->cmd_name; cmd++) { fprintf(stderr, "%20s - %s\n", cmd->cmd_name, cmd->cmd_helptext); } fprintf(stderr, "\n"); return 1; } errors = 0; fprintf(stdout, "Running tests:\n"); if (argc == 2 && !strcmp(argv[1], "all")) { for (cmd = check_cmds; cmd->cmd_name; cmd++) { errors += check_run_one(cmd, io); } return errors; } for (argn = 1; argn < argc; argn++) { const char *test; test = argv[argn]; for (cmd = check_cmds; cmd->cmd_name; cmd++) { if (!strcmp(cmd->cmd_name, test)) { break; } } if (!cmd->cmd_name) { fprintf(stderr, "Unknown test: %s\n", test); errors++; continue; } errors += check_run_one(cmd, io); } return errors; } DECL_CMD(env) { int i = 0; if (my_envp) { while (my_envp[i]) { printf("env: %s\n", my_envp[i++]); } } return 0; } DECL_CMD(sync) { sync(); return 0; } static int do_cp(ioenv_t *io, const char *cmd, const char *in_file, int in_fd, const char *out_file, int out_fd) { #define buffer_sz 32768 static char buffer[buffer_sz]; int nbytes_in; int nbytes_out; if (is_std_stream(in_fd)) { in_fd = io->io_map_fd[in_fd]; } if (is_std_stream(out_fd)) { out_fd = io->io_map_fd[out_fd]; } while ((nbytes_in = read(in_fd, buffer, buffer_sz)) > 0) { if ((nbytes_out = write(out_fd, buffer, nbytes_in)) < 0) { fprintf(stderr, "%s: unable to write to %s: %s\n", cmd, out_file, strerror(errno)); return 0; } } if (nbytes_in < 0) { fprintf(stderr, "%s: unable to read from %s: %s\n", cmd, in_file, strerror(errno)); return 0; } return 1; #undef buffer_sz } DECL_CMD(cp) { const char *src; const char *dest; int src_fd; int dest_fd; int error; if (argc != 3) { fprintf(stderr, "usage: cp \n"); return 1; } src = argv[1]; dest = argv[2]; src_fd = open(src, O_RDONLY, 0); if (src_fd < 0) { fprintf(stderr, "cp: unable to open %s: %s\n", src, strerror(errno)); return 1; } dest_fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (dest_fd < 0) { fprintf(stderr, "cp: unable to open %s: %s\n", dest, strerror(errno)); return 1; } error = 0; if (!do_cp(io, "cp", src, src_fd, dest, dest_fd)) { error = 1; } close(src_fd); close(dest_fd); return error; } DECL_CMD(echo) { int argn; int out_fd = io->io_map_fd[STDOUT_FILENO]; ssize_t error; // Note: We use 128 here because it is the size of the line discipline buffer. // I.e., as large as any of the arguments to echo could be. #define ECHO_BUF_SIZE 128 char buf[ECHO_BUF_SIZE]; // If *no* arguments are passed to echo, write a single newline. if (argc == 1) { error = write(out_fd, "\n", 1); if (error < 0) { fprintf(stderr, "echo: unable to write `\\n`\n"); return error; } } // Loop over provided arguments, printing each with a trailing space (except // the final argument, which has a trailing newline) for (argn = 1; argn < argc; argn++) { char *trailing = argn == argc - 1 ? "\n" : " "; snprintf(buf, ECHO_BUF_SIZE, "%s%s", argv[argn], trailing); error = write(out_fd, buf, strlen(buf)); if (error < 0) { fprintf(stderr, "echo: unable to write `%s`\n", buf); return error; } } #undef ECHO_BUF_SIZE return 0; } DECL_CMD(cat) { const char *file; int argn; int fd; int error; if (argc == 1) { return !do_cp(io, "cat", "", 0, "", 1); } error = 0; for (argn = 1; argn < argc; argn++) { file = argv[argn]; fd = open(file, O_RDONLY, 0); if (fd < 0) { fprintf(stderr, "cat: unable to open %s: %s\n", file, strerror(errno)); error = 1; continue; } if (!do_cp(io, "cat", file, fd, "", 1)) { error = 1; } close(fd); } return error; } DECL_CMD(mv) { const char *src; const char *dest; if (argc != 3) { fprintf(stderr, "usage: mv \n"); return 1; } src = argv[1]; dest = argv[2]; if (rename(src, dest) < 0) { fprintf(stderr, "mv: unable to move %s to %s: %s\n", src, dest, strerror(errno)); return 1; } return 0; } DECL_CMD(rm) { int argn; const char *file; int error = 0; if (argc == 1) { fprintf(stderr, "usage: rm [...]\n"); return 1; } for (argn = 1; argn < argc; argn++) { file = argv[argn]; if (unlink(file) < 0) { fprintf(stderr, "rm: unable to remove %s: %s\n", file, strerror(errno)); error = 1; } } return error; } DECL_CMD(ln) { const char *src; const char *dest; if (argc != 3) { fprintf(stderr, "usage: ln \n"); return 1; } src = argv[1]; dest = argv[2]; if (link(src, dest) < 0) { fprintf(stderr, "ln: couldn't link %s to %s: %s\n", dest, src, strerror(errno)); return 1; } return 0; } DECL_CMD(mkdir) { const char *dir; if (argc != 2) { fprintf(stderr, "usage: mkdir \n"); return 1; } dir = argv[1]; if (mkdir(dir, 0777) < 0) { fprintf(stderr, "mkdir: couldn't create %s: %s\n", dir, strerror(errno)); return 1; } return 0; } DECL_CMD(clear) { #define ESC "\x1B" fprintf(stdout, ESC "[H" ESC "[J"); #undef ESC return 0; } DECL_CMD(rmdir) { const char *dir; if (argc != 2) { fprintf(stderr, "usage: rmdir \n"); return 1; } dir = argv[1]; if (rmdir(dir) < 0) { fprintf(stderr, "rmdir: couldn't remove %s: %s\n", dir, strerror(errno)); return 1; } return 0; } DECL_CMD(exit) { exit(0); return 1; } DECL_CMD(help) { cmd_t *cmd; fprintf(stdout, "Shell commands:\n"); for (cmd = builtin_cmds; cmd->cmd_name; cmd++) { fprintf(stdout, "%20s - %s\n", cmd->cmd_name, cmd->cmd_helptext); } return 0; } DECL_CMD(cd) { const char *dir; if (argc > 2) { fprintf(stderr, "usage: cd \n"); return 1; } if (argc == 1) { dir = HOME; } else { dir = argv[1]; } if (chdir(dir) < 0) { fprintf(stderr, "sh: couldn't cd to %s: %s\n", dir, strerror(errno)); return 1; } return 0; } DECL_CMD(repeat) { long ntimes; if (argc < 3) { fprintf(stderr, "usage: repeat command [args ...]\n"); return 1; } ntimes = strtol(argv[1], NULL, 10); if (ntimes <= 0) { fprintf(stderr, "repeat: must be non-zero\n"); return 1; } while (ntimes--) { redirect_map_t map; int ii; char **new_argv = copy_args(argc - 2, &argv[2]); map.rm_nfds = 0; for (ii = 0; ii < 3; ii++) { int fd; fd = dup(io->io_map_fd[ii]); if (fd < 0) { fprintf(stderr, "repeat: dup(%d) failed: " "%s\n", io->io_map_fd[ii], strerror(errno)); return 1; } add_redirect(&map, fd, ii); } execute(argc - 2, new_argv, &map); free_args(argc - 2, new_argv); } return 0; } DECL_CMD(parallel) { int i, cmdbegin, ncmds = 0; char **cmd_argvs[32]; int cmd_argcs[32]; int cmd_pids[32]; if (argc < 2) { fprintf(stderr, "usage: parallel [args] -- [args] [-- ...]\n"); return 1; } /* Parse commands, dividing up argv */ for (cmdbegin = (i = 1); i < argc; i++) { if (!strcmp(argv[i], "--")) { /* Command delimiter - make sure command non-empty */ if (cmdbegin == i) { fprintf(stderr, "empty command\n"); return 1; } argv[i] = NULL; cmd_argcs[ncmds] = i - cmdbegin; cmd_argvs[ncmds] = &argv[cmdbegin]; ncmds++; if (ncmds > 32) { fprintf(stderr, "too many commands\n"); return 1; } cmdbegin = i + 1; } } if (cmdbegin == argc) { fprintf(stderr, "empty command\n"); return 1; } cmd_argcs[ncmds] = argc - cmdbegin; cmd_argvs[ncmds] = &argv[cmdbegin]; ncmds++; if (ncmds > 32) { fprintf(stderr, "too many commands\n"); return 1; } /* Fork and execute each command */ for (i = 0; i < ncmds; i++) { if (0 == (cmd_pids[i] = fork())) { int status, fd, ii; /* Build weird map thing (as in repeat) */ redirect_map_t map; map.rm_nfds = 0; for (ii = 0; ii < 3; ii++) { if (0 > (fd = dup(io->io_map_fd[ii]))) { exit(1); } add_redirect(&map, fd, ii); } /* Execute and return its status */ exit(execute(cmd_argcs[i], cmd_argvs[i], &map)); } } /* Wait for each command */ int status; for (i = 0; i < ncmds; i++) { wait(&status); } /* Return last status */ return status; } DECL_CMD(time) { if (argc < 1) { fprintf(stderr, "usage: time [cmd]\n"); return 1; } if (argc == 1) { fprintf(stdout, "time: %lu units\n", time(NULL)); return 0; } redirect_map_t map; ; map.rm_nfds = 0; for (unsigned i = 0; i < 3; i++) { int fd = dup(io->io_map_fd[i]); if (fd < 0) exit(fd); add_redirect(&map, fd, i); } time_t start = time(NULL); int ret = execute(argc - 1, &argv[1], &map); time_t end = time(NULL); fprintf(stdout, "time: %lu units\n", end - start); return ret; } static int do_redirect(redirect_map_t *map) { int ii; int newfd, oldfd; for (ii = 0; ii < map->rm_nfds; ii++) { oldfd = map->rm_redir[ii].r_sfd; newfd = map->rm_redir[ii].r_dfd; dbg((stderr, "do_redirect: dup2(%d,%d)\n", oldfd, newfd)); if (dup2(oldfd, newfd) < 0) { fprintf(stderr, "do_redirect: dup2() failed: " "%s\n", strerror(errno)); return -1; } close(oldfd); } return 0; } static void cleanup_redirects(redirect_map_t *map) { int ii; for (ii = 0; ii < map->rm_nfds; ii++) { close(map->rm_redir[ii].r_sfd); } } static void build_ioenv(redirect_map_t *map, ioenv_t *io) { int ii; /* Map stdin, stdout, stderr to themselves. */ for (ii = 0; ii < 3; ii++) { io->io_map_fd[ii] = ii; } /* Execute redirection mappings. */ for (ii = 0; ii < map->rm_nfds; ii++) { int sfd = map->rm_redir[ii].r_sfd; int dfd = map->rm_redir[ii].r_dfd; if (dfd >= 0 && dfd <= 2) { io->io_map_fd[dfd] = sfd; } } } static void destroy_ioenv(ioenv_t *io) {} static int builtin_exec(cmd_t *cmd, int argc, char *argv[], ioenv_t *io) { return (*cmd->cmd_func)(argc, argv, io); } static int execute(int argc, char *argv[], redirect_map_t *map) { int status, pid; cmd_t *cmd; for (cmd = builtin_cmds; cmd->cmd_name; cmd++) { if (!strcmp(cmd->cmd_name, argv[0])) { break; } } if (cmd->cmd_name) { ioenv_t io; build_ioenv(map, &io); status = builtin_exec(cmd, argc, argv, &io); destroy_ioenv(&io); cleanup_redirects(map); return 0; } if (!(pid = fork())) { if (do_redirect(map) < 0) { exit(1); } execve(argv[0], argv, my_envp); char *search_directories[] = {"/usr/bin/", "/bin/", "/sbin/"}; char buf[256]; for (unsigned i = 0; errno == ENOENT && i < sizeof(search_directories) / sizeof(char *); i++) { snprintf(buf, sizeof(buf), "%s/%s", search_directories[i], argv[0]); execve(buf, argv, my_envp); } if (errno == ENOENT) { fprintf(stderr, "sh: command not found: %s\n", argv[0]); } else { fprintf(stderr, "sh: exec failed for %s: %s\n", argv[0], strerror(errno)); } exit(errno); } else { if (0 > pid) { fprintf(stderr, "sh: fork failed errno = %d\n", errno); } } cleanup_redirects(map); int ret = wait(&status); if (status == EFAULT) { fprintf(stderr, "sh: child process accessed invalid memory\n"); } // free_args(argc, argv); return ret; } #define sh_isredirect(ch) ((ch) == '>' || (ch) == '<') static int parse_redirect_dfd(char *line, char **start_p) { char *start; start = *start_p; if (start == line) { return -1; } /* Skip redirect symbol. */ *start = 0; start--; /* Go backwards, skipping whitespace. */ while ((start != line) && isspace(*start)) start--; if (start == line) { return -1; } /* Go backwards, scanning digits. */ while ((start != line) && isdigit(*start)) start--; if (!(isspace(*start) || isdigit(*start))) { return -1; } /* This is the descriptor number. */ *start_p = start; return strtol(start, NULL, 10); } static int redirect_default_fd(int type) { if (type == '<') { return 0; } else if (type == '>') { return 1; } else { fprintf(stderr, "redirect_default_fd: Eh?\n"); return -1; } } static void add_redirect(redirect_map_t *map, int sfd, int dfd) { dbg((stderr, "add_redirect: %d -> %d\n", sfd, dfd)); map->rm_redir[map->rm_nfds].r_sfd = sfd; map->rm_redir[map->rm_nfds].r_dfd = dfd; ++map->rm_nfds; } static int parse_redirect_dup(char *line, redirect_map_t *map, int dfd, int mode, char *start, char **end_p) { int real_sfd, sfd; char *sfdstr; /* Skip whitespace. */ while (*start && isspace(*start)) start++; if (!*start) { fprintf(stderr, "sh: bad redirect at end of line\n"); return -1; } sfdstr = start; /* Scan digits. */ if (!isdigit(*start)) { fprintf(stderr, "sh: parse error in dup redirect: 1\n"); return -1; } while (*start && isdigit(*start)) start++; if (*start && !isspace(*start)) { fprintf(stderr, "sh: parse error in dup redirect: 2\n"); return -1; } /* Got a descriptor. */ *start = 0; real_sfd = strtol(sfdstr, NULL, 10); dbg((stderr, "redirect_dup: %d -> %d\n", real_sfd, dfd)); sfd = dup(real_sfd); if (sfd < 0) { fprintf(stderr, "sh: invalid file descriptor: %d\n", real_sfd); return -1; } add_redirect(map, sfd, dfd); *end_p = start + 1; return 0; } static int parse_redirect_norm(char *line, redirect_map_t *map, int dfd, int mode, char *start, char **end_p) { int sfd; char *path; /* Skip initial whitespace. */ while (*start && isspace(*start)) start++; if (!*start) { fprintf(stderr, "sh: bad redirect at end of line\n"); return -1; } path = start; /* Scan pathname. */ while (*start && !isspace(*start)) start++; *start = 0; dbg((stderr, "redirect_norm: %s -> %d\n", path, dfd)); sfd = open(path, mode, 0666); if (sfd < 0) { fprintf(stderr, "sh: unable to open %s: %s\n", path, strerror(errno)); return -1; } add_redirect(map, sfd, dfd); *end_p = start + 1; return 0; } static int parse_redirects(char *line, redirect_map_t *map) { char *tmp; map->rm_nfds = 0; tmp = line; for (;;) { char *start, *end; int type, dup, append; int mode; int dfd; dup = 0; append = 0; /* Find first redirect symbol. */ while (*tmp && !sh_isredirect(*tmp)) tmp++; if (!*tmp) { break; } start = tmp; type = *tmp; /* Parse the redirect. */ /* Destination file descriptor. */ dfd = parse_redirect_dfd(line, &start); if (dfd < 0) { dfd = redirect_default_fd(type); } /* Look for append or dup. */ tmp++; if (*tmp == '>') { if (type != '>') { fprintf(stderr, "sh: parse error at %c%c\n", type, *tmp); return -1; } append = 1; tmp++; } if (*tmp == '&') { dup = 1; tmp++; } /* Calculate open mode for file. */ if (type == '<') { mode = O_RDONLY; } else if (type == '>') { mode = O_WRONLY | O_CREAT; if (append) { mode |= O_APPEND; } else { mode |= O_TRUNC; } } else { fprintf(stderr, "sh: bad type in redirect: %c\n", type); return -1; } /* Parse the rest of the redirection. */ if (dup) { if (parse_redirect_dup(line, map, dfd, mode, tmp, &end) < 0) { return -1; } } else { if (parse_redirect_norm(line, map, dfd, mode, tmp, &end) < 0) { return -1; } } /* Clear the redirect from the string. */ while (start < end) *start++ = ' '; tmp = end; } return 0; } static void parse(char *line) { char *argv[ARGV_MAX]; int argc; char *tmp; size_t len; redirect_map_t map; argc = 0; tmp = line; len = strlen(line); if (line[len - 1] == '\n') { line[len - 1] = 0; } if (parse_redirects(line, &map) < 0) { return; } for (;;) { /* Ignore leading whitespace. */ while (*tmp && isspace(*tmp)) tmp++; if (!*tmp) { break; } argv[argc++] = tmp; /* Token is everything up to trailing whitespace. */ while (*tmp && !isspace(*tmp)) tmp++; if (!*tmp) { break; } /* Null-terminate token. */ *tmp++ = 0; } argv[argc] = NULL; if (!argc) { return; } execute(argc, argv, &map); } #define LINEBUF_SIZE 1024 static char buf1[LINEBUF_SIZE] = {NULL}; static char buf2[LINEBUF_SIZE] = {NULL}; int main(int argc, char *argv[], char *envp[]) { static char *linebuf = buf1; static char *prev_linebuf = buf2; ssize_t nbytes; char prompt[64]; my_envp = envp; snprintf(prompt, sizeof(prompt), "weenix -> "); fprintf(stdout, "%s", prompt); fflush(NULL); while ((nbytes = read(0, linebuf, LINEBUF_SIZE)) > 0) { linebuf[nbytes] = 0; if (nbytes == 3 && linebuf[0] == '!' && linebuf[1] == '!' && linebuf[2] == '\n') { fprintf(stdout, "%s\n", prev_linebuf); parse(prev_linebuf); } else { parse(linebuf); char *tmp = linebuf; linebuf = prev_linebuf; prev_linebuf = tmp; } fprintf(stdout, "%s", prompt); fflush(NULL); } fprintf(stdout, "exit\n"); #ifdef __static__ exit(0); #endif return 0; }