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[] =
130 "sh [FILE]...\n\n" "The BusyBox command interpreter (shell).\n\n";
133 static char cwd[1024];
134 static char *prompt = "# ";
136 #ifdef BB_FEATURE_SH_COMMAND_EDITING
137 void win_changed(int sig)
139 struct winsize win = { 0, 0 };
140 ioctl(0, TIOCGWINSZ, &win);
141 if (win.ws_col > 0) {
142 cmdedit_setwidth( win.ws_col - 1);
148 /* built-in 'cd <path>' handler */
149 static int shell_cd(struct job *cmd, struct jobSet *junk)
153 if (!cmd->progs[0].argv[1] == 1)
154 newdir = getenv("HOME");
156 newdir = cmd->progs[0].argv[1];
158 printf("cd: %s: %s\n", newdir, strerror(errno));
161 getcwd(cwd, sizeof(cwd));
166 /* built-in 'env' handler */
167 static int shell_env(struct job *dummy, struct jobSet *junk)
171 for (e = environ; *e; e++) {
172 fprintf(stdout, "%s\n", *e);
177 /* built-in 'exit' handler */
178 static int shell_exit(struct job *cmd, struct jobSet *junk)
180 if (!cmd->progs[0].argv[1] == 1)
184 exit(atoi(cmd->progs[0].argv[1]));
187 /* built-in 'fg' and 'bg' handler */
188 static int shell_fg_bg(struct job *cmd, struct jobSet *jobList)
191 struct job *job=NULL;
193 if (!jobList->head) {
194 if (!cmd->progs[0].argv[1] || cmd->progs[0].argv[2]) {
195 fprintf(stderr, "%s: exactly one argument is expected\n",
196 cmd->progs[0].argv[0]);
199 if (sscanf(cmd->progs[0].argv[1], "%%%d", &jobNum) != 1) {
200 fprintf(stderr, "%s: bad argument '%s'\n",
201 cmd->progs[0].argv[0], cmd->progs[0].argv[1]);
203 for (job = jobList->head; job; job = job->next) {
204 if (job->jobId == jobNum) {
214 fprintf(stderr, "%s: unknown job %d\n",
215 cmd->progs[0].argv[0], jobNum);
219 if (*cmd->progs[0].argv[0] == 'f') {
220 /* Make this job the foreground job */
221 if (tcsetpgrp(0, job->pgrp))
226 /* Restart the processes in the job */
227 for (i = 0; i < job->numProgs; i++)
228 job->progs[i].isStopped = 0;
230 kill(-job->pgrp, SIGCONT);
232 job->stoppedProgs = 0;
237 /* built-in 'help' handler */
238 static int shell_help(struct job *cmd, struct jobSet *junk)
240 struct builtInCommand *x;
242 fprintf(stdout, "\nBuilt-in commands:\n");
243 fprintf(stdout, "-------------------\n");
244 for (x = bltins; x->cmd; x++) {
245 fprintf(stdout, "%s\t%s\n", x->cmd, x->descr);
247 fprintf(stdout, "\n\n");
251 /* built-in 'jobs' handler */
252 static int shell_jobs(struct job *dummy, struct jobSet *jobList)
257 for (job = jobList->head; job; job = job->next) {
258 if (job->runningProgs == job->stoppedProgs)
259 statusString = "Stopped";
261 statusString = "Running";
263 printf(JOB_STATUS_FORMAT, job->jobId, statusString, job->text);
269 /* built-in 'pwd' handler */
270 static int shell_pwd(struct job *dummy, struct jobSet *junk)
272 getcwd(cwd, sizeof(cwd));
273 fprintf(stdout, "%s\n", cwd);
277 /* built-in 'export VAR=value' handler */
278 static int shell_export(struct job *cmd, struct jobSet *junk)
282 if (!cmd->progs[0].argv[1] == 1) {
283 return (shell_env(cmd, junk));
285 res = putenv(cmd->progs[0].argv[1]);
287 fprintf(stdout, "export: %s\n", strerror(errno));
291 /* Built-in '.' handler (read-in and execute commands from file) */
292 static int shell_source(struct job *cmd, struct jobSet *junk)
297 if (!cmd->progs[0].argv[1] == 1)
300 input = fopen(cmd->progs[0].argv[1], "r");
302 fprintf(stdout, "Couldn't open file '%s'\n",
303 cmd->progs[0].argv[1]);
307 /* Now run the file */
308 status = busy_loop(input);
312 /* built-in 'unset VAR' handler */
313 static int shell_unset(struct job *cmd, struct jobSet *junk)
315 if (!cmd->progs[0].argv[1] == 1) {
316 fprintf(stdout, "unset: parameter required.\n");
319 unsetenv(cmd->progs[0].argv[1]);
323 /* free up all memory from a job */
324 static void freeJob(struct job *cmd)
328 for (i = 0; i < cmd->numProgs; i++) {
329 free(cmd->progs[i].argv);
330 if (cmd->progs[i].redirections)
331 free(cmd->progs[i].redirections);
332 if (cmd->progs[i].freeGlob)
333 globfree(&cmd->progs[i].globResult);
341 /* remove a job from the jobList */
342 static void removeJob(struct jobSet *jobList, struct job *job)
347 if (job == jobList->head) {
348 jobList->head = job->next;
350 prevJob = jobList->head;
351 while (prevJob->next != job)
352 prevJob = prevJob->next;
353 prevJob->next = job->next;
359 /* Checks to see if any background processes have exited -- if they
360 have, figure out why and see if a job has completed */
361 static void checkJobs(struct jobSet *jobList)
368 while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
369 for (job = jobList->head; job; job = job->next) {
371 while (progNum < job->numProgs &&
372 job->progs[progNum].pid != childpid) progNum++;
373 if (progNum < job->numProgs)
377 if (WIFEXITED(status) || WIFSIGNALED(status)) {
380 job->progs[progNum].pid = 0;
382 if (!job->runningProgs) {
383 printf(JOB_STATUS_FORMAT, job->jobId, "Done", job->text);
384 removeJob(jobList, job);
389 job->progs[progNum].isStopped = 1;
391 if (job->stoppedProgs == job->numProgs) {
392 printf(JOB_STATUS_FORMAT, job->jobId, "Stopped",
398 if (childpid == -1 && errno != ECHILD)
402 static int getCommand(FILE * source, char *command)
404 if (source == stdin) {
405 #ifdef BB_FEATURE_SH_COMMAND_EDITING
408 len=fprintf(stdout, "%s %s", cwd, prompt);
410 promptStr=(char*)malloc(sizeof(char)*(len+1));
411 sprintf(promptStr, "%s %s", cwd, prompt);
412 cmdedit_read_input(promptStr, command);
416 fprintf(stdout, "%s %s", cwd, prompt);
421 if (!fgets(command, BUFSIZ - 2, source)) {
427 /* remove trailing newline */
428 command[strlen(command) - 1] = '\0';
433 static void globLastArgument(struct childProgram *prog, int *argcPtr,
437 int argcAlloced = *argcAllocedPtr;
443 if (argc > 1) { /* cmd->globResult is already initialized */
445 i = prog->globResult.gl_pathc;
452 rc = glob(prog->argv[argc - 1], flags, NULL, &prog->globResult);
453 if (rc == GLOB_NOSPACE) {
454 fprintf(stderr, "out of space during glob operation\n");
456 } else if (rc == GLOB_NOMATCH ||
457 (!rc && (prog->globResult.gl_pathc - i) == 1 &&
458 !strcmp(prog->argv[argc - 1],
459 prog->globResult.gl_pathv[i]))) {
460 /* we need to remove whatever \ quoting is still present */
461 src = dst = prog->argv[argc - 1];
469 argcAlloced += (prog->globResult.gl_pathc - i);
471 realloc(prog->argv, argcAlloced * sizeof(*prog->argv));
472 memcpy(prog->argv + (argc - 1), prog->globResult.gl_pathv + i,
473 sizeof(*(prog->argv)) * (prog->globResult.gl_pathc - i));
474 argc += (prog->globResult.gl_pathc - i - 1);
477 *argcAllocedPtr = argcAlloced;
481 /* Return cmd->numProgs as 0 if no command is present (e.g. an empty
482 line). If a valid command is found, commandPtr is set to point to
483 the beginning of the next command (if the original command had more
484 then one job associated with it) or NULL if no more commands are
486 static int parseCommand(char **commandPtr, struct job *job, int *isBg)
489 char *returnCommand = NULL;
490 char *src, *buf, *chptr;
497 struct childProgram *prog;
499 /* skip leading white space */
500 while (**commandPtr && isspace(**commandPtr))
503 /* this handles empty lines or leading '#' characters */
504 if (!**commandPtr || (**commandPtr == '#')) {
512 job->progs = malloc(sizeof(*job->progs));
514 /* We set the argv elements to point inside of this string. The
515 memory is freed by freeJob().
517 Getting clean memory relieves us of the task of NULL
518 terminating things and makes the rest of this look a bit
519 cleaner (though it is, admittedly, a tad less efficient) */
520 job->cmdBuf = command = calloc(1, strlen(*commandPtr) + 1);
524 prog->numRedirections = 0;
525 prog->redirections = NULL;
530 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
531 prog->argv[0] = job->cmdBuf;
535 while (*src && !done) {
542 fprintf(stderr, "character expected after \\\n");
547 /* in shell, "\'" should yield \' */
550 } else if (*src == '*' || *src == '?' || *src == '[' ||
551 *src == ']') *buf++ = '\\';
553 } else if (isspace(*src)) {
554 if (*prog->argv[argc]) {
556 /* +1 here leaves room for the NULL which ends argv */
557 if ((argc + 1) == argvAlloced) {
559 prog->argv = realloc(prog->argv,
560 sizeof(*prog->argv) *
563 prog->argv[argc] = buf;
565 globLastArgument(prog, &argc, &argvAlloced);
574 case '#': /* comment */
578 case '>': /* redirections */
580 i = prog->numRedirections++;
581 prog->redirections = realloc(prog->redirections,
582 sizeof(*prog->redirections) *
585 prog->redirections[i].fd = -1;
586 if (buf != prog->argv[argc]) {
587 /* the stuff before this character may be the file number
589 prog->redirections[i].fd =
590 strtol(prog->argv[argc], &chptr, 10);
592 if (*chptr && *prog->argv[argc]) {
594 globLastArgument(prog, &argc, &argvAlloced);
598 if (prog->redirections[i].fd == -1) {
600 prog->redirections[i].fd = 1;
602 prog->redirections[i].fd = 0;
607 prog->redirections[i].type =
608 REDIRECT_APPEND, src++;
610 prog->redirections[i].type = REDIRECT_OVERWRITE;
612 prog->redirections[i].type = REDIRECT_INPUT;
615 /* This isn't POSIX sh compliant. Oh well. */
617 while (isspace(*chptr))
621 fprintf(stderr, "file name expected after %c\n", *src);
626 prog->redirections[i].filename = buf;
627 while (*chptr && !isspace(*chptr))
630 src = chptr - 1; /* we src++ later */
631 prog->argv[argc] = ++buf;
635 /* finish this command */
636 if (*prog->argv[argc])
639 fprintf(stderr, "empty command in pipe\n");
643 prog->argv[argc] = NULL;
645 /* and start the next */
647 job->progs = realloc(job->progs,
648 sizeof(*job->progs) * job->numProgs);
649 prog = job->progs + (job->numProgs - 1);
650 prog->numRedirections = 0;
651 prog->redirections = NULL;
656 prog->argv = malloc(sizeof(*prog->argv) * argvAlloced);
657 prog->argv[0] = ++buf;
660 while (*src && isspace(*src))
664 fprintf(stderr, "empty command in pipe\n");
667 src--; /* we'll ++ it at the end of the loop */
671 case '&': /* background */
673 case ';': /* multiple commands */
675 returnCommand = *commandPtr + (src - *commandPtr) + 1;
682 fprintf(stderr, "character expected after \\\n");
685 if (*src == '*' || *src == '[' || *src == ']'
686 || *src == '?') *buf++ = '\\';
695 if (*prog->argv[argc]) {
697 globLastArgument(prog, &argc, &argvAlloced);
703 prog->argv[argc] = NULL;
705 if (!returnCommand) {
706 job->text = malloc(strlen(*commandPtr) + 1);
707 strcpy(job->text, *commandPtr);
709 /* This leaves any trailing spaces, which is a bit sloppy */
710 count = returnCommand - *commandPtr;
711 job->text = malloc(count + 1);
712 strncpy(job->text, *commandPtr, count);
713 job->text[count] = '\0';
716 *commandPtr = returnCommand;
721 static int runCommand(struct job newJob, struct jobSet *jobList, int inBg)
726 int pipefds[2]; /* pipefd[0] is for reading */
727 struct builtInCommand *x;
729 /* handle built-ins here -- we don't fork() so we can't background
731 for (x = bltins; x->cmd; x++) {
732 if (!strcmp(newJob.progs[0].argv[0], x->cmd)) {
733 return (x->function(&newJob, jobList));
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 execvp(newJob.progs[i].argv[0], newJob.progs[i].argv);
763 fatalError("sh: %s: %s\n", newJob.progs[i].argv[0],
767 /* put our child in the process group whose leader is the
768 first process in this pipe */
769 setpgid(newJob.progs[i].pid, newJob.progs[0].pid);
776 /* If there isn't another process, nextin is garbage
777 but it doesn't matter */
781 newJob.pgrp = newJob.progs[0].pid;
783 /* find the ID for the job to use */
785 for (job = jobList->head; job; job = job->next)
786 if (job->jobId >= newJob.jobId)
787 newJob.jobId = job->jobId + 1;
789 /* add the job to the list of running jobs */
790 if (!jobList->head) {
791 job = jobList->head = malloc(sizeof(*job));
793 for (job = jobList->head; job->next; job = job->next);
794 job->next = malloc(sizeof(*job));
800 job->runningProgs = job->numProgs;
801 job->stoppedProgs = 0;
804 /* we don't wait for background jobs to return -- append it
805 to the list of backgrounded jobs and leave it alone */
806 printf("[%d] %d\n", job->jobId,
807 newJob.progs[newJob.numProgs - 1].pid);
811 /* move the new process group into the foreground */
812 if (tcsetpgrp(0, newJob.pgrp))
819 static int setupRedirections(struct childProgram *prog)
824 struct redirectionSpecifier *redir = prog->redirections;
826 for (i = 0; i < prog->numRedirections; i++, redir++) {
827 switch (redir->type) {
831 case REDIRECT_OVERWRITE:
832 mode = O_RDWR | O_CREAT | O_TRUNC;
834 case REDIRECT_APPEND:
835 mode = O_RDWR | O_CREAT | O_APPEND;
839 openfd = open(redir->filename, mode, 0666);
841 /* this could get lost if stderr has been redirected, but
842 bash and ash both lose it as well (though zsh doesn't!) */
843 fprintf(stderr, "error opening %s: %s\n", redir->filename,
848 if (openfd != redir->fd) {
849 dup2(openfd, redir->fd);
858 static int busy_loop(FILE * input)
861 char *nextCommand = NULL;
862 struct jobSet jobList = { NULL, NULL };
868 command = (char *) calloc(BUFSIZ, sizeof(char));
870 /* don't pay any attention to this signal; it just confuses
871 things and isn't really meant for shells anyway */
872 signal(SIGTTOU, SIG_IGN);
876 /* no job is in the foreground */
878 /* see if any background processes have exited */
882 if (getCommand(input, command))
884 nextCommand = command;
887 if (!parseCommand(&nextCommand, &newJob, &inBg) &&
889 runCommand(newJob, &jobList, inBg);
892 /* a job is running in the foreground; wait for it */
894 while (!jobList.fg->progs[i].pid ||
895 jobList.fg->progs[i].isStopped) i++;
897 waitpid(jobList.fg->progs[i].pid, &status, WUNTRACED);
899 if (WIFEXITED(status) || WIFSIGNALED(status)) {
900 /* the child exited */
901 jobList.fg->runningProgs--;
902 jobList.fg->progs[i].pid = 0;
904 if (!jobList.fg->runningProgs) {
907 removeJob(&jobList, jobList.fg);
910 /* move the shell to the foreground */
911 if (tcsetpgrp(0, getpid()))
915 /* the child was stopped */
916 jobList.fg->stoppedProgs++;
917 jobList.fg->progs[i].isStopped = 1;
919 if (jobList.fg->stoppedProgs == jobList.fg->runningProgs) {
920 printf("\n" JOB_STATUS_FORMAT, jobList.fg->jobId,
921 "Stopped", jobList.fg->text);
927 /* move the shell to the foreground */
928 if (tcsetpgrp(0, getpid()))
939 int shell_main(int argc, char **argv)
946 /* initialize the cwd */
947 getcwd(cwd, sizeof(cwd));
949 #ifdef BB_FEATURE_SH_COMMAND_EDITING
950 signal(SIGWINCH, win_changed);
954 //if (argv[0] && argv[0][0] == '-') {
955 // shell_source("/etc/profile");
959 fprintf(stdout, "\n\nBusyBox v%s (%s) Built-in shell\n", BB_VER, BB_BT);
960 fprintf(stdout, "Enter 'help' for a list of built-in commands.\n\n");
963 usage("sh\n\nlash -- the BusyBox LAme SHell (command interpreter)\n");
965 input = fopen(argv[1], "r");
967 fatalError("sh: Couldn't open file '%s': %s\n", argv[1],
972 return (busy_loop(input));