Skip to content
Snippets Groups Projects
cli_hush.c 93.6 KiB
Newer Older
Wolfgang Denk's avatar
Wolfgang Denk committed
/*
 * sh.c -- a prototype Bourne shell grammar parser
 *      Intended to follow the original Thompson and Ritchie
 *      "small and simple is beautiful" philosophy, which
 *      incidentally is a good match to today's BusyBox.
 *
 * Copyright (C) 2000,2001  Larry Doolittle  <larry@doolittle.boa.org>
 *
 * Credits:
 *      The parser routines proper are all original material, first
 *      written Dec 2000 and Jan 2001 by Larry Doolittle.
 *      The execution engine, the builtins, and much of the underlying
 *      support has been adapted from busybox-0.49pre's lash,
 *      which is Copyright (C) 2000 by Lineo, Inc., and
 *      written by Erik Andersen <andersen@lineo.com>, <andersee@debian.org>.
 *      That, in turn, is based in part on ladsh.c, by Michael K. Johnson and
 *      Erik W. Troan, which they placed in the public domain.  I don't know
 *      how much of the Johnson/Troan code has survived the repeated rewrites.
 * Other credits:
 *      b_addchr() derived from similar w_addchar function in glibc-2.2
 *      setup_redirect(), redirect_opt_num(), and big chunks of main()
 *        and many builtins derived from contributions by Erik Andersen
 *      miscellaneous bugfixes from Matt Kraai
 *
 * There are two big (and related) architecture differences between
 * this parser and the lash parser.  One is that this version is
 * actually designed from the ground up to understand nearly all
 * of the Bourne grammar.  The second, consequential change is that
 * the parser and input reader have been turned inside out.  Now,
 * the parser is in control, and asks for input as needed.  The old
 * way had the input reader in control, and it asked for parsing to
 * take place as needed.  The new way makes it much easier to properly
 * handle the recursion implicit in the various substitutions, especially
 * across continuation lines.
 *
 * Bash grammar not implemented: (how many of these were in original sh?)
 *      $@ (those sure look like weird quoting rules)
 *      $_
 *      ! negation operator for pipes
 *      &> and >& redirection of stdout+stderr
 *      Brace Expansion
 *      Tilde Expansion
 *      fancy forms of Parameter Expansion
 *      aliases
 *      Arithmetic Expansion
 *      <(list) and >(list) Process Substitution
 *      reserved words: case, esac, select, function
 *      Here Documents ( << word )
 *      Functions
 * Major bugs:
 *      job handling woefully incomplete and buggy
 *      reserved word execution woefully incomplete and buggy
 * to-do:
 *      port selected bugfixes from post-0.49 busybox lash - done?
 *      finish implementing reserved words: for, while, until, do, done
 *      change { and } from special chars to reserved words
 *      builtins: break, continue, eval, return, set, trap, ulimit
 *      test magic exec
 *      handle children going into background
 *      clean up recognition of null pipes
 *      check setting of global_argc and global_argv
 *      control-C handling, probably with longjmp
 *      follow IFS rules more precisely, including update semantics
 *      figure out what to do with backslash-newline
 *      explain why we use signal instead of sigaction
 *      propagate syntax errors, die on resource errors?
 *      continuation lines, both explicit and implicit - done?
 *      memory leak finding and plugging - done?
 *      more testing, especially quoting rules and redirection
 *      document how quoting rules not precisely followed for variable assignments
 *      maybe change map[] to use 2-bit entries
 *      (eventually) remove all the printf's
 *
 * SPDX-License-Identifier:	GPL-2.0+
Wolfgang Denk's avatar
Wolfgang Denk committed
 */
Wolfgang Denk's avatar
Wolfgang Denk committed
#define __U_BOOT__
#ifdef __U_BOOT__
#include <malloc.h>         /* malloc, free, realloc*/
#include <linux/ctype.h>    /* isalpha, isdigit */
#include <common.h>        /* readline */
#include <console.h>
#include <bootretry.h>
#include <cli.h>
Simon Glass's avatar
Simon Glass committed
#include <cli_hush.h>
Wolfgang Denk's avatar
Wolfgang Denk committed
#include <command.h>        /* find_cmd */
#ifndef CONFIG_SYS_PROMPT_HUSH_PS2
#define CONFIG_SYS_PROMPT_HUSH_PS2	"> "
#endif
Wolfgang Denk's avatar
Wolfgang Denk committed
#endif
#ifndef __U_BOOT__
#include <ctype.h>     /* isalpha, isdigit */
#include <unistd.h>    /* getpid */
#include <stdlib.h>    /* getenv, atoi */
#include <string.h>    /* strchr */
#include <stdio.h>     /* popen etc. */
#include <glob.h>      /* glob, of course */
#include <stdarg.h>    /* va_list */
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>    /* should be pretty obvious */

#include <sys/stat.h>  /* ulimit */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

/* #include <dmalloc.h> */

Wolfgang Denk's avatar
Wolfgang Denk committed
#include "busybox.h"
#include "cmdedit.h"
#else
#define applet_name "hush"
#include "standalone.h"
#define hush_main main
#undef CONFIG_FEATURE_SH_FANCY_PROMPT
#define BB_BANNER
Wolfgang Denk's avatar
Wolfgang Denk committed
#endif
#endif
#define SPECIAL_VAR_SYMBOL 03
#define SUBSTED_VAR_SYMBOL 04
Wolfgang Denk's avatar
Wolfgang Denk committed
#ifndef __U_BOOT__
#define FLAG_EXIT_FROM_LOOP 1
#define FLAG_PARSE_SEMICOLON (1 << 1)		/* symbol ';' is special for parser */
#define FLAG_REPARSING       (1 << 2)		/* >= 2nd pass */

#endif

#ifdef __U_BOOT__
Wolfgang Denk's avatar
Wolfgang Denk committed
#define EXIT_SUCCESS 0
#define EOF -1
#define syntax() syntax_err()
#define xstrdup strdup
#define error_msg printf
#else
typedef enum {
	REDIRECT_INPUT     = 1,
	REDIRECT_OVERWRITE = 2,
	REDIRECT_APPEND    = 3,
	REDIRECT_HEREIS    = 4,
	REDIRECT_IO        = 5
} redir_type;

/* The descrip member of this structure is only used to make debugging
 * output pretty */
struct {int mode; int default_fd; char *descrip;} redir_table[] = {
	{ 0,                         0, "()" },
	{ O_RDONLY,                  0, "<"  },
	{ O_CREAT|O_TRUNC|O_WRONLY,  1, ">"  },
	{ O_CREAT|O_APPEND|O_WRONLY, 1, ">>" },
	{ O_RDONLY,                 -1, "<<" },
	{ O_RDWR,                    1, "<>" }
};
#endif

typedef enum {
	PIPE_SEQ = 1,
	PIPE_AND = 2,
	PIPE_OR  = 3,
	PIPE_BG  = 4,
} pipe_style;

/* might eventually control execution */
typedef enum {
	RES_NONE  = 0,
	RES_IF    = 1,
	RES_THEN  = 2,
	RES_ELIF  = 3,
	RES_ELSE  = 4,
	RES_FI    = 5,
	RES_FOR   = 6,
	RES_WHILE = 7,
	RES_UNTIL = 8,
	RES_DO    = 9,
	RES_DONE  = 10,
	RES_XXXX  = 11,
	RES_IN    = 12,
	RES_SNTX  = 13
} reserved_style;
#define FLAG_END   (1<<RES_NONE)
#define FLAG_IF    (1<<RES_IF)
#define FLAG_THEN  (1<<RES_THEN)
#define FLAG_ELIF  (1<<RES_ELIF)
#define FLAG_ELSE  (1<<RES_ELSE)
#define FLAG_FI    (1<<RES_FI)
#define FLAG_FOR   (1<<RES_FOR)
#define FLAG_WHILE (1<<RES_WHILE)
#define FLAG_UNTIL (1<<RES_UNTIL)
#define FLAG_DO    (1<<RES_DO)
#define FLAG_DONE  (1<<RES_DONE)
#define FLAG_IN    (1<<RES_IN)
#define FLAG_START (1<<RES_XXXX)

/* This holds pointers to the various results of parsing */
struct p_context {
	struct child_prog *child;
	struct pipe *list_head;
Wolfgang Denk's avatar
Wolfgang Denk committed
	struct pipe *pipe;
#ifndef __U_BOOT__
	struct redir_struct *pending_redirect;
#endif
	reserved_style w;
	int old_flag;				/* for figuring out valid reserved words */
	struct p_context *stack;
	int type;			/* define type of parser : ";$" common or special symbol */
	/* How about quoting status? */
};

#ifndef __U_BOOT__
struct redir_struct {
	redir_type type;			/* type of redirection */
	int fd;						/* file descriptor being redirected */
	int dup;					/* -1, or file descriptor being duplicated */
	struct redir_struct *next;	/* pointer to the next redirect in the list */
	glob_t word;				/* *word.gl_pathv is the filename */
};
#endif

struct child_prog {
#ifndef __U_BOOT__
	pid_t pid;					/* 0 if exited */
#endif
	char **argv;				/* program name and arguments */
	/* was quoted when parsed; copy of struct o_string.nonnull field */
Simon Glass's avatar
Simon Glass committed
	int *argv_nonnull;
Wolfgang Denk's avatar
Wolfgang Denk committed
#ifdef __U_BOOT__
	int    argc;                            /* number of program arguments */
#endif
	struct pipe *group;			/* if non-NULL, first in group or subshell */
#ifndef __U_BOOT__
	int subshell;				/* flag, non-zero if group must be forked */
	struct redir_struct *redirects;	/* I/O redirections */
	glob_t glob_result;			/* result of parameter globbing */
	int is_stopped;				/* is the program currently running? */
	struct pipe *family;		/* pointer back to the child's parent pipe */
#endif
	int sp;				/* number of SPECIAL_VAR_SYMBOL */
	int type;
};

struct pipe {
#ifndef __U_BOOT__
	int jobid;					/* job number */
#endif
	int num_progs;				/* total number of programs in job */
#ifndef __U_BOOT__
	int running_progs;			/* number of programs running */
	char *text;					/* name of job */
	char *cmdbuf;				/* buffer various argv's point into */
	pid_t pgrp;					/* process group ID for the job */
#endif
	struct child_prog *progs;	/* array of commands in pipe */
	struct pipe *next;			/* to track background commands */
#ifndef __U_BOOT__
	int stopped_progs;			/* number of programs alive, but stopped */
	int job_context;			/* bitmask defining current context */
#endif
	pipe_style followup;		/* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
	reserved_style r_mode;		/* supports if, for, while, until */
};

#ifndef __U_BOOT__
struct close_me {
	int fd;
	struct close_me *next;
};
#endif

struct variables {
	char *name;
	char *value;
	int flg_export;
	int flg_read_only;
	struct variables *next;
};

/* globals, connect us to the outside world
 * the first three support $?, $#, and $1 */
#ifndef __U_BOOT__
char **global_argv;
unsigned int global_argc;
#endif
Kim Phillips's avatar
Kim Phillips committed
static unsigned int last_return_code;
Wolfgang Denk's avatar
Wolfgang Denk committed
#ifndef __U_BOOT__
extern char **environ; /* This is in <unistd.h>, but protected with __USE_GNU */
#endif

/* "globals" within this file */
Wolfgang Denk's avatar
Wolfgang Denk committed
static uchar *ifs;
Wolfgang Denk's avatar
Wolfgang Denk committed
static char map[256];
#ifndef __U_BOOT__
static int fake_mode;
static int interactive;
static struct close_me *close_me_head;
static const char *cwd;
static struct pipe *job_list;
static unsigned int last_bg_pid;
static unsigned int last_jobid;
static unsigned int shell_terminal;
static char *PS1;
static char *PS2;
struct variables shell_ver = { "HUSH_VERSION", "0.01", 1, 1, 0 };
struct variables *top_vars = &shell_ver;
#else
static int flag_repeat = 0;
static int do_repeat = 0;
static struct variables *top_vars = NULL ;
Wolfgang Denk's avatar
Wolfgang Denk committed
#endif /*__U_BOOT__ */

#define B_CHUNK (100)
#define B_NOSPAC 1

typedef struct {
	char *data;
	int length;
	int maxlen;
	int quote;
	int nonnull;
} o_string;
#define NULL_O_STRING {NULL,0,0,0,0}
/* used for initialization:
	o_string foo = NULL_O_STRING; */

/* I can almost use ordinary FILE *.  Is open_memstream() universally
 * available?  Where is it documented? */
struct in_str {
	const char *p;
#ifndef __U_BOOT__
	char peek_buf[2];
#endif
	int __promptme;
	int promptmode;
#ifndef __U_BOOT__
	FILE *file;
#endif
	int (*get) (struct in_str *);
	int (*peek) (struct in_str *);
};
#define b_getch(input) ((input)->get(input))
#define b_peek(input) ((input)->peek(input))

#ifndef __U_BOOT__
#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"

struct built_in_command {
	char *cmd;					/* name */
	char *descr;				/* description */
	int (*function) (struct child_prog *);	/* function ptr */
};
#endif

/* define DEBUG_SHELL for debugging output (obviously ;-)) */
#if 0
#define DEBUG_SHELL
#endif

Wolfgang Denk's avatar
Wolfgang Denk committed
/* This should be in utility.c */
#ifdef DEBUG_SHELL
#ifndef __U_BOOT__
static void debug_printf(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	vfprintf(stderr, format, args);
	va_end(args);
}
#else
#define debug_printf(fmt,args...)	printf (fmt ,##args)
Wolfgang Denk's avatar
Wolfgang Denk committed
#endif
#else
static inline void debug_printf(const char *format, ...) { }
#endif
#define final_printf debug_printf

#ifdef __U_BOOT__
static void syntax_err(void) {
	 printf("syntax error\n");
}
#else
static void __syntax(char *file, int line) {
	error_msg("syntax error %s:%d", file, line);
}
#define syntax() __syntax(__FILE__, __LINE__)
#endif

#ifdef __U_BOOT__
static void *xmalloc(size_t size);
static void *xrealloc(void *ptr, size_t size);
#else
/* Index of subroutines: */
/*   function prototypes for builtins */
static int builtin_cd(struct child_prog *child);
static int builtin_env(struct child_prog *child);
static int builtin_eval(struct child_prog *child);
static int builtin_exec(struct child_prog *child);
static int builtin_exit(struct child_prog *child);
static int builtin_export(struct child_prog *child);
static int builtin_fg_bg(struct child_prog *child);
static int builtin_help(struct child_prog *child);
static int builtin_jobs(struct child_prog *child);
static int builtin_pwd(struct child_prog *child);
static int builtin_read(struct child_prog *child);
static int builtin_set(struct child_prog *child);
static int builtin_shift(struct child_prog *child);
static int builtin_source(struct child_prog *child);
static int builtin_umask(struct child_prog *child);
static int builtin_unset(struct child_prog *child);
static int builtin_not_written(struct child_prog *child);
#endif
/*   o_string manipulation: */
static int b_check_space(o_string *o, int len);
static int b_addchr(o_string *o, int ch);
static void b_reset(o_string *o);
static int b_addqchr(o_string *o, int ch, int quote);
#ifndef __U_BOOT__
Wolfgang Denk's avatar
Wolfgang Denk committed
static int b_adduint(o_string *o, unsigned int i);
Wolfgang Denk's avatar
Wolfgang Denk committed
/*  in_str manipulations: */
static int static_get(struct in_str *i);
static int static_peek(struct in_str *i);
static int file_get(struct in_str *i);
static int file_peek(struct in_str *i);
#ifndef __U_BOOT__
static void setup_file_in_str(struct in_str *i, FILE *f);
#else
static void setup_file_in_str(struct in_str *i);
#endif
static void setup_string_in_str(struct in_str *i, const char *s);
#ifndef __U_BOOT__
/*  close_me manipulations: */
static void mark_open(int fd);
static void mark_closed(int fd);
static void close_all(void);
Wolfgang Denk's avatar
Wolfgang Denk committed
#endif
/*  "run" the final data structures: */
static char *indenter(int i);
static int free_pipe_list(struct pipe *head, int indent);
static int free_pipe(struct pipe *pi, int indent);
/*  really run the final data structures: */
#ifndef __U_BOOT__
static int setup_redirects(struct child_prog *prog, int squirrel[]);
#endif
static int run_list_real(struct pipe *pi);
#ifndef __U_BOOT__
static void pseudo_exec(struct child_prog *child) __attribute__ ((noreturn));
#endif
static int run_pipe_real(struct pipe *pi);
/*   extended glob support: */
#ifndef __U_BOOT__
static int globhack(const char *src, int flags, glob_t *pglob);
static int glob_needed(const char *s);
static int xglob(o_string *dest, int flags, glob_t *pglob);
#endif
/*   variable assignment: */
static int is_assignment(const char *s);
/*   data structure manipulation: */
#ifndef __U_BOOT__
static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input);
#endif
static void initialize_context(struct p_context *ctx);
static int done_word(o_string *dest, struct p_context *ctx);
static int done_command(struct p_context *ctx);
static int done_pipe(struct p_context *ctx, pipe_style type);
/*   primary string parsing: */
#ifndef __U_BOOT__
static int redirect_dup_num(struct in_str *input);
static int redirect_opt_num(o_string *o);
static int process_command_subs(o_string *dest, struct p_context *ctx, struct in_str *input, int subst_end);
static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch);
#endif
static char *lookup_param(char *src);
static char *make_string(char **inp, int *nonnull);
Wolfgang Denk's avatar
Wolfgang Denk committed
static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input);
#ifndef __U_BOOT__
static int parse_string(o_string *dest, struct p_context *ctx, const char *src);
#endif
static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, int end_trigger);
/*   setup: */
static int parse_stream_outer(struct in_str *inp, int flag);
#ifndef __U_BOOT__
static int parse_string_outer(const char *s, int flag);
static int parse_file_outer(FILE *f);
#endif
#ifndef __U_BOOT__
/*   job management: */
static int checkjobs(struct pipe* fg_pipe);
static void insert_bg_job(struct pipe *pi);
static void remove_bg_job(struct pipe *pi);
#endif
/*     local variable support */
static char **make_list_in(char **inp, char *name);
static char *insert_var_value(char *inp);
static char *insert_var_value_sub(char *inp, int tag_subst);
Loading
Loading full blame...