OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [insight/] [readline/] [examples/] [rlfe.c] - Blame information for rev 1778

Go to most recent revision | Details | Compare with Previous | View Log

Line No. Rev Author Line
1 578 markom
/* A front-end using readline to "cook" input lines for Kawa.
2
 *
3
 * Copyright (C) 1999  Per Bothner
4
 *
5
 * This front-end program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License as published
7
 * by the Free Software Foundation; either version 2, or (at your option)
8
 * any later version.
9
 *
10
 * Some code from Johnson & Troan: "Linux Application Development"
11
 * (Addison-Wesley, 1998) was used directly or for inspiration.
12
 */
13
 
14
/* PROBLEMS/TODO:
15
 *
16
 * Only tested under Linux;  needs to be ported.
17
 *
18
 * When running mc -c under the Linux console, mc does not recognize
19
 * mouse clicks, which mc does when not running under fep.
20
 *
21
 * Pasting selected text containing tabs is like hitting the tab character,
22
 * which invokes readline completion.  We don't want this.  I don't know
23
 * if this is fixable without integrating fep into a terminal emulator.
24
 *
25
 * Echo suppression is a kludge, but can only be avoided with better kernel
26
 * support: We need a tty mode to disable "real" echoing, while still
27
 * letting the inferior think its tty driver to doing echoing.
28
 * Stevens's book claims SCR$ and BSD4.3+ have TIOCREMOTE.
29
 *
30
 * The latest readline may have some hooks we can use to avoid having
31
 * to back up the prompt.
32
 *
33
 * Desirable readline feature:  When in cooked no-echo mode (e.g. password),
34
 * echo characters are they are types with '*', but remove them when done.
35
 *
36
 * A synchronous output while we're editing an input line should be
37
 * inserted in the output view *before* the input line, so that the
38
 * lines being edited (with the prompt) float at the end of the input.
39
 *
40
 * A "page mode" option to emulate more/less behavior:  At each page of
41
 * output, pause for a user command.  This required parsing the output
42
 * to keep track of line lengths.  It also requires remembering the
43
 * output, if we want an option to scroll back, which suggests that
44
 * this should be integrated with a terminal emulator like xterm.
45
 */
46
 
47
#ifdef HAVE_CONFIG_H
48
#  include <config.h>
49
#endif
50
 
51
#include <stdio.h>
52
#include <fcntl.h>
53
#include <sys/types.h>
54
#include <sys/socket.h>
55
#include <netinet/in.h>
56
#include <arpa/inet.h>
57
#include <signal.h>
58
#include <netdb.h>
59
#include <stdlib.h>
60
#include <errno.h>
61
#include <grp.h>
62
#include <string.h>
63
#include <sys/stat.h>
64
#include <unistd.h>
65
#include <sys/ioctl.h>
66
#include <termios.h>
67
 
68
#ifdef READLINE_LIBRARY
69
#  include "readline.h"
70
#  include "history.h"
71
#else
72
#  include <readline/readline.h>
73
#  include <readline/history.h>
74
#endif
75
 
76
#ifndef COMMAND
77
#define COMMAND "/bin/sh"
78
#endif
79
#ifndef COMMAND_ARGS
80
#define COMMAND_ARGS COMMAND
81
#endif
82
 
83
#ifndef HAVE_MEMMOVE
84
#  if __GNUC__ > 1
85
#    define memmove(d, s, n)    __builtin_memcpy(d, s, n)
86
#  else
87
#    define memmove(d, s, n)    memcpy(d, s, n)
88
#  endif
89
#else
90
#  define memmove(d, s, n)      memcpy(d, s, n)
91
#endif
92
 
93
#define APPLICATION_NAME "Fep"
94
 
95
static int in_from_inferior_fd;
96
static int out_to_inferior_fd;
97
 
98
/* Unfortunately, we cannot safely display echo from the inferior process.
99
   The reason is that the echo bit in the pty is "owned" by the inferior,
100
   and if we try to turn it off, we could confuse the inferior.
101
   Thus, when echoing, we get echo twice:  First readline echoes while
102
   we're actually editing. Then we send the line to the inferior, and the
103
   terminal driver send back an extra echo.
104
   The work-around is to remember the input lines, and when we see that
105
   line come back, we supress the output.
106
   A better solution (supposedly available on SVR4) would be a smarter
107
   terminal driver, with more flags ... */
108
#define ECHO_SUPPRESS_MAX 1024
109
char echo_suppress_buffer[ECHO_SUPPRESS_MAX];
110
int echo_suppress_start = 0;
111
int echo_suppress_limit = 0;
112
 
113
#define DEBUG
114
 
115
#ifdef DEBUG
116
FILE *logfile = NULL;
117
#define DPRINT0(FMT) (fprintf(logfile, FMT), fflush(logfile))
118
#define DPRINT1(FMT, V1) (fprintf(logfile, FMT, V1), fflush(logfile))
119
#define DPRINT2(FMT, V1, V2) (fprintf(logfile, FMT, V1, V2), fflush(logfile))
120
#else
121
#define DPRINT0(FMT) /* Do nothing */
122
#define DPRINT1(FMT, V1) /* Do nothing */
123
#define DPRINT2(FMT, V1, V2) /* Do nothing */
124
#endif
125
 
126
struct termios orig_term;
127
 
128
/* Pid of child process. */
129
static pid_t child = -1;
130
 
131
static void
132
sig_child (int signo)
133
{
134
  int status;
135
  wait (&status);
136
  DPRINT0 ("(Child process died.)\n");
137
  tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
138
  exit (0);
139
}
140
 
141
volatile int propagate_sigwinch = 0;
142
 
143
/* sigwinch_handler
144
 * propagate window size changes from input file descriptor to
145
 * master side of pty.
146
 */
147
void sigwinch_handler(int signal) {
148
   propagate_sigwinch = 1;
149
}
150
 
151
/* get_master_pty() takes a double-indirect character pointer in which
152
 * to put a slave name, and returns an integer file descriptor.
153
 * If it returns < 0, an error has occurred.
154
 * Otherwise, it has returned the master pty file descriptor, and fills
155
 * in *name with the name of the corresponding slave pty.
156
 * Once the slave pty has been opened, you are responsible to free *name.
157
 */
158
 
159
int get_master_pty(char **name) {
160
   int i, j;
161
   /* default to returning error */
162
   int master = -1;
163
 
164
   /* create a dummy name to fill in */
165
   *name = strdup("/dev/ptyXX");
166
 
167
   /* search for an unused pty */
168
   for (i=0; i<16 && master <= 0; i++) {
169
      for (j=0; j<16 && master <= 0; j++) {
170
         (*name)[5] = 'p';
171
         (*name)[8] = "pqrstuvwxyzPQRST"[i];
172
         (*name)[9] = "0123456789abcdef"[j];
173
         /* open the master pty */
174
         if ((master = open(*name, O_RDWR)) < 0) {
175
            if (errno == ENOENT) {
176
               /* we are out of pty devices */
177
               free (*name);
178
               return (master);
179
            }
180
         }
181
         else {
182
           /* By substituting a letter, we change the master pty
183
            * name into the slave pty name.
184
            */
185
           (*name)[5] = 't';
186
           if (access(*name, R_OK|W_OK) != 0)
187
             {
188
               close(master);
189
               master = -1;
190
             }
191
         }
192
      }
193
   }
194
   if ((master < 0) && (i == 16) && (j == 16)) {
195
      /* must have tried every pty unsuccessfully */
196
      free (*name);
197
      return (master);
198
   }
199
 
200
   (*name)[5] = 't';
201
 
202
   return (master);
203
}
204
 
205
/* get_slave_pty() returns an integer file descriptor.
206
 * If it returns < 0, an error has occurred.
207
 * Otherwise, it has returned the slave file descriptor.
208
 */
209
 
210
int get_slave_pty(char *name) {
211
   struct group *gptr;
212
   gid_t gid;
213
   int slave = -1;
214
 
215
   /* chown/chmod the corresponding pty, if possible.
216
    * This will only work if the process has root permissions.
217
    * Alternatively, write and exec a small setuid program that
218
    * does just this.
219
    */
220
   if ((gptr = getgrnam("tty")) != 0) {
221
      gid = gptr->gr_gid;
222
   } else {
223
      /* if the tty group does not exist, don't change the
224
       * group on the slave pty, only the owner
225
       */
226
      gid = -1;
227
   }
228
 
229
   /* Note that we do not check for errors here.  If this is code
230
    * where these actions are critical, check for errors!
231
    */
232
   chown(name, getuid(), gid);
233
   /* This code only makes the slave read/writeable for the user.
234
    * If this is for an interactive shell that will want to
235
    * receive "write" and "wall" messages, OR S_IWGRP into the
236
    * second argument below.
237
    */
238
   chmod(name, S_IRUSR|S_IWUSR);
239
 
240
   /* open the corresponding slave pty */
241
   slave = open(name, O_RDWR);
242
   return (slave);
243
}
244
 
245
/* Certain special characters, such as ctrl/C, we want to pass directly
246
   to the inferior, rather than letting readline handle them. */
247
 
248
static char special_chars[20];
249
static int special_chars_count;
250
 
251
static void
252
add_special_char(int ch)
253
{
254
  if (ch != 0)
255
    special_chars[special_chars_count++] = ch;
256
}
257
 
258
static int eof_char;
259
 
260
static int
261
is_special_char(int ch)
262
{
263
  int i;
264
#if 0
265
  if (ch == eof_char && rl_point == rl_end)
266
    return 1;
267
#endif
268
  for (i = special_chars_count;  --i >= 0; )
269
    if (special_chars[i] == ch)
270
      return 1;
271
  return 0;
272
}
273
 
274
static char buf[1024];
275
/* buf[0 .. buf_count-1] is the what has been emitted on the current line.
276
   It is used as the readline prompt. */
277
static int buf_count = 0;
278
 
279
int num_keys = 0;
280
 
281
static void
282
null_prep_terminal (int meta)
283
{
284
}
285
 
286
static void
287
null_deprep_terminal ()
288
{
289
}
290
 
291
char pending_special_char;
292
 
293
static void
294
line_handler (char *line)
295
{
296
  if (line == NULL)
297
    {
298
      char buf[1];
299
      DPRINT0("saw eof!\n");
300
      buf[0] = '\004'; /* ctrl/d */
301
      write (out_to_inferior_fd, buf, 1);
302
    }
303
  else
304
    {
305
      static char enter[] = "\r";
306
      /*  Send line to inferior: */
307
      int length = strlen (line);
308
      if (length > ECHO_SUPPRESS_MAX-2)
309
        {
310
          echo_suppress_start = 0;
311
          echo_suppress_limit = 0;
312
        }
313
      else
314
        {
315
          if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
316
            {
317
              if (echo_suppress_limit - echo_suppress_start + length
318
                  <= ECHO_SUPPRESS_MAX - 2)
319
                {
320
                  memmove (echo_suppress_buffer,
321
                           echo_suppress_buffer + echo_suppress_start,
322
                           echo_suppress_limit - echo_suppress_start);
323
                  echo_suppress_limit -= echo_suppress_start;
324
                  echo_suppress_start = 0;
325
                }
326
              else
327
                {
328
                  echo_suppress_limit = 0;
329
                }
330
              echo_suppress_start = 0;
331
            }
332
          memcpy (echo_suppress_buffer + echo_suppress_limit,
333
                  line, length);
334
          echo_suppress_limit += length;
335
          echo_suppress_buffer[echo_suppress_limit++] = '\r';
336
          echo_suppress_buffer[echo_suppress_limit++] = '\n';
337
        }
338
      write (out_to_inferior_fd, line, length);
339
      if (pending_special_char == 0)
340
        {
341
          write (out_to_inferior_fd, enter, sizeof(enter)-1);
342
          if (*line)
343
            add_history (line);
344
        }
345
      free (line);
346
    }
347
  rl_callback_handler_remove ();
348
  buf_count = 0;
349
  num_keys = 0;
350
  if (pending_special_char != 0)
351
    {
352
      write (out_to_inferior_fd, &pending_special_char, 1);
353
      pending_special_char = 0;
354
    }
355
}
356
 
357
/* Value of rl_getc_function.
358
   Use this because readline should read from stdin, not rl_instream,
359
   points to the pty (so readline has monitor its terminal modes). */
360
 
361
int
362
my_rl_getc (FILE *dummy)
363
{
364
  int ch = rl_getc (stdin);
365
  if (is_special_char (ch))
366
    {
367
      pending_special_char = ch;
368
      return '\r';
369
    }
370
  return ch;
371
}
372
 
373
int
374
main(int argc, char** argv)
375
{
376
  char *path;
377
  int i;
378
  int master;
379
  char *name;
380
  int in_from_tty_fd;
381
  struct sigaction act;
382
  struct winsize ws;
383
  struct termios t;
384
  int maxfd;
385
  fd_set in_set;
386
  static char empty_string[1] = "";
387
  char *prompt = empty_string;
388
  int ioctl_err = 0;
389
 
390
#ifdef DEBUG
391
  logfile = fopen("LOG", "w");
392
#endif
393
 
394
  rl_readline_name = APPLICATION_NAME;
395
 
396
  if ((master = get_master_pty(&name)) < 0)
397
    {
398
      perror("ptypair: could not open master pty");
399
      exit(1);
400
    }
401
 
402
  DPRINT1("pty name: '%s'\n", name);
403
 
404
  /* set up SIGWINCH handler */
405
  act.sa_handler = sigwinch_handler;
406
  sigemptyset(&(act.sa_mask));
407
  act.sa_flags = 0;
408
  if (sigaction(SIGWINCH, &act, NULL) < 0)
409
    {
410
      perror("ptypair: could not handle SIGWINCH ");
411
      exit(1);
412
    }
413
 
414
  if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
415
    {
416
      perror("ptypair: could not get window size");
417
      exit(1);
418
    }
419
 
420
  if ((child = fork()) < 0)
421
    {
422
      perror("cannot fork");
423
      exit(1);
424
    }
425
 
426
  if (child == 0)
427
    {
428
      int slave;  /* file descriptor for slave pty */
429
 
430
      /* We are in the child process */
431
      close(master);
432
 
433
#ifdef TIOCSCTTY
434
      if ((slave = get_slave_pty(name)) < 0)
435
        {
436
          perror("ptypair: could not open slave pty");
437
          exit(1);
438
        }
439
      free(name);
440
#endif
441
 
442
      /* We need to make this process a session group leader, because
443
       * it is on a new PTY, and things like job control simply will
444
       * not work correctly unless there is a session group leader
445
       * and process group leader (which a session group leader
446
       * automatically is). This also disassociates us from our old
447
       * controlling tty.
448
       */
449
      if (setsid() < 0)
450
        {
451
          perror("could not set session leader");
452
        }
453
 
454
      /* Tie us to our new controlling tty. */
455
#ifdef TIOCSCTTY
456
      if (ioctl(slave, TIOCSCTTY, NULL))
457
        {
458
          perror("could not set new controlling tty");
459
        }
460
#else
461
      if ((slave = get_slave_pty(name)) < 0)
462
        {
463
          perror("ptypair: could not open slave pty");
464
          exit(1);
465
        }
466
      free(name);
467
#endif
468
 
469
      /* make slave pty be standard in, out, and error */
470
      dup2(slave, STDIN_FILENO);
471
      dup2(slave, STDOUT_FILENO);
472
      dup2(slave, STDERR_FILENO);
473
 
474
      /* at this point the slave pty should be standard input */
475
      if (slave > 2)
476
        {
477
          close(slave);
478
        }
479
 
480
      /* Try to restore window size; failure isn't critical */
481
      if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
482
        {
483
          perror("could not restore window size");
484
        }
485
 
486
      /* now start the shell */
487
      {
488
        static char* command_args[] = { COMMAND_ARGS, NULL };
489
        if (argc <= 1)
490
          execvp(COMMAND, command_args);
491
        else
492
          execvp(argv[1], &argv[1]);
493
      }
494
 
495
      /* should never be reached */
496
      exit(1);
497
    }
498
 
499
  /* parent */
500
  signal (SIGCHLD, sig_child);
501
  free(name);
502
 
503
  /* Note that we only set termios settings for standard input;
504
   * the master side of a pty is NOT a tty.
505
   */
506
  tcgetattr(STDIN_FILENO, &orig_term);
507
 
508
  t = orig_term;
509
  eof_char = t.c_cc[VEOF];
510
  /*  add_special_char(t.c_cc[VEOF]);*/
511
  add_special_char(t.c_cc[VINTR]);
512
  add_special_char(t.c_cc[VQUIT]);
513
  add_special_char(t.c_cc[VSUSP]);
514
#if defined (VDISCARD)
515
  add_special_char(t.c_cc[VDISCARD]);
516
#endif
517
 
518
#if 0
519
  t.c_lflag |= (ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
520
                ECHOK | ECHOKE | ECHONL | ECHOPRT );
521
#else
522
  t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
523
                 ECHOK | ECHOKE | ECHONL | ECHOPRT );
524
#endif
525
  t.c_iflag |= IGNBRK;
526
  t.c_cc[VMIN] = 1;
527
  t.c_cc[VTIME] = 0;
528
  tcsetattr(STDIN_FILENO, TCSANOW, &t);
529
  in_from_inferior_fd = master;
530
  out_to_inferior_fd = master;
531
  rl_instream = fdopen (master, "r");
532
  rl_getc_function = my_rl_getc;
533
 
534
  rl_prep_term_function = null_prep_terminal;
535
  rl_deprep_term_function = null_deprep_terminal;
536
  rl_callback_handler_install (prompt, line_handler);
537
 
538
  in_from_tty_fd = STDIN_FILENO;
539
  FD_ZERO (&in_set);
540
  maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
541
    : in_from_tty_fd;
542
  for (;;)
543
    {
544
      int num;
545
      FD_SET (in_from_inferior_fd, &in_set);
546
      FD_SET (in_from_tty_fd, &in_set);
547
 
548
      num = select(maxfd+1, &in_set, NULL, NULL, NULL);
549
 
550
      if (propagate_sigwinch)
551
        {
552
          struct winsize ws;
553
          if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
554
            {
555
              ioctl (master, TIOCSWINSZ, &ws);
556
            }
557
          propagate_sigwinch = 0;
558
          continue;
559
        }
560
 
561
      if (num <= 0)
562
        {
563
          perror ("select");
564
          exit (-1);
565
        }
566
      if (FD_ISSET (in_from_tty_fd, &in_set))
567
        {
568
          extern int readline_echoing_p;
569
          struct termios term_master;
570
          int do_canon = 1;
571
          int ioctl_ret;
572
 
573
          DPRINT1("[tty avail num_keys:%d]\n", num_keys);
574
 
575
          /* If we can't get tty modes for the master side of the pty, we
576
             can't handle non-canonical-mode programs.  Always assume the
577
             master is in canonical echo mode if we can't tell. */
578
          ioctl_ret = tcgetattr(master, &term_master);
579
 
580
          if (ioctl_ret >= 0)
581
            {
582
              DPRINT2 ("echo:%d, canon:%d\n",
583
                        (term_master.c_lflag & ECHO) != 0,
584
                        (term_master.c_lflag & ICANON) != 0);
585
              do_canon = (term_master.c_lflag & ICANON) != 0;
586
              readline_echoing_p = (term_master.c_lflag & ECHO) != 0;
587
            }
588
          else
589
            {
590
              if (ioctl_err == 0)
591
                DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
592
              ioctl_err = 1;
593
            }
594
 
595
          if (do_canon == 0 && num_keys == 0)
596
            {
597
              char ch[10];
598
              int count = read (STDIN_FILENO, ch, sizeof(ch));
599
              write (out_to_inferior_fd, ch, count);
600
            }
601
          else
602
            {
603
              if (num_keys == 0)
604
                {
605
                  int i;
606
                  /* Re-install callback handler for new prompt. */
607
                  if (prompt != empty_string)
608
                    free (prompt);
609
                  prompt = malloc (buf_count + 1);
610
                  if (prompt == NULL)
611
                    prompt = empty_string;
612
                  else
613
                    {
614
                      memcpy (prompt, buf, buf_count);
615
                      prompt[buf_count] = '\0';
616
                      DPRINT1("New prompt '%s'\n", prompt);
617
#if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED -- doesn't work */
618
                      rl_already_prompted = buf_count > 0;
619
#else
620
                      if (buf_count > 0)
621
                        write (1, "\r", 1);
622
#endif
623
                    }
624
                  rl_callback_handler_install (prompt, line_handler);
625
                }
626
              num_keys++;
627
              rl_callback_read_char ();
628
            }
629
        }
630
      else /* input from inferior. */
631
        {
632
          int i;
633
          int count;
634
          int old_count;
635
          if (buf_count > (sizeof(buf) >> 2))
636
            buf_count = 0;
637
          count = read (in_from_inferior_fd, buf+buf_count,
638
                        sizeof(buf) - buf_count);
639
          if (count <= 0)
640
            {
641
              DPRINT0 ("(Connection closed by foreign host.)\n");
642
              tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
643
              exit (0);
644
            }
645
          old_count = buf_count;
646
 
647
          /* Look for any pending echo that we need to suppress. */
648
          while (echo_suppress_start < echo_suppress_limit
649
                 && count > 0
650
                 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
651
            {
652
              count--;
653
              buf_count++;
654
              echo_suppress_start++;
655
            }
656
 
657
          /* Write to the terminal anything that was not suppressed. */
658
          if (count > 0)
659
            write (1, buf + buf_count, count);
660
 
661
          /* Finally, look for a prompt candidate.
662
           * When we get around to going input (from the keyboard),
663
           * we will consider the prompt to be anything since the last
664
           * line terminator.  So we need to save that text in the
665
           * initial part of buf.  However, anything before the
666
           * most recent end-of-line is not interesting. */
667
          buf_count += count;
668
#if 1
669
          for (i = buf_count;  --i >= old_count; )
670
#else
671
          for (i = buf_count - 1;  i-- >= buf_count - count; )
672
#endif
673
            {
674
              if (buf[i] == '\n' || buf[i] == '\r')
675
                {
676
                  i++;
677
                  memmove (buf, buf+i, buf_count - i);
678
                  buf_count -= i;
679
                  break;
680
                }
681
            }
682
          DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
683
        }
684
    }
685
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.