BSD 4_4 release
[unix-history] / usr / src / usr.bin / tail / tail.c
index fd48473..cc8ea14 100644 (file)
-static char *sccsid = "@(#)tail.c      4.4 (Berkeley) %G%";
-/* tail command 
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
  *
- *     tail where [file]
- *     where is +/-n[type]
- *     - means n lines before end
- *     + means nth line from beginning
- *     type 'b' means tail n blocks, not lines
- *     type 'c' means tail n characters
- *     Type 'r' means in lines in reverse order from end
- *      (for -r, default is entire buffer )
- *     option 'f' means loop endlessly trying to read more
- *             characters after the end of file, on the  assumption
- *             that the file is growing
-*/
-
-#include       <stdio.h>
-#include       <ctype.h>
-#include       <sys/types.h>
-#include       <sys/stat.h>
-#include       <errno.h>
-
-#define LBIN 4097
-struct stat    statb;
-int    follow;
-int    piped;
-char bin[LBIN];
-int errno;
-
-main(argc,argv)
-char **argv;
+ * This code is derived from software contributed to Berkeley by
+ * Edward Sze-Tyan Wang.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)tail.c     8.1 (Berkeley) 6/6/93";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "extern.h"
+
+int fflag, rflag, rval;
+char *fname;
+
+static void obsolete __P((char **));
+static void usage __P((void));
+
+int
+main(argc, argv)
+       int argc;
+       char *argv[];
 {
 {
-       long n,di;
-       register i,j,k;
-       char    *arg;
-       int partial,bylines,bkwds,fromend,lastnl;
+       struct stat sb;
+       FILE *fp;
+       long off;
+       enum STYLE style;
+       int ch, first;
        char *p;
 
        char *p;
 
-       arg = argv[1];
-       if(argc<=1 || *arg!='-'&&*arg!='+') {
-               arg = "-10l";
-               argc++;
-               argv--;
-       }
-       fromend = *arg=='-';
-       arg++;
-       if (isdigit(*arg)) {
-               n = 0;
-               while(isdigit(*arg))
-                       n = n*10 + *arg++ - '0';
-       } else
-               n = -1;
-       if(!fromend&&n>0)
-               n--;
-       if(argc>2) {
-               close(0);
-               if(open(argv[2],0)!=0) {
-                       perror(argv[2]);
-                       exit(1);
+       /*
+        * Tail's options are weird.  First, -n10 is the same as -n-10, not
+        * -n+10.  Second, the number options are 1 based and not offsets,
+        * so -n+1 is the first line, and -c-1 is the last byte.  Third, the
+        * number options for the -r option specify the number of things that
+        * get displayed, not the starting point in the file.  The one major
+        * incompatibility in this version as compared to historical versions
+        * is that the 'r' option couldn't be modified by the -lbc options,
+        * i.e. it was always done in lines.  This version treats -rc as a
+        * number of characters in reverse order.  Finally, the default for
+        * -r is the entire file, not 10 lines.
+        */
+#define        ARG(units, forward, backward) {                                 \
+       if (style)                                                      \
+               usage();                                                \
+       off = strtol(optarg, &p, 10) * (units);                         \
+       if (*p)                                                         \
+               err(1, "illegal offset -- %s", optarg);                 \
+       switch(optarg[0]) {                                             \
+       case '+':                                                       \
+               if (off)                                                \
+                       off -= (units);                                 \
+                       style = (forward);                              \
+               break;                                                  \
+       case '-':                                                       \
+               off = -off;                                             \
+               /* FALLTHROUGH */                                       \
+       default:                                                        \
+               style = (backward);                                     \
+               break;                                                  \
+       }                                                               \
+}
+
+       obsolete(argv);
+       style = NOTSET;
+       while ((ch = getopt(argc, argv, "b:c:fn:r")) != EOF)
+               switch(ch) {
+               case 'b':
+                       ARG(512, FBYTES, RBYTES);
+                       break;
+               case 'c':
+                       ARG(1, FBYTES, RBYTES);
+                       break;
+               case 'f':
+                       fflag = 1;
+                       break;
+               case 'n':
+                       ARG(1, FLINES, RLINES);
+                       break;
+               case 'r':
+                       rflag = 1;
+                       break;
+               case '?':
+               default:
+                       usage();
                }
                }
+       argc -= optind;
+       argv += optind;
+
+       if (fflag && argc > 1)
+               err(1, "-f option only appropriate for a single file");
+
+       /*
+        * If displaying in reverse, don't permit follow option, and convert
+        * style values.
+        */
+       if (rflag) {
+               if (fflag)
+                       usage();
+               if (style == FBYTES)
+                       style = RBYTES;
+               else if (style == FLINES)
+                       style = RLINES;
        }
        }
-       lseek(0,(long)0,1);
-       piped = errno==ESPIPE;
-       bylines = -1; bkwds = 0;
-       while(*arg)
-       switch(*arg++) {
-
-       case 'b':
-               n <<= 9;
-               if(bylines!=-1) goto errcom;
-               bylines=0;
-               break;
-       case 'c':
-               if(bylines!=-1) goto errcom;
-               bylines=0;
-               break;
-       case 'f':
-               follow = 1;
-               break;
-       case 'r':
-               if(n==-1) n = LBIN;
-               bkwds = 1; fromend = 1; bylines = 1;
-               break;
-       case 'l':
-               if(bylines!=-1) goto errcom;
-               bylines = 1;
-               break;
-       default:
-               goto errcom;
-       }
-       if (n==-1) n = 10;
-       if(bylines==-1) bylines = 1;
-       if(bkwds) follow=0;
-       if(fromend)
-               goto keep;
-
-                       /*seek from beginning */
-
-       if(bylines) {
-               j = 0;
-               while(n-->0) {
-                       do {
-                               if(j--<=0) {
-                                       p = bin;
-                                       j = read(0,p,BUFSIZ);
-                                       if(j--<=0)
-                                               fexit();
-                               }
-                       } while(*p++ != '\n');
+
+       /*
+        * If style not specified, the default is the whole file for -r, and
+        * the last 10 lines if not -r.
+        */
+       if (style == NOTSET)
+               if (rflag) {
+                       off = 0;
+                       style = REVERSE;
+               } else {
+                       off = 10;
+                       style = RLINES;
                }
                }
-               write(1,p,j);
-       } else  if(n>0) {
-               if(!piped)
-                       fstat(0,&statb);
-               if(piped||(statb.st_mode&S_IFMT)==S_IFCHR)
-                       while(n>0) {
-                               i = n>BUFSIZ?BUFSIZ:n;
-                               i = read(0,bin,i);
-                               if(i<=0)
-                                       fexit();
-                               n -= i;
+
+       if (*argv)
+               for (first = 1; fname = *argv++;) {
+                       if ((fp = fopen(fname, "r")) == NULL ||
+                           fstat(fileno(fp), &sb)) {
+                               ierr();
+                               continue;
                        }
                        }
-               else
-                       lseek(0,n,0);
-       }
-copy:
-       while((i=read(0,bin,BUFSIZ))>0)
-               write(1,bin,i);
-       fexit();
-
-                       /*seek from end*/
-
-keep:
-       if(n <= 0)
-               fexit();
-       if(!piped) {
-               fstat(0,&statb);
-               di = !bylines&&n<LBIN?n:LBIN-1;
-               if(statb.st_size > di)
-                       lseek(0,-di,2);
-               if(!bylines)
-                       goto copy;
-       }
-       partial = 1;
-       for(;;) {
-               i = 0;
-               do {
-                       j = read(0,&bin[i],LBIN-i);
-                       if(j<=0)
-                               goto brka;
-                       i += j;
-               } while(i<LBIN);
-               partial = 0;
-       }
-brka:
-       if(!bylines) {
-               k =
-                   n<=i ? i-n:
-                   partial ? 0:
-                   n>=LBIN ? i+1:
-                   i-n+LBIN;
-               k--;
-       } else {
-               if(bkwds && bin[i==0?LBIN-1:i-1]!='\n'){        /* force trailing newline */
-                       bin[i]='\n';
-                       if(++i>=LBIN) {i = 0; partial = 0;}
-               }
-               k = i;
-               j = 0;
-               do {
-                       lastnl = k;
-                       do {
-                               if(--k<0) {
-                                       if(partial) {
-                                               if(bkwds) write(1,bin,lastnl+1);
-                                               goto brkb;
-                                       }
-                                       k = LBIN -1;
-                               }
-                       } while(bin[k]!='\n'&&k!=i);
-                       if(bkwds && j>0){
-                               if(k<lastnl) write(1,&bin[k+1],lastnl-k);
-                               else {
-                                       write(1,&bin[k+1],LBIN-k-1);
-                                       write(1,bin,lastnl+1);
-                               }
+                       if (argc > 1) {
+                               (void)printf("%s==> %s <==\n",
+                                   first ? "" : "\n", fname);
+                               first = 0;
+                               (void)fflush(stdout);
                        }
                        }
-               } while(j++<n&&k!=i);
-brkb:
-               if(bkwds) exit(0);
-               if(k==i) do {
-                       if(++k>=LBIN)
-                               k = 0;
-               } while(bin[k]!='\n'&&k!=i);
-       }
-       if(k<i)
-               write(1,&bin[k+1],i-k-1);
+
+                       if (rflag)
+                               reverse(fp, style, off, &sb);
+                       else
+                               forward(fp, style, off, &sb);
+                       (void)fclose(fp);
+               }
        else {
        else {
-               write(1,&bin[k+1],LBIN-k-1);
-               write(1,bin,i);
+               fname = "stdin";
+
+               if (fstat(fileno(stdin), &sb)) {
+                       ierr();
+                       exit(1);
+               }
+
+               /*
+                * Determine if input is a pipe.  4.4BSD will set the SOCKET
+                * bit in the st_mode field for pipes.  Fix this then.
+                */
+               if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 &&
+                   errno == ESPIPE) {
+                       errno = 0;
+                       fflag = 0;              /* POSIX.2 requires this. */
+               }
+
+               if (rflag)
+                       reverse(stdin, style, off, &sb);
+               else
+                       forward(stdin, style, off, &sb);
        }
        }
-       fexit();
-errcom:
-       fprintf(stderr, "usage: tail [+\b_[n][lbc][rf]] [file]\n");
-       exit(2);
+       exit(rval);
 }
 
 }
 
-fexit()
-{      register int n;
-       if (!follow || piped) exit(0);
-       for (;;)
-       {       sleep(1);
-               while ((n = read (0, bin, BUFSIZ)) > 0)
-                       write (1, bin, n);
+/*
+ * Convert the obsolete argument form into something that getopt can handle.
+ * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't
+ * the option argument for a -b, -c or -n option gets converted.
+ */
+static void
+obsolete(argv)
+       char *argv[];
+{
+       register char *ap, *p, *t;
+       int len;
+       char *start;
+
+       while (ap = *++argv) {
+               /* Return if "--" or not an option of any form. */
+               if (ap[0] != '-') {
+                       if (ap[0] != '+')
+                               return;
+               } else if (ap[1] == '-')
+                       return;
+
+               switch(*++ap) {
+               /* Old-style option. */
+               case '0': case '1': case '2': case '3': case '4':
+               case '5': case '6': case '7': case '8': case '9':
+
+                       /* Malloc space for dash, new option and argument. */
+                       len = strlen(*argv);
+                       if ((start = p = malloc(len + 3)) == NULL)
+                               err(1, "%s", strerror(errno));
+                       *p++ = '-';
+
+                       /*
+                        * Go to the end of the option argument.  Save off any
+                        * trailing options (-3lf) and translate any trailing
+                        * output style characters.
+                        */
+                       t = *argv + len - 1;
+                       if (*t == 'f' || *t == 'r') {
+                               *p++ = *t;
+                               *t-- = '\0';
+                       }
+                       switch(*t) {
+                       case 'b':
+                               *p++ = 'b';
+                               *t = '\0';
+                               break;
+                       case 'c':
+                               *p++ = 'c';
+                               *t = '\0';
+                               break;
+                       case 'l':
+                               *t = '\0';
+                               /* FALLTHROUGH */
+                       case '0': case '1': case '2': case '3': case '4':
+                       case '5': case '6': case '7': case '8': case '9':
+                               *p++ = 'n';
+                               break;
+                       default:
+                               err(1, "illegal option -- %s", *argv);
+                       }
+                       *p++ = *argv[0];
+                       (void)strcpy(p, ap);
+                       *argv = start;
+                       continue;
+
+               /*
+                * Options w/ arguments, skip the argument and continue
+                * with the next option.
+                */
+               case 'b':
+               case 'c':
+               case 'n':
+                       if (!ap[1])
+                               ++argv;
+                       /* FALLTHROUGH */
+               /* Options w/o arguments, continue with the next option. */
+               case 'f':
+               case 'r':
+                       continue;
+
+               /* Illegal option, return and let getopt handle it. */
+               default:
+                       return;
+               }
        }
 }
        }
 }
+
+static void
+usage()
+{
+       (void)fprintf(stderr,
+           "usage: tail [-f | -r] [-b # | -c # | -n #] [file ...]\n");
+       exit(1);
+}