1 /* vi: set sw=4 ts=4: */
3 * lash -- the BusyBox Lame-Ass SHell
5 * Copyright (C) 2000 by Lineo, inc.
6 * Written by Erik Andersen <andersen@lineo.com>, <andersee@debian.org>
8 * Based in part on ladsh.c by Michael K. Johnson and Erik W. Troan, which is
9 * under the following liberal license: "We have placed this source code in the
10 * public domain. Use it in any project, free or commercial."
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
37 #include <sys/ioctl.h>
40 #ifdef BB_FEATURE_SH_COMMAND_EDITING
45 #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
48 enum redirectionType { REDIRECT_INPUT, REDIRECT_OVERWRITE,
53 struct job *head; /* head of list of running jobs */
54 struct job *fg; /* current foreground job */
57 struct redirectionSpecifier {
58 enum redirectionType type; /* type of redirection */
59 int fd; /* file descriptor being redirected */
60 char *filename; /* file to redirect fd to */
64 pid_t pid; /* 0 if exited */
65 char **argv; /* program name and arguments */
66 int numRedirections; /* elements in redirection array */
67 struct redirectionSpecifier *redirections; /* I/O redirections */
68 glob_t globResult; /* result of parameter globbing */
69 int freeGlob; /* should we globfree(&globResult)? */
70 int isStopped; /* is the program currently running? */
74 int jobId; /* job number */
75 int numProgs; /* total number of programs in job */
76 int runningProgs; /* number of programs running */
77 char *text; /* name of job */
78 char *cmdBuf; /* buffer various argv's point into */
79 pid_t pgrp; /* process group ID for the job */
80 struct childProgram *progs; /* array of programs in job */
81 struct job *next; /* to track background commands */
82 int stoppedProgs; /* number of programs alive, but stopped */
85 struct builtInCommand {
87 char *descr; /* description */
88 char *usage; /* usage */
89 int (*function) (struct job *, struct jobSet * jobList); /* function ptr */
92 /* Some function prototypes */
93 static int shell_cd(struct job *cmd, struct jobSet *junk);
94 static int shell_env(struct job *dummy, struct jobSet *junk);
95 static int shell_exit(struct job *cmd, struct jobSet *junk);
96 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList);
97 static int shell_help(struct job *cmd, struct jobSet *junk);
98 static int shell_jobs(struct job *dummy, struct jobSet *jobList);
99 static int shell_pwd(struct job *dummy, struct jobSet *junk);
100 static int shell_export(struct job *cmd, struct jobSet *junk);
101 static int shell_source(struct job *cmd, struct jobSet *jobList);
102 static int shell_unset(struct job *cmd, struct jobSet *junk);
104 static void checkJobs(struct jobSet *jobList);
105 static int getCommand(FILE * source, char *command);
106 static int parseCommand(char **commandPtr, struct job *job, int *isBg);
107 static int setupRedirections(struct childProgram *prog);
108 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg);
109 static int busy_loop(FILE * input);
112 /* Table of built-in functions */
113 static struct builtInCommand bltins[] = {
114 {"bg", "Resume a job in the background", "bg [%%job]", shell_fg_bg},
115 {"cd", "Change working directory", "cd [dir]", shell_cd},
116 //{"echo", "Echo arguments on stdout", "echo arg1 [...]", shell_echo},
117 {"env", "Print all environment variables", "env", shell_env},
118 {"exit", "Exit from shell()", "exit", shell_exit},
119 {"fg", "Bring job into the foreground", "fg [%%job]", shell_fg_bg},
120 {"jobs", "Lists the active jobs", "jobs", shell_jobs},
121 {"pwd", "Print current directory", "pwd", shell_pwd},
122 {"export", "Set environment variable", "export [VAR=value]", shell_export},
123 {"unset", "Unset environment variable", "unset VAR", shell_unset},
125 {".", "Source-in and run commands in a file", ". filename", shell_source},
126 {"help", "List shell built-in commands", "help", shell_help},
127 {NULL, NULL, NULL, NULL}
130 static const char shell_usage[] =
132 "sh [FILE]...\n\n" "The BusyBox command interpreter (shell).\n\n";
135 static char cwd[1024];
136 static char *prompt = "# ";
138 #ifdef BB_FEATURE_SH_COMMAND_EDITING
139 void win_changed(int sig)
141 struct winsize win = { 0, 0 };
142 ioctl(0, TIOCGWINSZ, &win);
143 if (win.ws_col > 0) {
144 cmdedit_setwidth( win.ws_col - 1);
150 /* built-in 'cd <path>' handler */
151 static int shell_cd(struct job *cmd, struct jobSet *junk)
155 if (!cmd->progs[0].argv[1] == 1)
156 newdir = getenv("HOME");
158 newdir = cmd->progs[0].argv[1];
160 printf("cd: %s: %s\n", newdir, strerror(errno));
163 getcwd(cwd, sizeof(cwd));
168 /* built-in 'env' handler */
169 static int shell_env(struct job *dummy, struct jobSet *junk)
173 for (e = environ; *e; e++) {
174 fprintf(stdout, "%s\n", *e);
179 /* built-in 'exit' handler */
180 static int shell_exit(struct job *cmd, struct jobSet *junk)
182 if (!cmd->progs[0].argv[1] == 1)
186 exit(atoi(cmd->progs[0].argv[1]));
189 /* built-in 'fg' and 'bg' handler */
190 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
193 struct job *job=NULL;
195 if (!jobList->head) {
196 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
197 fprintf(stderr, "%s: exactly one argument is expected\n",
198 cmd->progs[0].argv[0]);
201 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
202 fprintf(stderr, "%s: bad argument '%s'\n",
203 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
205 for (job = jobList->head; job; job = job->next) {
206 if (job->jobId == jobNum) {
216 fprintf(stderr, "%s: unknown job %d\n",
217 cmd->progs[0].argv[0], jobNum);
221 if (*cmd->progs[0].argv[0] == 'f') {
222 /* Make this job the foreground job */
223 if (tcsetpgrp(0, job->pgrp))
228 /* Restart the processes in the job */
229 for (i = 0; i < job->numProgs; i++)
230 job->progs[i].isStopped = 0;
232 kill(-job->pgrp, SIGCONT);
234 job->stoppedProgs = 0;
239 /* built-in 'help' handler */
240 static int shell_help(struct job *cmd, struct jobSet *junk)
242 struct builtInCommand *x;
244 fprintf(stdout, "\nBuilt-in commands:\n");
245 fprintf(stdout, "-------------------\n");
246 for (x = bltins; x->cmd; x++) {
247 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
249 fprintf(stdout, "\n\n");
253 /* built-in 'jobs' handler */
254 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
259 for (job = jobList->head; job; job = job->next) {
260 if (job->runningProgs == job->stoppedProgs)
261 statusString = "Stopped";
263 statusString = "Running";
265 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
271 /* built-in 'pwd' handler */
272 static int shell_pwd(struct job *dummy, struct jobSet *junk)
274 getcwd(cwd, sizeof(cwd));
275 fprintf(stdout, "%s\n", cwd);
279 /* built-in 'export VAR=value' handler */
280 static int shell_export(struct job *cmd, struct jobSet *junk)
284 if (!cmd->progs[0].argv[1] == 1) {
285 return (shell_env(cmd, junk));
287 res = putenv(cmd->progs[0].argv[1]);
289 fprintf(stdout, "export: %s\n", strerror(errno));
293 /* Built-in '.' handler (read-in and execute commands from file) */
294 static int shell_source(struct job *cmd, struct jobSet *junk)
299 if (!cmd->progs[0].argv[1] == 1)
302 input = fopen(cmd->progs[0].argv[1], "r");
304 fprintf(stdout, "Couldn't open file '%s'\n",
305 cmd->progs[0].argv[1]);
309 /* Now run the file */
310 status = busy_loop(input);
314 /* built-in 'unset VAR' handler */
315 static int shell_unset(struct job *cmd, struct jobSet *junk)
317 if (!cmd->progs[0].argv[1] == 1) {
318 fprintf(stdout, "unset: parameter required.\n");
321 unsetenv(cmd->progs[0].argv[1]);
325 /* free up all memory from a job */
326 static void freeJob(struct job *cmd)
330 for (i = 0; i < cmd->numProgs; i++) {
331 free(cmd->progs[i].argv);
332 if (cmd->progs[i].redirections)
333 free(cmd->progs[i].redirections);
334 if (cmd->progs[i].freeGlob)
335 globfree(&cmd->progs[i].globResult);
343 /* remove a job from the jobList */
344 static void removeJob(struct jobSet *jobList, struct job *job)
349 if (job == jobList->head) {
350 jobList->head = job->next;
352 prevJob = jobList->head;
353 while (prevJob->next != job)
354 prevJob = prevJob->next;
355 prevJob->next = job->next;
361 /* Checks to see if any background processes have exited -- if they
362 have, figure out why and see if a job has completed */
363 static void checkJobs(struct jobSet *jobList)
370 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
371 for (job = jobList->head; job; job = job->next) {
373 while (progNum < job->numProgs &&
374 job->progs[progNum].pid != childpid) progNum++;
375 if (progNum < job->numProgs)
379 if (WIFEXITED(status) || WIFSIGNALED(status)) {
382 job->progs[progNum].pid = 0;
384 if (!job->runningProgs) {
385 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
386 removeJob(jobList, job);
391 job->progs[progNum].isStopped = 1;
393 if (job->stoppedProgs == job->numProgs) {
394 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
400 if (childpid == -1 && errno != ECHILD)
404 static int getCommand(FILE * source, char *command)
406 if (source == stdin) {
407 #ifdef BB_FEATURE_SH_COMMAND_EDITING
410 len=fprintf(stdout, "%s %s", cwd, prompt);
412 promptStr=(char*)malloc(sizeof(char)*(len+1));
413 sprintf(promptStr, "%s %s", cwd, prompt);
414 cmdedit_read_input(promptStr, command);
418 fprintf(stdout, "%s %s", cwd, prompt);
423 if (!fgets(command, BUFSIZ - 2, source)) {
429 /* remove trailing newline */
430 command[strlen(command) - 1] = '\0';
435 static void globLastArgument(struct childProgram *prog, int *argcPtr,
439 int argcAlloced = *argcAllocedPtr;
445 if (argc > 1) { /* cmd->globResult is already initialized */
447 i = prog->globResult.gl_pathc;
454 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
455 if (rc == GLOB_NOSPACE) {
456 fprintf(stderr, "out of space during glob operation\n");
458 } else if (rc == GLOB_NOMATCH ||
459 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
460 !strcmp(prog->argv[argc - 1],
461 prog->globResult.gl_pathv[i]))) {
462 /* we need to remove whatever \ quoting is still present */
463 src = dst = prog->argv[argc - 1];
471 argcAlloced += (prog->globResult.gl_pathc - i);
473 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
474 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
475 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
476 argc += (prog->globResult.gl_pathc - i - 1);
479 *argcAllocedPtr = argcAlloced;
483 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
484 line). If a valid command is found, commandPtr is set to point to
485 the beginning of the next command (if the original command had more
486 then one job associated with it) or NULL if no more commands are
488 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
491 char *returnCommand = NULL;
492 char *src, *buf, *chptr;
499 struct childProgram *prog;
501 /* skip leading white space */
502 while (**commandPtr && isspace(**commandPtr))
505 /* this handles empty lines or leading '#' characters */
506 if (!**commandPtr || (**commandPtr == '#')) {
514 job->progs = malloc(sizeof(*job->progs));
516 /* We set the argv elements to point inside of this string. The
517 memory is freed by freeJob().
519 Getting clean memory relieves us of the task of NULL
520 terminating things and makes the rest of this look a bit
521 cleaner (though it is, admittedly, a tad less efficient) */
522 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
526 prog->numRedirections = 0;
527 prog->redirections = NULL;
532 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
533 prog->argv[0] = job->cmdBuf;
537 while (*src && !done) {
544 fprintf(stderr, "character expected after \\\n");
549 /* in shell, "\'" should yield \' */
552 } else if (*src == '*' || *src == '?' || *src == '[' ||
553 *src == ']') *buf++ = '\\';
555 } else if (isspace(*src)) {
556 if (*prog->argv[argc]) {
558 /* +1 here leaves room for the NULL which ends argv */
559 if ((argc + 1) == argvAlloced) {
561 prog->argv = realloc(prog->argv,
562 sizeof(*prog->argv) *
565 prog->argv[argc] = buf;
567 globLastArgument(prog, &argc, &argvAlloced);
576 case '#': /* comment */
580 case '>': /* redirections */
582 i = prog->numRedirections++;
583 prog->redirections = realloc(prog->redirections,
584 sizeof(*prog->redirections) *
587 prog->redirections[i].fd = -1;
588 if (buf != prog->argv[argc]) {
589 /* the stuff before this character may be the file number
591 prog->redirections[i].fd =
592 strtol(prog->argv[argc], &chptr, 10);
594 if (*chptr && *prog->argv[argc]) {
596 globLastArgument(prog, &argc, &argvAlloced);
600 if (prog->redirections[i].fd == -1) {
602 prog->redirections[i].fd = 1;
604 prog->redirections[i].fd = 0;
609 prog->redirections[i].type =
610 REDIRECT_APPEND, src++;
612 prog->redirections[i].type = REDIRECT_OVERWRITE;
614 prog->redirections[i].type = REDIRECT_INPUT;
617 /* This isn't POSIX sh compliant. Oh well. */
619 while (isspace(*chptr))
623 fprintf(stderr, "file name expected after %c\n", *src);
628 prog->redirections[i].filename = buf;
629 while (*chptr && !isspace(*chptr))
632 src = chptr - 1; /* we src++ later */
633 prog->argv[argc] = ++buf;
637 /* finish this command */
638 if (*prog->argv[argc])
641 fprintf(stderr, "empty command in pipe\n");
645 prog->argv[argc] = NULL;
647 /* and start the next */
649 job->progs = realloc(job->progs,
650 sizeof(*job->progs) * job->numProgs);
651 prog = job->progs + (job->numProgs - 1);
652 prog->numRedirections = 0;
653 prog->redirections = NULL;
658 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
659 prog->argv[0] = ++buf;
662 while (*src && isspace(*src))
666 fprintf(stderr, "empty command in pipe\n");
669 src--; /* we'll ++ it at the end of the loop */
673 case '&': /* background */
675 case ';': /* multiple commands */
677 returnCommand = *commandPtr + (src - *commandPtr) + 1;
684 fprintf(stderr, "character expected after \\\n");
687 if (*src == '*' || *src == '[' || *src == ']'
688 || *src == '?') *buf++ = '\\';
697 if (*prog->argv[argc]) {
699 globLastArgument(prog, &argc, &argvAlloced);
705 prog->argv[argc] = NULL;
707 if (!returnCommand) {
708 job->text = malloc(strlen(*commandPtr) + 1);
709 strcpy(job->text, *commandPtr);
711 /* This leaves any trailing spaces, which is a bit sloppy */
712 count = returnCommand - *commandPtr;
713 job->text = malloc(count + 1);
714 strncpy(job->text, *commandPtr, count);
715 job->text[count] = '\0';
718 *commandPtr = returnCommand;
723 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
728 int pipefds[2]; /* pipefd[0] is for reading */
729 struct builtInCommand *x;
731 /* handle built-ins here -- we don't fork() so we can't background
733 for (x = bltins; x->cmd; x++) {
734 if (!strcmp(newJob.progs[0].argv[0], x->cmd)) {
735 return (x->function(&newJob, jobList));
739 nextin = 0, nextout = 1;
740 for (i = 0; i < newJob.numProgs; i++) {
741 if ((i + 1) < newJob.numProgs) {
743 nextout = pipefds[1];
748 if (!(newJob.progs[i].pid = fork())) {
749 signal(SIGTTOU, SIG_DFL);
761 /* explicit redirections override pipes */
762 setupRedirections(newJob.progs + i);
764 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
765 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
769 /* put our child in the process group whose leader is the
770 first process in this pipe */
771 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
778 /* If there isn't another process, nextin is garbage
779 but it doesn't matter */
783 newJob.pgrp = newJob.progs[0].pid;
785 /* find the ID for the job to use */
787 for (job = jobList->head; job; job = job->next)
788 if (job->jobId >= newJob.jobId)
789 newJob.jobId = job->jobId + 1;
791 /* add the job to the list of running jobs */
792 if (!jobList->head) {
793 job = jobList->head = malloc(sizeof(*job));
795 for (job = jobList->head; job->next; job = job->next);
796 job->next = malloc(sizeof(*job));
802 job->runningProgs = job->numProgs;
803 job->stoppedProgs = 0;
806 /* we don't wait for background jobs to return -- append it
807 to the list of backgrounded jobs and leave it alone */
808 printf("[%d] %d\n", job->jobId,
809 newJob.progs[newJob.numProgs - 1].pid);
813 /* move the new process group into the foreground */
814 if (tcsetpgrp(0, newJob.pgrp))
821 static int setupRedirections(struct childProgram *prog)
826 struct redirectionSpecifier *redir = prog->redirections;
828 for (i = 0; i < prog->numRedirections; i++, redir++) {
829 switch (redir->type) {
833 case REDIRECT_OVERWRITE:
834 mode = O_RDWR | O_CREAT | O_TRUNC;
836 case REDIRECT_APPEND:
837 mode = O_RDWR | O_CREAT | O_APPEND;
841 openfd = open(redir->filename, mode, 0666);
843 /* this could get lost if stderr has been redirected, but
844 bash and ash both lose it as well (though zsh doesn't!) */
845 fprintf(stderr, "error opening %s: %s\n", redir->filename,
850 if (openfd != redir->fd) {
851 dup2(openfd, redir->fd);
860 static int busy_loop(FILE * input)
863 char *nextCommand = NULL;
864 struct jobSet jobList = { NULL, NULL };
870 command = (char *) calloc(BUFSIZ, sizeof(char));
872 /* don't pay any attention to this signal; it just confuses
873 things and isn't really meant for shells anyway */
874 signal(SIGTTOU, SIG_IGN);
878 /* no job is in the foreground */
880 /* see if any background processes have exited */
884 if (getCommand(input, command))
886 nextCommand = command;
889 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
891 runCommand(newJob, &jobList, inBg);
894 /* a job is running in the foreground; wait for it */
896 while (!jobList.fg->progs[i].pid ||
897 jobList.fg->progs[i].isStopped) i++;
899 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
901 if (WIFEXITED(status) || WIFSIGNALED(status)) {
902 /* the child exited */
903 jobList.fg->runningProgs--;
904 jobList.fg->progs[i].pid = 0;
906 if (!jobList.fg->runningProgs) {
909 removeJob(&jobList, jobList.fg);
912 /* move the shell to the foreground */
913 if (tcsetpgrp(0, getpid()))
917 /* the child was stopped */
918 jobList.fg->stoppedProgs++;
919 jobList.fg->progs[i].isStopped = 1;
921 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
922 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
923 "Stopped", jobList.fg->text);
929 /* move the shell to the foreground */
930 if (tcsetpgrp(0, getpid()))
941 int shell_main(int argc, char **argv)
948 /* initialize the cwd */
949 getcwd(cwd, sizeof(cwd));
951 #ifdef BB_FEATURE_SH_COMMAND_EDITING
952 signal(SIGWINCH, win_changed);
956 //if (argv[0] && argv[0][0] == '-') {
957 // shell_source("/etc/profile");
961 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
962 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
964 input = fopen(argv[1], "r");
966 fatalError("A: Couldn't open file '%s': %s\n", argv[1],
971 return (busy_loop(input));