/* * Copyright (c) 1993 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * * Stand-alone shell for system maintainance for Linux. * This program should NOT be built using shared libraries. * * 1.1.1, hacked to re-allow cmd line invocation of script file * Pat Adamo, padamo@unix.asb.com */ #include "sash.h" #include #include #include #include #include #include #include static const char enoent_msg[] = "Bad command or file name"; static const char unkerr_msg[] = "Unknown error!"; extern int intflag; extern void do_test(); typedef struct { char name[10]; char usage[30]; void (*func)(); int minargs; int maxargs; } CMDTAB; CMDTAB cmdtab[] = { "cat", "filename ...", do_cat, 2, MAXARGS, "cd", "[dirname]", do_cd, 1, 2, "chgrp", "gid filename ...", do_chgrp, 3, MAXARGS, "chmod", "mode filename ...", do_chmod, 3, MAXARGS, "chown", "uid filename ...", do_chown, 3, MAXARGS, "cmp", "filename1 filename2", do_cmp, 3, 3, "cp", "srcname ... destname", do_cp, 3, MAXARGS, "date", "date [MMDDhhmm[YYYY]]", do_date, 1, 2, "df", "[file-system]", do_df, 1, 2, "echo", "[args] ...", do_echo, 1, MAXARGS, "exec", "filename [args]", do_exec, 2, MAXARGS, "exit", "", do_exit, 1, 1, "free", "", do_free, 1, 1, "help", "", do_help, 1, MAXARGS, "hexdump", "[-s pos] filename", do_hexdump, 1, 4, "hostname", "[hostname]", do_hostname, 1, 2, "kill", "[-sig] pid ...", do_kill, 2, MAXARGS, "ln", "[-s] srcname ... destname", do_ln, 3, MAXARGS, "ls", "[-lidC] filename ...", do_ls, 1, MAXARGS, "mkdir", "dirname ...", do_mkdir, 2, MAXARGS, "mknod", "filename type major minor", do_mknod, 5, 5, "more", "filename ...", do_more, 2, MAXARGS, "mount", "[-t type] devname dirname", do_mount, 3, MAXARGS, "mv", "srcname ... destname", do_mv, 3, MAXARGS, "pid", "", do_pid, 1, 1, "printenv", "[name]", do_printenv, 1, 2, "ps", "", do_ps, 1, MAXARGS, "pwd", "", do_pwd, 1, 1, "quit", "", do_exit, 1, 1, "rm", "filename ...", do_rm, 2, MAXARGS, "rmdir", "dirname ...", do_rmdir, 2, MAXARGS, "setenv", "name value", do_setenv, 3, 3, "sleep", "seconds", do_sleep, 1, 2, "source", "filename", do_source, 2, 2, "sync", "", do_sync, 1, 1, "touch", "filename ...", do_touch, 2, MAXARGS, "umask", "[mask]", do_umask, 1, 2, "umount", "filename", do_umount, 2, 2, 0, 0, 0, 0, 0 }; typedef struct { char *name; char *value; } ALIAS; static ALIAS *aliastable; static int aliascount; static FILE *sourcefiles[MAXSOURCE]; static int sourcecount; volatile static BOOL intcrlf = TRUE; static void catchint(); static void catchquit(); static void catchchild(); static void readfile(); static void command(); static void runcmd(); static void showprompt(); static BOOL trybuiltin(); static BOOL command_in_path(); static ALIAS *findalias(); extern char ** environ; char buf[CMDLEN]; int exit_code = 0; int main(argc, argv, env) int argc; char **argv; char *env[]; { struct sigaction act; char *cp; int dofile = 0; if ((argc > 1) && !strcmp(argv[1], "-c")) { /* We are that fancy a shell */ buf[0] = '\0'; for (dofile = 2; dofile < argc; dofile++) { strncat(buf, argv[dofile], sizeof(buf)); if (dofile + 1 < argc) strncat(buf, " ", sizeof(buf)); } command(buf, FALSE); exit(exit_code); } if ((argc > 1) && strcmp(argv[1], "-t")) { dofile++; printf("Shell invoked to run file: %s\n",argv[1]); } else printf("\nSash command shell (OpenADK edition)\n"); fflush(stdout); signal(SIGINT, catchint); signal(SIGQUIT, catchquit); memset(&act, 0, sizeof(act)); act.sa_handler = catchchild; act.sa_flags = SA_RESTART; sigaction(SIGCHLD, &act, NULL); if (getenv("PATH") == NULL) putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin"); readfile(dofile ? argv[1] : NULL); exit(exit_code); } /* * Read commands from the specified file. * A null name pointer indicates to read from stdin. */ static void readfile(name) char *name; { FILE *fp; int cc; BOOL ttyflag; char *ptr; if (sourcecount >= MAXSOURCE) { fprintf(stderr, "Too many source files\n"); return; } fp = stdin; if (name) { fp = fopen(name, "r"); if (fp == NULL) { perror(name); return; } } sourcefiles[sourcecount++] = fp; ttyflag = isatty(fileno(fp)); while (TRUE) { fflush(stdout); if (fp == stdin) //using terminal, so show prompt showprompt(); if (intflag && !ttyflag && (fp != stdin)) { fclose(fp); sourcecount--; return; } if (fgets(buf, CMDLEN - 1, fp) == NULL) { if (ferror(fp) && (errno == EINTR)) { clearerr(fp); continue; } break; } cc = strlen(buf); while ((cc > 0) && isspace(buf[cc - 1])) cc--; buf[cc] = '\0'; /* remove leading spaces and look for a '#' */ ptr = &buf[0]; while (*ptr == ' ') { ptr++; } if (*ptr != '#') { if (fp != stdin) { //taking commands from file - echo printf("Command: %s\n",buf); } //end if (fp != stdin) command(buf, fp == stdin); } } if (ferror(fp)) { perror("Reading command line"); if (fp == stdin) exit(1); } clearerr(fp); if (fp != stdin) { fclose(fp); printf("Execution Finished, Exiting\n"); } sourcecount--; } /* * Parse and execute one null-terminated command line string. * This breaks the command line up into words, checks to see if the * command is an alias, and expands wildcards. */ static void command(cmd, do_history) int do_history; char *cmd; { ALIAS *alias; char **argv; int argc; int bg; char *c; char last_exit_code[10]; sprintf(last_exit_code, "%d", exit_code); intflag = FALSE; exit_code = 0; freechunks(); while (isblank(*cmd)) cmd++; if (do_history) { int i; static char *history[HISTORY_SIZE]; if (*cmd == '!') { if (cmd[1] == '!') i = 0; else { i = atoi(cmd+1) - 1; if (i < 0 || i >= HISTORY_SIZE) { printf("%s: Out of range\n", cmd); return; } } if (history[i] == NULL) { printf("%s: Null entry\n", cmd); return; } strcpy(cmd, history[i]); } else if (*cmd == 'h' && cmd[1] == '\0') { for (i=0; i0; i--) history[i] = history[i-1]; history[0] = strdup(cmd); } } if (c = strchr(cmd, '&')) { *c = '\0'; bg = 1; } else bg = 0; /* Set the last exit code */ setenv("?", last_exit_code, 1); if ((cmd = expandenvvar(cmd)) == NULL) return; if ((*cmd == '\0') || !makeargs(cmd, &argc, &argv)) return; /* * Search for the command in the alias table. * If it is found, then replace the command name with * the alias, and append any other arguments to it. */ alias = findalias(argv[0]); if (alias) { cmd = buf; strcpy(cmd, alias->value); while (--argc > 0) { strcat(cmd, " "); strcat(cmd, *++argv); } if (!makeargs(cmd, &argc, &argv)) return; } /* * BASH-style variable setting */ if (argc == 1) { c = index(argv[0], '='); if (c > argv[0]) { *c++ = '\0'; setenv(argv[0], c, 1); return; } } /* * Now look for the command in the builtin table, and execute * the command if found. */ if (!strcmp(argv[0], "builtin")) { --argc; ++argv; if (!*argv || !trybuiltin(argc, argv)) fprintf(stderr, "%s: %s\n", argv[-1], enoent_msg); return; } else if (!command_in_path(argv[0]) && trybuiltin(argc, argv)) return; /* * Not found, run the program along the PATH list. */ runcmd(cmd, bg, argc, argv); } /* * return true if we find this command in our * path. */ static BOOL command_in_path(char *cmd) { struct stat stat_buf; if (strchr(cmd, '/') == 0) { char * path; static char path_copy[PATHLEN]; /* Search path for binary */ for (path = getenv("PATH"); path && *path; ) { char * p2; strcpy(path_copy, path); if (p2 = strchr(path_copy, ':')) { *p2 = '\0'; } if (strlen(path_copy)) strcat(path_copy, "/"); strcat(path_copy, cmd); if (!stat(path_copy, &stat_buf) && (stat_buf.st_mode & 0111)) return(TRUE); p2 = strchr(path, ':'); if (p2) path = p2 + 1; else path = 0; } } else if (!stat(cmd, &stat_buf) && (stat_buf.st_mode & 0111)) return(TRUE); return(FALSE); } /* * Try to execute a built-in command. * Returns TRUE if the command is a built in, whether or not the * command succeeds. Returns FALSE if this is not a built-in command. */ static BOOL trybuiltin(argc, argv) int argc; char **argv; { CMDTAB *cmdptr; int oac; int newargc; int matches; int i; char *newargv[MAXARGS]; char *nametable[MAXARGS]; cmdptr = cmdtab - 1; do { cmdptr++; if (cmdptr->name[0] == 0) return FALSE; } while (strcmp(argv[0], cmdptr->name)); /* * Give a usage string if the number of arguments is too large * or too small. */ if ((argc < cmdptr->minargs) || (argc > cmdptr->maxargs)) { fprintf(stderr, "usage: %s %s\n", cmdptr->name, cmdptr->usage); fflush(stderr); return TRUE; } /* * Now for each command argument, see if it is a wildcard, and if * so, replace the argument with the list of matching filenames. */ newargv[0] = argv[0]; newargc = 1; oac = 0; while (++oac < argc) { if (argv[oac][0] == '"' || argv[oac][0] == '\'') { argv[oac]++; matches = 0; } else { matches = expandwildcards(argv[oac], MAXARGS, nametable); if (matches < 0) return TRUE; } if ((newargc + matches) >= MAXARGS) { fprintf(stderr, "Too many arguments\n"); return TRUE; } if (matches == 0) newargv[newargc++] = argv[oac]; for (i = 0; i < matches; i++) newargv[newargc++] = nametable[i]; } (*cmdptr->func)(newargc, newargv); return TRUE; } /* * Execute the specified command. */ static void runcmd(cmd, bg, argc, argv) char *cmd; int bg; int argc; char **argv; { register char * cp; int pid; int status; int oac; int newargc; int matches; int i; char *newargv[MAXARGS]; char *nametable[MAXARGS]; struct sigaction act; newargv[0] = argv[0]; /* * Now for each command argument, see if it is a wildcard, and if * so, replace the argument with the list of matching filenames. */ newargc = 1; oac = 0; while (++oac < argc) { if (argv[oac][0] == '"' || argv[oac][0] == '\'') { argv[oac]++; matches = 0; } else { matches = expandwildcards(argv[oac], MAXARGS, nametable); if (matches < 0) return; } if ((newargc + matches) >= MAXARGS) { fprintf(stderr, "Too many arguments\n"); return; } if (matches == 0) newargv[newargc++] = argv[oac]; for (i = 0; i < matches; i++) newargv[newargc++] = nametable[i]; } newargv[newargc] = 0; if (!bg) signal(SIGCHLD, SIG_DFL); /* * Do the fork and exec ourselves. * If this fails with ENOEXEC, then run the * shell anyway since it might be a shell script. */ if (!(pid = vfork())) { int ci; char errbuf[50]; /* * We are the child, so run the program. * First close any extra file descriptors we have opened. * be sure not to modify any globals after the vfork ! */ for (ci = 0; ci < sourcecount; ci++) if (sourcefiles[ci] != stdin) close(fileno(sourcefiles[ci])); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGCHLD, SIG_DFL); execvp(newargv[0], newargv); ci = errno; write(2, newargv[0], strlen(newargv[0])); write(2, ": ", 2); if (ci == ENOENT) write(2, enoent_msg, sizeof(enoent_msg) - 1); else if (strerror_r(ci, errbuf, sizeof(errbuf))) write(2, unkerr_msg, sizeof(unkerr_msg) - 1); else write(2, errbuf, strlen(errbuf)); write(2, "\n", 1); _exit(ci == ENOENT ? 127 : 126); } if (pid < 0) { memset(&act, 0, sizeof(act)); act.sa_handler = catchchild; act.sa_flags = SA_RESTART; sigaction(SIGCHLD, &act, NULL); perror("vfork failed"); return; } if (bg) { printf("[%d]\n", pid); return; } if (pid) { int cpid; status = 0; intcrlf = FALSE; for (;;) { cpid = wait4(pid, &status, 0, 0); if ((cpid < 0) && (errno == EINTR)) continue; if (cpid < 0) break; if (cpid != pid) { fprintf(stderr, "sh %d: child %d died\n", getpid(), cpid); continue; } } act.sa_handler = catchchild; memset(&act.sa_mask, 0, sizeof(act.sa_mask)); act.sa_flags = SA_RESTART; sigaction(SIGCHLD, &act, NULL); intcrlf = TRUE; if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) return; exit_code = WEXITSTATUS(status); } else exit_code = 1; return; } perror(argv[0]); exit(1); } void do_help(argc, argv) int argc; char **argv; { CMDTAB *cmdptr; for (cmdptr = cmdtab; cmdptr->name && cmdptr->name[0]; cmdptr++) printf("%-10s %s\n", cmdptr->name, cmdptr->usage); } /* * Look up an alias name, and return a pointer to it. * Returns NULL if the name does not exist. */ static ALIAS * findalias(name) char *name; { ALIAS *alias; int count; count = aliascount; for (alias = aliastable; count-- > 0; alias++) { if (strcmp(name, alias->name) == 0) return alias; } return NULL; } void do_source(argc, argv) int argc; char **argv; { readfile(argv[1]); } void do_pid(argc, argv) int argc; char **argv; { printf("%d\n", getpid()); } void do_exec(argc, argv) int argc; char **argv; { while (--sourcecount >= 0) { if (sourcefiles[sourcecount] != stdin) fclose(sourcefiles[sourcecount]); } argv[argc] = NULL; execvp(argv[1], &argv[1]); perror(argv[1]); exit(1); } /* * Display the prompt string. */ static void showprompt() { char *cp; char buf[60]; if ((cp = getenv("PS1")) != NULL) { printf("%s", cp); } else { *buf = '\0'; getcwd(buf, sizeof(buf) - 1); printf("%s> ", buf); } fflush(stdout); } static void catchint() { signal(SIGINT, catchint); intflag = TRUE; if (intcrlf) write(STDOUT, "\n", 1); } static void catchquit() { signal(SIGQUIT, catchquit); intflag = TRUE; if (intcrlf) write(STDOUT, "\n", 1); } static void catchchild() { char buf[40]; pid_t pid; int status; pid = wait4(-1, &status, WUNTRACED, 0); if (WIFSTOPPED(status)) sprintf(buf, "sh %d: Child %d stopped\n", getpid(), pid); else sprintf(buf, "sh %d: Child %d died\n", getpid(), pid); if (intcrlf) write(STDOUT, "\n", 1); write(STDOUT, buf, strlen(buf)); } /* END CODE */