source: rtems/cpukit/libmisc/shell/shell.c @ 18549295

5
Last change on this file since 18549295 was 18549295, checked in by Frank Kühndel <frank.kuehndel@…>, on 09/24/20 at 13:31:41

Fixing bug in line editing of the shell with CTRL-U.

This patch fixes a tiny bug in the command line editing of the RTEMS shell.
Typing CTRL-U in the shell should remove all characters left of the cursor.
After pressing CTRL-U, the current implementation does wrongly place the cursor
at the end of the line instead at its beginning.

To reproduce the bug, start the shell and type 'abc123' (no <RETURN>):

~/src/rtems $ qemu-system-arm -net none -nographic -M realview-pbx-a9 \

-m 256M -kernel build/arm/realview_pbx_a9_qemu/testsuites/libtests/dl10.exe

* BEGIN OF TEST libdl (RTL) 10 *
* TEST VERSION: 6.0.0.d9bdf166644f612dd628fe4951c12c6f8e94ba5f
* TEST STATE: USER_INPUT
* TEST BUILD: RTEMS_DEBUG RTEMS_NETWORKING RTEMS_POSIX_API RTEMS_SMP
* TEST TOOLS: 10.2.1 20200904 \

(RTEMS 6, RSB 31f936a7b74d60bda609a9960c6e1a705ba54974, Newlib a0d7982)

RTL (libdl) commands: dl, rtl

RTEMS Shell on /dev/foobar. Use 'help' to list commands.
SHLL / # abc123

Then move the cursor onto the '1' by hitting three times the <ARROW-LEFT> key.
Next type <CTRL>-U:

SHLL / # 123

Note that the cursor is at the end of the line (after '3') instead of correctly
at the beginning (on the '1'), now.

Continuing typing 'echo ' incorrectly results in the output:

SHLL / # 123echo 123

The patch changes this behavior so that the cursor in the second last step will
be on the '1' and typing 'echo ' will then correctly reflected as:

SHLL / # echo 123

Close #4096.

  • Property mode set to 100644
File size: 33.3 KB
Line 
1/*
2 *
3 *  Instantatiate a new terminal shell.
4 *
5 *  Author:
6 *
7 *   WORK: fernando.ruiz@ctv.es
8 *   HOME: correo@fernando-ruiz.com
9 *
10 *  The license and distribution terms for this file may be
11 *  found in the file LICENSE in this distribution or at
12 *  http://www.rtems.org/license/LICENSE.
13 */
14
15#ifdef HAVE_CONFIG_H
16#include "config.h"
17#endif
18
19#include <stdio.h>
20#include <time.h>
21
22#include <rtems.h>
23#include <rtems/error.h>
24#include <rtems/libio.h>
25#include <rtems/libio_.h>
26#include <rtems/shell.h>
27#include <rtems/shellconfig.h>
28#include <rtems/console.h>
29#include "internal.h"
30
31#include <termios.h>
32#include <string.h>
33#include <stdlib.h>
34#include <ctype.h>
35#include <sys/stat.h>
36#include <unistd.h>
37#include <errno.h>
38#include <pwd.h>
39#include <pthread.h>
40#include <assert.h>
41
42#define SHELL_STD_DEBUG 0
43
44#if SHELL_STD_DEBUG
45#include <rtems/bspIo.h>
46#define shell_std_debug(...) \
47  do { printk("shell[%08x]: ", rtems_task_self()); printk(__VA_ARGS__); } while (0)
48#else
49#define shell_std_debug(...)
50#endif
51
52const rtems_shell_env_t rtems_global_shell_env = {
53  .magic         = rtems_build_name('S', 'E', 'N', 'V'),
54  .managed       = false,
55  .devname       = CONSOLE_DEVICE_NAME,
56  .taskname      = "SHGL",
57  .exit_shell    = false,
58  .forever       = true,
59  .echo          = false,
60  .cwd           = "/",
61  .input         = NULL,
62  .output        = NULL,
63  .output_append = false,
64  .parent_stdin  = NULL,
65  .parent_stdout = NULL,
66  .parent_stderr = NULL,
67  .wake_on_end   = RTEMS_ID_NONE,
68  .exit_code     = NULL,
69  .login_check   = NULL,
70  .uid           = 0,
71  .gid           = 0
72};
73
74typedef struct rtems_shell_env_key_handle
75{
76  bool managed;
77  rtems_shell_env_t* env;
78} rtems_shell_env_key_handle;
79
80static pthread_once_t rtems_shell_once = PTHREAD_ONCE_INIT;
81
82static pthread_key_t rtems_shell_current_env_key;
83
84/*
85 *  Initialize the shell user/process environment information
86 */
87static rtems_shell_env_t *rtems_shell_init_env(
88  rtems_shell_env_t *shell_env_parent
89)
90{
91  rtems_shell_env_t *shell_env;
92
93  shell_env = malloc(sizeof(rtems_shell_env_t));
94  if ( !shell_env )
95    return NULL;
96
97  if ( shell_env_parent == NULL ) {
98    shell_env_parent = rtems_shell_get_current_env();
99  }
100  if ( shell_env_parent == NULL ) {
101    *shell_env = rtems_global_shell_env;
102  } else {
103    *shell_env = *shell_env_parent;
104  }
105  shell_env->managed = true;
106  shell_env->taskname = NULL;
107
108  return shell_env;
109}
110
111/*
112 *  Completely free a shell_env_t and all associated memory
113 */
114static void rtems_shell_env_free(
115  void *ptr
116)
117{
118  if ( ptr != NULL ) {
119    rtems_shell_env_key_handle *handle = (rtems_shell_env_key_handle *) ptr;
120    rtems_shell_env_t *shell_env = handle->env;
121
122    if ( handle->managed ) {
123      if ( shell_env->input )
124        free((void *)shell_env->input);
125      if ( shell_env->output )
126        free((void *)shell_env->output);
127      free( shell_env );
128    }
129
130    free( handle );
131  }
132}
133
134static void rtems_shell_create_file(const char *name, const char *content)
135{
136  FILE *fp = fopen(name, "wx");
137
138  if (fp != NULL) {
139    fputs(content, fp);
140    fclose(fp);
141  }
142}
143
144static void rtems_shell_init_commands(void)
145{
146  rtems_shell_cmd_t * const *c;
147  rtems_shell_alias_t * const *a;
148
149  for ( c = rtems_shell_Initial_commands ; *c  ; c++ ) {
150    rtems_shell_add_cmd_struct( *c );
151  }
152
153  for ( a = rtems_shell_Initial_aliases ; *a  ; a++ ) {
154    rtems_shell_alias_cmd( (*a)->name, (*a)->alias );
155  }
156}
157
158static void rtems_shell_init_once(void)
159{
160  struct passwd pwd;
161  struct passwd *pwd_res;
162
163  pthread_key_create(&rtems_shell_current_env_key, rtems_shell_env_free);
164
165  /* dummy call to init /etc dir */
166  getpwuid_r(0, &pwd, NULL, 0, &pwd_res);
167
168  rtems_shell_create_file("etc/issue",
169                          "\n"
170                          "Welcome to @V\\n"
171                          "Login into @S\\n");
172
173  rtems_shell_create_file("/etc/issue.net",
174                          "\n"
175                          "Welcome to %v\n"
176                          "running on %m\n");
177
178  rtems_shell_init_commands();
179  rtems_shell_register_monitor_commands();
180}
181
182void rtems_shell_init_environment(void)
183{
184  assert(pthread_once(&rtems_shell_once, rtems_shell_init_once) == 0);
185}
186
187/*
188 * Set the shell env into the current thread's shell key.
189 */
190static bool rtems_shell_set_shell_env(
191  rtems_shell_env_t* shell_env
192)
193{
194  /*
195   * The shell environment can be managed or it can be provided by a
196   * user. We need to create a handle to hold the env pointer.
197   */
198  rtems_shell_env_key_handle *handle;
199  int eno;
200
201  handle = malloc(sizeof(rtems_shell_env_key_handle));
202  if (handle == NULL) {
203    rtems_error(0, "no memory for shell env key handle)");
204    return false;
205  }
206
207  handle->managed = shell_env->managed;
208  handle->env = shell_env;
209
210  eno = pthread_setspecific(rtems_shell_current_env_key, handle);
211  if (eno != 0) {
212    rtems_error(0, "pthread_setspecific(shell_current_env_key): set");
213    return false;
214  }
215
216  return true;
217}
218
219/*
220 * Clear the current thread's shell key.
221 */
222static void rtems_shell_clear_shell_env(void)
223{
224  int eno;
225
226  /*
227   * Run the destructor manually.
228   */
229  rtems_shell_env_free(pthread_getspecific(rtems_shell_current_env_key));
230
231  /*
232   * Clear the key
233   */
234  eno = pthread_setspecific(rtems_shell_current_env_key, NULL);
235  if (eno != 0)
236    rtems_error(0, "pthread_setspecific(shell_current_env_key): clear");
237}
238
239/*
240 * Clear stdin, stdout and stderr file pointers so they will not be closed.
241 */
242static void rtems_shell_clear_shell_std_handles(void)
243{
244  stdin = NULL;
245  stdout = NULL;
246  stderr = NULL;
247}
248
249/*
250 *  Return the current shell environment
251 */
252rtems_shell_env_t *rtems_shell_get_current_env(void)
253{
254  rtems_shell_env_key_handle *handle;
255  handle = (rtems_shell_env_key_handle*)
256    pthread_getspecific(rtems_shell_current_env_key);
257  return handle == NULL ? NULL : handle->env;
258}
259
260/*
261 *  Duplication the current shell environment and if none is set
262 *  clear it.
263 */
264void rtems_shell_dup_current_env(rtems_shell_env_t *copy)
265{
266  rtems_shell_env_t *env = rtems_shell_get_current_env();
267  if (env != NULL) {
268    shell_std_debug("dup: existing parent\n");
269    *copy = *env;
270  }
271  else {
272    *copy = rtems_global_shell_env;
273    copy->magic         = rtems_build_name('S', 'E', 'N', 'V');
274    copy->devname       = CONSOLE_DEVICE_NAME;
275    copy->taskname      = "RTSH";
276    copy->parent_stdout = stdout;
277    copy->parent_stdin  = stdin;
278    copy->parent_stderr = stderr;
279    shell_std_debug("dup: global: copy: %p out: %d (%p) in: %d (%p)\n",
280                    copy,
281                    fileno(copy->parent_stdout), copy->parent_stdout,
282                    fileno(copy->parent_stdin), copy->parent_stdin);
283  }
284  /*
285   * Duplicated environments are not managed.
286   */
287  copy->managed = false;
288}
289
290/*
291 *  Get a line of user input with modest features
292 */
293static int rtems_shell_line_editor(
294  char       *cmds[],
295  int         count,
296  int         size,
297  const char *prompt,
298  FILE       *in,
299  FILE       *out
300)
301{
302  unsigned int extended_key;
303  int          c;
304  int          col;
305  int          last_col;
306  int          output;
307  char         line[size];
308  char         new_line[size];
309  int          up;
310  int          cmd = -1;
311  int          inserting = 1;
312  int          in_fileno = fileno(in);
313  int          out_fileno = fileno(out);
314
315  /*
316   * Only this task can use this file descriptor because calling
317   * fileno will block if another thread call made a call on this
318   * descriptor.
319   */
320  output = (out && isatty(in_fileno));
321
322  col = last_col = 0;
323
324  tcdrain(in_fileno);
325  if (out)
326    tcdrain(out_fileno);
327
328  if (output && prompt)
329    fprintf(out, "\r%s", prompt);
330
331  line[0] = 0;
332  new_line[0] = 0;
333
334  for (;;) {
335
336    if (output)
337      fflush(out);
338
339    extended_key = rtems_shell_getchar(in);
340
341    if (extended_key == EOF)
342      return -2;
343
344    c = extended_key & RTEMS_SHELL_KEYS_NORMAL_MASK;
345
346    /*
347     * Make the extended_key usable as a boolean.
348     */
349    extended_key &= ~RTEMS_SHELL_KEYS_NORMAL_MASK;
350
351    up = 0;
352
353    if (extended_key)
354    {
355      switch (c)
356      {
357        case RTEMS_SHELL_KEYS_END:
358          if (output)
359            fprintf(out, "%s", line + col);
360          col = (int) strlen (line);
361          break;
362
363        case RTEMS_SHELL_KEYS_HOME:
364          if (output) {
365            if (prompt)
366              fprintf(out,"\r%s", prompt);
367          }
368          col = 0;
369          break;
370
371        case RTEMS_SHELL_KEYS_LARROW:
372          c = 2;
373          extended_key = 0;
374          break;
375
376        case RTEMS_SHELL_KEYS_RARROW:
377          c = 6;
378          extended_key = 0;
379          break;
380
381        case RTEMS_SHELL_KEYS_UARROW:
382          c = 16;
383          extended_key = 0;
384          break;
385
386        case RTEMS_SHELL_KEYS_DARROW:
387          c = 14;
388          extended_key = 0;
389          break;
390
391        case RTEMS_SHELL_KEYS_DEL:
392          if (line[col] != '\0')
393          {
394            int end;
395            int bs;
396            strcpy (&line[col], &line[col + 1]);
397            if (output) {
398              fprintf(out,"\r%s%s ", prompt, line);
399              end = (int) strlen (line);
400              for (bs = 0; bs < ((end - col) + 1); bs++)
401                fputc('\b', out);
402            }
403          }
404          break;
405
406        case RTEMS_SHELL_KEYS_INS:
407          inserting = inserting ? 0 : 1;
408          break;
409      }
410    }
411    if (!extended_key)
412    {
413      switch (c)
414      {
415        case 1:                         /*Control-a*/
416          if (output) {
417            if (prompt)
418              fprintf(out,"\r%s", prompt);
419          }
420          col = 0;
421          break;
422
423        case 2:                         /* Control-B */
424          if (col > 0)
425          {
426            col--;
427            if (output)
428              fputc('\b', out);
429          }
430          break;
431
432        case 4:                         /* Control-D */
433          if (strlen(line)) {
434            if (col < strlen(line)) {
435              strcpy (line + col, line + col + 1);
436              if (output) {
437                int bs;
438                fprintf(out,"%s \b", line + col);
439                for (bs = 0; bs < ((int) strlen (line) - col); bs++)
440                  fputc('\b', out);
441              }
442            }
443            break;
444          }
445          /* Fall through */
446
447        case EOF:
448          if (output)
449            fputc('\n', out);
450          return -2;
451
452        case 5:                         /*Control-e*/
453          if (output)
454            fprintf(out, "%s", line + col);
455          col = (int) strlen (line);
456          break;
457
458        case 6:                         /* Control-F */
459          if ((col < size) && (line[col] != '\0')) {
460            if (output)
461              fputc(line[col], out);
462            col++;
463          }
464          break;
465
466        case 7:                         /* Control-G */
467          if (output) {
468            /*
469             * The (int) cast is needed because the width specifier (%*)
470             * must be an int, but strlen() returns a size_t. Without
471             * the case, the result is a printf() format warning.
472             */
473            fprintf(out,"\r%s%*c", prompt, (int) strlen (line), ' ');
474            fprintf(out,"\r%s\x7", prompt);
475          }
476          memset (line, '\0', strlen(line));
477          col = 0;
478          break;
479
480        case 11:                        /*Control-k*/
481          if (line[col]) {
482            if (output) {
483              int end = strlen(line);
484              int bs;
485              fprintf(out,"%*c", end - col, ' ');
486              for (bs = 0; bs < (end - col); bs++)
487                fputc('\b', out);
488            }
489            line[col] = '\0';
490          }
491          break;
492
493        case '\f':
494          if (output) {
495            int end;
496            int bs;
497            fputc('\f',out);
498            fprintf(out,"\r%s%s", prompt, line);
499            end = (int) strlen (line);
500            for (bs = 0; bs < (end - col); bs++)
501              fputc('\b', out);
502          }
503          break;
504
505        case '\b':
506        case '\x7f':
507          if (col > 0)
508          {
509            int bs;
510            col--;
511            strcpy (line + col, line + col + 1);
512            if (output) {
513              fprintf(out,"\b%s \b", line + col);
514              for (bs = 0; bs < ((int) strlen (line) - col); bs++)
515                fputc('\b', out);
516            }
517          }
518          break;
519
520        case '\n':
521        case '\r':
522        {
523          /*
524           * Process the command.
525           */
526          if (output)
527            fprintf(out,"\n");
528
529          /*
530           * Only process the command if we have a command and it is not
531           * repeated in the history.
532           */
533          if (strlen(line) == 0) {
534            cmd = -1;
535          } else {
536            if ((cmd < 0) || (strcmp(line, cmds[cmd]) != 0)) {
537              if (count > 1)
538                memmove(cmds[1], cmds[0], (count - 1) * size);
539              memmove (cmds[0], line, size);
540              cmd = 0;
541            } else {
542              if ((cmd > 1) && (strcmp(line, cmds[cmd]) == 0)) {
543                memmove(cmds[1], cmds[0], cmd * size);
544                memmove (cmds[0], line, size);
545                cmd = 0;
546              }
547            }
548          }
549        }
550        return cmd;
551
552        case 16:                         /* Control-P */
553          if ((cmd >= (count - 1)) || (strlen(cmds[cmd + 1]) == 0)) {
554            if (output)
555              fputc('\x7', out);
556            break;
557          }
558
559          up = 1;
560          /* drop through */
561
562        case 14:                        /* Control-N */
563        {
564          int last_cmd = cmd;
565          int clen = strlen (line);
566
567          if (prompt)
568            clen += strlen(prompt);
569
570          if (up) {
571            cmd++;
572          } else {
573            if (cmd < 0) {
574              if (output)
575                fprintf(out, "\x7");
576              break;
577            }
578            else
579              cmd--;
580          }
581
582          if ((last_cmd < 0) || (strcmp(cmds[last_cmd], line) != 0))
583            memcpy (new_line, line, size);
584
585          if (cmd < 0)
586            memcpy (line, new_line, size);
587          else
588            memcpy (line, cmds[cmd], size);
589
590          col = strlen (line);
591
592          if (output) {
593            fprintf(out,"\r%s%*c", prompt, clen, ' ');
594            fprintf(out,"\r%s%s", prompt, line);
595          }
596        }
597        break;
598
599        case 20:                        /* Control-T */
600          if (col > 0)
601          {
602            char tmp;
603            if (col == strlen(line)) {
604              col--;
605              if (output)
606                fprintf(out,"\b");
607            }
608            tmp           = line[col];
609            line[col]     = line[col - 1];
610            line[col - 1] = tmp;
611            if (output)
612              fprintf(out,"\b%c%c", line[col - 1], line[col]);
613            col++;
614          } else {
615            if (output)
616              fputc('\x7', out);
617          }
618          break;
619
620        case 21:                        /* Control-U */
621          if (col > 0)
622          {
623            int clen = strlen (line);
624            int bs;
625
626            strcpy (line, line + col);
627            if (output) {
628              fprintf(out,"\r%s%*c", prompt, clen, ' ');
629              fprintf(out,"\r%s%s", prompt, line);
630
631              for (bs = 0; bs < strlen(line); bs++) {
632                fputc('\b', out);
633              }
634            }
635            col = 0;
636          }
637          break;
638
639        default:
640          if ((col < (size - 1)) && (c >= ' ') && (c <= '~')) {
641            int end = strlen (line);
642            if (inserting && (col < end) && (end < size)) {
643              int ch, bs;
644              for (ch = end + 1; ch > col; ch--)
645                line[ch] = line[ch - 1];
646              if (output) {
647                fprintf(out, "%s", line + col);
648                for (bs = 0; bs < (end - col + 1); bs++)
649                  fputc('\b', out);
650              }
651            }
652            line[col++] = c;
653            if (col > end)
654              line[col] = '\0';
655            if (output)
656              fputc(c, out);
657          }
658          break;
659      }
660    }
661  }
662  return -2;
663}
664
665static bool rtems_shell_login(rtems_shell_env_t *env, FILE * in,FILE * out)
666{
667  FILE  *fd;
668  int    c;
669  time_t t;
670
671  if (out) {
672    if ((env->devname[5]!='p')||
673        (env->devname[6]!='t')||
674        (env->devname[7]!='y')) {
675      fd = fopen("/etc/issue","r");
676      if (fd) {
677        while ((c = fgetc(fd)) != EOF) {
678          if (c=='@')  {
679            switch (c = fgetc(fd)) {
680              case 'L':
681                fprintf(out,"%s", env->devname);
682                break;
683              case 'B':
684                fprintf(out,"0");
685                break;
686              case 'T':
687              case 'D':
688                time(&t);
689                fprintf(out,"%s",ctime(&t));
690                break;
691              case 'S':
692                fprintf(out,"RTEMS");
693                break;
694              case 'V':
695                fprintf(
696                  out,
697                  "%s\n%s",
698                  rtems_get_version_string(),
699                  rtems_get_copyright_notice()
700                );
701                break;
702              case '@':
703                fprintf(out,"@");
704                break;
705              default :
706                fprintf(out,"@%c",c);
707                break;
708            }
709          } else if (c=='\\')  {
710            switch(c=fgetc(fd)) {
711              case '\\': fprintf(out,"\\"); break;
712              case 'b':  fprintf(out,"\b"); break;
713              case 'f':  fprintf(out,"\f"); break;
714              case 'n':  fprintf(out,"\n"); break;
715              case 'r':  fprintf(out,"\r"); break;
716              case 's':  fprintf(out," ");  break;
717              case 't':  fprintf(out,"\t"); break;
718              case '@':  fprintf(out,"@");  break;
719            }
720          } else {
721            fputc(c,out);
722          }
723        }
724        fclose(fd);
725      }
726    } else {
727      fd = fopen("/etc/issue.net","r");
728      if (fd) {
729        while ((c=fgetc(fd))!=EOF) {
730          if (c=='%')  {
731            switch(c=fgetc(fd)) {
732              case 't':
733                fprintf(out,"%s", env->devname);
734                break;
735              case 'h':
736                fprintf(out,"0");
737                break;
738              case 'D':
739                fprintf(out," ");
740                break;
741              case 'd':
742                time(&t);
743                fprintf(out,"%s",ctime(&t));
744                break;
745              case 's':
746                fprintf(out,"RTEMS");
747                break;
748              case 'm':
749                fprintf(out,"(" CPU_NAME "/" CPU_MODEL_NAME ")");
750                break;
751              case 'r':
752                fprintf(out,rtems_get_version_string());
753                break;
754              case 'v':
755                fprintf(
756                  out,
757                  "%s\n%s",
758                  rtems_get_version_string(),
759                  rtems_get_copyright_notice()
760                );
761                break;
762              case '%':fprintf(out,"%%");
763                break;
764              default:
765                fprintf(out,"%%%c",c);
766                break;
767            }
768          } else {
769            fputc(c,out);
770          }
771        }
772        fclose(fd);
773      }
774    }
775  }
776
777  return rtems_shell_login_prompt(in, out, env->devname, env->login_check);
778}
779
780#if defined(SHELL_DEBUG)
781void rtems_shell_print_env(
782  rtems_shell_env_t * shell_env
783)
784{
785  if ( !shell_env ) {
786    printk( "shell_env is NULL\n" );
787
788    return;
789  }
790  printk( "shell_env=%p\n"
791    "shell_env->magic=0x%08x\t"
792    "shell_env->devname=%s\n"
793    "shell_env->taskname=%s\t"
794    "shell_env->exit_shell=%d\t"
795    "shell_env->forever=%d\n",
796    shell_env->magic,
797    shell_env->devname,
798    ((shell_env->taskname) ? shell_env->taskname : "NOT SET"),
799    shell_env->exit_shell,
800    shell_env->forever
801  );
802}
803#endif
804
805static rtems_task rtems_shell_task(rtems_task_argument task_argument)
806{
807  rtems_shell_env_t *shell_env = (rtems_shell_env_t*) task_argument;
808  rtems_id           wake_on_end = shell_env->wake_on_end;
809  rtems_shell_main_loop( shell_env );
810  rtems_shell_clear_shell_std_handles();
811  if (wake_on_end != RTEMS_INVALID_ID)
812    rtems_event_send (wake_on_end, RTEMS_EVENT_1);
813  rtems_task_exit();
814}
815
816static bool rtems_shell_init_user_env(void)
817{
818  rtems_status_code sc;
819
820  /* Make sure we have a private user environment */
821  sc = rtems_libio_set_private_env();
822  if (sc != RTEMS_SUCCESSFUL) {
823    rtems_error(sc, "rtems_libio_set_private_env():");
824    return false;
825  }
826
827  /* Make an effective root user */
828  seteuid(0);
829  setegid(0);
830
831  return chroot("/") == 0;
832}
833
834#define RTEMS_SHELL_MAXIMUM_ARGUMENTS (128)
835#define RTEMS_SHELL_CMD_SIZE          (128)
836#define RTEMS_SHELL_CMD_COUNT         (32)
837#define RTEMS_SHELL_PROMPT_SIZE       (128)
838
839bool rtems_shell_main_loop(
840  rtems_shell_env_t *shell_env
841)
842{
843  struct termios  term;
844  struct termios  previous_term;
845  char           *prompt = NULL;
846  int             cmd;
847  int             cmd_count = 1; /* assume a script and so only 1 command line */
848  char           *cmds[RTEMS_SHELL_CMD_COUNT];
849  char           *cmd_argv;
850  int             argc;
851  char           *argv[RTEMS_SHELL_MAXIMUM_ARGUMENTS];
852  bool            result = true;
853  bool            input_file = false;
854  int             line = 0;
855  FILE           *stdinToClose = NULL;
856  FILE           *stdoutToClose = NULL;
857
858  rtems_shell_init_environment();
859
860  if (shell_env->magic != rtems_build_name('S', 'E', 'N', 'V')) {
861    rtems_error(0, "invalid shell environment passed to the main loop)");
862    return false;
863  }
864
865  if (!rtems_shell_set_shell_env(shell_env))
866    return false;
867
868  if (!rtems_shell_init_user_env()) {
869    rtems_error(0, "rtems_shell_init_user_env");
870    rtems_shell_clear_shell_env();
871    return false;
872  }
873
874  shell_std_debug("env: %p\n", shell_env);
875
876  if (shell_env->output == NULL || strcmp(shell_env->output, "stdout") == 0) {
877    if (shell_env->parent_stdout != NULL)
878      stdout = shell_env->parent_stdout;
879  }
880  else if (strcmp(shell_env->output, "stderr") == 0) {
881    if (shell_env->parent_stderr != NULL)
882      stdout = shell_env->parent_stderr;
883    else
884      stdout = stderr;
885  } else if (strcmp(shell_env->output, "/dev/null") == 0) {
886    if (stdout == NULL) {
887      fprintf(stderr, "shell: stdout is NULLs\n");
888      rtems_shell_clear_shell_env();
889      return false;
890    }
891    fclose (stdout);
892  } else {
893    FILE *output = fopen(shell_env->output,
894                         shell_env->output_append ? "a" : "w");
895    if (output == NULL) {
896      fprintf(stderr, "shell: open output %s failed: %s\n",
897              shell_env->output, strerror(errno));
898      rtems_shell_clear_shell_env();
899      return false;
900    }
901    stdout = output;
902    stdoutToClose = output;
903  }
904
905  if (shell_env->input == NULL || strcmp(shell_env->input, "stdin") == 0) {
906    if (shell_env->parent_stdin != NULL)
907      stdin = shell_env->parent_stdin;
908  } else {
909    FILE *input = fopen(shell_env->input, "r");
910    if (input == NULL) {
911      fprintf(stderr, "shell: open input %s failed: %s\n",
912              shell_env->input, strerror(errno));
913      if (stdoutToClose != NULL)
914        fclose(stdoutToClose);
915      rtems_shell_clear_shell_env();
916      return false;
917    }
918    stdin = input;
919    stdinToClose = input;
920    shell_env->forever = false;
921    input_file = true;
922  }
923
924  if (!input_file) {
925    if (stdin == NULL) {
926      fprintf(stderr, "shell: stdin is NULLs\n");
927      if (stdoutToClose != NULL)
928        fclose(stdoutToClose);
929      rtems_shell_clear_shell_env();
930      return false;
931    }
932    /* Make a raw terminal, Linux Manuals */
933    if (tcgetattr(fileno(stdin), &previous_term) >= 0) {
934      term = previous_term;
935      term.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
936      term.c_oflag &= ~OPOST;
937      term.c_oflag |= (OPOST|ONLCR); /* But with cr+nl on output */
938      term.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
939      term.c_cflag |= CLOCAL | CREAD;
940      term.c_cc[VMIN]  = 1;
941      term.c_cc[VTIME] = 0;
942      if (tcsetattr (fileno(stdin), TCSADRAIN, &term) < 0) {
943        fprintf(stderr,
944                "shell: cannot set terminal attributes(%s)\n",shell_env->devname);
945      }
946    }
947    cmd_count = RTEMS_SHELL_CMD_COUNT;
948    prompt = malloc(RTEMS_SHELL_PROMPT_SIZE);
949    if (!prompt)
950        fprintf(stderr,
951                "shell: cannot allocate prompt memory\n");
952  }
953
954  shell_std_debug("child out: %d (%p)\n", fileno(stdout), stdout);
955  shell_std_debug("child  in: %d (%p)\n", fileno(stdin), stdin);
956
957   /* Do not buffer if interactive else leave buffered */
958  if (!input_file)
959    setvbuf(stdin, NULL, _IONBF, 0);
960  setvbuf(stdout, NULL, _IONBF, 0);
961
962  /*
963   * Allocate the command line buffers.
964   */
965  cmd_argv = malloc (RTEMS_SHELL_CMD_SIZE);
966  if (!cmd_argv) {
967    fprintf(stderr, "no memory for command line buffers\n" );
968  }
969
970  cmds[0] = calloc (cmd_count, RTEMS_SHELL_CMD_SIZE);
971  if (!cmds[0]) {
972    fprintf(stderr, "no memory for command line buffers\n" );
973  }
974
975  if (cmd_argv && cmds[0]) {
976
977    memset (cmds[0], 0, cmd_count * RTEMS_SHELL_CMD_SIZE);
978
979    for (cmd = 1; cmd < cmd_count; cmd++) {
980      cmds[cmd] = cmds[cmd - 1] + RTEMS_SHELL_CMD_SIZE;
981    }
982
983    do {
984      result = rtems_shell_init_user_env();
985
986      if (result) {
987        /*
988         *  By using result here, we can fall to the bottom of the
989         *  loop when the connection is dropped during login and
990         *  keep on trucking.
991         */
992        if (shell_env->login_check != NULL) {
993          result = rtems_shell_login(shell_env, stdin, stdout);
994        } else {
995          setuid(shell_env->uid);
996          setgid(shell_env->gid);
997          seteuid(shell_env->uid);
998          setegid(shell_env->gid);
999          rtems_current_user_env_getgroups();
1000
1001          result = true;
1002        }
1003      }
1004
1005      if (result)  {
1006        const char *c;
1007        memset (cmds[0], 0, cmd_count * RTEMS_SHELL_CMD_SIZE);
1008        if (!input_file) {
1009          rtems_shell_cat_file(stdout,"/etc/motd");
1010          fprintf(stdout, "\n"
1011                  "RTEMS Shell on %s. Use 'help' to list commands.\n",
1012                  shell_env->devname);
1013        }
1014
1015        if (input_file)
1016          chdir(shell_env->cwd);
1017        else
1018          chdir("/"); /* XXX: chdir to getpwent homedir */
1019
1020        shell_env->exit_shell = false;
1021
1022        for (;;) {
1023          /* Prompt section */
1024          if (prompt) {
1025            rtems_shell_get_prompt(shell_env, prompt,
1026                                   RTEMS_SHELL_PROMPT_SIZE);
1027          }
1028
1029          /* getcmd section */
1030          cmd = rtems_shell_line_editor(cmds, cmd_count,
1031                                        RTEMS_SHELL_CMD_SIZE, prompt,
1032                                        stdin, stdout);
1033
1034          if (cmd == -1)
1035            continue; /* empty line */
1036
1037          if (cmd == -2) {
1038            result = false;
1039            break; /*EOF*/
1040          }
1041
1042          line++;
1043
1044          if (shell_env->echo)
1045            fprintf(stdout, "%d: %s\n", line, cmds[cmd]);
1046
1047          /* evaluate cmd section */
1048          c = cmds[cmd];
1049          while (*c) {
1050            if (!isblank((unsigned char)*c))
1051              break;
1052            c++;
1053          }
1054
1055          if (*c == '\0')   /* empty line */
1056            continue;
1057
1058          if (*c == '#') {  /* comment character */
1059            cmds[cmd][0] = 0;
1060            continue;
1061          }
1062
1063          if (!strcmp(cmds[cmd],"bye") || !strcmp(cmds[cmd],"exit")) {
1064            fprintf(stdout, "Shell exiting\n" );
1065            break;
1066          }
1067
1068          /* exec cmd section */
1069          /* TODO:
1070           *  To avoid user crash catch the signals.
1071           *  Open a new stdio files with posibility of redirection *
1072           *  Run in a new shell task background. (unix &)
1073           *  Resuming. A little bash.
1074           */
1075          memcpy (cmd_argv, cmds[cmd], RTEMS_SHELL_CMD_SIZE);
1076          if (!rtems_shell_make_args(cmd_argv, &argc, argv,
1077                                     RTEMS_SHELL_MAXIMUM_ARGUMENTS)) {
1078            int exit_code = rtems_shell_execute_cmd(argv[0], argc, argv);
1079            if (shell_env->exit_code != NULL)
1080              *shell_env->exit_code = exit_code;
1081            if (exit_code != 0 && shell_env->exit_on_error)
1082              shell_env->exit_shell = true;
1083          }
1084
1085          /* end exec cmd section */
1086          if (shell_env->exit_shell)
1087            break;
1088        }
1089
1090        fflush( stdout );
1091        fflush( stderr );
1092      }
1093      shell_std_debug("end: %d %d\n", result, shell_env->forever);
1094    } while (result && shell_env->forever);
1095
1096  }
1097
1098  if (cmds[0])
1099    free (cmds[0]);
1100  if (cmd_argv)
1101    free (cmd_argv);
1102  if (prompt)
1103    free (prompt);
1104
1105  shell_std_debug("child in-to-close: %p\n", stdinToClose);
1106  shell_std_debug("child out-to-close: %p\n", stdoutToClose);
1107
1108  if (stdinToClose) {
1109    fclose( stdinToClose );
1110  } else {
1111    if (tcsetattr(fileno(stdin), TCSADRAIN, &previous_term) < 0) {
1112      fprintf(
1113        stderr,
1114        "shell: cannot reset terminal attributes (%s)\n",
1115        shell_env->devname
1116      );
1117    }
1118  }
1119  if ( stdoutToClose )
1120    fclose( stdoutToClose );
1121  rtems_shell_clear_shell_env();
1122  return result;
1123}
1124
1125/* ----------------------------------------------- */
1126static rtems_status_code rtems_shell_run (
1127  const char *task_name,
1128  size_t task_stacksize,
1129  rtems_task_priority task_priority,
1130  const char *devname,
1131  bool forever,
1132  bool wait,
1133  const char *input,
1134  const char *output,
1135  bool output_append,
1136  rtems_id wake_on_end,
1137  int *exit_code,
1138  bool echo,
1139  rtems_shell_login_check_t login_check
1140)
1141{
1142  rtems_id           task_id;
1143  rtems_status_code  sc;
1144  rtems_shell_env_t *shell_env;
1145  rtems_name         name;
1146
1147  rtems_shell_init_environment();
1148
1149  if ( task_name && strlen(task_name) >= 4)
1150    name = rtems_build_name(
1151      task_name[0], task_name[1], task_name[2], task_name[3]);
1152  else
1153    name = rtems_build_name( 'S', 'E', 'N', 'V' );
1154
1155  sc = rtems_task_create(
1156    name,
1157    task_priority,
1158    task_stacksize,
1159    RTEMS_PREEMPT | RTEMS_TIMESLICE | RTEMS_NO_ASR,
1160    RTEMS_LOCAL | RTEMS_FLOATING_POINT,
1161    &task_id
1162  );
1163  if (sc != RTEMS_SUCCESSFUL) {
1164    rtems_error(sc,"creating task %s in shell_init()",task_name);
1165    return sc;
1166  }
1167
1168  shell_env = rtems_shell_init_env( NULL );
1169  if ( !shell_env )  {
1170   rtems_error(RTEMS_NO_MEMORY,
1171               "allocating shell_env %s in shell_init()",task_name);
1172   return RTEMS_NO_MEMORY;
1173  }
1174
1175  shell_std_debug("run: env: %p\n", shell_env);
1176
1177  shell_env->devname       = devname;
1178  shell_env->taskname      = task_name;
1179
1180  shell_env->exit_shell    = false;
1181  shell_env->forever       = forever;
1182  shell_env->echo          = echo;
1183  shell_env->input         = input == NULL ? NULL : strdup (input);
1184  shell_env->output        = output == NULL ? NULL : strdup (output);
1185  shell_env->output_append = output_append;
1186  shell_env->parent_stdin  = stdin;
1187  shell_env->parent_stdout = stdout;
1188  shell_env->parent_stderr = stderr;
1189  shell_env->wake_on_end   = wake_on_end;
1190  shell_env->exit_code     = exit_code;
1191  shell_env->login_check   = login_check;
1192  shell_env->uid           = getuid();
1193  shell_env->gid           = getgid();
1194
1195  getcwd(shell_env->cwd, sizeof(shell_env->cwd));
1196
1197  shell_std_debug("run out: %d (%p)\n",
1198                  fileno(shell_env->parent_stdout), shell_env->parent_stdout);
1199  shell_std_debug("run  in: %d (%p)\n",
1200                  fileno(shell_env->parent_stdin), shell_env->parent_stdin);
1201
1202  sc = rtems_task_start(task_id, rtems_shell_task,
1203                        (rtems_task_argument) shell_env);
1204  if (sc != RTEMS_SUCCESSFUL) {
1205    rtems_error(sc,"starting task %s in shell_init()",task_name);
1206    free( (void*) shell_env->input );
1207    free( (void*) shell_env->output );
1208    free( shell_env );
1209    return sc;
1210  }
1211
1212  if (wait) {
1213    rtems_event_set out;
1214    sc = rtems_event_receive (RTEMS_EVENT_1, RTEMS_WAIT, 0, &out);
1215  }
1216
1217  shell_std_debug("run: end: sc:%d ec:%d\n", sc, *exit_code);
1218
1219  return sc;
1220}
1221
1222rtems_status_code rtems_shell_init(
1223  const char *task_name,
1224  size_t task_stacksize,
1225  rtems_task_priority task_priority,
1226  const char *devname,
1227  bool forever,
1228  bool wait,
1229  rtems_shell_login_check_t login_check
1230)
1231{
1232  rtems_id to_wake = RTEMS_ID_NONE;
1233  int exit_code = 0;
1234
1235  if ( wait )
1236    to_wake = rtems_task_self();
1237
1238  return rtems_shell_run(
1239    task_name,               /* task_name */
1240    task_stacksize,          /* task_stacksize */
1241    task_priority,           /* task_priority */
1242    devname,                 /* devname */
1243    forever,                 /* forever */
1244    wait,                    /* wait */
1245    "stdin",                 /* input */
1246    "stdout",                /* output */
1247    false,                   /* output_append */
1248    to_wake,                 /* wake_on_end */
1249    &exit_code,              /* exit code of command */
1250    false,                   /* echo */
1251    login_check              /* login check */
1252  );
1253}
1254
1255rtems_status_code rtems_shell_script (
1256  const char          *task_name,
1257  size_t               task_stacksize,
1258  rtems_task_priority  task_priority,
1259  const char*          input,
1260  const char*          output,
1261  bool                 output_append,
1262  bool                 wait,
1263  bool                 echo
1264)
1265{
1266  rtems_id to_wake = RTEMS_ID_NONE;
1267  int exit_code = 0;
1268  rtems_status_code sc;
1269
1270  shell_std_debug("script: in: %s out: %s\n", input, output);
1271
1272  if ( wait )
1273    to_wake = rtems_task_self();
1274
1275  sc = rtems_shell_run(
1276    task_name,       /* task_name */
1277    task_stacksize,  /* task_stacksize */
1278    task_priority,   /* task_priority */
1279    NULL,            /* devname */
1280    0,               /* forever */
1281    wait,            /* wait */
1282    input,           /* input */
1283    output,          /* output */
1284    output_append,   /* output_append */
1285    to_wake,         /* wake_on_end */
1286    &exit_code,      /* exit_code */
1287    echo,            /* echo */
1288    NULL             /* login check */
1289  );
1290
1291  if (sc == RTEMS_SUCCESSFUL)
1292  {
1293    /* Place holder until RTEMS 5 is released then the interface for
1294     * this call will change. */
1295  }
1296
1297  shell_std_debug("script: end: %d\n", sc);
1298
1299  return sc;
1300}
Note: See TracBrowser for help on using the repository browser.