summaryrefslogblamecommitdiffstats
path: root/cpukit/libmisc/shell/shell.c
blob: 9cefc802556fbf50296db999a8b026febf3c608f (plain) (tree)
1
2
3
4
5
6
7
8
9

        
  


                                             
  
                                                                
  

                                                           
                                         
   
 


                    
 
                  
                 

                  
                        
                        
                         
                        
                              
                          
                     




                    
                     

                   
                

                    
 









                                                                                   

                                                        
                                                  
                               
                         
                                       
                          

                         




                         


                        
                                 



                        

  





                                         

                                                           
                                                 

  
                                                             
   
                                               
                                     

 




                                                




                                                     
                                        
          
                                   
   

                             
 
                   
 
 
  

                                                           
                                 


           










                                                                            
 

                   

 
                                                                          
 







                               













                                                        


















                                                                         

                              





                                                                      


  












                                                                    

                                 



































                                                                         
 
 




                                                                           

                
                


  



                                                    



                                                     








                                                                

                                              
                 





                                               
                                   






                                                                       
   


  









                                                                

                                                 
                                   








                            
                 

                        




                              
                                      

                     
 
                     


                         
 
                                    
                                 
 

                  
 
            
 
                    





                                           
 
                                                    
 



                                                  
 
           
 




                                  
                          
                                           



                                    

                                              




                                     

                           

                



                                     

                                     


                           
 
                                     


                           





                                  
                                                 
                              












                                                        
                      


                
                                                     

                                              


                  
 



                                                       
                            






                                                       
                                                   
                                










                                                                    
                          



                                                     
                          
                                           


                                    

                                                       
                            





                                                       
                            





                                                                        






                                                     
                          
                              








                                                  
 
                  
                            










                                                




                      
                                                 
                              












                                                                  
                          
                              
 











                                                                        





                                                                




                   

                                                                     
                            


















                                                       
                              
















                                                                    
                            











                                                       
                              




                                          
                            


                                                              
                            






                                                       


                                                                          
                   
 
                                             
                              

                                                        



                                                     




                    
                
                                                               




                                                           
                                
                                               






                                                        
                            








                            

                                                                           


            
 
            


                                 

                                   
                                        
                        
                                    
                       
                                                



                                 
                       







                                            





                                              































                                                  
                                                

















                                                                 
                                                        

                       





                                              















                                         
                                                                           

 
                        

                               



                                    
 





                               




                                                              




                          
 
  






































































































                                                                                       







                                                               
                          
















                                                  







                                                                                   






                                                                                      


                                      








                                                                                    
             
           

         




                                                             










                                                
                                                                     
 


                                                                    
                                        

                                                  
                    
 
 






                                                    
                                                      









                                   



                                           
 



                                       
 
 










                                                                
                   
     
 
                                      
          

                  

   


                                       



                                                             
 



                                                             

                            
               




                                                          
     
 
        








                                                                  
                                                               
                





                                             

                        


                    
                                                              

                          

                                                   
                                                                      
                                      
                                                          


                                
 
                                      

                  



                                                    




                                                            
 


                                                                     
                                                                   


                                      
 

                           
                          
           


                 

                                                         
 


                                    
                                            


                    
 

                                            
 



                                                   
 


                                                                      
           
 






                                                                     

                                                             
                                                                      


                                                                     



                                                           
           



                                    
         
 

                         
       
                                                                  
                                           








                 
























                                                                       











                                       
                                        










































                                                                              
 
















                                                                           

   





































                                                                                  
 
                                                                       


                                                             
                     
                           
          
                                                                  






                                                         

                            
                                
                
 
 
                                                     












                                          



                             
                               

                          

                                 
                                           

                                                              
      
                       

                         
         
                  
                   
                                                   



                                       
                                                                 


              
                                           
                      

                                                                    

                          


                                               

                                       
 
                                   
                                     
                                  

                                                                     
                                           


                                    
                                         
                                         

                                      
 

                                                 




                                                                              
                                                  
                                                         
                               



                                                                 







                                                                  
                                           

            
 
 
                                   






                                       

 
                                   












                                                 
                                                
                                              
                                       
                                              
    

 
                                      
                                 
                                      


                                     


                                     

 
                                   

                       



                                                             
 









                                         
                                      
                               
                                      
    







                                                                    
 

            
/**
 * @file
 *
 * @brief Instantatiate a new terminal shell.
 */

/*
 * Copyright (c) 2001 Fernando Ruiz Casas <fruizcasas@gmail.com>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.org/license/LICENSE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <time.h>

#include <rtems.h>
#include <rtems/error.h>
#include <rtems/libio.h>
#include <rtems/libio_.h>
#include <rtems/shell.h>
#include <rtems/shellconfig.h>
#include <rtems/console.h>
#include "internal.h"

#include <termios.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <pthread.h>
#include <assert.h>

#define SHELL_STD_DEBUG 0

#if SHELL_STD_DEBUG
#include <rtems/bspIo.h>
#define shell_std_debug(...) \
  do { printk("shell[%08x]: ", rtems_task_self()); printk(__VA_ARGS__); } while (0)
#else
#define shell_std_debug(...)
#endif

#define SHELL_MAGIC rtems_build_name('S', 'E', 'N', 'V')

const rtems_shell_env_t rtems_global_shell_env = {
  .magic         = SHELL_MAGIC,
  .managed       = false,
  .devname       = CONSOLE_DEVICE_NAME,
  .taskname      = "RTSH",
  .exit_shell    = false,
  .forever       = true,
  .echo          = false,
  .cwd           = "/",
  .input         = NULL,
  .output        = NULL,
  .output_append = false,
  .parent_stdin  = NULL,
  .parent_stdout = NULL,
  .parent_stderr = NULL,
  .wake_on_end   = RTEMS_ID_NONE,
  .exit_code     = NULL,
  .login_check   = NULL,
  .uid           = 0,
  .gid           = 0
};

typedef struct rtems_shell_env_key_handle
{
  bool managed;
  rtems_shell_env_t* env;
} rtems_shell_env_key_handle;

static pthread_once_t rtems_shell_once = PTHREAD_ONCE_INIT;

static pthread_key_t rtems_shell_current_env_key;

/*
 *  Initialize the shell user/process environment information
 */
static rtems_shell_env_t *rtems_shell_init_env(
  rtems_shell_env_t *shell_env_parent
)
{
  rtems_shell_env_t *shell_env;

  shell_env = malloc(sizeof(rtems_shell_env_t));
  if ( !shell_env )
    return NULL;

  if ( shell_env_parent == NULL ) {
    shell_env_parent = rtems_shell_get_current_env();
  }
  if ( shell_env_parent == NULL ) {
    *shell_env = rtems_global_shell_env;
  } else {
    *shell_env = *shell_env_parent;
  }
  shell_env->managed = true;
  shell_env->taskname = NULL;

  return shell_env;
}

/*
 *  Completely free a shell_env_t and all associated memory
 */
static void rtems_shell_env_free(
  void *ptr
)
{
  if ( ptr != NULL ) {
    rtems_shell_env_key_handle *handle = (rtems_shell_env_key_handle *) ptr;
    rtems_shell_env_t *shell_env = handle->env;

    if ( handle->managed ) {
      if ( shell_env->input )
        free((void *)shell_env->input);
      if ( shell_env->output )
        free((void *)shell_env->output);
      free( shell_env );
    }

    free( handle );
  }
}

static void rtems_shell_create_file(const char *name, const char *content)
{
  FILE *fp = fopen(name, "wx");

  if (fp != NULL) {
    fputs(content, fp);
    fclose(fp);
  }
}

static void rtems_shell_init_commands(void)
{
  rtems_shell_cmd_t * const *c;
  rtems_shell_alias_t * const *a;

  for ( c = rtems_shell_Initial_commands ; *c  ; c++ ) {
    rtems_shell_add_cmd_struct( *c );
  }

  for ( a = rtems_shell_Initial_aliases ; *a  ; a++ ) {
    rtems_shell_alias_cmd( (*a)->name, (*a)->alias );
  }
}

static void rtems_shell_init_once(void)
{
  struct passwd pwd;
  struct passwd *pwd_res;

  pthread_key_create(&rtems_shell_current_env_key, rtems_shell_env_free);

  /* dummy call to init /etc dir */
  getpwuid_r(0, &pwd, NULL, 0, &pwd_res);

  rtems_shell_create_file("etc/issue",
                          "\n"
                          "Welcome to @V\\n"
                          "Login into @S\\n");

  rtems_shell_create_file("/etc/issue.net",
                          "\n"
                          "Welcome to %v\n"
                          "running on %m\n");

  rtems_shell_init_commands();
  rtems_shell_register_monitor_commands();
}

void rtems_shell_init_environment(void)
{
  assert(pthread_once(&rtems_shell_once, rtems_shell_init_once) == 0);
}

/*
 * Set the shell env into the current thread's shell key.
 */
static bool rtems_shell_set_shell_env(
  rtems_shell_env_t* shell_env
)
{
  /*
   * The shell environment can be managed or it can be provided by a
   * user. We need to create a handle to hold the env pointer.
   */
  rtems_shell_env_key_handle *handle;
  int eno;

  rtems_shell_init_environment();

  handle = malloc(sizeof(rtems_shell_env_key_handle));
  if (handle == NULL) {
    rtems_error(0, "no memory for shell env key handle)");
    return false;
  }

  handle->managed = shell_env->managed;
  handle->env = shell_env;

  eno = pthread_setspecific(rtems_shell_current_env_key, handle);
  if (eno != 0) {
    rtems_error(0, "pthread_setspecific(shell_current_env_key): set");
    return false;
  }

  return true;
}

/*
 * Clear the current thread's shell key.
 */
static void rtems_shell_clear_shell_env(void)
{
  int eno;

  /*
   * Run the destructor manually.
   */
  rtems_shell_env_free(pthread_getspecific(rtems_shell_current_env_key));

  /*
   * Clear the key
   */
  eno = pthread_setspecific(rtems_shell_current_env_key, NULL);
  if (eno != 0)
    rtems_error(0, "pthread_setspecific(shell_current_env_key): clear");
}

/*
 * Clear stdin, stdout and stderr file pointers so they will not be closed.
 */
static void rtems_shell_clear_shell_std_handles(void)
{
  stdin = NULL;
  stdout = NULL;
  stderr = NULL;
}

/*
 *  Return the current shell environment
 */
rtems_shell_env_t *rtems_shell_get_current_env(void)
{
  rtems_shell_env_key_handle *handle;
  handle = (rtems_shell_env_key_handle*)
    pthread_getspecific(rtems_shell_current_env_key);
  return handle == NULL ? NULL : handle->env;
}

/*
 *  Duplication the current shell environment and if none is set
 *  clear it.
 */
void rtems_shell_dup_current_env(rtems_shell_env_t *copy)
{
  rtems_shell_env_t *env = rtems_shell_get_current_env();
  if (env != NULL) {
    shell_std_debug("dup: existing parent\n");
    *copy = *env;

    /*
     * Duplicated environments are not managed.
     */
    copy->managed = false;
  } else {
    *copy = rtems_global_shell_env;
    copy->parent_stdout = stdout;
    copy->parent_stdin  = stdin;
    copy->parent_stderr = stderr;
    shell_std_debug("dup: global: copy: %p out: %d (%p) in: %d (%p)\n",
                    copy,
                    fileno(copy->parent_stdout), copy->parent_stdout,
                    fileno(copy->parent_stdin), copy->parent_stdin);
  }
}

/*
 *  Move a string in a buffer to the left (e.g. when a character
 *  is deleted). The string must be NUL-terminated and the
 *  NUL-character will be moved too.
 */
static void rtems_shell_move_left(char *start, size_t offset)
{
  memmove(start, start + offset, strlen(start + offset) + 1);
}

/*
 *  Get a line of user input with modest features
 */
static int rtems_shell_line_editor(
  char       *cmds[],
  int         count,
  int         size,
  const char *prompt,
  FILE       *in,
  FILE       *out
)
{
  unsigned int extended_key;
  int          c;
  int          col;
  int          last_col;
  char         line[size];
  char         new_line[size];
  int          up;
  int          cmd = -1;
  int          inserting = 1;
  int          in_fileno = fileno(in);

  col = last_col = 0;

  tcdrain(in_fileno);
  if (out != NULL) {
    tcdrain(fileno(out));
  }

  if (out != NULL && prompt != NULL)
    fprintf(out, "\r%s", prompt);

  line[0] = 0;
  new_line[0] = 0;

  for (;;) {

    if (out != NULL)
      fflush(out);

    extended_key = rtems_shell_getchar(in);

    if (extended_key == EOF)
      return -2;

    c = extended_key & RTEMS_SHELL_KEYS_NORMAL_MASK;

    /*
     * Make the extended_key usable as a boolean.
     */
    extended_key &= ~RTEMS_SHELL_KEYS_NORMAL_MASK;

    up = 0;

    if (extended_key)
    {
      switch (c)
      {
        case RTEMS_SHELL_KEYS_END:
          if (out != NULL)
            fprintf(out, "%s", line + col);
          col = (int) strlen (line);
          break;

        case RTEMS_SHELL_KEYS_HOME:
          if (out != NULL && prompt != NULL) {
            fprintf(out,"\r%s", prompt);
          }
          col = 0;
          break;

        case RTEMS_SHELL_KEYS_LARROW:
          c = 2;
          extended_key = 0;
          break;

        case RTEMS_SHELL_KEYS_RARROW:
          c = 6;
          extended_key = 0;
          break;

        case RTEMS_SHELL_KEYS_UARROW:
          c = 16;
          extended_key = 0;
          break;

        case RTEMS_SHELL_KEYS_DARROW:
          c = 14;
          extended_key = 0;
          break;

        case RTEMS_SHELL_KEYS_DEL:
          if (line[col] != '\0')
          {
            int end;
            int bs;
            rtems_shell_move_left(line + col, 1);
            if (out != NULL) {
              fprintf(out,"\r%s%s ", prompt, line);
              end = (int) strlen (line);
              for (bs = 0; bs < ((end - col) + 1); bs++)
                fputc('\b', out);
            }
          }
          break;

        case RTEMS_SHELL_KEYS_INS:
          inserting = inserting ? 0 : 1;
          break;
      }
    }
    if (!extended_key)
    {
      switch (c)
      {
        case 1:                         /*Control-a*/
          if (out != NULL && prompt != NULL) {
            fprintf(out,"\r%s", prompt);
          }
          col = 0;
          break;

        case 2:                         /* Control-B */
          if (col > 0)
          {
            col--;
            if (out != NULL)
              fputc('\b', out);
          }
          break;

        case 4:                         /* Control-D */
          if (strlen(line)) {
            if (col < strlen(line)) {
              rtems_shell_move_left(line + col, 1);
              if (out != NULL) {
                int bs;
                fprintf(out,"%s \b", line + col);
                for (bs = 0; bs < ((int) strlen (line) - col); bs++)
                  fputc('\b', out);
              }
            }
            break;
          }
          /* Fall through */

        case EOF:
          if (out != NULL)
            fputc('\n', out);
          return -2;

        case 5:                         /*Control-e*/
          if (out != NULL)
            fprintf(out, "%s", line + col);
          col = (int) strlen (line);
          break;

        case 6:                         /* Control-F */
          if ((col < size) && (line[col] != '\0')) {
            if (out != NULL)
              fputc(line[col], out);
            col++;
          }
          break;

        case 7:                         /* Control-G */
          if (out != NULL) {
            /*
             * The (int) cast is needed because the width specifier (%*)
             * must be an int, but strlen() returns a size_t. Without
             * the case, the result is a printf() format warning.
             */
            fprintf(out,"\r%s%*c", prompt, (int) strlen (line), ' ');
            fprintf(out,"\r%s\x7", prompt);
          }
          memset (line, '\0', strlen(line));
          col = 0;
          break;

        case 11:                        /*Control-k*/
          if (line[col]) {
            if (out != NULL) {
              int end = strlen(line);
              int bs;
              fprintf(out,"%*c", end - col, ' ');
              for (bs = 0; bs < (end - col); bs++)
                fputc('\b', out);
            }
            line[col] = '\0';
          }
          break;

        case '\f':
          if (out != NULL) {
            int end;
            int bs;
            fputc('\f',out);
            fprintf(out,"\r%s%s", prompt, line);
            end = (int) strlen (line);
            for (bs = 0; bs < (end - col); bs++)
              fputc('\b', out);
          }
          break;

        case '\b':
        case '\x7f':
          if (col > 0)
          {
            int bs;
            col--;
            rtems_shell_move_left(line + col, 1);
            if (out != NULL) {
              fprintf(out,"\b%s \b", line + col);
              for (bs = 0; bs < ((int) strlen (line) - col); bs++)
                fputc('\b', out);
            }
          }
          break;

        case '\n':
        case '\r':
        {
          /*
           * Process the command.
           */
          if (out != NULL)
            fprintf(out,"\n");

          /*
           * Only process the command if we have a command and it is not
           * repeated in the history.
           */
          if (strlen(line) == 0) {
            cmd = -1;
          } else {
            if ((cmd < 0) || (strcmp(line, cmds[cmd]) != 0)) {
              if (count > 1)
                memmove(cmds[1], cmds[0], (count - 1) * size);
              memmove (cmds[0], line, size);
              cmd = 0;
            } else {
              if ((cmd > 1) && (strcmp(line, cmds[cmd]) == 0)) {
                memmove(cmds[1], cmds[0], cmd * size);
                memmove (cmds[0], line, size);
                cmd = 0;
              }
            }
          }
        }
        return cmd;

        case 16:                         /* Control-P */
          if ((cmd >= (count - 1)) || (strlen(cmds[cmd + 1]) == 0)) {
            if (out != NULL)
              fputc('\x7', out);
            break;
          }

          up = 1;
          /* drop through */

        case 14:                        /* Control-N */
        {
          int last_cmd = cmd;
          int clen = strlen (line);

          if (prompt)
            clen += strlen(prompt);

          if (up) {
            cmd++;
          } else {
            if (cmd < 0) {
              if (out != NULL)
                fprintf(out, "\x7");
              break;
            }
            else
              cmd--;
          }

          if ((last_cmd < 0) || (strcmp(cmds[last_cmd], line) != 0))
            memcpy (new_line, line, size);

          if (cmd < 0)
            memcpy (line, new_line, size);
          else
            memcpy (line, cmds[cmd], size);

          col = strlen (line);

          if (out != NULL) {
            fprintf(out,"\r%s%*c", prompt, clen, ' ');
            fprintf(out,"\r%s%s", prompt, line);
          }
        }
        break;

        case 20:                        /* Control-T */
          if (col > 0)
          {
            char tmp;
            if (col == strlen(line)) {
              col--;
              if (out != NULL)
                fprintf(out,"\b");
            }
            tmp           = line[col];
            line[col]     = line[col - 1];
            line[col - 1] = tmp;
            if (out != NULL)
              fprintf(out,"\b%c%c", line[col - 1], line[col]);
            col++;
          } else {
            if (out != NULL)
              fputc('\x7', out);
          }
          break;

        case 21:                        /* Control-U */
          if (col > 0)
          {
            /* strlen() returns size_t but fprintf("%*...") below requires
             * int! */
            int clen = (int) strlen (line);
            int bs;

            rtems_shell_move_left(line, col);
            if (out != NULL) {
              fprintf(out,"\r%s%*c", prompt, clen, ' ');
              fprintf(out,"\r%s%s", prompt, line);

              for (bs = 0; bs < strlen(line); bs++) {
                fputc('\b', out);
              }
            }
            col = 0;
          }
          break;

        default:
          if ((col < (size - 1)) && (c >= ' ') && (c <= '~')) {
            int end = strlen (line);
            if (inserting && (col < end) && (end < size)) {
              int ch, bs;
              for (ch = end + 1; ch > col; ch--)
                line[ch] = line[ch - 1];
              if (out != NULL) {
                fprintf(out, "%s", line + col);
                for (bs = 0; bs < (end - col + 1); bs++)
                  fputc('\b', out);
              }
            }
            line[col++] = c;
            if (col > end)
              line[col] = '\0';
            if (out != NULL)
              fputc(c, out);
          }
          break;
      }
    }
  }
  return -2;
}

static bool rtems_shell_login(rtems_shell_env_t *env, FILE * in,FILE * out)
{
  FILE  *fd;
  int    c;
  time_t t;

  if (out) {
    if ((env->devname[5]!='p')||
        (env->devname[6]!='t')||
        (env->devname[7]!='y')) {
      fd = fopen("/etc/issue","r");
      if (fd) {
        while ((c = fgetc(fd)) != EOF) {
          if (c=='@')  {
            switch (c = fgetc(fd)) {
              case 'L':
                fprintf(out,"%s", env->devname);
                break;
              case 'B':
                fprintf(out,"0");
                break;
              case 'T':
              case 'D':
                time(&t);
                fprintf(out,"%s",ctime(&t));
                break;
              case 'S':
                fprintf(out,"RTEMS");
                break;
              case 'V':
                fprintf(
                  out,
                  "%s\n%s",
                  rtems_get_version_string(),
                  rtems_get_copyright_notice()
                );
                break;
              case '@':
                fprintf(out,"@");
                break;
              default :
                fprintf(out,"@%c",c);
                break;
            }
          } else if (c=='\\')  {
            switch(c=fgetc(fd)) {
              case '\\': fprintf(out,"\\"); break;
              case 'b':  fprintf(out,"\b"); break;
              case 'f':  fprintf(out,"\f"); break;
              case 'n':  fprintf(out,"\n"); break;
              case 'r':  fprintf(out,"\r"); break;
              case 's':  fprintf(out," ");  break;
              case 't':  fprintf(out,"\t"); break;
              case '@':  fprintf(out,"@");  break;
            }
          } else {
            fputc(c,out);
          }
        }
        fclose(fd);
      }
    } else {
      fd = fopen("/etc/issue.net","r");
      if (fd) {
        while ((c=fgetc(fd))!=EOF) {
          if (c=='%')  {
            switch(c=fgetc(fd)) {
              case 't':
                fprintf(out,"%s", env->devname);
                break;
              case 'h':
                fprintf(out,"0");
                break;
              case 'D':
                fprintf(out," ");
                break;
              case 'd':
                time(&t);
                fprintf(out,"%s",ctime(&t));
                break;
              case 's':
                fprintf(out,"RTEMS");
                break;
              case 'm':
                fprintf(out,"(" CPU_NAME "/" CPU_MODEL_NAME ")");
                break;
              case 'r':
                fprintf(out,rtems_get_version_string());
                break;
              case 'v':
                fprintf(
                  out,
                  "%s\n%s",
                  rtems_get_version_string(),
                  rtems_get_copyright_notice()
                );
	        break;
	      case '%':fprintf(out,"%%");
	        break;
	      default:
                fprintf(out,"%%%c",c);
                break;
            }
          } else {
            fputc(c,out);
          }
        }
        fclose(fd);
      }
    }
  }

  return rtems_shell_login_prompt(in, out, env->devname, env->login_check);
}

#if defined(SHELL_DEBUG)
void rtems_shell_print_env(
  rtems_shell_env_t * shell_env
)
{
  if ( !shell_env ) {
    printk( "shell_env is NULL\n" );

    return;
  }
  printk( "shell_env=%p\n"
    "shell_env->magic=0x%08x\t"
    "shell_env->devname=%s\n"
    "shell_env->taskname=%s\t"
    "shell_env->exit_shell=%d\t"
    "shell_env->forever=%d\n",
    shell_env->magic,
    shell_env->devname,
    ((shell_env->taskname) ? shell_env->taskname : "NOT SET"),
    shell_env->exit_shell,
    shell_env->forever
  );
}
#endif

/*
 * Wait for the string to return or timeout.
 */
static bool rtems_shell_term_wait_for(const int fd, const char* str, const int timeout)
{
  int msec = timeout;
  int i = 0;
  while (msec-- > 0 && str[i] != '\0') {
    char ch[2];
    if (read(fd, &ch[0], 1) == 1) {
      fflush(stdout);
      if (ch[0] != str[i++]) {
        return false;
      }
      msec = timeout;
    } else {
      usleep(1000);
    }
  }
  if (msec == 0) {
    return false;
  }
  return true;
}

/*
 * Buffer a string up to the end string
 */
static int rtems_shell_term_buffer_until(const int fd,
                                         char* buf,
                                         const int size,
                                         const char* end,
                                         const int timeout)
{
  int msec = timeout;
  int i = 0;
  int e = 0;
  memset(&buf[0], 0, size);
  while (msec-- > 0 && i < size && end[e] != '\0') {
    char ch[2];
    if (read(fd, &ch[0], 1) == 1) {
      fflush(stdout);
      buf[i++] = ch[0];
      if (ch[0] == end[e]) {
        e++;
      } else {
        e = 0;
      }
      msec = timeout;
    } else {
      usleep(1000);
    }
  }
  if (msec == 0 || end[e] != '\0') {
    return -1;
  }
  i -= e;
  if (i < size) {
    buf[i] = '\0';
  }
  return i;
}

/*
 * Determine if the terminal has the row and column values
 * swapped
 *
 * https://github.com/tmux/tmux/issues/3457
 *
 * Tmux has a bug where the lines and cols are swapped. There is a lag
 * in the time it takes to get the fix into code so see if tmux is
 * running and which version and work around the bug.
 *
 * The terminal device needs to have VMIN=0, and VTIME=0
 */
static bool rtems_shell_term_row_column_swapped(const int fd, const int timeout) {
  char buf[64];
  memset(&buf[0], 0, sizeof(buf));
  /*
   * CSI > Ps q
   *    Ps = 0   =>   DCS > | text ST
   */
  fputs("\033[>0q", stdout);
  fflush(stdout);
  if (rtems_shell_term_wait_for(fd, "\033P>|", timeout)) {
    int len = rtems_shell_term_buffer_until(fd, buf, sizeof(buf), "\033\\", timeout);
    if (len > 0) {
      if (memcmp(buf, "tmux ", 5) == 0) {
        static const char* bad_versions[] = {
          "3.2", "3.2a", "3.3", "3.3a"
        };
        size_t i;
        for (i = 0; i < RTEMS_ARRAY_SIZE(bad_versions); ++i) {
          if (strcmp(bad_versions[i], buf + 5) == 0) {
            return true;
          }
        }
      }
    }
  }
  return false;
}

/*
 * Direct method to get the size of an XTERM window.
 *
 * If you do not use an XTERM the env variables are not define.
 */
static void rtems_shell_winsize( void )
{
  const int fd = fileno(stdin);
  struct winsize ws;
  const int timeout = 150;
  char buf[64];
  bool ok = false;
  int lines = 0;
  int cols = 0;
  int r;
  r = ioctl(fd, TIOCGWINSZ, &ws);
  if (r == 0) {
    ok = true;
    lines = ws.ws_row;
    cols = ws.ws_col;
  } else if (isatty(fd)) {
    struct termios cterm;
    if (tcgetattr(fd, &cterm) >= 0) {
      struct termios term = cterm;
      term.c_cc[VMIN] = 0;
      term.c_cc[VTIME] = 0;
      if (tcsetattr (fd, TCSADRAIN, &term) >= 0) {
        memset(&buf[0], 0, sizeof(buf));
        /*
         * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Miscellaneous
         *
         * CSI 1 8 t
         */
        fputs("\033[18t", stdout);
        fflush(stdout);
        if (rtems_shell_term_wait_for(fd, "\033[8;", timeout)) {
          int len = rtems_shell_term_buffer_until(fd, buf, sizeof(buf), ";", timeout);
          if (len > 0) {
            int i;
            lines = 0;
            i = 0;
            while (i < len) {
              lines *= 10;
              lines += buf[i++] - '0';
            }
            len = rtems_shell_term_buffer_until(fd, buf, sizeof(buf), "t", timeout);
            if (len > 0) {
              cols = 0;
              i = 0;
              while (i < len) {
                cols *= 10;
                cols += buf[i++] - '0';
              }
              ok = true;
            }
          }
        }
      }
      if (rtems_shell_term_row_column_swapped(fd, timeout)) {
        int tmp = lines;
        lines = cols;
        cols = tmp;
      }
      tcsetattr (fd, TCSADRAIN, &cterm);
    }
  }
  if (ok) {
    snprintf(buf, sizeof(buf) - 1, "%d", lines);
    setenv("LINES", buf, 1);
    snprintf(buf, sizeof(buf) - 1, "%d", cols);
    setenv("COLUMNS", buf, 1);
  }
}

static rtems_task rtems_shell_task(rtems_task_argument task_argument)
{
  rtems_shell_env_t *shell_env = (rtems_shell_env_t*) task_argument;
  rtems_id           wake_on_end = shell_env->wake_on_end;
  rtems_shell_main_loop( shell_env );
  rtems_shell_clear_shell_std_handles();
  if (wake_on_end != RTEMS_INVALID_ID)
    rtems_event_send (wake_on_end, RTEMS_EVENT_1);
  rtems_task_exit();
}

static bool rtems_shell_init_user_env(void)
{
  rtems_status_code sc;

  /* Make sure we have a private user environment */
  sc = rtems_libio_set_private_env();
  if (sc != RTEMS_SUCCESSFUL) {
    rtems_error(sc, "rtems_libio_set_private_env():");
    return false;
  }

  /* Make an effective root user */
  seteuid(0);
  setegid(0);

  return chroot("/") == 0;
}

#define RTEMS_SHELL_MAXIMUM_ARGUMENTS (128)
#define RTEMS_SHELL_CMD_SIZE          (128)
#define RTEMS_SHELL_CMD_COUNT         (32)
#define RTEMS_SHELL_PROMPT_SIZE       (128)

static bool shell_main_loop(
  rtems_shell_env_t *shell_env,
  bool               interactive,
  FILE              *line_editor_output
)
{
  bool result = false;
  int line = 0;
  int cmd_count;
  char *cmds[RTEMS_SHELL_CMD_COUNT];
  char *cmd_argv;
  char *prompt;

  if (interactive) {
    prompt = malloc(RTEMS_SHELL_PROMPT_SIZE);
    if (prompt == NULL) {
      fprintf(stderr, "shell: cannot allocate prompt memory\n");
      return false;
    }

    cmd_count = RTEMS_SHELL_CMD_COUNT;
  } else {
    prompt = NULL;
    cmd_count = 1;
  }

  /*
   * Allocate the command line buffers.
   */
  cmd_argv = malloc (RTEMS_SHELL_CMD_SIZE);
  if (!cmd_argv) {
    fprintf(stderr, "no memory for command line buffers\n" );
  }

  cmds[0] = calloc (cmd_count, RTEMS_SHELL_CMD_SIZE);
  if (!cmds[0]) {
    fprintf(stderr, "no memory for command line buffers\n" );
  }

  if (cmd_argv && cmds[0]) {
    size_t cmd;

    memset (cmds[0], 0, cmd_count * RTEMS_SHELL_CMD_SIZE);

    for (cmd = 1; cmd < cmd_count; cmd++) {
      cmds[cmd] = cmds[cmd - 1] + RTEMS_SHELL_CMD_SIZE;
    }

    do {
      result = rtems_shell_init_user_env();

      if (result) {
        /*
         *  By using result here, we can fall to the bottom of the
         *  loop when the connection is dropped during login and
         *  keep on trucking.
         */
        if (shell_env->login_check != NULL) {
          result = rtems_shell_login(shell_env, stdin, stdout);
        } else {
          setuid(shell_env->uid);
          setgid(shell_env->gid);
          seteuid(shell_env->uid);
          setegid(shell_env->gid);
          rtems_current_user_env_getgroups();

          result = true;
        }
      }

      if (result)  {
        memset (cmds[0], 0, cmd_count * RTEMS_SHELL_CMD_SIZE);

        if (interactive) {
          rtems_shell_cat_file(stdout,"/etc/motd");
          fprintf(stdout, "\n"
                  "RTEMS Shell on %s. Use 'help' to list commands.\n",
                  shell_env->devname);
          chdir("/"); /* XXX: chdir to getpwent homedir */
        } else {
          chdir(shell_env->cwd);
        }

        shell_env->exit_shell = false;

        for (;;) {
          const char *c;
          int argc;
          char *argv[RTEMS_SHELL_MAXIMUM_ARGUMENTS];

          /* Prompt section */
          if (prompt) {
            rtems_shell_get_prompt(shell_env, prompt,
                                   RTEMS_SHELL_PROMPT_SIZE);
          }

          /* getcmd section */
          cmd = rtems_shell_line_editor(cmds, cmd_count,
                                        RTEMS_SHELL_CMD_SIZE, prompt,
                                        stdin, line_editor_output);

          if (cmd == -1)
            continue; /* empty line */

          if (cmd == -2) {
            result = false;
            break; /*EOF*/
          }

          line++;

          if (shell_env->echo)
            fprintf(stdout, "%d: %s\n", line, cmds[cmd]);

          /* evaluate cmd section */
          c = cmds[cmd];
          while (*c) {
            if (!isblank((unsigned char)*c))
              break;
            c++;
          }

          if (*c == '\0')   /* empty line */
            continue;

          if (*c == '#') {  /* comment character */
            cmds[cmd][0] = 0;
            continue;
          }

          if (!strcmp(cmds[cmd],"bye") || !strcmp(cmds[cmd],"exit")) {
            fprintf(stdout, "Shell exiting\n" );
            break;
          }

          /* exec cmd section */
          /* TODO:
           *  To avoid user crash catch the signals.
           *  Open a new stdio files with posibility of redirection *
           *  Run in a new shell task background. (unix &)
           *  Resuming. A little bash.
           */
          memcpy (cmd_argv, cmds[cmd], RTEMS_SHELL_CMD_SIZE);
          if (!rtems_shell_make_args(cmd_argv, &argc, argv,
                                     RTEMS_SHELL_MAXIMUM_ARGUMENTS)) {
            int exit_code;
            rtems_shell_winsize();
            exit_code = rtems_shell_execute_cmd(argv[0], argc, argv);
            if (shell_env->exit_code != NULL)
              *shell_env->exit_code = exit_code;
            if (exit_code != 0 && shell_env->exit_on_error)
              shell_env->exit_shell = true;
          }

          /* end exec cmd section */
          if (shell_env->exit_shell)
            break;
        }

        fflush( stdout );
        fflush( stderr );
      }
      shell_std_debug("end: %d %d\n", result, shell_env->forever);
    } while (result && shell_env->forever);
  }

  free(cmds[0]);
  free(cmd_argv);
  free(prompt);

  return result;
}

bool rtems_shell_run_main_loop(
  rtems_shell_env_t *shell_env,
  bool               interactive,
  FILE              *line_editor_output
)
{
  bool result;

  if (shell_env->magic != SHELL_MAGIC) {
    return false;
  }

  if (!rtems_shell_init_user_env()) {
    return false;
  }

  if (!rtems_shell_set_shell_env(shell_env)) {
    return false;
  }

  result = shell_main_loop(shell_env, interactive, line_editor_output);
  rtems_shell_clear_shell_env();
  return result;
}

bool rtems_shell_main_loop(
  rtems_shell_env_t *shell_env
)
{
  struct termios  term;
  struct termios  previous_term;
  bool            result;
  bool            interactive = true;
  FILE           *stdinToClose = NULL;
  FILE           *stdoutToClose = NULL;
  FILE           *line_editor_output;

  if (shell_env->magic != SHELL_MAGIC) {
    rtems_error(0, "invalid shell environment passed to the main loop)");
    return false;
  }

  if (!rtems_shell_set_shell_env(shell_env))
    return false;

  if (!rtems_shell_init_user_env()) {
    rtems_error(0, "rtems_shell_init_user_env");
    rtems_shell_clear_shell_env();
    return false;
  }

  shell_std_debug("env: %p\n", shell_env);

  if (shell_env->output == NULL || strcmp(shell_env->output, "stdout") == 0) {
    if (shell_env->parent_stdout != NULL)
      stdout = shell_env->parent_stdout;
  }
  else if (strcmp(shell_env->output, "stderr") == 0) {
    if (shell_env->parent_stderr != NULL)
      stdout = shell_env->parent_stderr;
    else
      stdout = stderr;
  } else if (strcmp(shell_env->output, "/dev/null") == 0) {
    if (stdout == NULL) {
      fprintf(stderr, "shell: stdout is NULLs\n");
      rtems_shell_clear_shell_env();
      return false;
    }
    fclose (stdout);
  } else {
    FILE *output = fopen(shell_env->output,
                         shell_env->output_append ? "a" : "w");
    if (output == NULL) {
      fprintf(stderr, "shell: open output %s failed: %s\n",
              shell_env->output, strerror(errno));
      rtems_shell_clear_shell_env();
      return false;
    }
    stdout = output;
    stdoutToClose = output;
  }

  if (shell_env->input == NULL || strcmp(shell_env->input, "stdin") == 0) {
    if (shell_env->parent_stdin != NULL)
      stdin = shell_env->parent_stdin;
  } else {
    FILE *input = fopen(shell_env->input, "r");
    if (input == NULL) {
      fprintf(stderr, "shell: open input %s failed: %s\n",
              shell_env->input, strerror(errno));
      if (stdoutToClose != NULL)
        fclose(stdoutToClose);
      rtems_shell_clear_shell_env();
      return false;
    }
    stdin = input;
    stdinToClose = input;
    shell_env->forever = false;
    interactive = false;
  }

  if (interactive) {
    if (stdin == NULL) {
      fprintf(stderr, "shell: stdin is NULLs\n");
      if (stdoutToClose != NULL)
        fclose(stdoutToClose);
      rtems_shell_clear_shell_env();
      return false;
    }
    /* Make a raw terminal, Linux Manuals */
    if (tcgetattr(fileno(stdin), &previous_term) >= 0) {
      term = previous_term;
      term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
      term.c_oflag &= ~OPOST;
      term.c_oflag |= (OPOST|ONLCR); /* But with cr+nl on output */
      term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
      term.c_cflag |= CLOCAL | CREAD;
      term.c_cc[VMIN]  = 1;
      term.c_cc[VTIME] = 0;
      if (tcsetattr (fileno(stdin), TCSADRAIN, &term) < 0) {
        fprintf(stderr,
                "shell: cannot set terminal attributes(%s)\n",shell_env->devname);
      }
    }
  }

  shell_std_debug("child out: %d (%p)\n", fileno(stdout), stdout);
  shell_std_debug("child  in: %d (%p)\n", fileno(stdin), stdin);

   /* Do not buffer if interactive else leave buffered */
  if (interactive)
    setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);

  if (isatty(fileno(stdin))) {
     line_editor_output = stdout;
  } else {
     line_editor_output = NULL;
  }

  result = shell_main_loop(shell_env, interactive, line_editor_output);
  shell_std_debug("child in-to-close: %p\n", stdinToClose);
  shell_std_debug("child out-to-close: %p\n", stdoutToClose);

  if (stdinToClose) {
    fclose( stdinToClose );
  } else {
    if (tcsetattr(fileno(stdin), TCSADRAIN, &previous_term) < 0) {
      fprintf(
        stderr,
        "shell: cannot reset terminal attributes (%s)\n",
        shell_env->devname
      );
    }
  }
  if ( stdoutToClose )
    fclose( stdoutToClose );
  rtems_shell_clear_shell_env();
  return result;
}

/* ----------------------------------------------- */
static rtems_status_code rtems_shell_run (
  const char *task_name,
  size_t task_stacksize,
  rtems_task_priority task_priority,
  const char *devname,
  bool forever,
  bool wait,
  const char *input,
  const char *output,
  bool output_append,
  rtems_id wake_on_end,
  bool echo,
  rtems_shell_login_check_t login_check
)
{
  rtems_id           task_id;
  rtems_status_code  sc;
  rtems_shell_env_t *shell_env;
  rtems_name         name;

  rtems_shell_init_environment();

  if ( task_name && strlen(task_name) >= 4)
    name = rtems_build_name(
      task_name[0], task_name[1], task_name[2], task_name[3]);
  else
    name = SHELL_MAGIC;

  sc = rtems_task_create(
    name,
    task_priority,
    task_stacksize,
    RTEMS_PREEMPT | RTEMS_TIMESLICE | RTEMS_NO_ASR,
    RTEMS_LOCAL | RTEMS_FLOATING_POINT,
    &task_id
  );
  if (sc != RTEMS_SUCCESSFUL) {
    rtems_error(sc,"creating task %s in shell_init()",task_name);
    return sc;
  }

  shell_env = rtems_shell_init_env( NULL );
  if ( !shell_env )  {
   rtems_error(RTEMS_NO_MEMORY,
               "allocating shell_env %s in shell_init()",task_name);
   return RTEMS_NO_MEMORY;
  }

  shell_std_debug("run: env: %p\n", shell_env);

  shell_env->devname       = devname;
  shell_env->taskname      = task_name;

  shell_env->exit_shell    = false;
  shell_env->forever       = forever;
  shell_env->echo          = echo;
  shell_env->input         = input == NULL ? NULL : strdup (input);
  shell_env->output        = output == NULL ? NULL : strdup (output);
  shell_env->output_append = output_append;
  shell_env->parent_stdin  = stdin;
  shell_env->parent_stdout = stdout;
  shell_env->parent_stderr = stderr;
  shell_env->wake_on_end   = wake_on_end;
  shell_env->login_check   = login_check;
  shell_env->uid           = getuid();
  shell_env->gid           = getgid();

  getcwd(shell_env->cwd, sizeof(shell_env->cwd));

  shell_std_debug("run out: %d (%p)\n",
                  fileno(shell_env->parent_stdout), shell_env->parent_stdout);
  shell_std_debug("run  in: %d (%p)\n",
                  fileno(shell_env->parent_stdin), shell_env->parent_stdin);

  sc = rtems_task_start(task_id, rtems_shell_task,
                        (rtems_task_argument) shell_env);
  if (sc != RTEMS_SUCCESSFUL) {
    rtems_error(sc,"starting task %s in shell_init()",task_name);
    free( (void*) shell_env->input );
    free( (void*) shell_env->output );
    free( shell_env );
    return sc;
  }

  if (wait) {
    rtems_event_set out;
    sc = rtems_event_receive (RTEMS_EVENT_1, RTEMS_WAIT, 0, &out);
  }

  shell_std_debug("run: end: sc:%d\n", sc);

  return sc;
}

rtems_status_code rtems_shell_init(
  const char *task_name,
  size_t task_stacksize,
  rtems_task_priority task_priority,
  const char *devname,
  bool forever,
  bool wait,
  rtems_shell_login_check_t login_check
)
{
  rtems_id to_wake = RTEMS_ID_NONE;

  if ( wait )
    to_wake = rtems_task_self();

  return rtems_shell_run(
    task_name,               /* task_name */
    task_stacksize,          /* task_stacksize */
    task_priority,           /* task_priority */
    devname,                 /* devname */
    forever,                 /* forever */
    wait,                    /* wait */
    "stdin",                 /* input */
    "stdout",                /* output */
    false,                   /* output_append */
    to_wake,                 /* wake_on_end */
    false,                   /* echo */
    login_check              /* login check */
  );
}

rtems_status_code rtems_shell_script (
  const char          *task_name,
  size_t               task_stacksize,
  rtems_task_priority  task_priority,
  const char*          input,
  const char*          output,
  bool                 output_append,
  bool                 wait,
  bool                 echo
)
{
  rtems_id to_wake = RTEMS_ID_NONE;
  rtems_status_code sc;

  shell_std_debug("script: in: %s out: %s\n", input, output);

  if ( wait )
    to_wake = rtems_task_self();

  sc = rtems_shell_run(
    task_name,       /* task_name */
    task_stacksize,  /* task_stacksize */
    task_priority,   /* task_priority */
    NULL,            /* devname */
    0,               /* forever */
    wait,            /* wait */
    input,           /* input */
    output,          /* output */
    output_append,   /* output_append */
    to_wake,         /* wake_on_end */
    echo,            /* echo */
    NULL             /* login check */
  );

  if (sc == RTEMS_SUCCESSFUL)
  {
    /* Place holder until RTEMS 5 is released then the interface for
     * this call will change. */
  }

  shell_std_debug("script: end: %d\n", sc);

  return sc;
}