/**************************************************************************** * apps/nshlib/nsh_parse.c * * Copyright (C) 2007-2013, 2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #ifdef CONFIG_NSH_CMDPARMS # include #endif #include #include "nsh.h" #include "nsh_console.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* If CONFIG_NSH_CMDPARMS or CONFIG_NSH_ARGCAT is enabled, then we will need * retain a list of memory allocations to be freed at the completion of * command processing. */ #undef HAVE_MEMLIST #if defined(CONFIG_NSH_CMDPARMS) || defined(CONFIG_NSH_ARGCAT) # define HAVE_MEMLIST 1 #endif #if defined(HAVE_MEMLIST) && !defined(CONFIG_NSH_MAXALLOCS) # ifdef CONFIG_NSH_ARGCAT # define CONFIG_NSH_MAXALLOCS (2*CONFIG_NSH_MAXARGUMENTS) # else # define CONFIG_NSH_MAXALLOCS CONFIG_NSH_MAXARGUMENTS # endif #endif /* Allocation list helper macros */ #ifdef HAVE_MEMLIST # define NSH_MEMLIST_TYPE struct nsh_memlist_s # define NSH_MEMLIST_INIT(m) memset(&(m), 0, sizeof(struct nsh_memlist_s)); # define NSH_MEMLIST_ADD(m,a) nsh_memlist_add(m,a) # define NSH_MEMLIST_FREE(m) nsh_memlist_free(m) #else # define NSH_MEMLIST_TYPE uint8_t # define NSH_MEMLIST_INIT(m) do { (m) = 0; } while (0) # define NSH_MEMLIST_ADD(m,a) # define NSH_MEMLIST_FREE(m,a) #endif /**************************************************************************** * Private Types ****************************************************************************/ /* These structure describes the parsed command line */ #ifndef CONFIG_NSH_DISABLEBG struct cmdarg_s { FAR struct nsh_vtbl_s *vtbl; /* For front-end interaction */ int fd; /* FD for output redirection */ int argc; /* Number of arguments in argv */ FAR char *argv[MAX_ARGV_ENTRIES]; /* Argument list */ }; #endif /* This structure describes the allocation list */ #ifdef HAVE_MEMLIST struct nsh_memlist_s { int nallocs; /* Number of allocations */ FAR char *allocations[CONFIG_NSH_MAXALLOCS]; }; #endif /**************************************************************************** * Private Function Prototypes ****************************************************************************/ #ifdef HAVE_MEMLIST static void nsh_memlist_add(FAR struct nsh_memlist_s *memlist, FAR char *address); static void nsh_memlist_free(FAR struct nsh_memlist_s *memlist); #endif #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); #ifdef CONFIG_NSH_CMDPARMS static FAR char *nsh_filecat(FAR struct nsh_vtbl_s *vtbl, FAR char *s1, FAR const char *filename); static FAR char *nsh_cmdparm(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline, FAR char **allocation); #endif #ifdef CONFIG_NSH_ARGCAT static int nsh_strcat(FAR char *s1, FAR const char *s2); #endif static FAR char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr, FAR NSH_MEMLIST_TYPE *memlist); #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, FAR NSH_MEMLIST_TYPE *memlist); #endif #ifndef CONFIG_NSH_DISABLEBG static int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist); #endif #ifdef CONFIG_NSH_CMDPARMS 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 ****************************************************************************/ static const char g_token_separator[] = " \t\n"; static const char g_line_separator[] = "\";\n"; static const char g_redirect1[] = ">"; static const char g_redirect2[] = ">>"; static const char g_exitstatus[] = "$?"; static const char g_success[] = "0"; static const char g_failure[] = "1"; /**************************************************************************** * Public Data ****************************************************************************/ /* If NuttX versioning information is available, Include that information * in the NSH greeting. */ #if CONFIG_VERSION_MAJOR != 0 || CONFIG_VERSION_MINOR != 0 const char g_nshgreeting[] = "\nNuttShell (NSH) NuttX-" CONFIG_VERSION_STRING "\n"; #else const char g_nshgreeting[] = "\nNuttShell (NSH)\n"; #endif /* Telnet login prompts */ #if defined(CONFIG_NSH_TELNET_LOGIN) && defined(CONFIG_NSH_TELNET) const char g_telnetgreeting[] = "\nWelcome to NuttShell(NSH) Telnet Server...\n"; const char g_userprompt[] = "login: "; const char g_passwordprompt[] = "password: "; const char g_loginsuccess[] = "\nUser Logged-in!\n"; const char g_badcredentials[] = "\nInvalid username or password\n"; const char g_loginfailure[] = "Login failed!\n"; #endif /* The NSH prompt */ const char g_nshprompt[] = "nsh> "; /* Common, message formats */ const char g_nshsyntax[] = "nsh: %s: syntax error\n"; const char g_fmtargrequired[] = "nsh: %s: missing required argument(s)\n"; const char g_fmtnomatching[] = "nsh: %s: no matching %s\n"; const char g_fmtarginvalid[] = "nsh: %s: argument invalid\n"; const char g_fmtargrange[] = "nsh: %s: value out of range\n"; const char g_fmtcmdnotfound[] = "nsh: %s: command not found\n"; const char g_fmtnosuch[] = "nsh: %s: no such %s: %s\n"; const char g_fmttoomanyargs[] = "nsh: %s: too many arguments\n"; const char g_fmtdeepnesting[] = "nsh: %s: nesting too deep\n"; const char g_fmtcontext[] = "nsh: %s: not valid in this context\n"; #ifdef CONFIG_NSH_STRERROR const char g_fmtcmdfailed[] = "nsh: %s: %s failed: %s\n"; #else const char g_fmtcmdfailed[] = "nsh: %s: %s failed: %d\n"; #endif const char g_fmtcmdoutofmemory[] = "nsh: %s: out of memory\n"; const char g_fmtinternalerror[] = "nsh: %s: Internal error\n"; #ifndef CONFIG_DISABLE_SIGNALS const char g_fmtsignalrecvd[] = "nsh: %s: Interrupted by signal\n"; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: nsh_memlist_add ****************************************************************************/ #ifdef HAVE_MEMLIST static void nsh_memlist_add(FAR struct nsh_memlist_s *memlist, FAR char *address) { if (memlist) { int index = memlist->nallocs; if (index < CONFIG_NSH_MAXALLOCS) { memlist->allocations[index] = address; memlist->nallocs = index + 1; } } } #endif /**************************************************************************** * Name: nsh_memlist_free ****************************************************************************/ #ifdef HAVE_MEMLIST static void nsh_memlist_free(FAR struct nsh_memlist_s *memlist) { if (memlist) { int index; for (index = 0; index < memlist->nallocs; index++) { free(memlist->allocations[index]); memlist->allocations[index] = NULL; } memlist->nallocs = 0; } } #endif /**************************************************************************** * Name: nsh_releaseargs ****************************************************************************/ #ifndef CONFIG_NSH_DISABLEBG static void nsh_releaseargs(struct cmdarg_s *arg) { FAR struct nsh_vtbl_s *vtbl = arg->vtbl; int i; #if CONFIG_NFILE_STREAMS > 0 /* If the output was redirected, then file descriptor should * be closed. The created task has its one, independent copy of * the file descriptor */ if (vtbl->np.np_redirect) { (void)close(arg->fd); } #endif /* Released the cloned vtbl instance */ nsh_release(vtbl); /* Release the cloned args */ for (i = 0; i < arg->argc; i++) { free(arg->argv[i]); } free(arg); } #endif /**************************************************************************** * Name: nsh_child ****************************************************************************/ #ifndef CONFIG_NSH_DISABLEBG static pthread_addr_t nsh_child(pthread_addr_t arg) { struct cmdarg_s *carg = (struct cmdarg_s *)arg; int ret; dbg("BG %s\n", carg->argv[0]); /* Execute the specified command on the child thread */ ret = nsh_command(carg->vtbl, carg->argc, carg->argv); /* Released the cloned arguments */ dbg("BG %s complete\n", carg->argv[0]); nsh_releaseargs(carg); return (void*)ret; } #endif /**************************************************************************** * Name: nsh_cloneargs ****************************************************************************/ #ifndef CONFIG_NSH_DISABLEBG 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; if (ret) { ret->vtbl = vtbl; ret->fd = fd; ret->argc = argc; for (i = 0; i < argc; i++) { ret->argv[i] = strdup(argv[i]); } } return ret; } #endif /**************************************************************************** * Name: nsh_saveresult ****************************************************************************/ static int nsh_saveresult(FAR struct nsh_vtbl_s *vtbl, bool result) { struct nsh_parser_s *np = &vtbl->np; #ifndef CONFIG_NSH_DISABLESCRIPT if (np->np_st[np->np_ndx].ns_state == NSH_PARSER_IF) { np->np_fail = false; np->np_st[np->np_ndx].ns_ifcond = result; return OK; } else #endif { np->np_fail = result; return result ? ERROR : OK; } } /**************************************************************************** * Name: nsh_execute ****************************************************************************/ static int nsh_execute(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char *argv[], FAR const char *redirfile, int oflags) { int fd = -1; int ret; /* Does this command correspond to an application filename? * nsh_fileapp() returns: * * -1 (ERROR) if the application task corresponding to 'argv[0]' could not * be started (possibly because it does not exist). * 0 (OK) if the application task corresponding to 'argv[0]' was * and successfully started. If CONFIG_SCHED_WAITPID is * defined, this return value also indicates that the * application returned successful status (EXIT_SUCCESS) * 1 If CONFIG_SCHED_WAITPID is defined, then this return value * indicates that the application task was spawned successfully * but returned failure exit status. * * Note the priority if not effected by nice-ness. */ #ifdef CONFIG_NSH_FILE_APPS ret = nsh_fileapp(vtbl, argv[0], argv, redirfile, oflags); if (ret >= 0) { /* nsh_fileapp() returned 0 or 1. This means that the built-in * command was successfully started (although it may not have ran * successfully). So certainly it is not an NSH command. */ /* Save the result: success if 0; failure if 1 */ return nsh_saveresult(vtbl, ret != OK); } /* No, not a file name command (or, at least, we were unable to start a * program of that name). Maybe it is a built-in application or an NSH * command. */ #endif /* Does this command correspond to a built-in command? * nsh_builtin() returns: * * -1 (ERROR) if the application task corresponding to 'argv[0]' could not * be started (possibly because it doesn not exist). * 0 (OK) if the application task corresponding to 'argv[0]' was * and successfully started. If CONFIG_SCHED_WAITPID is * defined, this return value also indicates that the * application returned successful status (EXIT_SUCCESS) * 1 If CONFIG_SCHED_WAITPID is defined, then this return value * indicates that the application task was spawned successfully * but returned failure exit status. * * Note the priority if not effected by nice-ness. */ #if defined(CONFIG_NSH_BUILTIN_APPS) && (!defined(CONFIG_NSH_FILE_APPS) || !defined(CONFIG_FS_BINFS)) #if CONFIG_NFILE_STREAMS > 0 ret = nsh_builtin(vtbl, argv[0], argv, redirfile, oflags); #else ret = nsh_builtin(vtbl, argv[0], argv, NULL, 0); #endif if (ret >= 0) { /* nsh_builtin() returned 0 or 1. This means that the built-in * command was successfully started (although it may not have ran * successfully). So certainly it is not an NSH command. */ /* Save the result: success if 0; failure if 1 */ return nsh_saveresult(vtbl, ret != OK); } /* No, not a built in command (or, at least, we were unable to start a * built-in command of that name). Treat it like an NSH command. */ #endif #if CONFIG_NFILE_STREAMS > 0 /* Redirected output? */ if (vtbl->np.np_redirect) { /* Open the redirection file. This file will eventually * be closed by a call to either nsh_release (if the command * is executed in the background) or by nsh_undirect if the * command is executed in the foreground. */ fd = open(redirfile, oflags, 0666); if (fd < 0) { nsh_output(vtbl, g_fmtcmdfailed, argv[0], "open", NSH_ERRNO); goto errout; } } #endif /* Handle the case where the command is executed in background. * However is app is to be started as built-in new process will * be created anyway, so skip this step. */ #ifndef CONFIG_NSH_DISABLEBG if (vtbl->np.np_bg) { struct sched_param param; struct nsh_vtbl_s *bkgvtbl; struct cmdarg_s *args; pthread_attr_t attr; pthread_t thread; /* Get a cloned copy of the vtbl with reference count=1. * after the command has been processed, the nsh_release() call * at the end of nsh_child() will destroy the clone. */ bkgvtbl = nsh_clone(vtbl); if (!bkgvtbl) { goto errout_with_redirect; } /* Create a container for the command arguments */ args = nsh_cloneargs(bkgvtbl, fd, argc, argv); if (!args) { nsh_release(bkgvtbl); goto errout_with_redirect; } #if CONFIG_NFILE_STREAMS > 0 /* Handle redirection of output via a file descriptor */ if (vtbl->np.np_redirect) { (void)nsh_redirect(bkgvtbl, fd, NULL); } #endif /* Get the execution priority of this task */ ret = sched_getparam(0, ¶m); if (ret != 0) { nsh_output(vtbl, g_fmtcmdfailed, argv[0], "sched_getparm", NSH_ERRNO); nsh_releaseargs(args); nsh_release(bkgvtbl); goto errout; } /* Determine the priority to execute the command */ if (vtbl->np.np_nice != 0) { int priority = param.sched_priority - vtbl->np.np_nice; if (vtbl->np.np_nice < 0) { int max_priority = sched_get_priority_max(SCHED_NSH); if (priority > max_priority) { priority = max_priority; } } else { int min_priority = sched_get_priority_min(SCHED_NSH); if (priority < min_priority) { priority = min_priority; } } param.sched_priority = priority; } /* Set up the thread attributes */ (void)pthread_attr_init(&attr); (void)pthread_attr_setschedpolicy(&attr, SCHED_NSH); (void)pthread_attr_setschedparam(&attr, ¶m); /* Execute the command as a separate thread at the appropriate priority */ ret = pthread_create(&thread, &attr, nsh_child, (pthread_addr_t)args); if (ret != 0) { nsh_output(vtbl, g_fmtcmdfailed, argv[0], "pthread_create", NSH_ERRNO_OF(ret)); nsh_releaseargs(args); nsh_release(bkgvtbl); goto errout; } /* Detach from the pthread since we are not going to join with it. * Otherwise, we would have a memory leak. */ (void)pthread_detach(thread); nsh_output(vtbl, "%s [%d:%d]\n", argv[0], thread, param.sched_priority); } else #endif { #if CONFIG_NFILE_STREAMS > 0 uint8_t save[SAVE_SIZE]; /* Handle redirection of output via a file descriptor */ if (vtbl->np.np_redirect) { nsh_redirect(vtbl, fd, save); } #endif /* Then execute the command in "foreground" -- i.e., while the user waits * for the next prompt. nsh_command will return: * * -1 (ERRROR) if the command was unsuccessful * 0 (OK) if the command was successful */ ret = nsh_command(vtbl, argc, argv); #if CONFIG_NFILE_STREAMS > 0 /* Restore the original output. Undirect will close the redirection * file descriptor. */ if (vtbl->np.np_redirect) { nsh_undirect(vtbl, save); } #endif /* Mark errors so that it is possible to test for non-zero return values * in nsh scripts. */ if (ret < 0) { goto errout; } } /* Return success if the command succeeded (or at least, starting of the * command task succeeded). */ return nsh_saveresult(vtbl, false); #ifndef CONFIG_NSH_DISABLEBG errout_with_redirect: #if CONFIG_NFILE_STREAMS > 0 if (vtbl->np.np_redirect) { close(fd); } #endif #endif errout: return nsh_saveresult(vtbl, true); } /**************************************************************************** * Name: nsh_filecat ****************************************************************************/ #ifdef CONFIG_NSH_CMDPARMS static FAR char *nsh_filecat(FAR struct nsh_vtbl_s *vtbl, FAR char *s1, FAR const char *filename) { struct stat buf; size_t s1size = 0; size_t allocsize; ssize_t nbytesread; FAR char *argument; int index; int fd; int ret; /* Get the size of the string */ if (s1) { s1size = (size_t)strlen(s1); } /* Get the size of file */ ret = stat(filename, &buf); if (ret != 0) { nsh_output(vtbl, g_fmtcmdfailed, "``", "stat", NSH_ERRNO); return NULL; } /* Get the total allocation size */ allocsize = s1size + (size_t)buf.st_size + 1; argument = (FAR char *)realloc(s1, allocsize); if (!argument) { nsh_output(vtbl, g_fmtcmdoutofmemory, "``"); return NULL; } /* Open the source file for reading */ fd = open(filename, O_RDONLY); if (fd < 0) { nsh_output(vtbl, g_fmtcmdfailed, "``", "open", NSH_ERRNO); goto errout_with_alloc; } /* Now copy the file. Loop until the entire file has been transferred to * the allocated string (after the original contents of s1size bytes. */ for (index = s1size; index < allocsize - 1; ) { /* Loop until we successfully read something , we encounter the * end-of-file, or until a read error occurs */ do { nbytesread = read(fd, &argument[index], IOBUFFERSIZE); if (nbytesread == 0) { /* Unexpected end of file -- Break out of the loop */ break; } else if (nbytesread < 0) { /* EINTR is not an error (but will still stop the copy) */ #ifndef CONFIG_DISABLE_SIGNALS if (errno == EINTR) { nsh_output(vtbl, g_fmtsignalrecvd, "``"); } else #endif { /* Read error */ nsh_output(vtbl, g_fmtcmdfailed, "``", "read", NSH_ERRNO); } goto errout_with_fd; } } while (nbytesread <= 0); /* Update the index based upon the number of bytes read */ index += nbytesread; } /* Make sure that the new string is null terminated */ argument[index] = '\0'; return argument; errout_with_fd: close(fd); errout_with_alloc: free(argument); return NULL; } #endif /**************************************************************************** * Name: nsh_cmdparm ****************************************************************************/ #ifdef CONFIG_NSH_CMDPARMS static FAR char *nsh_cmdparm(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline, FAR char **allocation) { FAR char *tmpfile; FAR char *argument; int ret; /* We cannot process the command argument if there is no allocation pointer */ if (!allocation) { return NULL; } /* 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, cmdline, tmpfile); if (ret != OK) { /* Report the failure */ nsh_output(vtbl, g_fmtcmdfailed, "``", "exec", NSH_ERRNO); free(tmpfile); return NULL; } /* Concatenate the file contents with the current allocation */ argument = nsh_filecat(vtbl, *allocation, tmpfile); *allocation = argument; /* We can now 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 argument; } #endif /**************************************************************************** * Name: nsh_strcat ****************************************************************************/ #ifdef CONFIG_NSH_ARGCAT static int nsh_strcat(FAR char *s1, FAR const char *s2) { } #endif /**************************************************************************** * Name: nsh_argument ****************************************************************************/ static FAR char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist) { FAR char *pbegin = *saveptr; FAR char *pend = NULL; FAR char *allocation = NULL; FAR char *argument = NULL; FAR const char *term; #ifndef CONFIG_DISABLE_ENVIRON bool quoted = false; #endif /* Find the beginning of the next token */ for (; *pbegin && strchr(g_token_separator, *pbegin) != NULL; pbegin++); /* If we are at the end of the string with nothing but delimiters found, * then return NULL, meaning that there are no further arguments on the line. */ if (!*pbegin) { return NULL; } /* Does the token begin with '>' -- redirection of output? */ if (*pbegin == '>') { /* Yes.. does it begin with ">>"? */ if (*(pbegin + 1) == '>') { *saveptr = pbegin + 2; pbegin = (char*)g_redirect2; } else { *saveptr = pbegin + 1; pbegin = (char*)g_redirect1; } } /* Does the token begin with '#' -- comment */ else if (*pbegin == '#') { /* Return NULL meaning that we are at the end of the line */ *saveptr = pbegin; pbegin = NULL; } #ifdef CONFIG_NSH_CMDPARMS /* Are we being asked to use the output from another command or program * as an input parameters for this command? */ else if (*pbegin == '`') { /* Yes, find the terminating 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; /* Then execute the command to get the paramter value */ argument = nsh_cmdparm(vtbl, pbegin, &allocation); } #endif /* Otherwise, it is a normal string and we have to parse using the normal * rules to find the end of the argument. */ else { /* However, the rules are a little different if the next argument is * a quoted string. */ if (*pbegin == '"') { /* A quoted string can only be terminated with another quotation * mark. */ pbegin++; term = "\""; #ifndef CONFIG_DISABLE_ENVIRON quoted = true; #endif } else { /* No, then any of the usual separators will terminate the argument */ term = g_token_separator; } /* Find the end of the string */ for (pend = pbegin + 1; *pend && strchr(term, *pend) == NULL; pend++); /* pend either points to the end of the string or to the first * delimiter after the string. */ if (*pend) { /* Turn the delimiter into a NUL terminator */ *pend++ = '\0'; } /* Save the pointer where we left off */ *saveptr = pend; #ifndef CONFIG_DISABLE_ENVIRON /* Check for references to environment variables */ if (pbegin[0] == '$' && !quoted) { /* Check for built-in variables */ if (strcmp(pbegin, g_exitstatus) == 0) { if (vtbl->np.np_fail) { return (char*)g_failure; } else { return (char*)g_success; } } /* Not a built-in? Return the value of the environment variable * with this name. */ else { char *value = getenv(pbegin+1); if (value) { return value; } else { return (char*)""; } } } #endif /* The argument to be returned is simply the beginning of the * delimited string. */ argument = pbegin; } /* If any memory was allocated for this argument, make sure that it is * added to the list of memory to be freed at the end of commend * processing. */ if (allocation) { NSH_MEMLIST_ADD(memlist, allocation); } /* Return the parsed argument. */ return argument; } /**************************************************************************** * Name: nsh_cmdenabled ****************************************************************************/ #ifndef CONFIG_NSH_DISABLESCRIPT 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; if (ret) { switch (np->np_st[np->np_ndx].ns_state) { case NSH_PARSER_NORMAL : case NSH_PARSER_IF: default: break; case NSH_PARSER_THEN: ret = !np->np_st[np->np_ndx].ns_ifcond; break; case NSH_PARSER_ELSE: ret = np->np_st[np->np_ndx].ns_ifcond; break; } } return ret; } #endif /**************************************************************************** * Name: nsh_ifthenelse ****************************************************************************/ #ifndef CONFIG_NSH_DISABLESCRIPT static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist) { struct nsh_parser_s *np = &vtbl->np; FAR char *cmd = *ppcmd; bool disabled; if (cmd) { /* Check if the command is preceeded by "if" */ if (strcmp(cmd, "if") == 0) { /* Get the cmd following the if */ *ppcmd = nsh_argument(vtbl, saveptr, memlist); if (!*ppcmd) { nsh_output(vtbl, g_fmtarginvalid, "if"); goto errout; } /* Verify that "if" is valid in this context */ if (np->np_st[np->np_ndx].ns_state != NSH_PARSER_NORMAL && np->np_st[np->np_ndx].ns_state != NSH_PARSER_THEN && np->np_st[np->np_ndx].ns_state != NSH_PARSER_ELSE) { nsh_output(vtbl, g_fmtcontext, "if"); goto errout; } /* Check if we have exceeded the maximum depth of nesting */ if (np->np_ndx >= CONFIG_NSH_NESTDEPTH-1) { nsh_output(vtbl, g_fmtdeepnesting, "if"); goto errout; } /* "Push" the old state and set the new state */ disabled = !nsh_cmdenabled(vtbl); np->np_ndx++; np->np_st[np->np_ndx].ns_state = NSH_PARSER_IF; np->np_st[np->np_ndx].ns_disabled = disabled; np->np_st[np->np_ndx].ns_ifcond = false; } else if (strcmp(cmd, "then") == 0) { /* Get the cmd following the then -- there shouldn't be one */ *ppcmd = nsh_argument(vtbl, saveptr, memlist); if (*ppcmd) { nsh_output(vtbl, g_fmtarginvalid, "then"); goto errout; } /* Verify that "then" is valid in this context */ if (np->np_st[np->np_ndx].ns_state != NSH_PARSER_IF) { nsh_output(vtbl, g_fmtcontext, "then"); goto errout; } np->np_st[np->np_ndx].ns_state = NSH_PARSER_THEN; } else if (strcmp(cmd, "else") == 0) { /* Get the cmd following the else -- there shouldn't be one */ *ppcmd = nsh_argument(vtbl, saveptr, memlist); if (*ppcmd) { nsh_output(vtbl, g_fmtarginvalid, "else"); goto errout; } /* Verify that "then" is valid in this context */ if (np->np_st[np->np_ndx].ns_state != NSH_PARSER_THEN) { nsh_output(vtbl, g_fmtcontext, "else"); goto errout; } np->np_st[np->np_ndx].ns_state = NSH_PARSER_ELSE; } else if (strcmp(cmd, "fi") == 0) { /* Get the cmd following the fi -- there should be one */ *ppcmd = nsh_argument(vtbl, saveptr, memlist); if (*ppcmd) { nsh_output(vtbl, g_fmtarginvalid, "fi"); goto errout; } /* Verify that "fi" is valid in this context */ if (np->np_st[np->np_ndx].ns_state != NSH_PARSER_THEN && np->np_st[np->np_ndx].ns_state != NSH_PARSER_ELSE) { nsh_output(vtbl, g_fmtcontext, "fi"); goto errout; } if (np->np_ndx < 1) /* Shouldn't happen */ { nsh_output(vtbl, g_fmtinternalerror, "if"); goto errout; } /* "Pop" the previous state */ np->np_ndx--; } else if (np->np_st[np->np_ndx].ns_state == NSH_PARSER_IF) { nsh_output(vtbl, g_fmtcontext, cmd); goto errout; } } return OK; errout: np->np_ndx = 0; np->np_st[0].ns_state = NSH_PARSER_NORMAL; np->np_st[0].ns_disabled = false; np->np_st[0].ns_ifcond = false; return ERROR; } #endif /**************************************************************************** * Name: nsh_nice ****************************************************************************/ #ifndef CONFIG_NSH_DISABLEBG static int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist) { FAR char *cmd = *ppcmd; vtbl->np.np_nice = 0; if (cmd) { /* Check if the command is preceded by "nice" */ if (strcmp(cmd, "nice") == 0) { /* Nicenesses range from -20 (most favorable scheduling) to 19 * (least favorable). Default is 10. */ vtbl->np.np_nice = 10; /* Get the cmd (or -d option of nice command) */ cmd = nsh_argument(vtbl, saveptr, memlist); if (cmd && strcmp(cmd, "-d") == 0) { FAR char *val = nsh_argument(vtbl, saveptr, memlist); if (val) { char *endptr; vtbl->np.np_nice = (int)strtol(val, &endptr, 0); if (vtbl->np.np_nice > 19 || vtbl->np.np_nice < -20 || endptr == val || *endptr != '\0') { nsh_output(vtbl, g_fmtarginvalid, "nice"); return ERROR; } cmd = nsh_argument(vtbl, saveptr, memlist); } } /* Return the real command name */ *ppcmd = cmd; } } return OK; } #endif /**************************************************************************** * Name: nsh_parse_funcparm * * Description: * This function parses and executes a simple NSH command. Output is * always redirected. This function supports command parameters like * * set FOO `hello` * * which would set the environment variable FOO to the output from * the hello program * ****************************************************************************/ #ifdef CONFIG_NSH_CMDPARMS static int nsh_parse_funcparm(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline, FAR const char *redirfile) { NSH_MEMLIST_TYPE memlist; 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 *)); NSH_MEMLIST_INIT(memlist); /* 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 command is never backgrounded . Remember the current backgrounding * 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, &memlist); /* 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 it change the last command * status. */ NSH_MEMLIST_FREE(&memlist); 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, &memlist); 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; NSH_MEMLIST_FREE(&memlist); return ret; } #endif /**************************************************************************** * Name: nsh_parse_command * * Description: * This function parses and executes one NSH command from the command line. * ****************************************************************************/ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) { NSH_MEMLIST_TYPE memlist; FAR char *argv[MAX_ARGV_ENTRIES]; FAR char *saveptr; FAR char *cmd; #if CONFIG_NFILE_STREAMS > 0 FAR char *redirfile = NULL; int oflags = 0; #endif int argc; int ret; /* Initialize parser state */ memset(argv, 0, MAX_ARGV_ENTRIES*sizeof(FAR char *)); NSH_MEMLIST_INIT(memlist); #ifndef CONFIG_NSH_DISABLEBG vtbl->np.np_bg = false; #endif #if CONFIG_NFILE_STREAMS > 0 vtbl->np.np_redirect = false; #endif /* Parse out the command at the beginning of the line */ saveptr = cmdline; cmd = nsh_argument(vtbl, &saveptr, &memlist); /* Handler if-then-else-fi */ #ifndef CONFIG_NSH_DISABLESCRIPT if (nsh_ifthenelse(vtbl, &cmd, &saveptr, &memlist) != 0) { NSH_MEMLIST_FREE(&memlist); return nsh_saveresult(vtbl, true); } #endif /* Handle nice */ #ifndef CONFIG_NSH_DISABLEBG if (nsh_nice(vtbl, &cmd, &saveptr, &memlist) != 0) { NSH_MEMLIST_FREE(&memlist); return nsh_saveresult(vtbl, true); } #endif /* 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 it change the last command * status. */ NSH_MEMLIST_FREE(&memlist); 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-3]: Possibly '>' or '>>' * argv[argc-2]: Possibly * argv[argc-1]: Possibly '&' * argv[argc]: NULL terminating pointer * * Maximum size is CONFIG_NSH_MAXARGUMENTS+5 */ argv[0] = cmd; for (argc = 1; argc < MAX_ARGV_ENTRIES-1; argc++) { argv[argc] = nsh_argument(vtbl, &saveptr, &memlist); if (!argv[argc]) { break; } } argv[argc] = NULL; /* Check if the command should run in background */ #ifndef CONFIG_NSH_DISABLEBG if (argc > 1 && strcmp(argv[argc-1], "&") == 0) { vtbl->np.np_bg = true; argv[argc-1] = NULL; argc--; } #endif #if CONFIG_NFILE_STREAMS > 0 /* Check if the output was re-directed using > or >> */ if (argc > 2) { /* Check for redirection to a new file */ if (strcmp(argv[argc-2], g_redirect1) == 0) { vtbl->np.np_redirect = true; oflags = O_WRONLY|O_CREAT|O_TRUNC; redirfile = nsh_getfullpath(vtbl, argv[argc-1]); argc -= 2; } /* Check for redirection by appending to an existing file */ else if (strcmp(argv[argc-2], g_redirect2) == 0) { vtbl->np.np_redirect = true; oflags = O_WRONLY|O_CREAT|O_APPEND; redirfile = nsh_getfullpath(vtbl, argv[argc-1]); argc -= 2; } } #endif /* 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, oflags); /* Free any allocated resources */ #if CONFIG_NFILE_STREAMS > 0 /* Free the redirected output file path */ if (redirfile) { nsh_freefullpath(redirfile); } #endif NSH_MEMLIST_FREE(&memlist); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: nsh_parse * * Description: * This function parses and executes the line of text received from the * user. This may consist of one or more NSH commands. Multiple NSH * commands are separated by semi-colons. * ****************************************************************************/ int nsh_parse(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) { #ifdef NSH_DISABLE_SEMICOLON return nsh_parse_command(vtbl, cmdline); #else FAR char *start = cmdline; FAR char *working = cmdline; FAR char *ptr; size_t len; int ret; /* Loop until all of the commands on the command line have been processed */ for (;;) { /* A command may be terminated with a newline character, the end of the * line, or a semicolon. NOTE that the set of delimiting characters * includes the quotation mark. We need to handle quotation marks here * because a semicolon or newline character within a quoted string must * be ignored. */ len = strcspn(working, g_line_separator); ptr = working + len; /* Check for the last command on the line. This means that the none * of the delimiting characters was found or that the newline character * was found. Anything after the newline character is ignored (there * should not be anything. */ if (*ptr == '\0' || *ptr == '\n') { /* Parse the last command on the line */ return nsh_parse_command(vtbl, start); } /* Check for a command terminated with ';'. There is probably another * command on the command line after this one. */ else if (*ptr == ';') { /* Terminate the line */ *ptr++ = '\0'; /* Parse this command */ ret = nsh_parse_command(vtbl, start); if (ret != OK) { /* nsh_parse_command may return (1) -1 (ERROR) meaning that the * command failed or we failed to start the command application * or (2) 1 meaning that the application task was spawned * successfully but returned failure exit status. */ return ret; } /* Then set the start of the next command on the command line */ start = ptr; working = ptr; } /* Check if we encountered a quoted string */ else /* if (*ptr == '"') */ { /* Find the closing quotation mark */ FAR char *tmp = strchr(ptr, '"'); if (!tmp) { /* No closing quotation mark! */ nsh_output(vtbl, g_fmtnomatching, "[\"]", "[\"]"); return ERROR; } /* Otherwise, continue parsing after the closing quotation mark */ working = tmp++; } } #endif }