Upload Tizen:Base source
[framework/base/util-linux-ng.git] / text-utils / more.c
1 /*
2  * Copyright (C) 1980 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17
18 /*
19 ** more.c - General purpose tty output filter and file perusal program
20 **
21 **      by Eric Shienbrood, UC Berkeley
22 **
23 **      modified by Geoff Peck, UCB to add underlining, single spacing
24 **      modified by John Foderaro, UCB to add -c and MORE environment variable
25 **      modified by Erik Troan <ewt@redhat.com> to be more posix and so compile
26 **        on linux/axp.
27 **      modified by Kars de Jong <jongk@cs.utwente.nl> to use terminfo instead
28 **        of termcap.
29         1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
30         - added Native Language Support
31         1999-03-19 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
32         - more nls translatable strings
33         1999-05-09 aeb - applied a RedHat patch (setjmp->sigsetjmp); without it
34         a second ^Z would fail.
35         1999-05-09 aeb - undone Kars' work, so that more works without
36         libcurses (and hence can be in /bin with libcurses being in /usr/lib
37         which may not be mounted). However, when termcap is not present curses
38         can still be used.
39 */
40
41 #include <stdio.h>
42 #include <string.h>
43 #include <unistd.h>
44 #include <stdlib.h>             /* for alloca() */
45 #include <stdarg.h>             /* for va_start() etc */
46 #include <sys/param.h>
47 #include <ctype.h>
48 #include <signal.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <termios.h>
52 #include <setjmp.h>
53 #include <sys/ioctl.h>
54 #include <sys/stat.h>
55 #include <sys/file.h>
56 #include <sys/wait.h>
57 #include "xstrncpy.h"
58 #include "nls.h"
59 #include "widechar.h"
60
61 #define _REGEX_RE_COMP
62 #include <regex.h>
63 #undef _REGEX_RE_COMP
64
65 #define VI              "vi"    /* found on the user's path */
66
67 #define Fopen(s,m)      (Currline = 0,file_pos=0,fopen(s,m))
68 #define Ftell(f)        file_pos
69 #define Fseek(f,off)    (file_pos=off,fseek(f,off,0))
70 #define Getc(f)         (++file_pos, getc(f))
71 #define Ungetc(c,f)     (--file_pos, ungetc(c,f))
72 #define putcerr(c)      fputc(c, stderr)
73 #define putserr(s)      fputs(s, stderr)
74 #define putsout(s)      fputs(s, stdout)
75
76 #define stty(fd,argp)  tcsetattr(fd,TCSANOW,argp)
77
78 /* some function declarations */
79 void initterm(void);
80 void kill_line(void);
81 void doclear(void);
82 void cleareol(void);
83 void clreos(void);
84 void home(void);
85 void error (char *mess);
86 void do_shell (char *filename);
87 int  colon (char *filename, int cmd, int nlines);
88 int  expand (char **outbuf, char *inbuf);
89 void argscan(char *s,char *argv0);
90 void rdline (register FILE *f);
91 void copy_file(register FILE *f);
92 void search(char buf[], FILE *file, register int n);
93 void skipf (register int nskip);
94 void skiplns(register int n, register FILE *f);
95 void screen (register FILE *f, register int num_lines);
96 int  command (char *filename, register FILE *f);
97 void erasep (register int col);
98 void show (register char ch);
99 void set_tty(void);
100 void reset_tty(void);
101 void ttyin (char buf[], register int nmax, char pchar);
102 int  number(char *cmd);
103 int  readch (void);
104 int  get_line(register FILE *f, int *length);
105 void prbuf (register char *s, register int n);
106 void execute (char *filename, char *cmd, ...);
107 FILE *checkf (char *, int *);
108
109 #define TBUFSIZ 1024
110 #define LINSIZ  256
111 #define ctrl(letter)    (letter & 077)
112 #define RUBOUT  '\177'
113 #define ESC     '\033'
114 #define QUIT    '\034'
115
116 struct termios  otty, savetty0;
117 long            file_pos, file_size;
118 int             fnum, no_intty, no_tty, slow_tty;
119 int             dum_opt, dlines;
120 void            onquit(int), onsusp(int), chgwinsz(int), end_it(int);
121 int             nscroll = 11;   /* Number of lines scrolled by 'd' */
122 int             fold_opt = 1;   /* Fold long lines */
123 int             stop_opt = 1;   /* Stop after form feeds */
124 int             ssp_opt = 0;    /* Suppress white space */
125 int             ul_opt = 1;     /* Underline as best we can */
126 int             promptlen;
127 int             Currline;       /* Line we are currently at */
128 int             startup = 1;
129 int             firstf = 1;
130 int             notell = 1;
131 int             docrterase = 0;
132 int             docrtkill = 0;
133 int             bad_so; /* True if overwriting does not turn off standout */
134 int             inwait, Pause, errors;
135 int             within; /* true if we are within a file,
136                         false if we are between files */
137 int             hard, dumb, noscroll, hardtabs, clreol, eatnl;
138 int             catch_susp;     /* We should catch the SIGTSTP signal */
139 char            **fnames;       /* The list of file names */
140 int             nfiles;         /* Number of files left to process */
141 char            *shell;         /* The name of the shell to use */
142 int             shellp;         /* A previous shell command exists */
143 sigjmp_buf      restore;
144 char            Line[LINSIZ+2]; /* Line buffer */
145 int             Lpp = 24;       /* lines per page */
146 char            *Clear;         /* clear screen */
147 char            *eraseln;       /* erase line */
148 char            *Senter, *Sexit;/* enter and exit standout mode */
149 char            *ULenter, *ULexit;      /* enter and exit underline mode */
150 char            *chUL;          /* underline character */
151 char            *chBS;          /* backspace character */
152 char            *Home;          /* go to home */
153 char            *cursorm;       /* cursor movement */
154 char            cursorhome[40]; /* contains cursor movement to home */
155 char            *EodClr;        /* clear rest of screen */
156 int             Mcol = 80;      /* number of columns */
157 int             Wrap = 1;       /* set if automargins */
158 int             soglitch;       /* terminal has standout mode glitch */
159 int             ulglitch;       /* terminal has underline mode glitch */
160 int             pstate = 0;     /* current UL state */
161 static int      magic(FILE *, char *);
162 struct {
163     long chrctr, line;
164 } context, screen_start;
165 extern char     PC;             /* pad character */
166
167 #ifdef HAVE_NCURSES_H
168 # include <ncurses.h>
169 #elif defined(HAVE_NCURSES_NCURSES_H)
170 # include <ncurses/ncurses.h>
171 #endif
172
173 #if defined(HAVE_NCURSES_H) || defined(HAVE_NCURSES_NCURSES_H)
174 # include <term.h>                      /* include after <curses.h> */
175
176 static void
177 my_putstring(char *s) {
178         tputs (s, 1, putchar);          /* putp(s); */
179 }
180
181 static void
182 my_setupterm(char *term, int fildes, int *errret) {
183      setupterm(term, fildes, errret);
184 }
185
186 static int
187 my_tgetnum(char *s, char *ss) {
188      return tigetnum(ss);
189 }
190
191 static int
192 my_tgetflag(char *s, char *ss) {
193      return tigetflag(ss);
194 }
195
196 static char *
197 my_tgetstr(char *s, char *ss) {
198      return tigetstr(ss);
199 }
200
201 static char *
202 my_tgoto(char *cap, int col, int row) {
203      return tparm(cap, col, row);
204 }
205
206 #elif defined(HAVE_LIBTERMCAP)          /* !ncurses */
207
208 #include <termcap.h>
209
210 char termbuffer[4096];
211 char tcbuffer[4096];
212 char *strbuf = termbuffer;
213
214 static void
215 my_putstring(char *s) {
216      tputs (s, 1, putchar);
217 }
218
219 static void
220 my_setupterm(char *term, int fildes, int *errret) {
221      *errret = tgetent(tcbuffer, term);
222 }
223
224 static int
225 my_tgetnum(char *s, char *ss) {
226      return tgetnum(s);
227 }
228
229 static int
230 my_tgetflag(char *s, char *ss) {
231      return tgetflag(s);
232 }
233
234 static char *
235 my_tgetstr(char *s, char *ss) {
236      return tgetstr(s, &strbuf);
237 }
238
239 static char *
240 my_tgoto(char *cap, int col, int row) {
241      return tgoto(cap, col, row);
242 }
243
244 #endif /* HAVE_LIBTERMCAP */
245
246 static void
247 idummy(int *kk) {}
248
249 static void
250 Fdummy(FILE **ff) {}
251
252 static void
253 usage(char *s) {
254         char *p = strrchr(s, '/');
255         fprintf(stderr,
256                 _("usage: %s [-dflpcsu] [+linenum | +/pattern] name1 name2 ...\n"),
257                 p ? p + 1 : s);
258 }
259
260 int main(int argc, char **argv) {
261     FILE        *f;
262     char        *s;
263     char        *p;
264     int         ch;
265     int         left;
266     int         prnames = 0;
267     int         initopt = 0;
268     int         srchopt = 0;
269     int         clearit = 0;
270     int         initline = 0;
271     char        initbuf[80];
272
273     setlocale(LC_ALL, "");
274     bindtextdomain(PACKAGE, LOCALEDIR);
275     textdomain(PACKAGE);
276     
277     /* avoid gcc complaints about register variables that
278        may be clobbered by a longjmp, by forcing our variables here
279        to be non-register */
280     Fdummy(&f); idummy(&left); idummy(&prnames);
281     idummy(&initopt); idummy(&srchopt); idummy(&initline);
282
283     nfiles = argc;
284     fnames = argv;
285     setlocale(LC_ALL, "");
286     initterm ();
287     nscroll = Lpp/2 - 1;
288     if (nscroll <= 0)
289         nscroll = 1;
290     if((s = getenv("MORE")) != NULL) argscan(s,argv[0]);
291     while (--nfiles > 0) {
292         if ((ch = (*++fnames)[0]) == '-') {
293             argscan(*fnames+1,argv[0]);
294         }
295         else if (ch == '+') {
296             s = *fnames;
297             if (*++s == '/') {
298                 srchopt++;
299                 for (++s, p = initbuf; p < initbuf + 79 && *s != '\0';)
300                     *p++ = *s++;
301                 *p = '\0';
302             }
303             else {
304                 initopt++;
305                 for (initline = 0; *s != '\0'; s++)
306                     if (isdigit (*s))
307                         initline = initline*10 + *s -'0';
308                 --initline;
309             }
310         }
311         else break;
312     }
313     /* allow clreol only if Home and eraseln and EodClr strings are
314      *  defined, and in that case, make sure we are in noscroll mode
315      */
316     if (clreol) {
317         if((Home == NULL) || (*Home == '\0') ||
318            (eraseln == NULL) || (*eraseln == '\0') ||
319            (EodClr == NULL) || (*EodClr == '\0') )
320               clreol = 0;
321         else noscroll = 1;
322     }
323     if (dlines == 0)
324             dlines = Lpp - 1;   /* was: Lpp - (noscroll ? 1 : 2) */
325     left = dlines;
326     if (nfiles > 1)
327         prnames++;
328     if (!no_intty && nfiles == 0) {
329         usage(argv[0]);
330         exit(1);
331     }
332     else
333         f = stdin;
334     if (!no_tty) {
335         signal(SIGQUIT, onquit);
336         signal(SIGINT, end_it);
337 #ifdef SIGWINCH
338         signal(SIGWINCH, chgwinsz);
339 #endif
340         if (signal (SIGTSTP, SIG_IGN) == SIG_DFL) {
341             signal(SIGTSTP, onsusp);
342             catch_susp++;
343         }
344         stty (fileno(stderr), &otty);
345     }
346     if (no_intty) {
347         if (no_tty)
348             copy_file (stdin);
349         else {
350             if ((ch = Getc (f)) == '\f')
351                 doclear();
352             else {
353                 Ungetc (ch, f);
354                 if (noscroll && (ch != EOF)) {
355                     if (clreol)
356                         home ();
357                     else
358                         doclear ();
359                 }
360             }
361             if (srchopt)
362             {
363                 search (initbuf, stdin, 1);
364                 if (noscroll)
365                     left--;
366             }
367             else if (initopt)
368                 skiplns (initline, stdin);
369             screen (stdin, left);
370         }
371         no_intty = 0;
372         prnames++;
373         firstf = 0;
374     }
375
376     while (fnum < nfiles) {
377         if ((f = checkf (fnames[fnum], &clearit)) != NULL) {
378             context.line = context.chrctr = 0;
379             Currline = 0;
380             if (firstf) sigsetjmp (restore, 1);
381             if (firstf) {
382                 firstf = 0;
383                 if (srchopt) {
384                     search (initbuf, f, 1);
385                     if (noscroll)
386                         left--;
387                 }
388                 else if (initopt)
389                     skiplns (initline, f);
390             }
391             else if (fnum < nfiles && !no_tty) {
392                 sigsetjmp (restore, 1);
393                 left = command (fnames[fnum], f);
394             }
395             if (left != 0) {
396                 if ((noscroll || clearit) && (file_size != LONG_MAX)) {
397                     if (clreol)
398                         home ();
399                     else
400                         doclear ();
401                 }
402                 if (prnames) {
403                     if (bad_so)
404                         erasep (0);
405                     if (clreol)
406                         cleareol ();
407                     putsout("::::::::::::::");
408                     if (promptlen > 14)
409                         erasep (14);
410                     putchar('\n');
411                     if(clreol) cleareol();
412                     puts(fnames[fnum]);
413                     if(clreol) cleareol();
414                     puts("::::::::::::::");
415                     if (left > Lpp - 4)
416                         left = Lpp - 4;
417                 }
418                 if (no_tty)
419                     copy_file (f);
420                 else {
421                     within++;
422                     screen(f, left);
423                     within = 0;
424                 }
425             }
426             sigsetjmp (restore, 1);
427             fflush(stdout);
428             fclose(f);
429             screen_start.line = screen_start.chrctr = 0L;
430             context.line = context.chrctr = 0L;
431         }
432         fnum++;
433         firstf = 0;
434     }
435     reset_tty ();
436     exit(0);
437 }
438
439 void argscan(char *s, char *argv0) {
440         int seen_num = 0;
441
442         while (*s != '\0') {
443                 switch (*s) {
444                   case '0': case '1': case '2':
445                   case '3': case '4': case '5':
446                   case '6': case '7': case '8':
447                   case '9':
448                         if (!seen_num) {
449                                 dlines = 0;
450                                 seen_num = 1;
451                         }
452                         dlines = dlines*10 + *s - '0';
453                         break;
454                   case 'd':
455                         dum_opt = 1;
456                         break;
457                   case 'l':
458                         stop_opt = 0;
459                         break;
460                   case 'f':
461                         fold_opt = 0;
462                         break;
463                   case 'p':
464                         noscroll++;
465                         break;
466                   case 'c':
467                         clreol++;
468                         break;
469                   case 's':
470                         ssp_opt = 1;
471                         break;
472                   case 'u':
473                         ul_opt = 0;
474                         break;
475                   case '-': case ' ': case '\t':
476                         break;
477                   default:
478                         fprintf(stderr,
479                                 _("%s: unknown option \"-%c\"\n"), argv0, *s);
480                         usage(argv0);
481                         exit(1);
482                         break;
483                 }
484                 s++;
485         }
486 }
487
488
489 /*
490 ** Check whether the file named by fs is an ASCII file which the user may
491 ** access.  If it is, return the opened file. Otherwise return NULL.
492 */
493
494 FILE *
495 checkf (fs, clearfirst)
496         register char *fs;
497         int *clearfirst;
498 {
499         struct stat stbuf;
500         register FILE *f;
501         int c;
502
503         if (stat (fs, &stbuf) == -1) {
504                 (void)fflush(stdout);
505                 if (clreol)
506                         cleareol ();
507                 perror(fs);
508                 return((FILE *)NULL);
509         }
510         if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
511                 printf(_("\n*** %s: directory ***\n\n"), fs);
512                 return((FILE *)NULL);
513         }
514         if ((f = Fopen(fs, "r")) == NULL) {
515                 (void)fflush(stdout);
516                 perror(fs);
517                 return((FILE *)NULL);
518         }
519         if (magic(f, fs))
520                 return((FILE *)NULL);
521         fcntl(fileno(f), F_SETFD, FD_CLOEXEC );
522         c = Getc(f);
523         *clearfirst = (c == '\f');
524         Ungetc (c, f);
525         if ((file_size = stbuf.st_size) == 0)
526                 file_size = LONG_MAX;
527         return(f);
528 }
529
530 /*
531  * magic --
532  *      check for file magic numbers.  This code would best be shared with
533  *      the file(1) program or, perhaps, more should not try to be so smart.
534  */
535 static int
536 magic(f, fs)
537         FILE *f;
538         char *fs;
539 {
540         signed char twobytes[2];
541
542         /* don't try to look ahead if the input is unseekable */
543         if (fseek(f, 0L, SEEK_SET))
544                 return 0;
545
546         if (fread(twobytes, 2, 1, f) == 1) {
547                 switch(twobytes[0] + (twobytes[1]<<8)) {
548                 case 0407:      /* a.out obj */
549                 case 0410:      /* a.out exec */
550                 case 0413:      /* a.out demand exec */
551                 case 0405:
552                 case 0411:
553                 case 0177545:
554                 case 0x457f:            /* simple ELF detection */
555                         printf(_("\n******** %s: Not a text file ********\n\n"), fs);
556                         (void)fclose(f);
557                         return 1;
558                 }
559         }
560         (void)fseek(f, 0L, SEEK_SET);           /* rewind() not necessary */
561         return 0;
562 }
563
564 /*
565 ** Print out the contents of the file f, one screenful at a time.
566 */
567
568 #define STOP -10
569
570 void screen (register FILE *f, register int num_lines)
571 {
572     register int c;
573     register int nchars;
574     int length;                 /* length of current line */
575     static int prev_len = 1;    /* length of previous line */
576
577     for (;;) {
578         while (num_lines > 0 && !Pause) {
579             if ((nchars = get_line (f, &length)) == EOF)
580             {
581                 if (clreol)
582                     clreos();
583                 return;
584             }
585             if (ssp_opt && length == 0 && prev_len == 0)
586                 continue;
587             prev_len = length;
588             if (bad_so || ((Senter && *Senter == ' ') && (promptlen > 0)))
589                 erasep (0);
590             /* must clear before drawing line since tabs on some terminals
591              * do not erase what they tab over.
592              */
593             if (clreol)
594                 cleareol ();
595             prbuf (Line, length);
596             if (nchars < promptlen)
597                 erasep (nchars);        /* erasep () sets promptlen to 0 */
598             else promptlen = 0;
599             /* is this needed?
600              * if (clreol)
601              *  cleareol();     * must clear again in case we wrapped *
602              */
603             if (nchars < Mcol || !fold_opt)
604                 prbuf("\n", 1); /* will turn off UL if necessary */
605             if (nchars == STOP)
606                 break;
607             num_lines--;
608         }
609         if (pstate) {
610                 my_putstring (ULexit);
611                 pstate = 0;
612         }
613         fflush(stdout);
614         if ((c = Getc(f)) == EOF)
615         {
616             if (clreol)
617                 clreos ();
618             return;
619         }
620
621         if (Pause && clreol)
622             clreos ();
623         Ungetc (c, f);
624         sigsetjmp (restore, 1);
625         Pause = 0; startup = 0;
626         if ((num_lines = command (NULL, f)) == 0)
627             return;
628         if (hard && promptlen > 0)
629                 erasep (0);
630         if (noscroll && num_lines >= dlines)
631         {
632             if (clreol)
633                 home();
634             else
635                 doclear ();
636         }
637         screen_start.line = Currline;
638         screen_start.chrctr = Ftell (f);
639     }
640 }
641
642 /*
643 ** Come here if a quit signal is received
644 */
645
646 void onquit(int dummy) {
647     signal(SIGQUIT, SIG_IGN);
648     if (!inwait) {
649         putchar ('\n');
650         if (!startup) {
651             signal(SIGQUIT, onquit);
652             siglongjmp (restore, 1);
653         }
654         else
655             Pause++;
656     }
657     else if (!dum_opt && notell) {
658         promptlen += fprintf(stderr, _("[Use q or Q to quit]"));
659         notell = 0;
660     }
661     signal(SIGQUIT, onquit);
662 }
663
664 /*
665 ** Come here if a signal for a window size change is received
666 */
667
668 #ifdef SIGWINCH
669 void chgwinsz(int dummy) {
670     struct winsize win;
671
672     (void) signal(SIGWINCH, SIG_IGN);
673     if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) {
674         if (win.ws_row != 0) {
675             Lpp = win.ws_row;
676             nscroll = Lpp/2 - 1;
677             if (nscroll <= 0)
678                 nscroll = 1;
679             dlines = Lpp - 1;   /* was: Lpp - (noscroll ? 1 : 2) */
680         }
681         if (win.ws_col != 0)
682             Mcol = win.ws_col;
683     }
684     (void) signal(SIGWINCH, chgwinsz);
685 }
686 #endif
687
688 /*
689 ** Clean up terminal state and exit. Also come here if interrupt signal received
690 */
691
692 void end_it (int dummy) {
693     reset_tty ();
694     if (clreol) {
695         putchar ('\r');
696         clreos ();
697         fflush (stdout);
698     }
699     else if (!clreol && (promptlen > 0)) {
700         kill_line ();
701         fflush (stdout);
702     }
703     else
704         putcerr('\n');
705     _exit(0);
706 }
707
708 void copy_file(register FILE *f) {
709     register int c;
710
711     while ((c = getc(f)) != EOF)
712         putchar(c);
713 }
714
715 #define ringbell()      putcerr('\007')
716
717 /* See whether the last component of the path name "path" is equal to the
718 ** string "string"
719 */
720
721 static int tailequ (char *path, register char *string)
722 {
723         register char *tail;
724
725         tail = path + strlen(path);
726         while (--tail >= path)
727                 if (*tail == '/')
728                         break;
729         ++tail;
730         while (*tail++ == *string++)
731                 if (*tail == '\0')
732                         return(1);
733         return(0);
734 }
735
736 static void prompt (char *filename)
737 {
738     if (clreol)
739         cleareol ();
740     else if (promptlen > 0)
741         kill_line ();
742     if (!hard) {
743         promptlen = 0;
744         if (Senter && Sexit) {
745             my_putstring (Senter);
746             promptlen += (2 * soglitch);
747         }
748         if (clreol)
749             cleareol ();
750         promptlen += printf(_("--More--"));
751         if (filename != NULL) {
752             promptlen += printf(_("(Next file: %s)"), filename);
753         } else if (!no_intty) {
754             promptlen += printf("(%d%%)", (int) ((file_pos * 100) / file_size));
755         }
756         if (dum_opt) {
757             promptlen += printf(_("[Press space to continue, 'q' to quit.]"));
758         }
759         if (Senter && Sexit)
760             my_putstring (Sexit);
761         if (clreol)
762             clreos ();
763         fflush(stdout);
764     }
765     else
766         ringbell();
767     inwait++;
768 }
769
770 /*
771  * Get a logical line
772  */
773
774 int get_line(register FILE *f, int *length)
775 {
776     int c;
777     char *p;
778     int column;
779     static int colflg;
780
781 #ifdef HAVE_WIDECHAR
782     int i;
783     wchar_t wc;
784     int wc_width;
785     mbstate_t state, state_bak;         /* Current status of the stream. */
786     char mbc[MB_LEN_MAX];               /* Buffer for one multibyte char. */
787     size_t mblength;                    /* Byte length of multibyte char. */
788     size_t mbc_pos = 0;                 /* Postion of the MBC. */
789     int use_mbc_buffer_flag = 0;        /* If 1, mbc has data. */
790     int break_flag = 0;                 /* If 1, exit while(). */
791     long file_pos_bak = Ftell (f);
792
793     memset (&state, '\0', sizeof (mbstate_t));
794 #endif
795
796     p = Line;
797     column = 0;
798     c = Getc (f);
799     if (colflg && c == '\n') {
800         Currline++;
801         c = Getc (f);
802     }
803     while (p < &Line[LINSIZ - 1]) {
804 #ifdef HAVE_WIDECHAR
805         if (fold_opt && use_mbc_buffer_flag && MB_CUR_MAX > 1) {
806             use_mbc_buffer_flag = 0;
807             state_bak = state;
808             mbc[mbc_pos++] = c;
809 process_mbc:
810             mblength = mbrtowc (&wc, mbc, mbc_pos, &state);
811
812             switch (mblength) {
813               case (size_t)-2:    /* Incomplete multibyte character. */
814                 use_mbc_buffer_flag = 1;
815                 state = state_bak;
816                 break;
817
818               case (size_t)-1:    /* Invalid as a multibyte character. */
819                 *p++ = mbc[0];
820                 state = state_bak;
821                 column++;
822                 file_pos_bak++;
823                 
824                 if (column >= Mcol) {
825                     Fseek (f, file_pos_bak);
826                 } else {
827                     memmove (mbc, mbc + 1, --mbc_pos);
828                     if (mbc_pos > 0) {
829                         mbc[mbc_pos] = '\0';
830                         goto process_mbc;
831                     }
832                 }
833                 break;
834
835               default:
836                 wc_width = wcwidth (wc);
837
838                 if (column + wc_width > Mcol) {
839                     Fseek (f, file_pos_bak);
840                     break_flag = 1;
841                 } else {
842                     for (i = 0; i < mbc_pos; i++)
843                       *p++ = mbc[i];
844                     if (wc_width > 0)
845                       column += wc_width;
846                 }
847             }
848
849             if (break_flag || column >= Mcol)
850               break;
851
852             c = Getc (f);
853             continue;
854         }
855 #endif 
856         if (c == EOF) {
857             if (p > Line) {
858                 *p = '\0';
859                 *length = p - Line;
860                 return (column);
861             }
862             *length = p - Line;
863             return (EOF);
864         }
865         if (c == '\n') {
866             Currline++;
867             break;
868         }
869
870         *p++ = c;
871 #if 0
872         if (c == '\033') {      /* ESC */
873                 c = Getc(f);
874                 while (c > ' ' && c < '0' && p < &Line[LINSIZ - 1]) {
875                         *p++ = c;
876                         c = Getc(f);
877                 }
878                 if (c >= '0' && c < '\177' && p < &Line[LINSIZ - 1]) {
879                         *p++ = c;
880                         c = Getc(f);
881                         continue;
882                 }
883         }
884 #endif
885         if (c == '\t') {
886             if (!hardtabs || (column < promptlen && !hard)) {
887                 if (hardtabs && eraseln && !dumb) {
888                     column = 1 + (column | 7);
889                     my_putstring (eraseln);
890                     promptlen = 0;
891                 }
892                 else {
893                     for (--p; p < &Line[LINSIZ - 1];) {
894                         *p++ = ' ';
895                         if ((++column & 7) == 0)
896                             break;
897                     }
898                     if (column >= promptlen) promptlen = 0;
899                 }
900             } else
901                 column = 1 + (column | 7);
902         } else if (c == '\b' && column > 0) {
903             column--;
904         } else if (c == '\r') {
905             int next = Getc(f);
906             if (next == '\n') {
907                 p--;
908                 Currline++;
909                 break;
910             }
911             Ungetc(next,f);
912             column = 0;
913         } else if (c == '\f' && stop_opt) {
914                 p[-1] = '^';
915                 *p++ = 'L';
916                 column += 2;
917                 Pause++;
918         } else if (c == EOF) {
919             *length = p - Line;
920             return (column);
921         } else {
922 #ifdef HAVE_WIDECHAR
923             if (fold_opt && MB_CUR_MAX > 1) {
924                 memset (mbc, '\0', MB_LEN_MAX);
925                 mbc_pos = 0;
926                 mbc[mbc_pos++] = c;
927                 state_bak = state;
928
929                 mblength = mbrtowc (&wc, mbc, mbc_pos, &state);
930
931                 /* The value of mblength is always less than 2 here. */
932                 switch (mblength) {
933                   case (size_t)-2:
934                     p--;
935                     file_pos_bak = Ftell (f) - 1;
936                     state = state_bak;
937                     use_mbc_buffer_flag = 1;
938                     break;
939
940                   case (size_t)-1:
941                     state = state_bak;
942                     column++;
943                     break;
944
945                   default:
946                     wc_width = wcwidth (wc);
947                     if (wc_width > 0)
948                       column += wc_width;
949                 }
950             } else
951 #endif
952               {
953                 if (isprint(c))
954                    column++;
955               }
956         }
957
958         if (column >= Mcol && fold_opt)
959                 break;
960         c = Getc (f);
961     }
962     if (column >= Mcol && Mcol > 0) {
963         if (!Wrap) {
964             *p++ = '\n';
965         }
966     }
967     colflg = column == Mcol && fold_opt;
968     if (colflg && eatnl && Wrap) {
969         *p++ = '\n'; /* simulate normal wrap */
970     }
971     *length = p - Line;
972     *p = 0;
973     return (column);
974 }
975
976 /*
977 ** Erase the rest of the prompt, assuming we are starting at column col.
978 */
979
980 void erasep (register int col)
981 {
982
983     if (promptlen == 0)
984         return;
985     if (hard) {
986         putchar ('\n');
987     }
988     else {
989         if (col == 0)
990             putchar ('\r');
991         if (!dumb && eraseln)
992             my_putstring (eraseln);
993         else
994             for (col = promptlen - col; col > 0; col--)
995                 putchar (' ');
996     }
997     promptlen = 0;
998 }
999
1000 /*
1001 ** Erase the current line entirely
1002 */
1003
1004 void kill_line()
1005 {
1006     erasep(0);
1007     if (!eraseln || dumb)
1008         putchar('\r');
1009 }
1010
1011 /*
1012  * force clear to end of line
1013  */
1014 void cleareol()
1015 {
1016     my_putstring(eraseln);
1017 }
1018
1019 void clreos()
1020 {
1021     my_putstring(EodClr);
1022 }
1023
1024 /* Print a buffer of n characters */
1025
1026 void prbuf (register char *s, register int n)
1027 {
1028     register char c;                    /* next output character */
1029     register int state;                 /* next output char's UL state */
1030 #define wouldul(s,n)    ((n) >= 2 && (((s)[0] == '_' && (s)[1] == '\b') || ((s)[1] == '\b' && (s)[2] == '_')))
1031
1032     while (--n >= 0)
1033         if (!ul_opt)
1034             putchar (*s++);
1035         else {
1036             if (*s == ' ' && pstate == 0 && ulglitch && wouldul(s+1, n-1)) {
1037                 s++;
1038                 continue;
1039             }
1040             if ((state = wouldul(s, n)) != 0) {
1041                 c = (*s == '_')? s[2] : *s ;
1042                 n -= 2;
1043                 s += 3;
1044             } else
1045                 c = *s++;
1046             if (state != pstate) {
1047                 if (c == ' ' && state == 0 && ulglitch && wouldul(s, n-1))
1048                     state = 1;
1049                 else
1050                     my_putstring(state ? ULenter : ULexit);
1051             }
1052             if (c != ' ' || pstate == 0 || state != 0 || ulglitch == 0)
1053 #ifdef HAVE_WIDECHAR
1054             {
1055                 wchar_t wc;
1056                 size_t mblength;
1057                 mbstate_t state;
1058                 memset (&state, '\0', sizeof (mbstate_t));
1059                 s--; n++;
1060                 mblength = mbrtowc (&wc, s, n, &state);
1061                 if (mblength == (size_t) -2 || mblength == (size_t) -1)
1062                         mblength = 1;
1063                 while (mblength--)
1064                         putchar (*s++);
1065                 n += mblength;
1066             }
1067 #else
1068                 putchar(c);
1069 #endif /* HAVE_WIDECHAR */
1070             if (state && *chUL) {
1071                 putsout(chBS);
1072                 my_putstring(chUL);
1073             }
1074             pstate = state;
1075         }
1076 }
1077
1078 /*
1079 **  Clear the screen
1080 */
1081 void
1082 doclear()
1083 {
1084     if (Clear && !hard) {
1085         my_putstring(Clear);
1086
1087         /* Put out carriage return so that system doesn't
1088         ** get confused by escape sequences when expanding tabs
1089         */
1090         putchar ('\r');
1091         promptlen = 0;
1092     }
1093 }
1094
1095 /*
1096  * Go to home position
1097  */
1098 void
1099 home()
1100 {
1101     my_putstring(Home);
1102 }
1103
1104 static int lastcmd, lastarg, lastp;
1105 static int lastcolon;
1106 char shell_line[1000];
1107
1108 /*
1109 ** Read a command and do it. A command consists of an optional integer
1110 ** argument followed by the command character.  Return the number of lines
1111 ** to display in the next screenful.  If there is nothing more to display
1112 ** in the current file, zero is returned.
1113 */
1114
1115 int command (char *filename, register FILE *f)
1116 {
1117     register int nlines;
1118     register int retval = 0;
1119     register int c;
1120     char colonch;
1121     int done;
1122     char comchar, cmdbuf[80];
1123
1124 #define ret(val) retval=val;done++;break
1125
1126     done = 0;
1127     if (!errors)
1128         prompt (filename);
1129     else
1130         errors = 0;
1131     for (;;) {
1132         nlines = number (&comchar);
1133         lastp = colonch = 0;
1134         if (comchar == '.') {   /* Repeat last command */
1135                 lastp++;
1136                 comchar = lastcmd;
1137                 nlines = lastarg;
1138                 if (lastcmd == ':')
1139                         colonch = lastcolon;
1140         }
1141         lastcmd = comchar;
1142         lastarg = nlines;
1143         if ((cc_t) comchar == otty.c_cc[VERASE]) {
1144             kill_line ();
1145             prompt (filename);
1146             continue;
1147         }
1148         switch (comchar) {
1149         case ':':
1150             retval = colon (filename, colonch, nlines);
1151             if (retval >= 0)
1152                 done++;
1153             break;
1154         case 'b':
1155         case ctrl('B'):
1156             {
1157                 register int initline;
1158
1159                 if (no_intty) {
1160                     ringbell();
1161                     return (-1);
1162                 }
1163
1164                 if (nlines == 0) nlines++;
1165
1166                 putchar ('\r');
1167                 erasep (0);
1168                 putchar('\n');
1169                 if (clreol)
1170                         cleareol ();
1171                 if (nlines != 1)
1172                         printf(_("...back %d pages"), nlines);
1173                 else
1174                         putsout(_("...back 1 page"));
1175                 if (clreol)
1176                         cleareol ();
1177                 putchar('\n');
1178
1179                 initline = Currline - dlines * (nlines + 1);
1180                 if (! noscroll)
1181                     --initline;
1182                 if (initline < 0) initline = 0;
1183                 Fseek(f, 0L);
1184                 Currline = 0;   /* skiplns() will make Currline correct */
1185                 skiplns(initline, f);
1186                 if (! noscroll) {
1187                     ret(dlines + 1);
1188                 }
1189                 else {
1190                     ret(dlines);
1191                 }
1192             }
1193         case ' ':
1194         case 'z':
1195             if (nlines == 0) nlines = dlines;
1196             else if (comchar == 'z') dlines = nlines;
1197             ret (nlines);
1198         case 'd':
1199         case ctrl('D'):
1200             if (nlines != 0) nscroll = nlines;
1201             ret (nscroll);
1202         case 'q':
1203         case 'Q':
1204             end_it (0);
1205         case 's':
1206         case 'f':
1207         case ctrl('F'):
1208             if (nlines == 0) nlines++;
1209             if (comchar == 'f')
1210                 nlines *= dlines;
1211             putchar ('\r');
1212             erasep (0);
1213             putchar('\n');
1214             if (clreol)
1215                 cleareol ();
1216             if (nlines == 1)
1217                     putsout(_("...skipping one line"));
1218             else
1219                     printf(_("...skipping %d lines"), nlines);
1220
1221             if (clreol)
1222                 cleareol ();
1223             putchar('\n');
1224
1225             while (nlines > 0) {
1226                 while ((c = Getc (f)) != '\n')
1227                     if (c == EOF) {
1228                         retval = 0;
1229                         done++;
1230                         goto endsw;
1231                     }
1232                     Currline++;
1233                     nlines--;
1234             }
1235             ret (dlines);
1236         case '\n':
1237             if (nlines != 0)
1238                 dlines = nlines;
1239             else
1240                 nlines = 1;
1241             ret (nlines);
1242         case '\f':
1243             if (!no_intty) {
1244                 doclear ();
1245                 Fseek (f, screen_start.chrctr);
1246                 Currline = screen_start.line;
1247                 ret (dlines);
1248             }
1249             else {
1250                 ringbell();
1251                 break;
1252             }
1253         case '\'':
1254             if (!no_intty) {
1255                 kill_line ();
1256                 putsout(_("\n***Back***\n\n"));
1257                 Fseek (f, context.chrctr);
1258                 Currline = context.line;
1259                 ret (dlines);
1260             }
1261             else {
1262                 ringbell();
1263                 break;
1264             }
1265         case '=':
1266             kill_line ();
1267             promptlen = printf("%d", Currline);
1268             fflush (stdout);
1269             break;
1270         case 'n':
1271             lastp++;
1272         case '/':
1273             if (nlines == 0) nlines++;
1274             kill_line ();
1275             putchar('/');
1276             promptlen = 1;
1277             fflush (stdout);
1278             if (lastp) {
1279                 putcerr('\r');
1280                 search (NULL, f, nlines);       /* Use previous r.e. */
1281             }
1282             else {
1283                 ttyin (cmdbuf, sizeof(cmdbuf)-2, '/');
1284                 putcerr('\r');
1285                 search (cmdbuf, f, nlines);
1286             }
1287             ret (dlines-1);
1288         case '!':
1289             do_shell (filename);
1290             break;
1291         case '?':
1292         case 'h':
1293             if (noscroll) doclear();
1294             putsout(_("\n"
1295 "Most commands optionally preceded by integer argument k.  "
1296 "Defaults in brackets.\n"
1297 "Star (*) indicates argument becomes new default.\n"));
1298             puts("---------------------------------------"
1299                 "----------------------------------------");
1300             putsout(_(
1301 "<space>                 Display next k lines of text [current screen size]\n"
1302 "z                       Display next k lines of text [current screen size]*\n"
1303 "<return>                Display next k lines of text [1]*\n"
1304 "d or ctrl-D             Scroll k lines [current scroll size, initially 11]*\n"
1305 "q or Q or <interrupt>   Exit from more\n"
1306 "s                       Skip forward k lines of text [1]\n"
1307 "f                       Skip forward k screenfuls of text [1]\n"
1308 "b or ctrl-B             Skip backwards k screenfuls of text [1]\n"
1309 "'                       Go to place where previous search started\n"
1310 "=                       Display current line number\n"
1311 "/<regular expression>   Search for kth occurrence of regular expression [1]\n"
1312 "n                       Search for kth occurrence of last r.e [1]\n"
1313 "!<cmd> or :!<cmd>       Execute <cmd> in a subshell\n"
1314 "v                       Start up /usr/bin/vi at current line\n"
1315 "ctrl-L                  Redraw screen\n"
1316 ":n                      Go to kth next file [1]\n"
1317 ":p                      Go to kth previous file [1]\n"
1318 ":f                      Display current file name and line number\n"
1319 ".                       Repeat previous command\n"));
1320             puts("---------------------------------------"
1321                 "----------------------------------------");
1322             prompt(filename);
1323             break;
1324         case 'v':       /* This case should go right before default */
1325             if (!no_intty) {
1326                     /*
1327                      * Earlier: call vi +n file. This also works for emacs.
1328                      * POSIX: call vi -c n file (when editor is vi or ex).
1329                      */
1330                     char *editor, *p;
1331                     int n = (Currline - dlines <= 0 ? 1 :
1332                              Currline - (dlines + 1) / 2);
1333                     int split = 0;
1334
1335                     editor = getenv("VISUAL");
1336                     if (editor == NULL || *editor == '\0')
1337                             editor = getenv("EDITOR");
1338                     if (editor == NULL || *editor == '\0')
1339                             editor = VI;
1340
1341                     p = strrchr(editor, '/');
1342                     if (p)
1343                             p++;
1344                     else
1345                             p = editor;
1346                     if (!strcmp(p, "vi") || !strcmp(p, "ex")) {
1347                             sprintf(cmdbuf, "-c %d", n);
1348                             split = 1;
1349                     } else {
1350                             sprintf(cmdbuf, "+%d", n);
1351                     }
1352
1353                     kill_line();
1354                     printf("%s %s %s", editor, cmdbuf, fnames[fnum]);
1355                     if (split) {
1356                             cmdbuf[2] = 0;
1357                             execute(filename, editor, editor, cmdbuf,
1358                                     cmdbuf+3, fnames[fnum], (char *)0);
1359                     } else
1360                             execute(filename, editor, editor,
1361                                     cmdbuf, fnames[fnum], (char *)0);
1362                     break;
1363             }
1364             /* fall through */
1365         default:
1366             if (dum_opt) {
1367                 kill_line ();
1368                 if (Senter && Sexit) {
1369                     my_putstring (Senter);
1370                     promptlen = printf(_("[Press 'h' for instructions.]"))
1371                             + 2 * soglitch;
1372                     my_putstring (Sexit);
1373                 }
1374                 else
1375                     promptlen = printf(_("[Press 'h' for instructions.]"));
1376                 fflush (stdout);
1377             }
1378             else
1379                 ringbell();
1380             break;
1381         }
1382         if (done) break;
1383     }
1384     putchar ('\r');
1385 endsw:
1386     inwait = 0;
1387     notell++;
1388     return (retval);
1389 }
1390
1391 static char ch;
1392
1393 /*
1394  * Execute a colon-prefixed command.
1395  * Returns <0 if not a command that should cause
1396  * more of the file to be printed.
1397  */
1398
1399 int colon (char *filename, int cmd, int nlines) {
1400         if (cmd == 0)
1401                 ch = readch ();
1402         else
1403                 ch = cmd;
1404         lastcolon = ch;
1405         switch (ch) {
1406         case 'f':
1407                 kill_line ();
1408                 if (!no_intty)
1409                         promptlen = printf(_("\"%s\" line %d"), fnames[fnum], Currline);
1410                 else
1411                         promptlen = printf(_("[Not a file] line %d"), Currline);
1412                 fflush (stdout);
1413                 return (-1);
1414         case 'n':
1415                 if (nlines == 0) {
1416                         if (fnum >= nfiles - 1)
1417                                 end_it (0);
1418                         nlines++;
1419                 }
1420                 putchar ('\r');
1421                 erasep (0);
1422                 skipf (nlines);
1423                 return (0);
1424         case 'p':
1425                 if (no_intty) {
1426                         ringbell();
1427                         return (-1);
1428                 }
1429                 putchar ('\r');
1430                 erasep (0);
1431                 if (nlines == 0)
1432                         nlines++;
1433                 skipf (-nlines);
1434                 return (0);
1435         case '!':
1436                 do_shell (filename);
1437                 return (-1);
1438         case 'q':
1439         case 'Q':
1440                 end_it (0);
1441         default:
1442                 ringbell();
1443                 return (-1);
1444         }
1445 }
1446
1447 /*
1448 ** Read a decimal number from the terminal. Set cmd to the non-digit which
1449 ** terminates the number.
1450 */
1451
1452 int number(char *cmd)
1453 {
1454         register int i;
1455
1456         i = 0; ch = otty.c_cc[VKILL];
1457         for (;;) {
1458                 ch = readch ();
1459                 if (isdigit(ch))
1460                         i = i*10 + ch - '0';
1461                 else if ((cc_t) ch == otty.c_cc[VKILL])
1462                         i = 0;
1463                 else {
1464                         *cmd = ch;
1465                         break;
1466                 }
1467         }
1468         return (i);
1469 }
1470
1471 void do_shell (char *filename)
1472 {
1473         char cmdbuf[200];
1474         int rc;
1475         char *expanded;
1476
1477         kill_line ();
1478         putchar('!');
1479         fflush (stdout);
1480         promptlen = 1;
1481         if (lastp)
1482                 putsout(shell_line);
1483         else {
1484                 ttyin (cmdbuf, sizeof(cmdbuf)-2, '!');
1485                 expanded = 0;
1486                 rc = expand (&expanded, cmdbuf);
1487                 if (expanded) {
1488                         if (strlen(expanded) < sizeof(shell_line))
1489                                 strcpy(shell_line, expanded);
1490                         else
1491                                 rc = -1;
1492                         free(expanded);
1493                 }
1494                 if (rc < 0) {
1495                         putserr(_("  Overflow\n"));
1496                         prompt (filename);
1497                         return;
1498                 } else if (rc > 0) {
1499                         kill_line ();
1500                         promptlen = printf("!%s", shell_line);
1501                 }
1502         }
1503         fflush (stdout);
1504         putcerr('\n');
1505         promptlen = 0;
1506         shellp = 1;
1507         execute (filename, shell, shell, "-c", shell_line, 0);
1508 }
1509
1510 /*
1511 ** Search for nth ocurrence of regular expression contained in buf in the file
1512 */
1513
1514 void search(char buf[], FILE *file, register int n)
1515 {
1516     long startline = Ftell (file);
1517     register long line1 = startline;
1518     register long line2 = startline;
1519     register long line3 = startline;
1520     register int lncount;
1521     int saveln, rv;
1522     char *s;
1523
1524     context.line = saveln = Currline;
1525     context.chrctr = startline;
1526     lncount = 0;
1527     if ((s = re_comp (buf)) != 0)
1528         error (s);
1529     while (!feof (file)) {
1530         line3 = line2;
1531         line2 = line1;
1532         line1 = Ftell (file);
1533         rdline (file);
1534         lncount++;
1535         if ((rv = re_exec (Line)) == 1) {
1536                 if (--n == 0) {
1537                     if (lncount > 3 || (lncount > 1 && no_intty))
1538                     {
1539                         putchar('\n');
1540                         if (clreol)
1541                             cleareol ();
1542                         putsout(_("...skipping\n"));
1543                     }
1544                     if (!no_intty) {
1545                         Currline -= (lncount >= 3 ? 3 : lncount);
1546                         Fseek (file, line3);
1547                         if (noscroll) {
1548                             if (clreol) {
1549                                 home ();
1550                                 cleareol ();
1551                             }
1552                             else
1553                                 doclear ();
1554                         }
1555                     }
1556                     else {
1557                         kill_line ();
1558                         if (noscroll) {
1559                             if (clreol) {
1560                                 home ();
1561                                 cleareol ();
1562                             }
1563                             else
1564                                 doclear ();
1565                         }
1566                         puts(Line);
1567                     }
1568                     break;
1569                 }
1570         } else if (rv == -1)
1571             error (_("Regular expression botch"));
1572     }
1573     if (feof (file)) {
1574         if (!no_intty) {
1575             Currline = saveln;
1576             Fseek (file, startline);
1577         }
1578         else {
1579             putsout(_("\nPattern not found\n"));
1580             end_it (0);
1581         }
1582         error (_("Pattern not found"));
1583     }
1584 }
1585
1586 /*VARARGS2*/
1587 void execute (char *filename, char *cmd, ...)
1588 {
1589         int id;
1590         int n;
1591         va_list argp;
1592         char * arg;
1593         char ** args;
1594         int argcount;
1595
1596         fflush (stdout);
1597         reset_tty ();
1598         for (n = 10; (id = fork ()) < 0 && n > 0; n--)
1599             sleep (5);
1600         if (id == 0) {
1601             if (!isatty(0)) {
1602                 close(0);
1603                 open("/dev/tty", 0);
1604             }
1605
1606             va_start(argp, cmd);
1607             arg = va_arg(argp, char *);
1608             argcount = 0;
1609             while (arg) {
1610                 argcount++;
1611                 arg = va_arg(argp, char *);
1612             }
1613             va_end(argp);
1614
1615             args = alloca(sizeof(char *) * (argcount + 1));
1616             args[argcount] = NULL;
1617             
1618             va_start(argp, cmd);
1619             arg = va_arg(argp, char *);
1620             argcount = 0;
1621             while (arg) {
1622                 args[argcount] = arg;
1623                 argcount++;
1624                 arg = va_arg(argp, char *);
1625             }
1626             va_end(argp);
1627         
1628             execvp (cmd, args);
1629             putserr(_("exec failed\n"));
1630             exit (1);
1631         }
1632         if (id > 0) {
1633             signal (SIGINT, SIG_IGN);
1634             signal (SIGQUIT, SIG_IGN);
1635             if (catch_susp)
1636                 signal(SIGTSTP, SIG_DFL);
1637             while (wait(0) > 0);
1638             signal (SIGINT, end_it);
1639             signal (SIGQUIT, onquit);
1640             if (catch_susp)
1641                 signal(SIGTSTP, onsusp);
1642         } else
1643             putserr(_("can't fork\n"));
1644         set_tty ();
1645         puts("------------------------");
1646         prompt (filename);
1647 }
1648 /*
1649 ** Skip n lines in the file f
1650 */
1651
1652 void skiplns (register int n, register FILE *f)
1653 {
1654     register int c;
1655
1656     while (n > 0) {
1657         while ((c = Getc (f)) != '\n')
1658             if (c == EOF)
1659                 return;
1660             n--;
1661             Currline++;
1662     }
1663 }
1664
1665 /*
1666 ** Skip nskip files in the file list (from the command line). Nskip may be
1667 ** negative.
1668 */
1669
1670 void skipf (register int nskip)
1671 {
1672     if (nskip == 0) return;
1673     if (nskip > 0) {
1674         if (fnum + nskip > nfiles - 1)
1675             nskip = nfiles - fnum - 1;
1676     }
1677     else if (within)
1678         ++fnum;
1679     fnum += nskip;
1680     if (fnum < 0)
1681         fnum = 0;
1682     puts(_("\n...Skipping "));
1683     if (clreol)
1684         cleareol ();
1685     if (nskip > 0)
1686             putsout(_("...Skipping to file "));
1687     else
1688             putsout(_("...Skipping back to file "));
1689     puts(fnames[fnum]);
1690     if (clreol)
1691         cleareol ();
1692     putchar('\n');
1693     --fnum;
1694 }
1695
1696 /*----------------------------- Terminal I/O -------------------------------*/
1697
1698 void initterm()
1699 {
1700     int         ret;
1701     char        *padstr;
1702     char        *term;
1703     struct winsize win;
1704
1705 #ifdef do_SIGTTOU
1706 retry:
1707 #endif
1708     no_tty = tcgetattr(fileno(stdout), &otty);
1709     if (!no_tty) {      
1710         docrterase = (otty.c_cc[VERASE] != 255);
1711         docrtkill =  (otty.c_cc[VKILL] != 255);
1712 #ifdef do_SIGTTOU
1713         {
1714             int tgrp;
1715             /*
1716              * Wait until we're in the foreground before we save the
1717              * the terminal modes.
1718              */
1719             if ((tgrp = tcgetpgrp(fileno(stdout))) < 0) {
1720                 perror("tcgetpgrp");
1721                 exit(1);
1722             }
1723             if (tgrp != getpgrp(0)) {
1724                 kill(0, SIGTTOU);
1725                 goto retry;
1726             }
1727         }
1728 #endif
1729         if ((term = getenv("TERM")) == 0) {
1730             dumb++; ul_opt = 0;
1731         }
1732         my_setupterm(term, 1, &ret);
1733         if (ret <= 0) {
1734             dumb++; ul_opt = 0;
1735         }
1736         else {
1737 #ifdef TIOCGWINSZ
1738             if (ioctl(fileno(stdout), TIOCGWINSZ, &win) < 0) {
1739 #endif
1740                 Lpp = my_tgetnum("li","lines");
1741                 Mcol = my_tgetnum("co","cols");
1742 #ifdef TIOCGWINSZ
1743             } else {
1744                 if ((Lpp = win.ws_row) == 0)
1745                     Lpp = my_tgetnum("li","lines");
1746                 if ((Mcol = win.ws_col) == 0)
1747                     Mcol = my_tgetnum("co","cols");
1748             }
1749 #endif
1750             if ((Lpp <= 0) || my_tgetflag("hc","hc")) {
1751                 hard++; /* Hard copy terminal */
1752                 Lpp = 24;
1753             }
1754
1755             if (my_tgetflag("xn","xenl"))
1756                 eatnl++; /* Eat newline at last column + 1; dec, concept */
1757             if (Mcol <= 0)
1758                 Mcol = 80;
1759
1760             if (tailequ (fnames[0], "page"))
1761                 noscroll++;
1762             Wrap = my_tgetflag("am","am");
1763             bad_so = my_tgetflag ("xs","xhp");
1764             eraseln = my_tgetstr("ce","el");
1765             Clear = my_tgetstr("cl","clear");
1766             Senter = my_tgetstr("so","smso");
1767             Sexit = my_tgetstr("se","rmso");
1768             if ((soglitch = my_tgetnum("sg","xmc")) < 0)
1769                 soglitch = 0;
1770
1771             /*
1772              *  Set up for underlining:  some terminals don't need it;
1773              *  others have start/stop sequences, still others have an
1774              *  underline char sequence which is assumed to move the
1775              *  cursor forward one character.  If underline sequence
1776              *  isn't available, settle for standout sequence.
1777              */
1778
1779             if (my_tgetflag("ul","ul") || my_tgetflag("os","os"))
1780                 ul_opt = 0;
1781             if ((chUL = my_tgetstr("uc","uc")) == NULL )
1782                 chUL = "";
1783             if (((ULenter = my_tgetstr("us","smul")) == NULL ||
1784                  (ULexit = my_tgetstr("ue","rmul")) == NULL) && !*chUL) {
1785                 if ((ULenter = Senter) == NULL || (ULexit = Sexit) == NULL) {
1786                         ULenter = "";
1787                         ULexit = "";
1788                 } else
1789                         ulglitch = soglitch;
1790             } else {
1791                 ulglitch = 0;
1792             }
1793
1794             if ((padstr = my_tgetstr("pc","pad")) != NULL)
1795                 PC = *padstr;
1796             Home = my_tgetstr("ho","home");
1797             if (Home == 0 || *Home == '\0') {
1798                 if ((cursorm = my_tgetstr("cm","cup")) != NULL) {
1799                     const char *t = (const char *)my_tgoto(cursorm, 0, 0);
1800                     xstrncpy(cursorhome, t, sizeof(cursorhome));
1801                     Home = cursorhome;
1802                 }
1803             }
1804             EodClr = my_tgetstr("cd","ed");
1805             if ((chBS = my_tgetstr("le","cub1")) == NULL)
1806                 chBS = "\b";
1807
1808         }
1809         if ((shell = getenv("SHELL")) == NULL)
1810             shell = "/bin/sh";
1811     }
1812     no_intty = tcgetattr(fileno(stdin), &otty);
1813     tcgetattr(fileno(stderr), &otty);
1814     savetty0 = otty;
1815     slow_tty = cfgetispeed(&otty) < B1200;
1816     hardtabs = (otty.c_oflag & TABDLY) != XTABS;
1817     if (!no_tty) {
1818         otty.c_lflag &= ~(ICANON|ECHO);
1819         otty.c_cc[VMIN] = 1;
1820         otty.c_cc[VTIME] = 0;
1821     }
1822 }
1823
1824 int readch () {
1825         unsigned char c;
1826
1827         errno = 0;
1828         if (read (fileno(stderr), &c, 1) <= 0) {
1829                 if (errno != EINTR)
1830                         end_it(0);
1831                 else
1832                         c = otty.c_cc[VKILL];
1833         }
1834         return (c);
1835 }
1836
1837 static char *BS = "\b";
1838 static char *BSB = "\b \b";
1839 static char *CARAT = "^";
1840 #define ERASEONECOLUMN \
1841     if (docrterase) \
1842         putserr(BSB); \
1843     else \
1844         putserr(BS);
1845
1846 void ttyin (char buf[], register int nmax, char pchar) {
1847     char *sp;
1848     int c;
1849     int slash = 0;
1850     int maxlen;
1851
1852     sp = buf;
1853     maxlen = 0;
1854     while (sp - buf < nmax) {
1855         if (promptlen > maxlen) maxlen = promptlen;
1856         c = readch ();
1857         if (c == '\\') {
1858             slash++;
1859         }
1860         else if (((cc_t) c == otty.c_cc[VERASE]) && !slash) {
1861             if (sp > buf) {
1862 #ifdef HAVE_WIDECHAR
1863                 if (MB_CUR_MAX > 1)
1864                   {
1865                     wchar_t wc;
1866                     size_t pos = 0, mblength;
1867                     mbstate_t state, state_bak;
1868
1869                     memset (&state, '\0', sizeof (mbstate_t));
1870
1871                     while (1) {
1872                          state_bak = state;
1873                          mblength = mbrtowc (&wc, buf + pos, sp - buf, &state);
1874
1875                          state = (mblength == (size_t)-2
1876                                  || mblength == (size_t)-1) ? state_bak : state;
1877                          mblength = (mblength == (size_t)-2
1878                                      || mblength == (size_t)-1
1879                                      || mblength == 0) ? 1 : mblength;
1880
1881                          if (buf + pos + mblength >= sp)
1882                          break;
1883
1884                          pos += mblength;
1885                     }
1886
1887                     if (mblength == 1) {
1888                       ERASEONECOLUMN
1889                     }
1890                     else {
1891                         int wc_width;
1892                         wc_width = wcwidth (wc);
1893                         wc_width = (wc_width < 1) ? 1 : wc_width;
1894                         while (wc_width--) {
1895                             ERASEONECOLUMN
1896                         }
1897                     }
1898
1899                     while (mblength--) {
1900                         --promptlen;
1901                         --sp;
1902                     }
1903                   }
1904                 else
1905 #endif
1906                   {
1907                     --promptlen;
1908                     ERASEONECOLUMN
1909                     --sp;
1910                   }
1911
1912                 if ((*sp < ' ' && *sp != '\n') || *sp == RUBOUT) {
1913                     --promptlen;
1914                     ERASEONECOLUMN
1915                 }
1916                 continue;
1917             }
1918             else {
1919                 if (!eraseln) promptlen = maxlen;
1920                 siglongjmp (restore, 1);
1921             }
1922         }
1923         else if (((cc_t) c == otty.c_cc[VKILL]) && !slash) {
1924             if (hard) {
1925                 show (c);
1926                 putchar ('\n');
1927                 putchar (pchar);
1928             }
1929             else {
1930                 putchar ('\r');
1931                 putchar (pchar);
1932                 if (eraseln)
1933                     erasep (1);
1934                 else if (docrtkill)
1935                     while (promptlen-- > 1)
1936                         putserr(BSB);
1937                 promptlen = 1;
1938             }
1939             sp = buf;
1940             fflush (stdout);
1941             continue;
1942         }
1943         if (slash && ((cc_t) c == otty.c_cc[VKILL]
1944                    || (cc_t) c == otty.c_cc[VERASE])) {
1945             ERASEONECOLUMN
1946             --sp;
1947         }
1948         if (c != '\\')
1949             slash = 0;
1950         *sp++ = c;
1951         if ((c < ' ' && c != '\n' && c != ESC) || c == RUBOUT) {
1952             c += (c == RUBOUT) ? -0100 : 0100;
1953             putserr(CARAT);
1954             promptlen++;
1955         }
1956         if (c != '\n' && c != ESC) {
1957             putcerr(c);
1958             promptlen++;
1959         }
1960         else
1961             break;
1962     }
1963     *--sp = '\0';
1964     if (!eraseln) promptlen = maxlen;
1965     if (sp - buf >= nmax - 1)
1966         error (_("Line too long"));
1967 }
1968
1969 /* return: 0 - unchanged, 1 - changed, -1 - overflow (unchanged) */
1970 int expand (char **outbuf, char *inbuf) {
1971     char *inpstr;
1972     char *outstr;
1973     char c;
1974     char *temp;
1975     int changed = 0;
1976     int tempsz, xtra, offset;
1977
1978     xtra = strlen (fnames[fnum]) + strlen (shell_line) + 1;
1979     tempsz = 200 + xtra;
1980     temp = malloc(tempsz);
1981     if (!temp) {
1982             error (_("Out of memory"));
1983             return -1;
1984     }
1985     inpstr = inbuf;
1986     outstr = temp;
1987     while ((c = *inpstr++) != '\0'){
1988         offset = outstr-temp;
1989         if (tempsz-offset-1 < xtra) {
1990                 tempsz += 200 + xtra;
1991                 temp = realloc(temp, tempsz);
1992                 if (!temp) {
1993                         error (_("Out of memory"));
1994                         return -1;
1995                 }
1996                 outstr = temp + offset;
1997         }
1998         switch (c) {
1999         case '%':
2000             if (!no_intty) {
2001                 strcpy (outstr, fnames[fnum]);
2002                 outstr += strlen (fnames[fnum]);
2003                 changed++;
2004             } else
2005                 *outstr++ = c;
2006             break;
2007         case '!':
2008             if (!shellp)
2009                 error (_("No previous command to substitute for"));
2010             strcpy (outstr, shell_line);
2011             outstr += strlen (shell_line);
2012             changed++;
2013             break;
2014         case '\\':
2015             if (*inpstr == '%' || *inpstr == '!') {
2016                 *outstr++ = *inpstr++;
2017                 break;
2018             }
2019         default:
2020             *outstr++ = c;
2021         }
2022     }
2023     *outstr++ = '\0';
2024     *outbuf = temp;
2025     return (changed);
2026 }
2027
2028 void show (char c) {
2029     if ((c < ' ' && c != '\n' && c != ESC) || c == RUBOUT) {
2030         c += (c == RUBOUT) ? -0100 : 0100;
2031         putserr(CARAT);
2032         promptlen++;
2033     }
2034     putcerr(c);
2035     promptlen++;
2036 }
2037
2038 void error (char *mess)
2039 {
2040     if (clreol)
2041         cleareol ();
2042     else
2043         kill_line ();
2044     promptlen += strlen (mess);
2045     if (Senter && Sexit) {
2046         my_putstring (Senter);
2047         putsout(mess);
2048         my_putstring (Sexit);
2049     }
2050     else
2051         putsout(mess);
2052     fflush(stdout);
2053     errors++;
2054     siglongjmp (restore, 1);
2055 }
2056
2057
2058 void set_tty () {
2059         otty.c_lflag &= ~(ICANON|ECHO);
2060         otty.c_cc[VMIN] = 1;    /* read at least 1 char */
2061         otty.c_cc[VTIME] = 0;   /* no timeout */
2062         stty(fileno(stderr), &otty);
2063 }
2064
2065 static int
2066 ourputch(int c) {
2067         return putc(c, stdout);
2068 }
2069
2070 void
2071 reset_tty () {
2072     if (no_tty)
2073         return;
2074     if (pstate) {
2075         tputs(ULexit, 1, ourputch);     /* putchar - if that isnt a macro */
2076         fflush(stdout);
2077         pstate = 0;
2078     }
2079     otty.c_lflag |= ICANON|ECHO;
2080     otty.c_cc[VMIN] = savetty0.c_cc[VMIN];
2081     otty.c_cc[VTIME] = savetty0.c_cc[VTIME];
2082     stty(fileno(stderr), &savetty0);
2083 }
2084
2085 void rdline (register FILE *f)
2086 {
2087     register int  c;
2088     register char *p;
2089
2090     p = Line;
2091     while ((c = Getc (f)) != '\n' && c != EOF && p - Line < LINSIZ - 1)
2092         *p++ = c;
2093     if (c == '\n')
2094         Currline++;
2095     *p = '\0';
2096 }
2097
2098 /* Come here when we get a suspend signal from the terminal */
2099
2100 void onsusp (int dummy) {
2101     sigset_t signals, oldmask;
2102
2103     /* ignore SIGTTOU so we don't get stopped if csh grabs the tty */
2104     signal(SIGTTOU, SIG_IGN);
2105     reset_tty ();
2106     fflush (stdout);
2107     signal(SIGTTOU, SIG_DFL);
2108     /* Send the TSTP signal to suspend our process group */
2109     signal(SIGTSTP, SIG_DFL);
2110
2111     /* unblock SIGTSTP or we won't be able to suspend ourself */
2112     sigemptyset(&signals);
2113     sigaddset(&signals, SIGTSTP);
2114     sigprocmask(SIG_UNBLOCK, &signals, &oldmask);
2115
2116     kill (0, SIGTSTP);
2117     /* Pause for station break */
2118
2119     sigprocmask(SIG_SETMASK, &oldmask, NULL);
2120
2121     /* We're back */
2122     signal (SIGTSTP, onsusp);
2123     set_tty ();
2124     if (inwait)
2125             siglongjmp (restore, 1);
2126 }