* Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
*/
-// Handle embedded NUL bytes in the command line.
+// A section of code that gets repeatedly or conditionally executed is stored
+// as a string and parsed each time it's run.
+
+
+
+// Wheee, debugging.
+
+// Terminal control
+#define ENABLE_BBSH_TTY 0
+
+// &, fg, bg, jobs. (ctrl-z with tty.)
+#define ENABLE_BBSH_JOBCTL 0
+
+// Flow control (if, while, for, functions { })
+#define ENABLE_BBSH_FLOWCTL 0
+
+#define ENABLE_BBSH_ENVVARS 0 // Environment variable support
+
+// Local and synthetic variables, fancy prompts, set, $?, etc.
+#define ENABLE_BBSH_LOCALVARS 0
+
+// Pipes and redirects: | > < >> << && || & () ;
+#define ENABLE_BBSH_PIPES 0
+
+/* Fun:
+
+ echo `echo hello#comment " woot` and more
+*/
#include <busybox.h>
-static int handle(char *command)
+// A single executable, its arguments, and other information we know about it.
+#define BBSH_FLAG_EXIT 1
+#define BBSH_FLAG_SUSPEND 2
+#define BBSH_FLAG_PIPE 4
+#define BBSH_FLAG_AND 8
+#define BBSH_FLAG_OR 16
+#define BBSH_FLAG_AMP 32
+#define BBSH_FLAG_SEMI 64
+#define BBSH_FLAG_PAREN 128
+
+// What we know about a single process.
+struct command {
+ struct command *next;
+ int flags; // exit, suspend, && ||
+ int pid; // pid (or exit code)
+ int argc;
+ char *argv[0];
+};
+
+// A collection of processes piped into/waiting on each other.
+struct pipeline {
+ struct pipeline *next;
+ int job_id;
+ struct command *cmd;
+ char *cmdline;
+ int cmdlinelen;
+};
+
+static void free_list(void *list, void (*freeit)(void *data))
+{
+ while(list) {
+ void **next = (void **)list;
+ void *list_next = *next;
+ freeit(list);
+ free(list);
+ list = list_next;
+ }
+}
+
+// Parse one word from the command line, appending one or more argv[] entries
+// to struct command. Handles environment variable substitution and
+// substrings. Returns pointer to next used byte, or NULL if it
+// hit an ending token.
+static char *parse_word(char *start, struct command **cmd)
+{
+ char *end;
+
+ // Detect end of line (and truncate line at comment)
+ if (ENABLE_BBSH_PIPES && strchr("><&|(;", *start)) return 0;
+
+ // Grab next word. (Add dequote and envvar logic here)
+ end = start;
+ while (*end && !isspace(*end)) end++;
+ (*cmd)->argv[(*cmd)->argc++] = xstrndup(start, end-start);
+
+ // Allocate more space if there's no room for NULL terminator.
+
+ if (!((*cmd)->argc & 7))
+ *cmd = xrealloc(*cmd,
+ sizeof(struct command) + ((*cmd)->argc+8)*sizeof(char *));
+ (*cmd)->argv[(*cmd)->argc] = 0;
+ return end;
+}
+
+// Parse a line of text into a pipeline.
+// Returns a pointer to the next line.
+
+static char *parse_pipeline(char *cmdline, struct pipeline *pipe)
{
- int argc=0;
- char *argv[10], *start = command;
+ struct command **cmd = &(pipe->cmd);
+ char *start = pipe->cmdline = cmdline;
+
+ if (!cmdline) return 0;
+
+ if (ENABLE_BBSH_JOBCTL) pipe->cmdline = cmdline;
// Parse command into argv[]
for (;;) {
char *end;
- // Skip leading whitespace and detect EOL.
- while(isspace(*start)) start++;
- if(!*start || *start=='#') break;
+ // Skip leading whitespace and detect end of line.
+ while (isspace(*start)) start++;
+ if (!*start || *start=='#') {
+ if (ENABLE_BBSH_JOBCTL) pipe->cmdlinelen = start-cmdline;
+ return 0;
+ }
- // Grab next word. (Add dequote and envvar logic here)
- end=start;
- while(*end && !isspace(*end)) end++;
- argv[argc++]=xstrndup(start,end-start);
- start=end;
+ // Allocate next command structure if necessary
+ if (!*cmd) *cmd = xzalloc(sizeof(struct command)+8*sizeof(char *));
+
+ // Parse next argument and add the results to argv[]
+ end = parse_word(start, cmd);
+
+ // If we hit the end of this command, how did it end?
+ if (!end) {
+ if (ENABLE_BBSH_PIPES && *start) {
+ if (*start==';') {
+ start++;
+ break;
+ }
+ // handle | & < > >> << || &&
+ }
+ break;
+ }
+ start = end;
}
- argv[argc]=0;
- if (!argc) return 0;
- if (argc==2 && !strcmp(argv[0],"cd")) chdir(argv[1]);
- else if(!strcmp(argv[0],"exit")) exit(argc>1 ? atoi(argv[1]) : 0);
+ if (ENABLE_BBSH_JOBCTL) pipe->cmdlinelen = start-cmdline;
+
+ return start;
+}
+
+// Execute the commands in a pipeline
+static int run_pipeline(struct pipeline *pipe)
+{
+ struct command *cmd = pipe->cmd;
+ if (!cmd || !cmd->argc) return 0;
+
+ // Handle local commands. This is totally fake and plastic.
+ if (cmd->argc==2 && !strcmp(cmd->argv[0],"cd"))
+ chdir(cmd->argv[1]);
+ else if(!strcmp(cmd->argv[0],"exit"))
+ exit(cmd->argc>1 ? atoi(cmd->argv[1]) : 0);
else {
int status;
pid_t pid=fork();
if(!pid) {
- run_applet_by_name(argv[0],argc,argv);
- execvp(argv[0],argv);
- printf("No %s",argv[0]);
+ run_applet_by_name(cmd->argv[0],cmd->argc,cmd->argv);
+ execvp(cmd->argv[0],cmd->argv);
+ printf("No %s",cmd->argv[0]);
exit(1);
} else waitpid(pid, &status, 0);
}
- while(argc) free(argv[--argc]);
return 0;
}
+static void free_cmd(void *data)
+{
+ struct command *cmd=(struct command *)data;
+
+ while(cmd->argc) free(cmd->argv[--cmd->argc]);
+}
+
+
+static void handle(char *command)
+{
+ struct pipeline pipe;
+ char *start = command;
+
+ for (;;) {
+ memset(&pipe,0,sizeof(struct pipeline));
+ start = parse_pipeline(start, &pipe);
+ if (!pipe.cmd) break;
+
+ run_pipeline(&pipe);
+ free_list(pipe.cmd, free_cmd);
+ }
+}
+
int bbsh_main(int argc, char *argv[])
{
char *command=NULL;
else {
unsigned cmdlen=0;
for (;;) {
+ struct pipeline pipe;
+
if(!f) putchar('$');
- if(1 > getline(&command,&cmdlen,f ? : stdin)) break;
+ if(1 > getline(&command, &cmdlen,f ? : stdin)) break;
+
handle(command);
}
if (ENABLE_FEATURE_CLEAN_UP) free(command);