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 {"exit", "Exit from shell()", "exit", shell_exit},
117 {"fg", "Bring job into the foreground", "fg [%%job]", shell_fg_bg},
118 {"jobs", "Lists the active jobs", "jobs", shell_jobs},
119 {"export", "Set environment variable", "export [VAR=value]", shell_export},
120 {"unset", "Unset environment variable", "unset VAR", shell_unset},
121 {NULL, NULL, NULL, NULL}
124 /* Table of built-in functions */
125 static struct builtInCommand bltins_forking[] = {
126 {"env", "Print all environment variables", "env", shell_env},
127 {"pwd", "Print current directory", "pwd", shell_pwd},
128 {".", "Source-in and run commands in a file", ". filename", shell_source},
129 {"help", "List shell built-in commands", "help", shell_help},
130 {NULL, NULL, NULL, NULL}
133 static const char shell_usage[] =
136 #ifndef BB_FEATURE_TRIVIAL_HELP
137 "\nlash: The BusyBox command interpreter (shell).\n\n"
141 static char cwd[1024];
142 static char *prompt = "# ";
144 #ifdef BB_FEATURE_SH_COMMAND_EDITING
145 void win_changed(int sig)
147 struct winsize win = { 0, 0 };
148 ioctl(0, TIOCGWINSZ, &win);
149 if (win.ws_col > 0) {
150 cmdedit_setwidth( win.ws_col - 1);
156 /* built-in 'cd <path>' handler */
157 static int shell_cd(struct job *cmd, struct jobSet *junk)
161 if (!cmd->progs[0].argv[1] == 1)
162 newdir = getenv("HOME");
164 newdir = cmd->progs[0].argv[1];
166 printf("cd: %s: %s\n", newdir, strerror(errno));
169 getcwd(cwd, sizeof(cwd));
174 /* built-in 'env' handler */
175 static int shell_env(struct job *dummy, struct jobSet *junk)
179 for (e = environ; *e; e++) {
180 fprintf(stdout, "%s\n", *e);
185 /* built-in 'exit' handler */
186 static int shell_exit(struct job *cmd, struct jobSet *junk)
188 if (!cmd->progs[0].argv[1] == 1)
191 return(atoi(cmd->progs[0].argv[1]));
194 /* built-in 'fg' and 'bg' handler */
195 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
198 struct job *job=NULL;
200 if (!jobList->head) {
201 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
202 fprintf(stderr, "%s: exactly one argument is expected\n",
203 cmd->progs[0].argv[0]);
206 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
207 fprintf(stderr, "%s: bad argument '%s'\n",
208 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
210 for (job = jobList->head; job; job = job->next) {
211 if (job->jobId == jobNum) {
221 fprintf(stderr, "%s: unknown job %d\n",
222 cmd->progs[0].argv[0], jobNum);
226 if (*cmd->progs[0].argv[0] == 'f') {
227 /* Make this job the foreground job */
228 if (tcsetpgrp(0, job->pgrp))
233 /* Restart the processes in the job */
234 for (i = 0; i < job->numProgs; i++)
235 job->progs[i].isStopped = 0;
237 kill(-job->pgrp, SIGCONT);
239 job->stoppedProgs = 0;
244 /* built-in 'help' handler */
245 static int shell_help(struct job *cmd, struct jobSet *junk)
247 struct builtInCommand *x;
249 fprintf(stdout, "\nBuilt-in commands:\n");
250 fprintf(stdout, "-------------------\n");
251 for (x = bltins; x->cmd; x++) {
252 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
254 for (x = bltins_forking; x->cmd; x++) {
255 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
257 fprintf(stdout, "\n\n");
261 /* built-in 'jobs' handler */
262 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
267 for (job = jobList->head; job; job = job->next) {
268 if (job->runningProgs == job->stoppedProgs)
269 statusString = "Stopped";
271 statusString = "Running";
273 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
279 /* built-in 'pwd' handler */
280 static int shell_pwd(struct job *dummy, struct jobSet *junk)
282 getcwd(cwd, sizeof(cwd));
283 fprintf(stdout, "%s\n", cwd);
287 /* built-in 'export VAR=value' handler */
288 static int shell_export(struct job *cmd, struct jobSet *junk)
292 if (!cmd->progs[0].argv[1] == 1) {
293 return (shell_env(cmd, junk));
295 res = putenv(cmd->progs[0].argv[1]);
297 fprintf(stdout, "export: %s\n", strerror(errno));
301 /* Built-in '.' handler (read-in and execute commands from file) */
302 static int shell_source(struct job *cmd, struct jobSet *junk)
307 if (!cmd->progs[0].argv[1] == 1)
310 input = fopen(cmd->progs[0].argv[1], "r");
312 fprintf(stdout, "Couldn't open file '%s'\n",
313 cmd->progs[0].argv[1]);
317 /* Now run the file */
318 status = busy_loop(input);
322 /* built-in 'unset VAR' handler */
323 static int shell_unset(struct job *cmd, struct jobSet *junk)
325 if (!cmd->progs[0].argv[1] == 1) {
326 fprintf(stdout, "unset: parameter required.\n");
329 unsetenv(cmd->progs[0].argv[1]);
333 /* free up all memory from a job */
334 static void freeJob(struct job *cmd)
338 for (i = 0; i < cmd->numProgs; i++) {
339 free(cmd->progs[i].argv);
340 if (cmd->progs[i].redirections)
341 free(cmd->progs[i].redirections);
342 if (cmd->progs[i].freeGlob)
343 globfree(&cmd->progs[i].globResult);
351 /* remove a job from the jobList */
352 static void removeJob(struct jobSet *jobList, struct job *job)
357 if (job == jobList->head) {
358 jobList->head = job->next;
360 prevJob = jobList->head;
361 while (prevJob->next != job)
362 prevJob = prevJob->next;
363 prevJob->next = job->next;
369 /* Checks to see if any background processes have exited -- if they
370 have, figure out why and see if a job has completed */
371 static void checkJobs(struct jobSet *jobList)
378 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
379 for (job = jobList->head; job; job = job->next) {
381 while (progNum < job->numProgs &&
382 job->progs[progNum].pid != childpid) progNum++;
383 if (progNum < job->numProgs)
387 if (WIFEXITED(status) || WIFSIGNALED(status)) {
390 job->progs[progNum].pid = 0;
392 if (!job->runningProgs) {
393 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
394 removeJob(jobList, job);
399 job->progs[progNum].isStopped = 1;
401 if (job->stoppedProgs == job->numProgs) {
402 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
408 if (childpid == -1 && errno != ECHILD)
412 static int getCommand(FILE * source, char *command)
414 if (source == stdin) {
415 #ifdef BB_FEATURE_SH_COMMAND_EDITING
418 len=fprintf(stdout, "%s %s", cwd, prompt);
420 promptStr=(char*)malloc(sizeof(char)*(len+1));
421 sprintf(promptStr, "%s %s", cwd, prompt);
422 cmdedit_read_input(promptStr, command);
426 fprintf(stdout, "%s %s", cwd, prompt);
431 if (!fgets(command, BUFSIZ - 2, source)) {
437 /* remove trailing newline */
438 command[strlen(command) - 1] = '\0';
443 static void globLastArgument(struct childProgram *prog, int *argcPtr,
447 int argcAlloced = *argcAllocedPtr;
453 if (argc > 1) { /* cmd->globResult is already initialized */
455 i = prog->globResult.gl_pathc;
462 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
463 if (rc == GLOB_NOSPACE) {
464 fprintf(stderr, "out of space during glob operation\n");
466 } else if (rc == GLOB_NOMATCH ||
467 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
468 !strcmp(prog->argv[argc - 1],
469 prog->globResult.gl_pathv[i]))) {
470 /* we need to remove whatever \ quoting is still present */
471 src = dst = prog->argv[argc - 1];
479 argcAlloced += (prog->globResult.gl_pathc - i);
481 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
482 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
483 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
484 argc += (prog->globResult.gl_pathc - i - 1);
487 *argcAllocedPtr = argcAlloced;
491 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
492 line). If a valid command is found, commandPtr is set to point to
493 the beginning of the next command (if the original command had more
494 then one job associated with it) or NULL if no more commands are
496 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
499 char *returnCommand = NULL;
500 char *src, *buf, *chptr;
507 struct childProgram *prog;
509 /* skip leading white space */
510 while (**commandPtr && isspace(**commandPtr))
513 /* this handles empty lines or leading '#' characters */
514 if (!**commandPtr || (**commandPtr == '#')) {
522 job->progs = malloc(sizeof(*job->progs));
524 /* We set the argv elements to point inside of this string. The
525 memory is freed by freeJob().
527 Getting clean memory relieves us of the task of NULL
528 terminating things and makes the rest of this look a bit
529 cleaner (though it is, admittedly, a tad less efficient) */
530 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
534 prog->numRedirections = 0;
535 prog->redirections = NULL;
540 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
541 prog->argv[0] = job->cmdBuf;
545 while (*src && !done) {
552 fprintf(stderr, "character expected after \\\n");
557 /* in shell, "\'" should yield \' */
560 } else if (*src == '*' || *src == '?' || *src == '[' ||
561 *src == ']') *buf++ = '\\';
563 } else if (isspace(*src)) {
564 if (*prog->argv[argc]) {
566 /* +1 here leaves room for the NULL which ends argv */
567 if ((argc + 1) == argvAlloced) {
569 prog->argv = realloc(prog->argv,
570 sizeof(*prog->argv) *
573 prog->argv[argc] = buf;
575 globLastArgument(prog, &argc, &argvAlloced);
584 case '#': /* comment */
588 case '>': /* redirections */
590 i = prog->numRedirections++;
591 prog->redirections = realloc(prog->redirections,
592 sizeof(*prog->redirections) *
595 prog->redirections[i].fd = -1;
596 if (buf != prog->argv[argc]) {
597 /* the stuff before this character may be the file number
599 prog->redirections[i].fd =
600 strtol(prog->argv[argc], &chptr, 10);
602 if (*chptr && *prog->argv[argc]) {
604 globLastArgument(prog, &argc, &argvAlloced);
608 if (prog->redirections[i].fd == -1) {
610 prog->redirections[i].fd = 1;
612 prog->redirections[i].fd = 0;
617 prog->redirections[i].type =
618 REDIRECT_APPEND, src++;
620 prog->redirections[i].type = REDIRECT_OVERWRITE;
622 prog->redirections[i].type = REDIRECT_INPUT;
625 /* This isn't POSIX sh compliant. Oh well. */
627 while (isspace(*chptr))
631 fprintf(stderr, "file name expected after %c\n", *src);
636 prog->redirections[i].filename = buf;
637 while (*chptr && !isspace(*chptr))
640 src = chptr - 1; /* we src++ later */
641 prog->argv[argc] = ++buf;
645 /* finish this command */
646 if (*prog->argv[argc])
649 fprintf(stderr, "empty command in pipe\n");
653 prog->argv[argc] = NULL;
655 /* and start the next */
657 job->progs = realloc(job->progs,
658 sizeof(*job->progs) * job->numProgs);
659 prog = job->progs + (job->numProgs - 1);
660 prog->numRedirections = 0;
661 prog->redirections = NULL;
666 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
667 prog->argv[0] = ++buf;
670 while (*src && isspace(*src))
674 fprintf(stderr, "empty command in pipe\n");
677 src--; /* we'll ++ it at the end of the loop */
681 case '&': /* background */
683 case ';': /* multiple commands */
685 returnCommand = *commandPtr + (src - *commandPtr) + 1;
692 fprintf(stderr, "character expected after \\\n");
695 if (*src == '*' || *src == '[' || *src == ']'
696 || *src == '?') *buf++ = '\\';
705 if (*prog->argv[argc]) {
707 globLastArgument(prog, &argc, &argvAlloced);
713 prog->argv[argc] = NULL;
715 if (!returnCommand) {
716 job->text = malloc(strlen(*commandPtr) + 1);
717 strcpy(job->text, *commandPtr);
719 /* This leaves any trailing spaces, which is a bit sloppy */
720 count = returnCommand - *commandPtr;
721 job->text = malloc(count + 1);
722 strncpy(job->text, *commandPtr, count);
723 job->text[count] = '\0';
726 *commandPtr = returnCommand;
732 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
737 int pipefds[2]; /* pipefd[0] is for reading */
738 struct builtInCommand *x;
739 #ifdef BB_FEATURE_STANDALONE_SHELL
740 const struct BB_applet *a = applets;
744 nextin = 0, nextout = 1;
745 for (i = 0; i < newJob.numProgs; i++) {
746 if ((i + 1) < newJob.numProgs) {
748 nextout = pipefds[1];
753 /* Match any built-ins here */
754 for (x = bltins; x->cmd; x++) {
755 if (!strcmp(newJob.progs[i].argv[0], x->cmd)) {
756 return (x->function(&newJob, jobList));
760 if (!(newJob.progs[i].pid = fork())) {
761 signal(SIGTTOU, SIG_DFL);
773 /* explicit redirections override pipes */
774 setupRedirections(newJob.progs + i);
776 /* Match any built-ins here */
777 for (x = bltins_forking; x->cmd; x++) {
778 if (!strcmp(newJob.progs[i].argv[0], x->cmd)) {
779 exit (x->function(&newJob, jobList));
782 #ifdef BB_FEATURE_STANDALONE_SHELL
783 /* Handle busybox internals here */
784 while (a->name != 0) {
785 if (strcmp(newJob.progs[i].argv[0], a->name) == 0) {
787 char** argv=newJob.progs[i].argv;
788 for(argc=0;*argv!=NULL; argv++, argc++);
789 exit((*(a->main)) (argc, newJob.progs[i].argv));
795 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
796 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
800 /* put our child in the process group whose leader is the
801 first process in this pipe */
802 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
809 /* If there isn't another process, nextin is garbage
810 but it doesn't matter */
814 newJob.pgrp = newJob.progs[0].pid;
816 /* find the ID for the job to use */
818 for (job = jobList->head; job; job = job->next)
819 if (job->jobId >= newJob.jobId)
820 newJob.jobId = job->jobId + 1;
822 /* add the job to the list of running jobs */
823 if (!jobList->head) {
824 job = jobList->head = malloc(sizeof(*job));
826 for (job = jobList->head; job->next; job = job->next);
827 job->next = malloc(sizeof(*job));
833 job->runningProgs = job->numProgs;
834 job->stoppedProgs = 0;
837 /* we don't wait for background jobs to return -- append it
838 to the list of backgrounded jobs and leave it alone */
839 printf("[%d] %d\n", job->jobId,
840 newJob.progs[newJob.numProgs - 1].pid);
844 /* move the new process group into the foreground */
845 if (tcsetpgrp(0, newJob.pgrp))
852 static int setupRedirections(struct childProgram *prog)
857 struct redirectionSpecifier *redir = prog->redirections;
859 for (i = 0; i < prog->numRedirections; i++, redir++) {
860 switch (redir->type) {
864 case REDIRECT_OVERWRITE:
865 mode = O_RDWR | O_CREAT | O_TRUNC;
867 case REDIRECT_APPEND:
868 mode = O_RDWR | O_CREAT | O_APPEND;
872 openfd = open(redir->filename, mode, 0666);
874 /* this could get lost if stderr has been redirected, but
875 bash and ash both lose it as well (though zsh doesn't!) */
876 fprintf(stderr, "error opening %s: %s\n", redir->filename,
881 if (openfd != redir->fd) {
882 dup2(openfd, redir->fd);
891 static int busy_loop(FILE * input)
894 char *nextCommand = NULL;
895 struct jobSet jobList = { NULL, NULL };
901 command = (char *) calloc(BUFSIZ, sizeof(char));
903 /* don't pay any attention to this signal; it just confuses
904 things and isn't really meant for shells anyway */
905 signal(SIGTTOU, SIG_IGN);
909 /* no job is in the foreground */
911 /* see if any background processes have exited */
915 if (getCommand(input, command))
917 nextCommand = command;
920 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
922 runCommand(newJob, &jobList, inBg);
925 /* a job is running in the foreground; wait for it */
927 while (!jobList.fg->progs[i].pid ||
928 jobList.fg->progs[i].isStopped) i++;
930 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
932 if (WIFEXITED(status) || WIFSIGNALED(status)) {
933 /* the child exited */
934 jobList.fg->runningProgs--;
935 jobList.fg->progs[i].pid = 0;
937 if (!jobList.fg->runningProgs) {
940 removeJob(&jobList, jobList.fg);
943 /* move the shell to the foreground */
944 if (tcsetpgrp(0, getpid()))
948 /* the child was stopped */
949 jobList.fg->stoppedProgs++;
950 jobList.fg->progs[i].isStopped = 1;
952 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
953 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
954 "Stopped", jobList.fg->text);
960 /* move the shell to the foreground */
961 if (tcsetpgrp(0, getpid()))
972 int shell_main(int argc, char **argv)
979 /* initialize the cwd */
980 getcwd(cwd, sizeof(cwd));
982 #ifdef BB_FEATURE_SH_COMMAND_EDITING
984 signal(SIGWINCH, win_changed);
988 //if (argv[0] && argv[0][0] == '-') {
989 // shell_source("/etc/profile");
993 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
994 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
997 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
999 input = fopen(argv[1], "r");
1001 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
1006 return (busy_loop(input));