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)
192 exit(atoi(cmd->progs[0].argv[1]));
195 /* built-in 'fg' and 'bg' handler */
196 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
199 struct job *job=NULL;
201 if (!jobList->head) {
202 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
203 fprintf(stderr, "%s: exactly one argument is expected\n",
204 cmd->progs[0].argv[0]);
207 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
208 fprintf(stderr, "%s: bad argument '%s'\n",
209 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
211 for (job = jobList->head; job; job = job->next) {
212 if (job->jobId == jobNum) {
222 fprintf(stderr, "%s: unknown job %d\n",
223 cmd->progs[0].argv[0], jobNum);
227 if (*cmd->progs[0].argv[0] == 'f') {
228 /* Make this job the foreground job */
229 if (tcsetpgrp(0, job->pgrp))
234 /* Restart the processes in the job */
235 for (i = 0; i < job->numProgs; i++)
236 job->progs[i].isStopped = 0;
238 kill(-job->pgrp, SIGCONT);
240 job->stoppedProgs = 0;
245 /* built-in 'help' handler */
246 static int shell_help(struct job *cmd, struct jobSet *junk)
248 struct builtInCommand *x;
250 fprintf(stdout, "\nBuilt-in commands:\n");
251 fprintf(stdout, "-------------------\n");
252 for (x = bltins; x->cmd; x++) {
253 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
255 for (x = bltins_forking; x->cmd; x++) {
256 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
258 fprintf(stdout, "\n\n");
262 /* built-in 'jobs' handler */
263 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
268 for (job = jobList->head; job; job = job->next) {
269 if (job->runningProgs == job->stoppedProgs)
270 statusString = "Stopped";
272 statusString = "Running";
274 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
280 /* built-in 'pwd' handler */
281 static int shell_pwd(struct job *dummy, struct jobSet *junk)
283 getcwd(cwd, sizeof(cwd));
284 fprintf(stdout, "%s\n", cwd);
288 /* built-in 'export VAR=value' handler */
289 static int shell_export(struct job *cmd, struct jobSet *junk)
293 if (!cmd->progs[0].argv[1] == 1) {
294 return (shell_env(cmd, junk));
296 res = putenv(cmd->progs[0].argv[1]);
298 fprintf(stdout, "export: %s\n", strerror(errno));
302 /* Built-in '.' handler (read-in and execute commands from file) */
303 static int shell_source(struct job *cmd, struct jobSet *junk)
308 if (!cmd->progs[0].argv[1] == 1)
311 input = fopen(cmd->progs[0].argv[1], "r");
313 fprintf(stdout, "Couldn't open file '%s'\n",
314 cmd->progs[0].argv[1]);
318 /* Now run the file */
319 status = busy_loop(input);
323 /* built-in 'unset VAR' handler */
324 static int shell_unset(struct job *cmd, struct jobSet *junk)
326 if (!cmd->progs[0].argv[1] == 1) {
327 fprintf(stdout, "unset: parameter required.\n");
330 unsetenv(cmd->progs[0].argv[1]);
334 /* free up all memory from a job */
335 static void freeJob(struct job *cmd)
339 for (i = 0; i < cmd->numProgs; i++) {
340 free(cmd->progs[i].argv);
341 if (cmd->progs[i].redirections)
342 free(cmd->progs[i].redirections);
343 if (cmd->progs[i].freeGlob)
344 globfree(&cmd->progs[i].globResult);
352 /* remove a job from the jobList */
353 static void removeJob(struct jobSet *jobList, struct job *job)
358 if (job == jobList->head) {
359 jobList->head = job->next;
361 prevJob = jobList->head;
362 while (prevJob->next != job)
363 prevJob = prevJob->next;
364 prevJob->next = job->next;
370 /* Checks to see if any background processes have exited -- if they
371 have, figure out why and see if a job has completed */
372 static void checkJobs(struct jobSet *jobList)
379 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
380 for (job = jobList->head; job; job = job->next) {
382 while (progNum < job->numProgs &&
383 job->progs[progNum].pid != childpid) progNum++;
384 if (progNum < job->numProgs)
388 if (WIFEXITED(status) || WIFSIGNALED(status)) {
391 job->progs[progNum].pid = 0;
393 if (!job->runningProgs) {
394 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
395 removeJob(jobList, job);
400 job->progs[progNum].isStopped = 1;
402 if (job->stoppedProgs == job->numProgs) {
403 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
409 if (childpid == -1 && errno != ECHILD)
413 static int getCommand(FILE * source, char *command)
415 if (source == stdin) {
416 #ifdef BB_FEATURE_SH_COMMAND_EDITING
419 len=fprintf(stdout, "%s %s", cwd, prompt);
421 promptStr=(char*)malloc(sizeof(char)*(len+1));
422 sprintf(promptStr, "%s %s", cwd, prompt);
423 cmdedit_read_input(promptStr, command);
427 fprintf(stdout, "%s %s", cwd, prompt);
432 if (!fgets(command, BUFSIZ - 2, source)) {
438 /* remove trailing newline */
439 command[strlen(command) - 1] = '\0';
444 static void globLastArgument(struct childProgram *prog, int *argcPtr,
448 int argcAlloced = *argcAllocedPtr;
454 if (argc > 1) { /* cmd->globResult is already initialized */
456 i = prog->globResult.gl_pathc;
463 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
464 if (rc == GLOB_NOSPACE) {
465 fprintf(stderr, "out of space during glob operation\n");
467 } else if (rc == GLOB_NOMATCH ||
468 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
469 !strcmp(prog->argv[argc - 1],
470 prog->globResult.gl_pathv[i]))) {
471 /* we need to remove whatever \ quoting is still present */
472 src = dst = prog->argv[argc - 1];
480 argcAlloced += (prog->globResult.gl_pathc - i);
482 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
483 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
484 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
485 argc += (prog->globResult.gl_pathc - i - 1);
488 *argcAllocedPtr = argcAlloced;
492 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
493 line). If a valid command is found, commandPtr is set to point to
494 the beginning of the next command (if the original command had more
495 then one job associated with it) or NULL if no more commands are
497 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
500 char *returnCommand = NULL;
501 char *src, *buf, *chptr;
508 struct childProgram *prog;
510 /* skip leading white space */
511 while (**commandPtr && isspace(**commandPtr))
514 /* this handles empty lines or leading '#' characters */
515 if (!**commandPtr || (**commandPtr == '#')) {
523 job->progs = malloc(sizeof(*job->progs));
525 /* We set the argv elements to point inside of this string. The
526 memory is freed by freeJob().
528 Getting clean memory relieves us of the task of NULL
529 terminating things and makes the rest of this look a bit
530 cleaner (though it is, admittedly, a tad less efficient) */
531 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
535 prog->numRedirections = 0;
536 prog->redirections = NULL;
541 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
542 prog->argv[0] = job->cmdBuf;
546 while (*src && !done) {
553 fprintf(stderr, "character expected after \\\n");
558 /* in shell, "\'" should yield \' */
561 } else if (*src == '*' || *src == '?' || *src == '[' ||
562 *src == ']') *buf++ = '\\';
564 } else if (isspace(*src)) {
565 if (*prog->argv[argc]) {
567 /* +1 here leaves room for the NULL which ends argv */
568 if ((argc + 1) == argvAlloced) {
570 prog->argv = realloc(prog->argv,
571 sizeof(*prog->argv) *
574 prog->argv[argc] = buf;
576 globLastArgument(prog, &argc, &argvAlloced);
585 case '#': /* comment */
589 case '>': /* redirections */
591 i = prog->numRedirections++;
592 prog->redirections = realloc(prog->redirections,
593 sizeof(*prog->redirections) *
596 prog->redirections[i].fd = -1;
597 if (buf != prog->argv[argc]) {
598 /* the stuff before this character may be the file number
600 prog->redirections[i].fd =
601 strtol(prog->argv[argc], &chptr, 10);
603 if (*chptr && *prog->argv[argc]) {
605 globLastArgument(prog, &argc, &argvAlloced);
609 if (prog->redirections[i].fd == -1) {
611 prog->redirections[i].fd = 1;
613 prog->redirections[i].fd = 0;
618 prog->redirections[i].type =
619 REDIRECT_APPEND, src++;
621 prog->redirections[i].type = REDIRECT_OVERWRITE;
623 prog->redirections[i].type = REDIRECT_INPUT;
626 /* This isn't POSIX sh compliant. Oh well. */
628 while (isspace(*chptr))
632 fprintf(stderr, "file name expected after %c\n", *src);
637 prog->redirections[i].filename = buf;
638 while (*chptr && !isspace(*chptr))
641 src = chptr - 1; /* we src++ later */
642 prog->argv[argc] = ++buf;
646 /* finish this command */
647 if (*prog->argv[argc])
650 fprintf(stderr, "empty command in pipe\n");
654 prog->argv[argc] = NULL;
656 /* and start the next */
658 job->progs = realloc(job->progs,
659 sizeof(*job->progs) * job->numProgs);
660 prog = job->progs + (job->numProgs - 1);
661 prog->numRedirections = 0;
662 prog->redirections = NULL;
667 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
668 prog->argv[0] = ++buf;
671 while (*src && isspace(*src))
675 fprintf(stderr, "empty command in pipe\n");
678 src--; /* we'll ++ it at the end of the loop */
682 case '&': /* background */
684 case ';': /* multiple commands */
686 returnCommand = *commandPtr + (src - *commandPtr) + 1;
693 fprintf(stderr, "character expected after \\\n");
696 if (*src == '*' || *src == '[' || *src == ']'
697 || *src == '?') *buf++ = '\\';
706 if (*prog->argv[argc]) {
708 globLastArgument(prog, &argc, &argvAlloced);
714 prog->argv[argc] = NULL;
716 if (!returnCommand) {
717 job->text = malloc(strlen(*commandPtr) + 1);
718 strcpy(job->text, *commandPtr);
720 /* This leaves any trailing spaces, which is a bit sloppy */
721 count = returnCommand - *commandPtr;
722 job->text = malloc(count + 1);
723 strncpy(job->text, *commandPtr, count);
724 job->text[count] = '\0';
727 *commandPtr = returnCommand;
733 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
738 int pipefds[2]; /* pipefd[0] is for reading */
739 struct builtInCommand *x;
740 #ifdef BB_FEATURE_STANDALONE_SHELL
741 const struct BB_applet *a = applets;
745 nextin = 0, nextout = 1;
746 for (i = 0; i < newJob.numProgs; i++) {
747 if ((i + 1) < newJob.numProgs) {
749 nextout = pipefds[1];
754 /* Match any built-ins here */
755 for (x = bltins; x->cmd; x++) {
756 if (!strcmp(newJob.progs[i].argv[0], x->cmd)) {
757 return (x->function(&newJob, jobList));
761 if (!(newJob.progs[i].pid = fork())) {
762 signal(SIGTTOU, SIG_DFL);
774 /* explicit redirections override pipes */
775 setupRedirections(newJob.progs + i);
777 /* Match any built-ins here */
778 for (x = bltins_forking; x->cmd; x++) {
779 if (!strcmp(newJob.progs[i].argv[0], x->cmd)) {
780 exit (x->function(&newJob, jobList));
783 #ifdef BB_FEATURE_STANDALONE_SHELL
784 /* Handle busybox internals here */
785 while (a->name != 0) {
786 if (strcmp(newJob.progs[i].argv[0], a->name) == 0) {
788 char** argv=newJob.progs[i].argv;
789 for(argc=0;*argv!=NULL; argv++, argc++);
790 exit((*(a->main)) (argc, newJob.progs[i].argv));
796 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
797 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
801 /* put our child in the process group whose leader is the
802 first process in this pipe */
803 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
810 /* If there isn't another process, nextin is garbage
811 but it doesn't matter */
815 newJob.pgrp = newJob.progs[0].pid;
817 /* find the ID for the job to use */
819 for (job = jobList->head; job; job = job->next)
820 if (job->jobId >= newJob.jobId)
821 newJob.jobId = job->jobId + 1;
823 /* add the job to the list of running jobs */
824 if (!jobList->head) {
825 job = jobList->head = malloc(sizeof(*job));
827 for (job = jobList->head; job->next; job = job->next);
828 job->next = malloc(sizeof(*job));
834 job->runningProgs = job->numProgs;
835 job->stoppedProgs = 0;
838 /* we don't wait for background jobs to return -- append it
839 to the list of backgrounded jobs and leave it alone */
840 printf("[%d] %d\n", job->jobId,
841 newJob.progs[newJob.numProgs - 1].pid);
845 /* move the new process group into the foreground */
846 if (tcsetpgrp(0, newJob.pgrp))
853 static int setupRedirections(struct childProgram *prog)
858 struct redirectionSpecifier *redir = prog->redirections;
860 for (i = 0; i < prog->numRedirections; i++, redir++) {
861 switch (redir->type) {
865 case REDIRECT_OVERWRITE:
866 mode = O_RDWR | O_CREAT | O_TRUNC;
868 case REDIRECT_APPEND:
869 mode = O_RDWR | O_CREAT | O_APPEND;
873 openfd = open(redir->filename, mode, 0666);
875 /* this could get lost if stderr has been redirected, but
876 bash and ash both lose it as well (though zsh doesn't!) */
877 fprintf(stderr, "error opening %s: %s\n", redir->filename,
882 if (openfd != redir->fd) {
883 dup2(openfd, redir->fd);
892 static int busy_loop(FILE * input)
895 char *nextCommand = NULL;
896 struct jobSet jobList = { NULL, NULL };
902 command = (char *) calloc(BUFSIZ, sizeof(char));
904 /* don't pay any attention to this signal; it just confuses
905 things and isn't really meant for shells anyway */
906 signal(SIGTTOU, SIG_IGN);
910 /* no job is in the foreground */
912 /* see if any background processes have exited */
916 if (getCommand(input, command))
918 nextCommand = command;
921 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
923 runCommand(newJob, &jobList, inBg);
926 /* a job is running in the foreground; wait for it */
928 while (!jobList.fg->progs[i].pid ||
929 jobList.fg->progs[i].isStopped) i++;
931 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
933 if (WIFEXITED(status) || WIFSIGNALED(status)) {
934 /* the child exited */
935 jobList.fg->runningProgs--;
936 jobList.fg->progs[i].pid = 0;
938 if (!jobList.fg->runningProgs) {
941 removeJob(&jobList, jobList.fg);
944 /* move the shell to the foreground */
945 if (tcsetpgrp(0, getpid()))
949 /* the child was stopped */
950 jobList.fg->stoppedProgs++;
951 jobList.fg->progs[i].isStopped = 1;
953 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
954 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
955 "Stopped", jobList.fg->text);
961 /* move the shell to the foreground */
962 if (tcsetpgrp(0, getpid()))
973 int shell_main(int argc, char **argv)
980 /* initialize the cwd */
981 getcwd(cwd, sizeof(cwd));
983 #ifdef BB_FEATURE_SH_COMMAND_EDITING
985 signal(SIGWINCH, win_changed);
989 //if (argv[0] && argv[0][0] == '-') {
990 // shell_source("/etc/profile");
994 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
995 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
998 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
1000 input = fopen(argv[1], "r");
1002 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
1007 return (busy_loop(input));