Newer
Older
/****************************************************************************
* Copyright (C) 2007, 2008 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <spudmonkey@racsa.co.cr>
*
* 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.
*
****************************************************************************/
/****************************************************************************
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
/****************************************************************************
****************************************************************************/
/****************************************************************************
****************************************************************************/
const char *cmd; /* Name of the command */
cmd_t handler; /* Function that handles the command */
ubyte minargs; /* Minimum number of arguments (including command) */
ubyte maxargs; /* Maximum number of arguments (including command) */
const char *usage; /* Usage instructions for 'help' command */
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int cmd_help(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv);
static int cmd_exit(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv);
static int cmd_unrecognized(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv);
/****************************************************************************
****************************************************************************/
static const char g_delim[] = " \t\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";
static const struct cmdmap_s g_cmdmap[] =
{
{ "cat", cmd_cat, 2, 2, "<path>" },
{ "cp", cmd_cp, 3, 3, "<source-path> <dest-path>" },
{ "echo", cmd_echo, 0, NSH_MAX_ARGUMENTS, "[<string|$name> [<string|$name>...]]" },
{ "echo", cmd_echo, 0, NSH_MAX_ARGUMENTS, "[<string> [<string>...]]" },
{ "exec", cmd_exec, 2, 3, "<hex-address>" },
{ "exit", cmd_exit, 1, 1, NULL },
{ "help", cmd_help, 1, 1, NULL },
#if defined(CONFIG_NET) && CONFIG_NSOCKET_DESCRIPTORS > 0
{ "ifconfig", cmd_ifconfig, 1, 1, NULL },
#endif
#if !defined(CONFIG_DISABLE_MOUNTPOINT) && CONFIG_NFILE_DESCRIPTORS > 0
#endif
{ "mkfifo", cmd_mkfifo, 2, 2, "<path>" },
#ifdef CONFIG_FS_FAT /* Need at least one filesytem in configuration */
{ "mount", cmd_mount, 4, 5, "-t <fstype> <block-device> <dir-path>" },
#if !defined(CONFIG_DISABLE_MOUNTPOINT) && CONFIG_NFILE_DESCRIPTORS > 0
{ "rm", cmd_rm, 2, 2, "<file-path>" },
{ "rmdir", cmd_rmdir, 2, 2, "<dir-path>" },
#if CONFIG_NFILE_DESCRIPTORS > 0 && CONFIG_NFILE_STREAMS > 0
{ "sh", cmd_sh, 2, 2, "<script-path>" },
# endif /* CONFIG_NFILE_STREAMS */
#ifndef CONFIG_DISABLE_SIGNALS
{ "sleep", cmd_sleep, 2, 2, "<sec>" },
#endif /* CONFIG_DISABLE_SIGNALS */
#if !defined(CONFIG_DISABLE_MOUNTPOINT) && CONFIG_NFILE_DESCRIPTORS > 0
# ifdef CONFIG_FS_FAT /* Need at least one filesytem in configuration */
#ifndef CONFIG_DISABLE_SIGNALS
{ "usleep", cmd_usleep, 2, 2, "<usec>" },
#endif /* CONFIG_DISABLE_SIGNALS */
/****************************************************************************
****************************************************************************/
const char g_fmtargrequired[] = "nsh: %s: missing required argument(s)\n";
const char g_fmtarginvalid[] = "nsh: %s: argument invalid\n";
const char g_fmtcmdnotfound[] = "nsh: %s: command not found\n";
const char g_fmtcmdnotimpl[] = "nsh: %s: command not implemented\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_fmtcontext[] = "nsh: %s: not valid in this context\n";
#else
const char g_fmtcmdfailed[] = "nsh: %s: %s failed: %d\n";
#endif
const char g_fmtcmdoutofmemory[] = "nsh: %s: out of memory\n";
/****************************************************************************
****************************************************************************/
/****************************************************************************
****************************************************************************/
static int cmd_help(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv)
{
const struct cmdmap_s *ptr;
nsh_output(vtbl, "NSH command forms:\n");
nsh_output(vtbl, " [nice [-d <niceness>>]] <cmd> [[> <file>|>> <file>] &]\n");
nsh_output(vtbl, "OR\n");
nsh_output(vtbl, " if <cmd>\n");
nsh_output(vtbl, " then\n");
nsh_output(vtbl, " [sequence of <cmd>]\n");
nsh_output(vtbl, " else\n");
nsh_output(vtbl, " [sequence of <cmd>]\n");
nsh_output(vtbl, " fi\n");
for (ptr = g_cmdmap; ptr->cmd; ptr++)
{
if (ptr->usage)
{
/****************************************************************************
****************************************************************************/
static int cmd_unrecognized(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv)
/****************************************************************************
* Name: cmd_exit
****************************************************************************/
static int cmd_exit(FAR struct nsh_vtbl_s *vtbl, int argc, char **argv)
/****************************************************************************
* Name: nsh_execute
****************************************************************************/
static int nsh_execute(int argc, char *argv[])
{
const struct cmdmap_s *cmdmap;
const char *cmd;
/* Parse all of the arguments following the command name. The form
* of argv is:
*
* argv[0]: Task name "nsh_execute"
* argv[1]: This is string version of the vtbl needed to execute
* the command (under telnetd). It is a string because
* binary values cannot be provided via char *argv[]
* argv[2]: The command name. This is argv[0] when the arguments
* are, finally, received by the command vtblr
* argv[3]: The beginning of argument (up to NSH_MAX_ARGUMENTS)
* argv[argc]: NULL terminating pointer
*
* Maximum size is NSH_MAX_ARGUMENTS+4
cmd = argv[2];
argc -= 2;
/* See if the command is one that we understand */
for (cmdmap = g_cmdmap; cmdmap->cmd; cmdmap++)
{
if (strcmp(cmdmap->cmd, cmd) == 0)
{
/* Check if a valid number of arguments was provided. We
* do this simple, imperfect checking here so that it does
* not have to be performed in each command.
*/
if (argc < cmdmap->minargs)
{
/* Fewer than the minimum number were provided */
return ERROR;
}
else if (argc > cmdmap->maxargs)
{
/* More than the maximum number were provided */
return ERROR;
}
else
{
/* A valid number of arguments were provided (this does
* not mean they are right).
*/
handler = cmdmap->handler;
break;
}
}
}
}
/****************************************************************************
****************************************************************************/
char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr)
char *pbegin = *saveptr;
char *pend = NULL;
const char *term;
#ifndef CONFIG_DISABLE_ENVIRON
boolean quoted = FALSE;
#endif
/* Find the beginning of the next token */
for (;
*pbegin && strchr(g_delim, *pbegin) != NULL;
pbegin++);
/* If we are at the end of the string with nothing
* but delimiters found, then return NULL.
*/
if (!*pbegin)
{
return NULL;
}
/* Does the token begin with '>' */
if (*pbegin == '>')
{
/* Yes.. does it begin with ">>"? */
if (*(pbegin + 1) == '>')
{
*saveptr = pbegin + 2;
if (*pbegin == '"')
{
/* Yes.. then only another '"' can terminate the string */
pbegin++;
term = "\"";
#ifndef CONFIG_DISABLE_ENVIRON
quoted = TRUE;
#endif
}
else
{
/* No, then any of the usual terminators will terminate the argument */
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 null terminator */
*pend++ = '\0';
}
/* Save the pointer where we left off */
*saveptr = pend;
}
/* Check for references to environment variables */
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 */
#ifndef CONFIG_DISABLE_ENVIRON
char *value = getenv(pbegin+1);
if (value)
{
return value;
}
else
{
return (char*)"";
}
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
/****************************************************************************
* Name: nsh_ifthenelse
****************************************************************************/
static inline int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, FAR char **saveptr)
{
FAR char *cmd = *ppcmd;
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);
if (!*ppcmd)
{
nsh_output(vtbl, g_fmtarginvalid, "if");
return ERROR;
}
/* Verify that "if" is valid in this context */
if (vtbl->np.np_state != NSH_PARSER_NORMAL)
{
nsh_output(vtbl, g_fmtcontext, "if");
return ERROR;
}
vtbl->np.np_state = NSH_PARSER_IF;
}
else if (strcmp(cmd, "then") == 0)
{
/* Get the cmd following the then -- there shouldn't be one */
*ppcmd = nsh_argument(vtbl, saveptr);
if (*ppcmd)
{
nsh_output(vtbl, g_fmtarginvalid, "then");
return ERROR;
}
/* Verify that "then" is valid in this context */
if (vtbl->np.np_state != NSH_PARSER_IF)
{
nsh_output(vtbl, g_fmtcontext, "then");
return ERROR;
}
vtbl->np.np_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);
if (*ppcmd)
{
nsh_output(vtbl, g_fmtarginvalid, "else");
return ERROR;
}
/* Verify that "then" is valid in this context */
if (vtbl->np.np_state != NSH_PARSER_THEN)
{
nsh_output(vtbl, g_fmtcontext, "else");
return ERROR;
}
vtbl->np.np_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);
if (*ppcmd)
{
nsh_output(vtbl, g_fmtarginvalid, "fi");
return ERROR;
}
/* Verify that "fi" is valid in this context */
if (vtbl->np.np_state != NSH_PARSER_THEN && vtbl->np.np_state != NSH_PARSER_ELSE)
{
nsh_output(vtbl, g_fmtcontext, "fi");
return ERROR;
}
vtbl->np.np_state = NSH_PARSER_NORMAL;
}
else if (vtbl->np.np_state == NSH_PARSER_IF)
{
nsh_output(vtbl, g_fmtcontext, cmd);
return ERROR;
}
}
return OK;
}
/****************************************************************************
* Name: nsh_cmdenabled
****************************************************************************/
static inline boolean nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl)
{
switch (vtbl->np.np_state)
{
case NSH_PARSER_NORMAL :
case NSH_PARSER_IF:
default:
break;
case NSH_PARSER_THEN:
return !vtbl->np.np_ifcond;
case NSH_PARSER_ELSE:
return vtbl->np.np_ifcond;
}
return TRUE;
}
/****************************************************************************
* Name: nsh_saveresult
****************************************************************************/
static inline void nsh_saveresult(FAR struct nsh_vtbl_s *vtbl, boolean result)
{
vtbl->np.np_fail = result;
if (vtbl->np.np_state == NSH_PARSER_IF)
{
vtbl->np.np_ifcond = result;
}
}
/****************************************************************************
* Name: nsh_nice
****************************************************************************/
static inline int nsh_nice(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, FAR char **saveptr)
{
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);
if (cmd && strcmp(cmd, "-d") == 0)
{
FAR char *val = nsh_argument(vtbl, saveptr);
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);
}
}
/* Return the real command name */
*ppcmd = cmd;
}
}
return OK;
}
/****************************************************************************
****************************************************************************/
/****************************************************************************
****************************************************************************/
void user_initialize(void)
{
/* stub */
}
/****************************************************************************
****************************************************************************/
int user_start(int argc, char *argv[])
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
int mid_priority;
int ret;
/* Set the priority of this task to something in the middle so that 'nice'
* can both raise and lower the priority.
*/
mid_priority = (sched_get_priority_max(SCHED_RR) + sched_get_priority_min(SCHED_RR)) >> 1;
{
struct sched_param param;
param.sched_priority = mid_priority;
(void)sched_setscheduler(0, SCHED_RR, ¶m);
}
/* If both the console and telnet are selected as front-ends, then run
* the telnet front end on another thread.
*/
#if defined(CONFIG_EXAMPLES_NSH_CONSOLE) && defined(CONFIG_EXAMPLES_NSH_TELNET)
# ifndef CONFIG_CUSTOM_STACK
ret = task_create("nsh_telnetmain", mid_priority, CONFIG_EXAMPLES_NSH_STACKSIZE,
nsh_telnetmain, NULL);
# else
ret = task_create("nsh_telnetmain", mid_priority, nsh_telnetmain, NULL);
# endif
if (ret < 0)
{
fprintf(stderr, g_fmtcmdfailed, "user_start", "task_create", NSH_ERRNO);
}
/* If only the telnet front-end is selected, run it on this thread */
#elif defined(CONFIG_EXAMPLES_NSH_TELNET)
return nsh_telnetmain(0, NULL);
#endif
/* If the serial console front end is selected, then run it on this thread */
#ifdef CONFIG_EXAMPLES_NSH_CONSOLE
return nsh_consolemain(0, NULL);
#endif
/****************************************************************************
* Name: nsh_parse
****************************************************************************/
int nsh_parse(FAR struct nsh_vtbl_s *vtbl, char *cmdline)
FAR char *argv[NSH_MAX_ARGUMENTS+7];
FAR char strvtbl[2*sizeof(FAR char*)+3];
vtbl->np.np_bg = FALSE;
vtbl->np.np_redirect = FALSE;
/* Parse out the command at the beginning of the line */
if (nsh_ifthenelse(vtbl, &cmd, &saveptr) != 0)
{
goto errout;
}
if (nsh_nice(vtbl, &cmd, &saveptr) != 0)
{
goto errout;
/* Check if any command was provided -OR- if command processing is
* currently disabled.
*/
/* 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]: Not really used. It is just a place hold for where the
* task name would be if the same command were executed
* in the "background"
* argv[1]: This is string version of the vtbl needed to execute
* the command (under telnetd). It is a string because
* binary values cannot be provided via char *argv[]. NOTE
* that this value is filled in later.
* argv[2]: The command name. This is argv[0] when the arguments
* are, finally, received by the command vtblr
* argv[3]: The beginning of argument (up to NSH_MAX_ARGUMENTS)
* argv[argc-3]: Possibly '>' or '>>'
* argv[argc-2]: Possibly <file>
* argv[argc-1]: Possibly '&'
* argv[argc]: NULL terminating pointer
*
* Maximum size is NSH_MAX_ARGUMENTS+7
*/
argv[0] = "nsh_execute";
argv[2] = cmd;
for (argc = 3; argc < NSH_MAX_ARGUMENTS+6; argc++)
{
argv[argc] = nsh_argument(vtbl, &saveptr);
argv[argc-1] = NULL;
argc--;
}
/* Check if the output was re-directed using > or >> */
if (argc > 5)
{
/* 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 = 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 = argv[argc-1];
argc -= 2;
{
/* 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.
*/
nsh_output(vtbl, g_fmtcmdfailed, cmd, "open", NSH_ERRNO);
/* Check if the maximum number of arguments was exceeded */
if (argc > NSH_MAX_ARGUMENTS+3)
{
nsh_output(vtbl, g_fmttoomanyargs, cmd);
}
/* Handle the case where the command is executed in background */
{
struct sched_param param;
int priority;
struct nsh_vtbl_s *bkgvtbl;
/* 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_execute() will destroy the clone.
*/
/* Place a string copy of the cloned vtbl in the argument list */
sprintf(strvtbl, "%p\n", bkgvtbl);
argv[1] = strvtbl;
/* Handle redirection of output via a file descriptor */
}
/* Get the execution priority of this task */
ret = sched_getparam(0, ¶m);
if (ret != 0)
{
nsh_output(vtbl, g_fmtcmdfailed, cmd, "sched_getparm", NSH_ERRNO);
}
/* Determine the priority to execute the command */
priority = param.sched_priority;
priority -= vtbl->np.np_nice;
if (vtbl->np.np_nice < 0)
int max_priority = sched_get_priority_max(SCHED_RR);
if (priority > max_priority)
}
else
{
int min_priority = sched_get_priority_min(SCHED_RR);
if (priority < min_priority)
/* Execute the command as a separate task at the appropriate priority */
ret = task_create("nsh_execute", priority, CONFIG_EXAMPLES_NSH_STACKSIZE,
ret = task_create("nsh_execute", priority, (main_t)nsh_execute, &argv[1]);
nsh_output(vtbl, g_fmtcmdfailed, cmd, "task_create", NSH_ERRNO);
}
nsh_output(vtbl, "%s [%d:%d:%d]\n", cmd, ret, priority, param.sched_priority);
/* If the output was redirected, then file descriptor should
* be closed. The created task has its one, independent copy of
* the file descriptor
*/
/* Increment the reference count on the vtbl. This reference count will
* be decremented at the end of nsh_execute() and exists only for compatibility
* with the background command logic.
*/
/* Place a string copy of the original vtbl in the argument list */
sprintf(strvtbl, "%p\n", vtbl);
argv[1] = strvtbl;
/* Handle redirection of output via a file descriptor */
/* Then execute the command in "foreground" -- i.e., while the user waits
* for the next prompt.
*/
ret = nsh_execute(argc, argv);
/* Restore the original output. Undirect will close the redirection
* file descriptor.
{
nsh_undirect(vtbl, save);
}
if (ret < 0)
{
/* Return success if the command succeeded (or at least, starting of the
* command task succeeded).
*/
errout_with_redirect:
if (vtbl->np.np_redirect)
errout:
nsh_saveresult(vtbl, TRUE);