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;
725 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
730 int pipefds[2]; /* pipefd[0] is for reading */
731 struct builtInCommand *x;
732 #ifdef BB_FEATURE_STANDALONE_SHELL
733 const struct BB_applet *a = applets;
737 nextin = 0, nextout = 1;
738 for (i = 0; i < newJob.numProgs; i++) {
739 if ((i + 1) < newJob.numProgs) {
741 nextout = pipefds[1];
746 if (!(newJob.progs[i].pid = fork())) {
747 signal(SIGTTOU, SIG_DFL);
759 /* explicit redirections override pipes */
760 setupRedirections(newJob.progs + i);
762 /* Match any built-ins here */
763 for (x = bltins; x->cmd; x++) {
764 if (!strcmp(newJob.progs[i].argv[0], x->cmd)) {
765 exit (x->function(&newJob, jobList));
768 #ifdef BB_FEATURE_STANDALONE_SHELL
769 /* Handle busybox internals here */
770 while (a->name != 0) {
771 if (strcmp(newJob.progs[i].argv[0], a->name) == 0) {
773 char** argv=newJob.progs[i].argv;
774 for(argc=0;*argv!=NULL; argv++, argc++);
775 exit((*(a->main)) (argc, newJob.progs[i].argv));
781 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
782 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
786 /* put our child in the process group whose leader is the
787 first process in this pipe */
788 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
795 /* If there isn't another process, nextin is garbage
796 but it doesn't matter */
800 newJob.pgrp = newJob.progs[0].pid;
802 /* find the ID for the job to use */
804 for (job = jobList->head; job; job = job->next)
805 if (job->jobId >= newJob.jobId)
806 newJob.jobId = job->jobId + 1;
808 /* add the job to the list of running jobs */
809 if (!jobList->head) {
810 job = jobList->head = malloc(sizeof(*job));
812 for (job = jobList->head; job->next; job = job->next);
813 job->next = malloc(sizeof(*job));
819 job->runningProgs = job->numProgs;
820 job->stoppedProgs = 0;
823 /* we don't wait for background jobs to return -- append it
824 to the list of backgrounded jobs and leave it alone */
825 printf("[%d] %d\n", job->jobId,
826 newJob.progs[newJob.numProgs - 1].pid);
830 /* move the new process group into the foreground */
831 if (tcsetpgrp(0, newJob.pgrp))
838 static int setupRedirections(struct childProgram *prog)
843 struct redirectionSpecifier *redir = prog->redirections;
845 for (i = 0; i < prog->numRedirections; i++, redir++) {
846 switch (redir->type) {
850 case REDIRECT_OVERWRITE:
851 mode = O_RDWR | O_CREAT | O_TRUNC;
853 case REDIRECT_APPEND:
854 mode = O_RDWR | O_CREAT | O_APPEND;
858 openfd = open(redir->filename, mode, 0666);
860 /* this could get lost if stderr has been redirected, but
861 bash and ash both lose it as well (though zsh doesn't!) */
862 fprintf(stderr, "error opening %s: %s\n", redir->filename,
867 if (openfd != redir->fd) {
868 dup2(openfd, redir->fd);
877 static int busy_loop(FILE * input)
880 char *nextCommand = NULL;
881 struct jobSet jobList = { NULL, NULL };
887 command = (char *) calloc(BUFSIZ, sizeof(char));
889 /* don't pay any attention to this signal; it just confuses
890 things and isn't really meant for shells anyway */
891 signal(SIGTTOU, SIG_IGN);
895 /* no job is in the foreground */
897 /* see if any background processes have exited */
901 if (getCommand(input, command))
903 nextCommand = command;
906 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
908 runCommand(newJob, &jobList, inBg);
911 /* a job is running in the foreground; wait for it */
913 while (!jobList.fg->progs[i].pid ||
914 jobList.fg->progs[i].isStopped) i++;
916 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
918 if (WIFEXITED(status) || WIFSIGNALED(status)) {
919 /* the child exited */
920 jobList.fg->runningProgs--;
921 jobList.fg->progs[i].pid = 0;
923 if (!jobList.fg->runningProgs) {
926 removeJob(&jobList, jobList.fg);
929 /* move the shell to the foreground */
930 if (tcsetpgrp(0, getpid()))
934 /* the child was stopped */
935 jobList.fg->stoppedProgs++;
936 jobList.fg->progs[i].isStopped = 1;
938 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
939 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
940 "Stopped", jobList.fg->text);
946 /* move the shell to the foreground */
947 if (tcsetpgrp(0, getpid()))
958 int shell_main(int argc, char **argv)
965 /* initialize the cwd */
966 getcwd(cwd, sizeof(cwd));
968 #ifdef BB_FEATURE_SH_COMMAND_EDITING
970 signal(SIGWINCH, win_changed);
974 //if (argv[0] && argv[0][0] == '-') {
975 // shell_source("/etc/profile");
979 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
980 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
983 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
985 input = fopen(argv[1], "r");
987 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
992 return (busy_loop(input));