Skip to content
Snippets Groups Projects
cli_hush.c 93.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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;
    	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);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    #ifndef __U_BOOT__
    /* Table of built-in functions.  They can be forked or not, depending on
     * context: within pipes, they fork.  As simple commands, they do not.
     * When used in non-forking context, they can change global variables
     * in the parent shell process.  If forked, of course they can not.
     * For example, 'unset foo | whatever' will parse and run, but foo will
     * still be set at the end. */
    static struct built_in_command bltins[] = {
    	{"bg", "Resume a job in the background", builtin_fg_bg},
    	{"break", "Exit for, while or until loop", builtin_not_written},
    	{"cd", "Change working directory", builtin_cd},
    	{"continue", "Continue for, while or until loop", builtin_not_written},
    	{"env", "Print all environment variables", builtin_env},
    	{"eval", "Construct and run shell command", builtin_eval},
    	{"exec", "Exec command, replacing this shell with the exec'd process",
    		builtin_exec},
    	{"exit", "Exit from shell()", builtin_exit},
    	{"export", "Set environment variable", builtin_export},
    	{"fg", "Bring job into the foreground", builtin_fg_bg},
    	{"jobs", "Lists the active jobs", builtin_jobs},
    	{"pwd", "Print current directory", builtin_pwd},
    	{"read", "Input environment variable", builtin_read},
    	{"return", "Return from a function", builtin_not_written},
    	{"set", "Set/unset shell local variables", builtin_set},
    	{"shift", "Shift positional parameters", builtin_shift},
    	{"trap", "Trap signals", builtin_not_written},
    	{"ulimit","Controls resource limits", builtin_not_written},
    	{"umask","Sets file creation mask", builtin_umask},
    	{"unset", "Unset environment variable", builtin_unset},
    	{".", "Source-in and run commands in a file", builtin_source},
    	{"help", "List shell built-in commands", builtin_help},
    	{NULL, NULL, NULL}
    };
    
    static const char *set_cwd(void)
    {
    	if(cwd==unknown)
    		cwd = NULL;     /* xgetcwd(arg) called free(arg) */
    	cwd = xgetcwd((char *)cwd);
    	if (!cwd)
    		cwd = unknown;
    	return cwd;
    }
    
    /* built-in 'eval' handler */
    static int builtin_eval(struct child_prog *child)
    {
    	char *str = NULL;
    	int rcode = EXIT_SUCCESS;
    
    	if (child->argv[1]) {
    		str = make_string(child->argv + 1);
    		parse_string_outer(str, FLAG_EXIT_FROM_LOOP |
    					FLAG_PARSE_SEMICOLON);
    		free(str);
    		rcode = last_return_code;
    	}
    	return rcode;
    }
    
    /* built-in 'cd <path>' handler */
    static int builtin_cd(struct child_prog *child)
    {
    	char *newdir;
    	if (child->argv[1] == NULL)
    
    		newdir = env_get("HOME");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	else
    		newdir = child->argv[1];
    	if (chdir(newdir)) {
    		printf("cd: %s: %s\n", newdir, strerror(errno));
    		return EXIT_FAILURE;
    	}
    	set_cwd();
    	return EXIT_SUCCESS;
    }
    
    /* built-in 'env' handler */
    static int builtin_env(struct child_prog *dummy)
    {
    	char **e = environ;
    	if (e == NULL) return EXIT_FAILURE;
    	for (; *e; e++) {
    		puts(*e);
    	}
    	return EXIT_SUCCESS;
    }
    
    /* built-in 'exec' handler */
    static int builtin_exec(struct child_prog *child)
    {
    	if (child->argv[1] == NULL)
    		return EXIT_SUCCESS;   /* Really? */
    	child->argv++;
    	pseudo_exec(child);
    	/* never returns */
    }
    
    /* built-in 'exit' handler */
    static int builtin_exit(struct child_prog *child)
    {
    	if (child->argv[1] == NULL)
    		exit(last_return_code);
    	exit (atoi(child->argv[1]));
    }
    
    /* built-in 'export VAR=value' handler */
    static int builtin_export(struct child_prog *child)
    {
    	int res = 0;
    	char *name = child->argv[1];
    
    	if (name == NULL) {
    		return (builtin_env(child));
    	}
    
    	name = strdup(name);
    
    	if(name) {
    		char *value = strchr(name, '=');
    
    		if (!value) {
    			char *tmp;
    			/* They are exporting something without an =VALUE */
    
    			value = get_local_var(name);
    			if (value) {
    				size_t ln = strlen(name);
    
    				tmp = realloc(name, ln+strlen(value)+2);
    				if(tmp==NULL)
    					res = -1;
    				else {
    					sprintf(tmp+ln, "=%s", value);
    					name = tmp;
    				}
    			} else {
    				/* bash does not return an error when trying to export
    				 * an undefined variable.  Do likewise. */
    				res = 1;
    			}
    		}
    	}
    	if (res<0)
    		perror_msg("export");
    	else if(res==0)
    		res = set_local_var(name, 1);
    	else
    		res = 0;
    	free(name);
    	return res;
    }
    
    /* built-in 'fg' and 'bg' handler */
    static int builtin_fg_bg(struct child_prog *child)
    {
    	int i, jobnum;
    	struct pipe *pi=NULL;
    
    	if (!interactive)
    		return EXIT_FAILURE;
    	/* If they gave us no args, assume they want the last backgrounded task */
    	if (!child->argv[1]) {
    		for (pi = job_list; pi; pi = pi->next) {
    			if (pi->jobid == last_jobid) {
    				break;
    			}
    		}
    		if (!pi) {
    			error_msg("%s: no current job", child->argv[0]);
    			return EXIT_FAILURE;
    		}
    	} else {
    		if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) {
    			error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]);
    			return EXIT_FAILURE;
    		}
    		for (pi = job_list; pi; pi = pi->next) {
    			if (pi->jobid == jobnum) {
    				break;
    			}
    		}
    		if (!pi) {
    			error_msg("%s: %d: no such job", child->argv[0], jobnum);
    			return EXIT_FAILURE;
    		}
    	}
    
    	if (*child->argv[0] == 'f') {
    		/* Put the job into the foreground.  */
    		tcsetpgrp(shell_terminal, pi->pgrp);
    	}
    
    	/* Restart the processes in the job */
    	for (i = 0; i < pi->num_progs; i++)
    		pi->progs[i].is_stopped = 0;
    
    	if ( (i=kill(- pi->pgrp, SIGCONT)) < 0) {
    		if (i == ESRCH) {
    			remove_bg_job(pi);
    		} else {
    			perror_msg("kill (SIGCONT)");
    		}
    	}
    
    	pi->stopped_progs = 0;
    	return EXIT_SUCCESS;
    }
    
    /* built-in 'help' handler */
    static int builtin_help(struct child_prog *dummy)
    {
    	struct built_in_command *x;
    
    	printf("\nBuilt-in commands:\n");
    	printf("-------------------\n");
    	for (x = bltins; x->cmd; x++) {
    		if (x->descr==NULL)
    			continue;
    		printf("%s\t%s\n", x->cmd, x->descr);
    	}
    	printf("\n\n");
    	return EXIT_SUCCESS;
    }
    
    /* built-in 'jobs' handler */
    static int builtin_jobs(struct child_prog *child)
    {
    	struct pipe *job;
    	char *status_string;
    
    	for (job = job_list; job; job = job->next) {
    		if (job->running_progs == job->stopped_progs)
    			status_string = "Stopped";
    		else
    			status_string = "Running";
    
    		printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text);
    	}
    	return EXIT_SUCCESS;
    }
    
    
    /* built-in 'pwd' handler */
    static int builtin_pwd(struct child_prog *dummy)
    {
    	puts(set_cwd());
    	return EXIT_SUCCESS;
    }
    
    /* built-in 'read VAR' handler */
    static int builtin_read(struct child_prog *child)
    {
    	int res;
    
    	if (child->argv[1]) {
    		char string[BUFSIZ];
    		char *var = 0;
    
    		string[0] = 0;  /* In case stdin has only EOF */
    		/* read string */
    		fgets(string, sizeof(string), stdin);
    		chomp(string);
    		var = malloc(strlen(child->argv[1])+strlen(string)+2);
    		if(var) {
    			sprintf(var, "%s=%s", child->argv[1], string);
    			res = set_local_var(var, 0);
    		} else
    			res = -1;
    		if (res)
    			fprintf(stderr, "read: %m\n");
    		free(var);      /* So not move up to avoid breaking errno */
    		return res;
    	} else {
    		do res=getchar(); while(res!='\n' && res!=EOF);
    		return 0;
    	}
    }
    
    /* built-in 'set VAR=value' handler */
    static int builtin_set(struct child_prog *child)
    {
    	char *temp = child->argv[1];
    	struct variables *e;
    
    	if (temp == NULL)
    		for(e = top_vars; e; e=e->next)
    			printf("%s=%s\n", e->name, e->value);
    	else
    		set_local_var(temp, 0);
    
    		return EXIT_SUCCESS;
    }
    
    
    /* Built-in 'shift' handler */
    static int builtin_shift(struct child_prog *child)
    {
    	int n=1;
    	if (child->argv[1]) {
    		n=atoi(child->argv[1]);
    	}
    	if (n>=0 && n<global_argc) {
    		/* XXX This probably breaks $0 */
    		global_argc -= n;
    		global_argv += n;
    		return EXIT_SUCCESS;
    	} else {
    		return EXIT_FAILURE;
    	}
    }
    
    /* Built-in '.' handler (read-in and execute commands from file) */
    static int builtin_source(struct child_prog *child)
    {
    	FILE *input;
    	int status;
    
    	if (child->argv[1] == NULL)
    		return EXIT_FAILURE;
    
    	/* XXX search through $PATH is missing */
    	input = fopen(child->argv[1], "r");
    	if (!input) {
    		error_msg("Couldn't open file '%s'", child->argv[1]);
    		return EXIT_FAILURE;
    	}
    
    	/* Now run the file */
    	/* XXX argv and argc are broken; need to save old global_argv
    	 * (pointer only is OK!) on this stack frame,
    	 * set global_argv=child->argv+1, recurse, and restore. */
    	mark_open(fileno(input));
    	status = parse_file_outer(input);
    	mark_closed(fileno(input));
    	fclose(input);
    	return (status);
    }
    
    static int builtin_umask(struct child_prog *child)
    {
    	mode_t new_umask;
    	const char *arg = child->argv[1];
    	char *end;
    	if (arg) {
    		new_umask=strtoul(arg, &end, 8);
    		if (*end!='\0' || end == arg) {
    			return EXIT_FAILURE;
    		}
    	} else {
    		printf("%.3o\n", (unsigned int) (new_umask=umask(0)));
    	}
    	umask(new_umask);
    	return EXIT_SUCCESS;
    }
    
    /* built-in 'unset VAR' handler */
    static int builtin_unset(struct child_prog *child)
    {
    	/* bash returned already true */
    	unset_local_var(child->argv[1]);
    	return EXIT_SUCCESS;
    }
    
    static int builtin_not_written(struct child_prog *child)
    {
    	printf("builtin_%s not written\n",child->argv[0]);
    	return EXIT_FAILURE;
    }
    #endif
    
    static int b_check_space(o_string *o, int len)
    {
    	/* It would be easy to drop a more restrictive policy
    	 * in here, such as setting a maximum string length */
    	if (o->length + len > o->maxlen) {
    		char *old_data = o->data;
    		/* assert (data == NULL || o->maxlen != 0); */
    		o->maxlen += max(2*len, B_CHUNK);
    		o->data = realloc(o->data, 1 + o->maxlen);
    		if (o->data == NULL) {
    			free(old_data);
    		}
    	}
    	return o->data == NULL;
    }
    
    static int b_addchr(o_string *o, int ch)
    {
    	debug_printf("b_addchr: %c %d %p\n", ch, o->length, o);
    	if (b_check_space(o, 1)) return B_NOSPAC;
    	o->data[o->length] = ch;
    	o->length++;
    	o->data[o->length] = '\0';
    	return 0;
    }
    
    static void b_reset(o_string *o)
    {
    	o->length = 0;
    	o->nonnull = 0;
    	if (o->data != NULL) *o->data = '\0';
    }
    
    static void b_free(o_string *o)
    {
    	b_reset(o);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	o->data = NULL;
    	o->maxlen = 0;
    }
    
    /* My analysis of quoting semantics tells me that state information
     * is associated with a destination, not a source.
     */
    static int b_addqchr(o_string *o, int ch, int quote)
    {
    	if (quote && strchr("*?[\\",ch)) {
    		int rc;
    		rc = b_addchr(o, '\\');
    		if (rc) return rc;
    	}
    	return b_addchr(o, ch);
    }
    
    
    #ifndef __U_BOOT__
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static int b_adduint(o_string *o, unsigned int i)
    {
    	int r;
    	char *p = simple_itoa(i);
    	/* no escape checking necessary */
    	do r=b_addchr(o, *p++); while (r==0 && *p);
    	return r;
    }
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    
    static int static_get(struct in_str *i)
    {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if (ch=='\0') return EOF;
    	return ch;
    }
    
    static int static_peek(struct in_str *i)
    {
    	return *i->p;
    }
    
    #ifndef __U_BOOT__
    static inline void cmdedit_set_initial_prompt(void)
    {
    
    #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	PS1 = NULL;
    #else
    
    	PS1 = env_get("PS1");
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	if(PS1==0)
    		PS1 = "\\w \\$ ";
    #endif
    }
    
    static inline void setup_prompt_string(int promptmode, char **prompt_str)
    {
    	debug_printf("setup_prompt_string %d ",promptmode);
    
    #ifndef CONFIG_FEATURE_SH_FANCY_PROMPT
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	/* Set up the prompt */
    	if (promptmode == 1) {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    		PS1=xmalloc(strlen(cwd)+4);
    		sprintf(PS1, "%s %s", cwd, ( geteuid() != 0 ) ?  "$ ":"# ");
    		*prompt_str = PS1;
    	} else {
    		*prompt_str = PS2;
    	}
    #else
    	*prompt_str = (promptmode==1)? PS1 : PS2;
    #endif
    	debug_printf("result %s\n",*prompt_str);
    }
    #endif
    
    
    #ifdef __U_BOOT__
    static int uboot_cli_readline(struct in_str *i)
    {
    	char *prompt;
    
    	char __maybe_unused *ps_prompt = NULL;
    
    
    	if (i->promptmode == 1)
    		prompt = CONFIG_SYS_PROMPT;
    	else
    		prompt = CONFIG_SYS_PROMPT_HUSH_PS2;
    
    
    #ifdef CONFIG_CMDLINE_PS_SUPPORT
    	if (i->promptmode == 1)
    
    		ps_prompt = env_get("PS1");
    
    		ps_prompt = env_get("PS2");
    
    	if (ps_prompt)
    		prompt = ps_prompt;
    #endif