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 {"env", "Print all environment variables", "env", shell_env},
117 {"exit", "Exit from shell()", "exit", shell_exit},
118 {"fg", "Bring job into the foreground", "fg [%%job]", shell_fg_bg},
119 {"jobs", "Lists the active jobs", "jobs", shell_jobs},
120 {"pwd", "Print current directory", "pwd", shell_pwd},
121 {"export", "Set environment variable", "export [VAR=value]", shell_export},
122 {"unset", "Unset environment variable", "unset VAR", shell_unset},
123 {".", "Source-in and run commands in a file", ". filename", shell_source},
124 {"help", "List shell built-in commands", "help", shell_help},
125 {NULL, NULL, NULL, NULL}
128 static const char shell_usage[] =
131 #ifndef BB_FEATURE_TRIVIAL_HELP
132 "\nlash: The BusyBox command interpreter (shell).\n\n"
136 static char cwd[1024];
137 static char *prompt = "# ";
139 #ifdef BB_FEATURE_SH_COMMAND_EDITING
140 void win_changed(int sig)
142 struct winsize win = { 0, 0 };
143 ioctl(0, TIOCGWINSZ, &win);
144 if (win.ws_col > 0) {
145 cmdedit_setwidth( win.ws_col - 1);
151 /* built-in 'cd <path>' handler */
152 static int shell_cd(struct job *cmd, struct jobSet *junk)
156 if (!cmd->progs[0].argv[1] == 1)
157 newdir = getenv("HOME");
159 newdir = cmd->progs[0].argv[1];
161 printf("cd: %s: %s\n", newdir, strerror(errno));
164 getcwd(cwd, sizeof(cwd));
169 /* built-in 'env' handler */
170 static int shell_env(struct job *dummy, struct jobSet *junk)
174 for (e = environ; *e; e++) {
175 fprintf(stdout, "%s\n", *e);
180 /* built-in 'exit' handler */
181 static int shell_exit(struct job *cmd, struct jobSet *junk)
183 if (!cmd->progs[0].argv[1] == 1)
187 exit(atoi(cmd->progs[0].argv[1]));
190 /* built-in 'fg' and 'bg' handler */
191 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
194 struct job *job=NULL;
196 if (!jobList->head) {
197 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
198 fprintf(stderr, "%s: exactly one argument is expected\n",
199 cmd->progs[0].argv[0]);
202 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
203 fprintf(stderr, "%s: bad argument '%s'\n",
204 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
206 for (job = jobList->head; job; job = job->next) {
207 if (job->jobId == jobNum) {
217 fprintf(stderr, "%s: unknown job %d\n",
218 cmd->progs[0].argv[0], jobNum);
222 if (*cmd->progs[0].argv[0] == 'f') {
223 /* Make this job the foreground job */
224 if (tcsetpgrp(0, job->pgrp))
229 /* Restart the processes in the job */
230 for (i = 0; i < job->numProgs; i++)
231 job->progs[i].isStopped = 0;
233 kill(-job->pgrp, SIGCONT);
235 job->stoppedProgs = 0;
240 /* built-in 'help' handler */
241 static int shell_help(struct job *cmd, struct jobSet *junk)
243 struct builtInCommand *x;
245 fprintf(stdout, "\nBuilt-in commands:\n");
246 fprintf(stdout, "-------------------\n");
247 for (x = bltins; x->cmd; x++) {
248 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
250 fprintf(stdout, "\n\n");
254 /* built-in 'jobs' handler */
255 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
260 for (job = jobList->head; job; job = job->next) {
261 if (job->runningProgs == job->stoppedProgs)
262 statusString = "Stopped";
264 statusString = "Running";
266 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
272 /* built-in 'pwd' handler */
273 static int shell_pwd(struct job *dummy, struct jobSet *junk)
275 getcwd(cwd, sizeof(cwd));
276 fprintf(stdout, "%s\n", cwd);
280 /* built-in 'export VAR=value' handler */
281 static int shell_export(struct job *cmd, struct jobSet *junk)
285 if (!cmd->progs[0].argv[1] == 1) {
286 return (shell_env(cmd, junk));
288 res = putenv(cmd->progs[0].argv[1]);
290 fprintf(stdout, "export: %s\n", strerror(errno));
294 /* Built-in '.' handler (read-in and execute commands from file) */
295 static int shell_source(struct job *cmd, struct jobSet *junk)
300 if (!cmd->progs[0].argv[1] == 1)
303 input = fopen(cmd->progs[0].argv[1], "r");
305 fprintf(stdout, "Couldn't open file '%s'\n",
306 cmd->progs[0].argv[1]);
310 /* Now run the file */
311 status = busy_loop(input);
315 /* built-in 'unset VAR' handler */
316 static int shell_unset(struct job *cmd, struct jobSet *junk)
318 if (!cmd->progs[0].argv[1] == 1) {
319 fprintf(stdout, "unset: parameter required.\n");
322 unsetenv(cmd->progs[0].argv[1]);
326 /* free up all memory from a job */
327 static void freeJob(struct job *cmd)
331 for (i = 0; i < cmd->numProgs; i++) {
332 free(cmd->progs[i].argv);
333 if (cmd->progs[i].redirections)
334 free(cmd->progs[i].redirections);
335 if (cmd->progs[i].freeGlob)
336 globfree(&cmd->progs[i].globResult);
344 /* remove a job from the jobList */
345 static void removeJob(struct jobSet *jobList, struct job *job)
350 if (job == jobList->head) {
351 jobList->head = job->next;
353 prevJob = jobList->head;
354 while (prevJob->next != job)
355 prevJob = prevJob->next;
356 prevJob->next = job->next;
362 /* Checks to see if any background processes have exited -- if they
363 have, figure out why and see if a job has completed */
364 static void checkJobs(struct jobSet *jobList)
371 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
372 for (job = jobList->head; job; job = job->next) {
374 while (progNum < job->numProgs &&
375 job->progs[progNum].pid != childpid) progNum++;
376 if (progNum < job->numProgs)
380 if (WIFEXITED(status) || WIFSIGNALED(status)) {
383 job->progs[progNum].pid = 0;
385 if (!job->runningProgs) {
386 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
387 removeJob(jobList, job);
392 job->progs[progNum].isStopped = 1;
394 if (job->stoppedProgs == job->numProgs) {
395 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
401 if (childpid == -1 && errno != ECHILD)
405 static int getCommand(FILE * source, char *command)
407 if (source == stdin) {
408 #ifdef BB_FEATURE_SH_COMMAND_EDITING
411 len=fprintf(stdout, "%s %s", cwd, prompt);
413 promptStr=(char*)malloc(sizeof(char)*(len+1));
414 sprintf(promptStr, "%s %s", cwd, prompt);
415 cmdedit_read_input(promptStr, command);
419 fprintf(stdout, "%s %s", cwd, prompt);
424 if (!fgets(command, BUFSIZ - 2, source)) {
430 /* remove trailing newline */
431 command[strlen(command) - 1] = '\0';
436 static void globLastArgument(struct childProgram *prog, int *argcPtr,
440 int argcAlloced = *argcAllocedPtr;
446 if (argc > 1) { /* cmd->globResult is already initialized */
448 i = prog->globResult.gl_pathc;
455 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
456 if (rc == GLOB_NOSPACE) {
457 fprintf(stderr, "out of space during glob operation\n");
459 } else if (rc == GLOB_NOMATCH ||
460 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
461 !strcmp(prog->argv[argc - 1],
462 prog->globResult.gl_pathv[i]))) {
463 /* we need to remove whatever \ quoting is still present */
464 src = dst = prog->argv[argc - 1];
472 argcAlloced += (prog->globResult.gl_pathc - i);
474 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
475 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
476 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
477 argc += (prog->globResult.gl_pathc - i - 1);
480 *argcAllocedPtr = argcAlloced;
484 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
485 line). If a valid command is found, commandPtr is set to point to
486 the beginning of the next command (if the original command had more
487 then one job associated with it) or NULL if no more commands are
489 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
492 char *returnCommand = NULL;
493 char *src, *buf, *chptr;
500 struct childProgram *prog;
502 /* skip leading white space */
503 while (**commandPtr && isspace(**commandPtr))
506 /* this handles empty lines or leading '#' characters */
507 if (!**commandPtr || (**commandPtr == '#')) {
515 job->progs = malloc(sizeof(*job->progs));
517 /* We set the argv elements to point inside of this string. The
518 memory is freed by freeJob().
520 Getting clean memory relieves us of the task of NULL
521 terminating things and makes the rest of this look a bit
522 cleaner (though it is, admittedly, a tad less efficient) */
523 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
527 prog->numRedirections = 0;
528 prog->redirections = NULL;
533 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
534 prog->argv[0] = job->cmdBuf;
538 while (*src && !done) {
545 fprintf(stderr, "character expected after \\\n");
550 /* in shell, "\'" should yield \' */
553 } else if (*src == '*' || *src == '?' || *src == '[' ||
554 *src == ']') *buf++ = '\\';
556 } else if (isspace(*src)) {
557 if (*prog->argv[argc]) {
559 /* +1 here leaves room for the NULL which ends argv */
560 if ((argc + 1) == argvAlloced) {
562 prog->argv = realloc(prog->argv,
563 sizeof(*prog->argv) *
566 prog->argv[argc] = buf;
568 globLastArgument(prog, &argc, &argvAlloced);
577 case '#': /* comment */
581 case '>': /* redirections */
583 i = prog->numRedirections++;
584 prog->redirections = realloc(prog->redirections,
585 sizeof(*prog->redirections) *
588 prog->redirections[i].fd = -1;
589 if (buf != prog->argv[argc]) {
590 /* the stuff before this character may be the file number
592 prog->redirections[i].fd =
593 strtol(prog->argv[argc], &chptr, 10);
595 if (*chptr && *prog->argv[argc]) {
597 globLastArgument(prog, &argc, &argvAlloced);
601 if (prog->redirections[i].fd == -1) {
603 prog->redirections[i].fd = 1;
605 prog->redirections[i].fd = 0;
610 prog->redirections[i].type =
611 REDIRECT_APPEND, src++;
613 prog->redirections[i].type = REDIRECT_OVERWRITE;
615 prog->redirections[i].type = REDIRECT_INPUT;
618 /* This isn't POSIX sh compliant. Oh well. */
620 while (isspace(*chptr))
624 fprintf(stderr, "file name expected after %c\n", *src);
629 prog->redirections[i].filename = buf;
630 while (*chptr && !isspace(*chptr))
633 src = chptr - 1; /* we src++ later */
634 prog->argv[argc] = ++buf;
638 /* finish this command */
639 if (*prog->argv[argc])
642 fprintf(stderr, "empty command in pipe\n");
646 prog->argv[argc] = NULL;
648 /* and start the next */
650 job->progs = realloc(job->progs,
651 sizeof(*job->progs) * job->numProgs);
652 prog = job->progs + (job->numProgs - 1);
653 prog->numRedirections = 0;
654 prog->redirections = NULL;
659 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
660 prog->argv[0] = ++buf;
663 while (*src && isspace(*src))
667 fprintf(stderr, "empty command in pipe\n");
670 src--; /* we'll ++ it at the end of the loop */
674 case '&': /* background */
676 case ';': /* multiple commands */
678 returnCommand = *commandPtr + (src - *commandPtr) + 1;
685 fprintf(stderr, "character expected after \\\n");
688 if (*src == '*' || *src == '[' || *src == ']'
689 || *src == '?') *buf++ = '\\';
698 if (*prog->argv[argc]) {
700 globLastArgument(prog, &argc, &argvAlloced);
706 prog->argv[argc] = NULL;
708 if (!returnCommand) {
709 job->text = malloc(strlen(*commandPtr) + 1);
710 strcpy(job->text, *commandPtr);
712 /* This leaves any trailing spaces, which is a bit sloppy */
713 count = returnCommand - *commandPtr;
714 job->text = malloc(count + 1);
715 strncpy(job->text, *commandPtr, count);
716 job->text[count] = '\0';
719 *commandPtr = returnCommand;
724 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
729 int pipefds[2]; /* pipefd[0] is for reading */
730 struct builtInCommand *x;
732 /* handle built-ins here -- we don't fork() so we can't background
734 for (x = bltins; x->cmd; x++) {
735 if (!strcmp(newJob.progs[0].argv[0], x->cmd)) {
736 return (x->function(&newJob, jobList));
740 nextin = 0, nextout = 1;
741 for (i = 0; i < newJob.numProgs; i++) {
742 if ((i + 1) < newJob.numProgs) {
744 nextout = pipefds[1];
749 if (!(newJob.progs[i].pid = fork())) {
750 signal(SIGTTOU, SIG_DFL);
762 /* explicit redirections override pipes */
763 setupRedirections(newJob.progs + i);
765 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
766 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
770 /* put our child in the process group whose leader is the
771 first process in this pipe */
772 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
779 /* If there isn't another process, nextin is garbage
780 but it doesn't matter */
784 newJob.pgrp = newJob.progs[0].pid;
786 /* find the ID for the job to use */
788 for (job = jobList->head; job; job = job->next)
789 if (job->jobId >= newJob.jobId)
790 newJob.jobId = job->jobId + 1;
792 /* add the job to the list of running jobs */
793 if (!jobList->head) {
794 job = jobList->head = malloc(sizeof(*job));
796 for (job = jobList->head; job->next; job = job->next);
797 job->next = malloc(sizeof(*job));
803 job->runningProgs = job->numProgs;
804 job->stoppedProgs = 0;
807 /* we don't wait for background jobs to return -- append it
808 to the list of backgrounded jobs and leave it alone */
809 printf("[%d] %d\n", job->jobId,
810 newJob.progs[newJob.numProgs - 1].pid);
814 /* move the new process group into the foreground */
815 if (tcsetpgrp(0, newJob.pgrp))
822 static int setupRedirections(struct childProgram *prog)
827 struct redirectionSpecifier *redir = prog->redirections;
829 for (i = 0; i < prog->numRedirections; i++, redir++) {
830 switch (redir->type) {
834 case REDIRECT_OVERWRITE:
835 mode = O_RDWR | O_CREAT | O_TRUNC;
837 case REDIRECT_APPEND:
838 mode = O_RDWR | O_CREAT | O_APPEND;
842 openfd = open(redir->filename, mode, 0666);
844 /* this could get lost if stderr has been redirected, but
845 bash and ash both lose it as well (though zsh doesn't!) */
846 fprintf(stderr, "error opening %s: %s\n", redir->filename,
851 if (openfd != redir->fd) {
852 dup2(openfd, redir->fd);
861 static int busy_loop(FILE * input)
864 char *nextCommand = NULL;
865 struct jobSet jobList = { NULL, NULL };
871 command = (char *) calloc(BUFSIZ, sizeof(char));
873 /* don't pay any attention to this signal; it just confuses
874 things and isn't really meant for shells anyway */
875 signal(SIGTTOU, SIG_IGN);
879 /* no job is in the foreground */
881 /* see if any background processes have exited */
885 if (getCommand(input, command))
887 nextCommand = command;
890 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
892 runCommand(newJob, &jobList, inBg);
895 /* a job is running in the foreground; wait for it */
897 while (!jobList.fg->progs[i].pid ||
898 jobList.fg->progs[i].isStopped) i++;
900 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
902 if (WIFEXITED(status) || WIFSIGNALED(status)) {
903 /* the child exited */
904 jobList.fg->runningProgs--;
905 jobList.fg->progs[i].pid = 0;
907 if (!jobList.fg->runningProgs) {
910 removeJob(&jobList, jobList.fg);
913 /* move the shell to the foreground */
914 if (tcsetpgrp(0, getpid()))
918 /* the child was stopped */
919 jobList.fg->stoppedProgs++;
920 jobList.fg->progs[i].isStopped = 1;
922 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
923 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
924 "Stopped", jobList.fg->text);
930 /* move the shell to the foreground */
931 if (tcsetpgrp(0, getpid()))
942 int shell_main(int argc, char **argv)
949 /* initialize the cwd */
950 getcwd(cwd, sizeof(cwd));
952 #ifdef BB_FEATURE_SH_COMMAND_EDITING
954 signal(SIGWINCH, win_changed);
958 //if (argv[0] && argv[0][0] == '-') {
959 // shell_source("/etc/profile");
963 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
964 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
967 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
969 input = fopen(argv[1], "r");
971 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
976 return (busy_loop(input));