From 9a3db48a553a6d62d9954d6b20327e95505578cc Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Fri, 10 Jan 2014 18:42:24 -0600 Subject: Add support for backquoated commands as command arguments --- apps/ChangeLog.txt | 3 + apps/nshlib/Kconfig | 44 ++++++++ apps/nshlib/nsh.h | 9 ++ apps/nshlib/nsh_parse.c | 269 ++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 307 insertions(+), 18 deletions(-) (limited to 'apps') diff --git a/apps/ChangeLog.txt b/apps/ChangeLog.txt index 57cc12c48..0b377bf91 100644 --- a/apps/ChangeLog.txt +++ b/apps/ChangeLog.txt @@ -766,4 +766,7 @@ * apps/nshlib/nsh_parse.c: Put the logic that executes a command after parsing parameters into a separate file so that it can be reused (2014-1-10). + * apps/nshlib/nsh_parse.c: Add initial support of commands enclosed + in back quotes as command arguments. Functionality still incomplete + on initial commit (2014-1-10). diff --git a/apps/nshlib/Kconfig b/apps/nshlib/Kconfig index 2a96b851b..7d12d4d65 100644 --- a/apps/nshlib/Kconfig +++ b/apps/nshlib/Kconfig @@ -277,6 +277,34 @@ config NSH_DISABLE_SEMICOLON command separated by a semicolon. You can disable this feature to save a little memory on FLASH challenged platforms. +config NSH_FUNCPARMS + bool "Enable function parameters" + default n + ---help--- + If selected, then the output from cmds, from file applications, and + from NSH built-in commands can be used as arguments to other + commands. The entity to be executed is identified by enclosing the + command line in back quotes. For example, + + set FOO `myprogram $BAR` + + Will execute the program named myprogram passing it the value of the + environment variable BAR. The value of the environment variable FOO + is then set output of myprogram on stdout. + + This feature is disabled by default. + +config NSH_TMPDIR + string "Temporary file directory" + default "/tmp" + depends on NSH_FUNCPARMS + ---help--- + If NSH_FUNCPARMS is selected, then function output will be retained + in a temporary file. In that case, this string must be provided to + specify the full path to a directory where temporary files can be + created. This would be a good application of RAM disk: To provide + temporary storage for function output. + config NSH_MAXARGUMENTS int "Maximum number of command arguments" default 6 @@ -284,6 +312,22 @@ config NSH_MAXARGUMENTS The maximum number of NSH command arguments. Default: 6 +config NSH_ENVCAT + bool "Concatenation of environment variables" + default n + ---help--- + Support concatenation of strings with environment variables. For + example: + + set FOO XYZ + set BAR 123 + set FOOBAR ABC_${FOO}_${BAR} + + would set the environment variable FOO to XYZ, BAR to 123 and FOOBAR + to ABC_XYZ_123. If NSH_ENV, then a slightly small FLASH footprint + results but then also only simple environment variables like $FOO + can be used on the command line. + config NSH_NESTDEPTH int "Maximum command nesting" default 3 diff --git a/apps/nshlib/nsh.h b/apps/nshlib/nsh.h index fe85f34ce..1466b7187 100644 --- a/apps/nshlib/nsh.h +++ b/apps/nshlib/nsh.h @@ -72,6 +72,15 @@ # undef CONFIG_NSH_TELNET # undef CONFIG_NSH_FILE_APPS # undef CONFIG_NSH_TELNET +# undef CONFIG_NSH_FUNCPARMS +#endif + +/* If CONFIG_NSH_FUNCPARMS is selected, then the path to a directory to + * hold temporary files must be provided. + */ + +#if defined(CONFIG_NSH_FUNCPARMS) && !defined(CONFIG_NSH_TMPDIR) +# define CONFIG_NSH_TMPDIR "/tmp" #endif /* Telnetd requires networking support */ diff --git a/apps/nshlib/nsh_parse.c b/apps/nshlib/nsh_parse.c index 3a944261a..10ac3a258 100644 --- a/apps/nshlib/nsh_parse.c +++ b/apps/nshlib/nsh_parse.c @@ -74,6 +74,36 @@ struct cmdarg_s * Private Function Prototypes ****************************************************************************/ +#ifndef CONFIG_NSH_DISABLEBG +static void nsh_releaseargs(struct cmdarg_s *arg); +static pthread_addr_t nsh_child(pthread_addr_t arg); +static struct cmdarg_s *nsh_cloneargs(FAR struct nsh_vtbl_s *vtbl, + int fd, int argc, char *argv[]); +#endif + +static int nsh_saveresult(FAR struct nsh_vtbl_s *vtbl, bool result); +static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, + int argc, FAR char *argv[], FAR const char *redirfile, + int oflags); +static char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr); + +#ifndef CONFIG_NSH_DISABLESCRIPT +static bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl); +static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, + FAR char **ppcmd, FAR char **saveptr); +#endif + +#ifndef CONFIG_NSH_DISABLEBG +static int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr); +#endif + +#ifdef CONFIG_NSH_FUNCPARMS +static int nsh_parse_funcparm(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline, + FAR const char *redirfile); +#endif +static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline); + /**************************************************************************** * Private Data ****************************************************************************/ @@ -207,8 +237,8 @@ static pthread_addr_t nsh_child(pthread_addr_t arg) ****************************************************************************/ #ifndef CONFIG_NSH_DISABLEBG -static inline struct cmdarg_s *nsh_cloneargs(FAR struct nsh_vtbl_s *vtbl, - int fd, int argc, char *argv[]) +static struct cmdarg_s *nsh_cloneargs(FAR struct nsh_vtbl_s *vtbl, + int fd, int argc, char *argv[]) { struct cmdarg_s *ret = (struct cmdarg_s *)zalloc(sizeof(struct cmdarg_s)); int i; @@ -253,7 +283,7 @@ static int nsh_saveresult(FAR struct nsh_vtbl_s *vtbl, bool result) } /**************************************************************************** - * Name: nsh_argument + * Name: nsh_execute ****************************************************************************/ static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, @@ -361,7 +391,7 @@ static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, #endif /* Handle the case where the command is executed in background. - * However is app is to be started as builtin new process will + * However is app is to be started as built-in new process will * be created anyway, so skip this step. */ @@ -532,10 +562,10 @@ errout: * Name: nsh_argument ****************************************************************************/ -static char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr) +static char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, FAR char **saveptr) { - char *pbegin = *saveptr; - char *pend = NULL; + FAR char *pbegin = *saveptr; + FAR char *pend = NULL; const char *term; #ifndef CONFIG_DISABLE_ENVIRON bool quoted = false; @@ -583,15 +613,101 @@ static char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr) *saveptr = pbegin; pbegin = NULL; } + +#ifdef CONFIG_NSH_FUNCPARMS + /* Are we being asked to use the output from another command or program + * as an input parameters for this command? + */ + + else if (*pbegin == '`') + { + FAR char *tmpfile; + int ret; + + /* Yes, find the terminated backquote */ + + for (++pbegin, pend = pbegin; *pend && *pend != '`'; pend++); + + /* pend either points to the end of the string or to the closing + * backquote. + */ + + if (!*pend) + { + nsh_output(vtbl, g_nshsyntax, "``"); + return NULL; + } + + /* Turn the closing backquote into a NUL terminator and save the + * pointer where we left off. + */ + + *pend++ = '\0'; + *saveptr = pend; + + /* Create a unique file name using the task ID */ + + tmpfile = NULL; + ret = asprintf(&tmpfile, "%s/TMP%d.dat", CONFIG_NSH_TMPDIR, getpid()); + if (ret < 0 || !tmpfile) + { + nsh_output(vtbl, g_fmtcmdoutofmemory, "``"); + return NULL; + } + + /* Execute the command that will re-direct the output of the command + * to the temporary file. This is a simple command that can't handle + * most options. + */ + + ret = nsh_parse_funcparm(vtbl, pbegin, tmpfile); + if (ret != OK) + { + /* Report the failure */ +#warning Logic Missing + + free(tmpfile); + return NULL; + } + + /* mmap() the file contain the output from the command so that + * we can refer to it like any string. + */ +#warning Logic Missing + + /* setup so that we can unmap the memory region and close the + * file after the command completes. + */ +#warning Logic Missing + + /* We can not unlink the tmpfile and free the tmpfile string */ + + ret = unlink(tmpfile); + if (ret < 0) + { + nsh_output(vtbl, g_fmtcmdfailed, "``", "unlink", NSH_ERRNO); + } + + free(tmpfile); + return "OH NO!"; + } +#endif + + /* Otherwise, it is a normal string and we have to parse using the normal + * rules to find the end of the argument. + */ + else { - /* Otherwise, we are going to have to parse to find the end of - * the token. Does the token begin with '"'? + /* However, the rules are a little different if the next argument is + * a quoted string. */ if (*pbegin == '"') { - /* Yes.. then only another '"' can terminate the string */ + /* A quoted string can only be terminated with another quotation + * mark. + */ pbegin++; term = "\""; @@ -612,13 +728,13 @@ static char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr) *pend && strchr(term, *pend) == NULL; pend++); - /* pend either points to the end of the string or to - * the first delimiter after the string. + /* pend either points to the end of the string or to the first + * delimiter after the string. */ if (*pend) { - /* Turn the delimiter into a null terminator */ + /* Turn the delimiter into a NUL terminator */ *pend++ = '\0'; } @@ -676,7 +792,7 @@ static char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr) ****************************************************************************/ #ifndef CONFIG_NSH_DISABLESCRIPT -static inline bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl) +static bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl) { struct nsh_parser_s *np = &vtbl->np; bool ret = !np->np_st[np->np_ndx].ns_disabled; @@ -708,8 +824,8 @@ static inline bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl) ****************************************************************************/ #ifndef CONFIG_NSH_DISABLESCRIPT -static inline int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, - FAR char **ppcmd, FAR char **saveptr) +static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr) { struct nsh_parser_s *np = &vtbl->np; FAR char *cmd = *ppcmd; @@ -848,8 +964,8 @@ errout: ****************************************************************************/ #ifndef CONFIG_NSH_DISABLEBG -static inline int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, - FAR char **saveptr) +static int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr) { FAR char *cmd = *ppcmd; @@ -896,6 +1012,121 @@ static inline int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, } #endif +/**************************************************************************** + * Name: nsh_parse_funcparm + * + * Description: + * This function parses and executes a simple NSH command. Output is + * always redirected. Function function supports function paramers like + * + * set FOO `hello` + * + * which would set the environment variable FOO to the output from + * the hello program + * + ****************************************************************************/ + +#ifdef CONFIG_NSH_FUNCPARMS +static int nsh_parse_funcparm(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline, + FAR const char *redirfile) +{ + FAR char *argv[MAX_ARGV_ENTRIES]; + FAR char *saveptr; + FAR char *cmd; + bool bgsave; + bool redirsave; + int argc; + int ret; + + /* Initialize parser state */ + + memset(argv, 0, MAX_ARGV_ENTRIES*sizeof(FAR char *)); + + /* If any options like nice, redirection, or backgrounding are attempted, + * these will not be recognized and will just be passed through as + * normal, invalid commands or parameters. + */ + +#ifndef CONFIG_NSH_DISABLEBG + /* The function is never backgrounded . Remember the current backgrouding + * state + */ + + bgsave = vtbl->np.np_bg; + vtbl->np.np_bg = false; +#endif + + /* Output is always redirected. Remember the current redirection state */ + + redirsave = vtbl->np.np_redirect; + vtbl->np.np_redirect = true; + + /* Parse out the command at the beginning of the line */ + + saveptr = cmdline; + cmd = nsh_argument(vtbl, &saveptr); + + /* Check if any command was provided -OR- if command processing is + * currently disabled. + */ + +#ifndef CONFIG_NSH_DISABLESCRIPT + if (!cmd || !nsh_cmdenabled(vtbl)) +#else + if (!cmd) +#endif + { + /* An empty line is not an error and an unprocessed command cannot + * generate an error, but neither should they change the last + * command status. + */ + + return OK; + } + + /* Parse all of the arguments following the command name. The form + * of argv is: + * + * argv[0]: The command name. + * argv[1]: The beginning of argument (up to CONFIG_NSH_MAXARGUMENTS) + * argv[argc]: NULL terminating pointer + * + * Maximum size is CONFIG_NSH_MAXARGUMENTS+1 + */ + + argv[0] = cmd; + for (argc = 1; argc < MAX_ARGV_ENTRIES-1; argc++) + { + argv[argc] = nsh_argument(vtbl, &saveptr); + if (!argv[argc]) + { + break; + } + } + + argv[argc] = NULL; + + /* Check if the maximum number of arguments was exceeded */ + + if (argc > CONFIG_NSH_MAXARGUMENTS) + { + nsh_output(vtbl, g_fmttoomanyargs, cmd); + } + + /* Then execute the command */ + + ret = nsh_execute(vtbl, argc, argv, redirfile, O_WRONLY|O_CREAT|O_TRUNC); + + /* Restore the backgrounding and redirection state */ + +#ifndef CONFIG_NSH_DISABLEBG + vtbl->np.np_bg = bgsave; +#endif + vtbl->np.np_redirect = redirsave; + return ret; +} +#endif + /**************************************************************************** * Name: nsh_parse_command * @@ -919,9 +1150,11 @@ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) /* Initialize parser state */ memset(argv, 0, MAX_ARGV_ENTRIES*sizeof(FAR char *)); + #ifndef CONFIG_NSH_DISABLEBG vtbl->np.np_bg = false; #endif + #if CONFIG_NFILE_STREAMS > 0 vtbl->np.np_redirect = false; #endif -- cgit v1.2.3