diff --git a/commands/Makefile b/commands/Makefile index bcf8f8717..04f9c536e 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -25,7 +25,7 @@ SUBDIR= add_route arp ash at backup btrace \ rotate rsh rshd service setup shar \ slip spell sprofalyze sprofdiff srccrc \ svclog svrctl swifi synctree sysenv \ - syslogd tail tcpd tcpdp tcpstat telnet \ + syslogd tcpd tcpdp tcpstat telnet \ telnetd term termcap tget time \ truncate udpstat umount \ unstack update uud uue version vol \ diff --git a/commands/tail/Makefile b/commands/tail/Makefile deleted file mode 100644 index 91982ec16..000000000 --- a/commands/tail/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -PROG= tail -MAN= - -.include diff --git a/commands/tail/tail.c b/commands/tail/tail.c deleted file mode 100644 index 09f285480..000000000 --- a/commands/tail/tail.c +++ /dev/null @@ -1,361 +0,0 @@ -/* tail - copy the end of a file Author: Norbert Schlenker */ - -/* Syntax: tail [-f] [-c number | -n number] [file] - * tail -[number][c|l][f] [file] (obsolescent) - * tail +[number][c|l][f] [file] (obsolescent) - * Flags: - * -c number Measure starting point in bytes. If number begins - * with '+', the starting point is relative to the - * the file's beginning. If number begins with '-' - * or has no sign, the starting point is relative to - * the end of the file. - * -f Keep trying to read after EOF on files and FIFOs. - * -n number Measure starting point in lines. The number - * following the flag has significance similar to - * that described for the -c flag. - * - * If neither -c nor -n are specified, the default is tail -n 10. - * - * In the obsolescent syntax, an argument with a 'c' following the - * (optional) number is equivalent to "-c number" in the standard - * syntax, with number including the leading sign ('+' or '-') of the - * argument. An argument with 'l' following the number is equivalent - * to "-n number" in the standard syntax. If the number is not - * specified, 10 is used as the default. If neither 'c' nor 'l' are - * specified, 'l' is assumed. The character 'f' may be suffixed to - * the argument and is equivalent to specifying "-f" in the standard - * syntax. Look for lines marked "OBSOLESCENT". - * - * If no file is specified, standard input is assumed. - * - * P1003.2 does not specify tail's behavior when a count of 0 is given. - * It also does not specify clearly whether the first byte (line) of a - * file should be numbered 0 or 1. Historical behavior is that the - * first byte is actually number 1 (contrary to all Unix standards). - * Historically, a count of 0 (or -0) results in no output whatsoever, - * while a count of +0 results in the entire file being copied (just like - * +1). The implementor does not agree with these behaviors, but has - * copied them slavishly. Look for lines marked "HISTORICAL". - * - * Author: Norbert Schlenker - * Copyright: None. Released to the public domain. - * Reference: P1003.2 section 4.59 (draft 10) - * Notes: Under Minix, this program requires chmem =30000. - * Bugs: No internationalization support; all messages are in English. - */ - -/* Force visible Posix names */ -#ifndef _POSIX_SOURCE -#define _POSIX_SOURCE 1 -#endif - -/* External interfaces */ -#include -#include -#include -#include -#include -#include - -/* External interfaces that should have been standardized into */ -extern char *optarg; -extern int optind; - -/* We expect this constant to be defined in in a Posix program, - * but we'll specify it here just in case it's been left out. - */ -#ifndef LINE_MAX -#define LINE_MAX 2048 /* minimum acceptable lower bound */ -#endif - -/* Magic numbers suggested or required by Posix specification */ -#define SUCCESS 0 /* exit code in case of success */ -#define FAILURE 1 /* or failure */ -#define DEFAULT_COUNT 10 /* default number of lines or bytes */ -#define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT) -#define SLEEP_INTERVAL 1 /* sleep for one second intervals with -f */ - -#define FALSE 0 -#define TRUE 1 - -/* Internal functions - prototyped under Minix */ -int main(int argc, char **argv); -int tail(int count, int bytes, int read_until_killed); -int keep_reading(void); -void usage(void); - -int main(argc, argv) -int argc; -char *argv[]; -{ - int cflag = FALSE; - int nflag = FALSE; - int fflag = FALSE; - int number = -DEFAULT_COUNT; - char *suffix; - int opt; - struct stat stat_buf; - -/* Determining whether this invocation is via the standard syntax or - * via an obsolescent one is a nasty kludge. Here it is, but there is - * no pretense at elegance. - */ - if (argc == 1) { /* simple: default read of a pipe */ - exit(tail(-DEFAULT_COUNT, 0, fflag)); - } - if ((argv[1][0] == '+') || /* OBSOLESCENT */ - (argv[1][0] == '-' && ((isdigit(argv[1][1])) || - (argv[1][1] == 'l') || - (argv[1][1] == 'c' && argv[1][2] == 'f')))) { - --argc; ++argv; - if (isdigit(argv[0][1])) { - number = (int)strtol(argv[0], &suffix, 10); - if (number == 0) { /* HISTORICAL */ - if (argv[0][0] == '+') - number = 1; - else - exit(SUCCESS); - } - } else { - number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT; - suffix = &(argv[0][1]); - } - if (*suffix != '\0') { - if (*suffix == 'c') { - cflag = TRUE; - ++suffix; - } - else - if (*suffix == 'l') { - nflag = TRUE; - ++suffix; - } - } - if (*suffix != '\0') { - if (*suffix == 'f') { - fflag = TRUE; - ++suffix; - } - } - if (*suffix != '\0') { /* bad form: assume to be a file name */ - number = -DEFAULT_COUNT; - cflag = nflag = FALSE; - fflag = FALSE; - } else { - --argc; ++argv; - } - } else { /* new standard syntax */ - while ((opt = getopt(argc, argv, "c:fn:")) != EOF) { - switch (opt) { - case 'c': - cflag = TRUE; - if (*optarg == '+' || *optarg == '-') - number = atoi(optarg); - else - if (isdigit(*optarg)) - number = -atoi(optarg); - else - usage(); - if (number == 0) { /* HISTORICAL */ - if (*optarg == '+') - number = 1; - else - exit(SUCCESS); - } - break; - case 'f': - fflag = TRUE; - break; - case 'n': - nflag = TRUE; - if (*optarg == '+' || *optarg == '-') - number = atoi(optarg); - else - if (isdigit(*optarg)) - number = -atoi(optarg); - else - usage(); - if (number == 0) { /* HISTORICAL */ - if (*optarg == '+') - number = 1; - else - exit(SUCCESS); - } - break; - default: - usage(); - /* NOTREACHED */ - } - } - argc -= optind; - argv += optind; - } - - if (argc > 1 || /* too many arguments */ - (cflag && nflag)) { /* both bytes and lines specified */ - usage(); - } - - if (argc > 0) { /* an actual file */ - if (freopen(argv[0], "r", stdin) != stdin) { - fputs("tail: could not open ", stderr); - fputs(argv[0], stderr); - fputs("\n", stderr); - exit(FAILURE); - } - /* There is an optimization possibility here. If a file is being - * read, we need not look at the front of it. If we seek backwards - * from the end, we can (potentially) avoid looking at most of the - * file. Some systems fail when asked to seek backwards to a point - * before the start of the file, so we avoid that possibility. - */ - if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) { - long offset = cflag ? (long)number : (long)number * LINE_MAX; - - if (-offset < stat_buf.st_size) - fseek(stdin, offset, SEEK_END); - } - } else { - fflag = FALSE; /* force -f off when reading a pipe */ - } - exit(tail(number, cflag, fflag)); - /* NOTREACHED */ -} - -int tail(count, bytes, read_until_killed) -int count; /* lines or bytes desired */ -int bytes; /* TRUE if we want bytes */ -int read_until_killed; /* keep reading at EOF */ -{ - int c; - char *buf; /* pointer to input buffer */ - char *buf_end; /* and one past its end */ - char *start; /* pointer to first desired character in buf */ - char *finish; /* pointer past last desired character */ - int wrapped_once = FALSE; /* TRUE after buf has been filled once */ - -/* This is magic. If count is positive, it means start at the count'th - * line or byte, with the first line or byte considered number 1. Thus, - * we want to SKIP one less line or byte than the number specified. In - * the negative case, we look backward from the end of the file for the - * (count + 1)'th newline or byte, so we really want the count to be one - * LARGER than was specified (in absolute value). In either case, the - * right thing to do is: - */ - --count; - -/* Count is positive: skip the desired lines or bytes and then copy. */ - if (count >= 0) { - while (count > 0 && (c = getchar()) != EOF) { - if (bytes || c == '\n') - --count; - } - while ((c = getchar()) != EOF) { - if (putchar(c) == EOF) - return FAILURE; - } - if (read_until_killed) - return keep_reading(); - return ferror(stdin) ? FAILURE : SUCCESS; - } - -/* Count is negative: allocate a reasonably large buffer. */ - if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) { - fputs("tail: out of memory\n", stderr); - return FAILURE; - } - buf_end = buf + (MIN_BUFSIZE + 1); - -/* Read the entire file into the buffer. */ - finish = buf; - while ((c = getchar()) != EOF) { - *finish++ = c; - if (finish == buf_end) { - finish = buf; - wrapped_once = TRUE; - } - } - if (ferror(stdin)) - return FAILURE; - -/* Back up inside the buffer. The count has already been adjusted to - * back up exactly one character too far, so we will bump the buffer - * pointer once after we're done. - * - * BUG: For large line counts, the buffer may not be large enough to - * hold all the lines. The specification allows the program to - * fail in such a case - this program will simply dump the entire - * buffer's contents as its best attempt at the desired behavior. - */ - if (finish != buf || wrapped_once) { /* file was not empty */ - start = (finish == buf) ? buf_end - 1 : finish - 1; - while (start != finish) { - if ((bytes || *start == '\n') && ++count == 0) - break; - if (start == buf) { - start = buf_end - 1; - if (!wrapped_once) /* never wrapped: stop now */ - break; - } else { - --start; - } - } - if (++start == buf_end) { /* bump after going too far */ - start = buf; - } - if (finish > start) { - fwrite(start, 1, finish - start, stdout); - } else { - fwrite(start, 1, buf_end - start, stdout); - fwrite(buf, 1, finish - buf, stdout); - } - } - if (read_until_killed) - return keep_reading(); - return ferror(stdout) ? FAILURE : SUCCESS; -} - -/* Wake at intervals to reread standard input. Copy anything read to - * standard output and then go to sleep again. - */ -int keep_reading() -{ - char buf[1024]; - int n; - int i; - off_t pos; - struct stat st; - - fflush(stdout); - - pos = lseek(0, (off_t) 0, SEEK_CUR); - for (;;) { - for (i = 0; i < 60; i++) { - while ((n = read(0, buf, sizeof(buf))) > 0) { - if (write(1, buf, n) < 0) return FAILURE; - } - if (n < 0) return FAILURE; - - sleep(SLEEP_INTERVAL); - } - - /* Rewind if suddenly truncated. */ - if (pos != -1) { - if (fstat(0, &st) == -1) { - pos = -1; - } else - if (st.st_size < pos) { - pos = lseek(0, (off_t) 0, SEEK_SET); - } else { - pos = st.st_size; - } - } - } -} - -/* Tell the user the standard syntax. */ -void usage() -{ - fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr); - exit(FAILURE); -} diff --git a/man/man1/Makefile b/man/man1/Makefile index f6aa1517f..2a3717b27 100644 --- a/man/man1/Makefile +++ b/man/man1/Makefile @@ -16,7 +16,7 @@ MAN= ash.1 at.1 \ remsync.1 rget.1 rlogin.1 rsh.1 rz.1 \ shar.1 spell.1 \ svc.1 svrctl.1 \ - synctree.1 sysenv.1 sz.1 tail.1 telnet.1 template.1 \ + synctree.1 sysenv.1 sz.1 telnet.1 template.1 \ term.1 termcap.1 tget.1 time.1 \ truncate.1 umount.1 \ uud.1 uue.1 vol.1 whereis.1 which.1 \ diff --git a/man/man1/tail.1 b/man/man1/tail.1 deleted file mode 100644 index cee0df13f..000000000 --- a/man/man1/tail.1 +++ /dev/null @@ -1,48 +0,0 @@ -.TH TAIL 1 -.SH NAME -tail \- print the last few lines of a file -.SH SYNOPSIS -\fBtail\fR [\fB\-c \fIn\fR] [\fB\-f] [\fB\-n \fIn\fR] [\fIfile\fR] ...\fR -.br -.de FL -.TP -\\fB\\$1\\fR -\\$2 -.. -.de EX -.TP 20 -\\fB\\$1\\fR -# \\$2 -.. -.SH OPTIONS -.TP 5 -.B \-c -# The count refers to characters -.TP 5 -.B \-f -# On FIFO or special file, keep reading after EOF -.TP 5 -.B \-n -# The count refers to lines -.SH EXAMPLES -.TP 20 -.B tail \-n 6 -# Print last 6 lines of \fIstdin\fR -.TP 20 -.B tail \-c 20 file -# Print the last 20 characters of \fIfile\fR -.TP 20 -.B tail \-n 1 file1 file2 -# Print last line of two files -.TP 20 -.B tail \-n +8 file -# Print the tail starting with line 8 -.SH DESCRIPTION -.PP -The last few lines of one or more files are printed. -The default count is 10 lines. -The default file is \fIstdin\fR. -If the value of \fIn\fR for the \fB\-c\fR or \fB\-n\fR flags starts with -a + sign, counting starts at the beginning, rather than the end of the file. -.SH "SEE ALSO" -.BR head (1). diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 046617037..377f04e60 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -25,7 +25,7 @@ SUBDIR= asa \ \ sdiff sed seq shlock \ shuffle sort split stat su \ - tee tic touch tput \ + tail tee tic touch tput \ tr true tsort tty ul uname unexpand unifdef \ uniq units unvis unzip users \ uuidgen vis \ diff --git a/usr.bin/tail/Makefile b/usr.bin/tail/Makefile new file mode 100644 index 000000000..d189e21ec --- /dev/null +++ b/usr.bin/tail/Makefile @@ -0,0 +1,7 @@ +# $NetBSD: Makefile,v 1.3 1994/11/23 07:41:55 jtc Exp $ +# @(#)Makefile 8.1 (Berkeley) 6/6/93 + +PROG= tail +SRCS= forward.c misc.c read.c reverse.c tail.c + +.include diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h new file mode 100644 index 000000000..24507b7a5 --- /dev/null +++ b/usr.bin/tail/extern.h @@ -0,0 +1,52 @@ +/* $NetBSD: extern.h,v 1.10 2011/09/03 09:02:20 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. 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. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +#define WR(p, size) \ + if (write(STDOUT_FILENO, p, size) != size) \ + oerr(); + +enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE }; + +void forward(FILE *, enum STYLE, off_t, struct stat *); +void reverse(FILE *, enum STYLE, off_t, struct stat *); + +int displaybytes(FILE *, off_t); +int displaylines(FILE *, off_t); + +void xerr(int fatal, const char *fmt, ...) __printflike(2, 3); +void xerrx(int fatal, const char *fmt, ...) __printflike(2, 3); +void ierr(void); +void oerr(void); + +extern int fflag, rflag, rval; +extern const char *fname; diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c new file mode 100644 index 000000000..4d355f5d9 --- /dev/null +++ b/usr.bin/tail/forward.c @@ -0,0 +1,367 @@ +/* $NetBSD: forward.c,v 1.32 2013/10/18 20:47:07 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. 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. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: forward.c,v 1.32 2013/10/18 20:47:07 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "extern.h" + +static int rlines(FILE *, off_t, struct stat *); + +/* defines for inner loop actions */ +#define USE_SLEEP 0 +#define USE_KQUEUE 1 +#define ADD_EVENTS 2 + +/* + * forward -- display the file, from an offset, forward. + * + * There are eight separate cases for this -- regular and non-regular + * files, by bytes or lines and from the beginning or end of the file. + * + * FBYTES byte offset from the beginning of the file + * REG seek + * NOREG read, counting bytes + * + * FLINES line offset from the beginning of the file + * REG read, counting lines + * NOREG read, counting lines + * + * RBYTES byte offset from the end of the file + * REG seek + * NOREG cyclically read characters into a wrap-around buffer + * + * RLINES + * REG mmap the file and step back until reach the correct offset. + * NOREG cyclically read lines into a wrap-around array of buffers + */ +void +forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) +{ +#ifndef __minix + int ch, n; +#else + int ch; +#endif + int kq=-1, action=USE_SLEEP; + struct stat statbuf; +#ifndef __minix + struct kevent ev[2]; +#endif + + switch(style) { + case FBYTES: + if (off == 0) + break; + if (S_ISREG(sbp->st_mode)) { + if (sbp->st_size < off) + off = sbp->st_size; + if (fseeko(fp, off, SEEK_SET) == -1) { + ierr(); + return; + } + } else while (off--) + if ((ch = getc(fp)) == EOF) { + if (ferror(fp)) { + ierr(); + return; + } + break; + } + break; + case FLINES: + if (off == 0) + break; + for (;;) { + if ((ch = getc(fp)) == EOF) { + if (ferror(fp)) { + ierr(); + return; + } + break; + } + if (ch == '\n' && !--off) + break; + } + break; + case RBYTES: + if (S_ISREG(sbp->st_mode)) { + if (sbp->st_size >= off && + fseeko(fp, -off, SEEK_END) == -1) { + ierr(); + return; + } + } else if (off == 0) { + while (getc(fp) != EOF); + if (ferror(fp)) { + ierr(); + return; + } + } else { + if (displaybytes(fp, off)) + return; + } + break; + case RLINES: + if (S_ISREG(sbp->st_mode)) { + if (!off) { + if (fseek(fp, 0L, SEEK_END) == -1) { + ierr(); + return; + } + } else { + if (rlines(fp, off, sbp)) + return; + } + } else if (off == 0) { + while (getc(fp) != EOF); + if (ferror(fp)) { + ierr(); + return; + } + } else { + if (displaylines(fp, off)) + return; + } + break; + default: + break; + } + + if (fflag) { +#ifndef __minix + kq = kqueue(); + if (kq < 0) + xerr(1, "kqueue"); + action = ADD_EVENTS; +#else + action = USE_SLEEP; +#endif + } + + for (;;) { + while ((ch = getc(fp)) != EOF) { + if (putchar(ch) == EOF) + oerr(); + } + if (ferror(fp)) { + ierr(); + return; + } + (void)fflush(stdout); + if (!fflag) + break; + + clearerr(fp); + + switch (action) { +#ifndef __minix + case ADD_EVENTS: + n = 0; + + memset(ev, 0, sizeof(ev)); + if (fflag == 2 && fileno(fp) != STDIN_FILENO) { + EV_SET(&ev[n], fileno(fp), EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_DELETE | NOTE_RENAME, 0, 0); + n++; + } + EV_SET(&ev[n], fileno(fp), EVFILT_READ, + EV_ADD | EV_ENABLE, 0, 0, 0); + n++; + + if (kevent(kq, ev, n, NULL, 0, NULL) == -1) { + close(kq); + kq = -1; + action = USE_SLEEP; + } else { + action = USE_KQUEUE; + } + break; + + case USE_KQUEUE: + if (kevent(kq, NULL, 0, ev, 1, NULL) == -1) + xerr(1, "kevent"); + + if (ev[0].filter == EVFILT_VNODE) { + /* file was rotated, wait until it reappears */ + action = USE_SLEEP; + } else if (ev[0].data < 0) { + /* file shrank, reposition to end */ + if (fseek(fp, 0L, SEEK_END) == -1) { + ierr(); + return; + } + } + break; +#endif + + case USE_SLEEP: + /* + * We pause for one second after displaying any data + * that has accumulated since we read the file. + */ + (void) sleep(1); + + if (fflag == 2 && fileno(fp) != STDIN_FILENO && + stat(fname, &statbuf) != -1) { + if (statbuf.st_ino != sbp->st_ino || + statbuf.st_dev != sbp->st_dev || + statbuf.st_rdev != sbp->st_rdev || + statbuf.st_nlink == 0) { + fp = freopen(fname, "r", fp); + if (fp == NULL) { + ierr(); + goto out; + } + *sbp = statbuf; + if (kq != -1) + action = ADD_EVENTS; + } else if (kq != -1) + action = USE_KQUEUE; + } + break; + } + } +out: + if (fflag && kq != -1) + close(kq); +} + +/* + * rlines -- display the last offset lines of the file. + * + * Non-zero return means than a (non-fatal) error occurred. + */ +static int +rlines(FILE *fp, off_t off, struct stat *sbp) +{ + off_t file_size; + off_t file_remaining; + char *p = NULL; + char *start = NULL; + off_t mmap_size; + off_t mmap_offset; + off_t mmap_remaining = 0; + +#define MMAP_MAXSIZE (10 * 1024 * 1024) + + if (!(file_size = sbp->st_size)) + return 0; + file_remaining = file_size; + + if (file_remaining > MMAP_MAXSIZE) { + mmap_size = MMAP_MAXSIZE; + mmap_offset = file_remaining - MMAP_MAXSIZE; + } else { + mmap_size = file_remaining; + mmap_offset = 0; + } + + while (off) { + start = mmap(NULL, (size_t)mmap_size, PROT_READ, + MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset); + if (start == MAP_FAILED) { + xerr(0, "%s", fname); + return 1; + } + + mmap_remaining = mmap_size; + /* Last char is special, ignore whether newline or not. */ + for (p = start + mmap_remaining - 1 ; --mmap_remaining ; ) + if (*--p == '\n' && !--off) { + ++p; + break; + } + + file_remaining -= mmap_size - mmap_remaining; + + if (off == 0) + break; + + if (file_remaining == 0) + break; + + if (munmap(start, mmap_size)) { + xerr(0, "%s", fname); + return 1; + } + + if (mmap_offset >= MMAP_MAXSIZE) { + mmap_offset -= MMAP_MAXSIZE; + } else { + mmap_offset = 0; + mmap_size = file_remaining; + } + } + + /* + * Output the (perhaps partial) data in this mmap'd block. + */ + WR(p, mmap_size - mmap_remaining); + file_remaining += mmap_size - mmap_remaining; + if (munmap(start, mmap_size)) { + xerr(0, "%s", fname); + return 1; + } + + /* + * Set the file pointer to reflect the length displayed. + * This will cause the caller to redisplay the data if/when + * needed. + */ + if (fseeko(fp, file_remaining, SEEK_SET) == -1) { + ierr(); + return 1; + } + return 0; +} diff --git a/usr.bin/tail/misc.c b/usr.bin/tail/misc.c new file mode 100644 index 000000000..0af8f68f0 --- /dev/null +++ b/usr.bin/tail/misc.c @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. 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. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: misc.c,v 1.7 2011/09/03 09:02:20 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +void +ierr(void) +{ + xerr(0, "%s", fname); +} + +void +oerr(void) +{ + xerr(1, "stdout"); +} + +void +xerr(int fatal, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); + if (fatal) + exit(1); + rval = 1; +} + +void +xerrx(int fatal, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarnx(fmt, ap); + va_end(ap); + if (fatal) + exit(1); + rval = 1; +} diff --git a/usr.bin/tail/read.c b/usr.bin/tail/read.c new file mode 100644 index 000000000..4e9374df2 --- /dev/null +++ b/usr.bin/tail/read.c @@ -0,0 +1,210 @@ +/* $NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. 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. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "extern.h" + +/* + * displaybytes -- read bytes to an offset from the end and display. + * + * This is the function that reads to a byte offset from the end of the input, + * storing the data in a wrap-around buffer which is then displayed. If the + * rflag is set, the data is displayed in lines in reverse order, and this + * routine has the usual nastiness of trying to find the newlines. Otherwise, + * it is displayed from the character closest to the beginning of the input to + * the end. + * + * Non-zero return means than a (non-fatal) error occurred. + */ +int +displaybytes(FILE *fp, off_t off) +{ + int ch, len, tlen; + char *ep, *p, *t; + int wrap; + char *sp; + + if ((sp = p = malloc(off)) == NULL) + xerr(1, "malloc"); + + for (wrap = 0, ep = p + off; (ch = getc(fp)) != EOF;) { + *p = ch; + if (++p == ep) { + wrap = 1; + p = sp; + } + } + if (ferror(fp)) { + ierr(); + return 1; + } + + if (rflag) { + for (t = p - 1, len = 0; t >= sp; --t, ++len) + if (*t == '\n' && len) { + WR(t + 1, len); + len = 0; + } + if (wrap) { + tlen = len; + for (t = ep - 1, len = 0; t >= p; --t, ++len) + if (*t == '\n') { + if (len) { + WR(t + 1, len); + len = 0; + } + if (tlen) { + WR(sp, tlen); + tlen = 0; + } + } + if (len) + WR(t + 1, len); + if (tlen) + WR(sp, tlen); + } + } else { + if (wrap && (len = ep - p)) + WR(p, len); + if ((len = p - sp) != 0) + WR(sp, len); + } + return 0; +} + +/* + * displaylines -- read lines to an offset from the end and display. + * + * This is the function that reads to a line offset from the end of the input, + * storing the data in an array of buffers which is then displayed. If the + * rflag is set, the data is displayed in lines in reverse order, and this + * routine has the usual nastiness of trying to find the newlines. Otherwise, + * it is displayed from the line closest to the beginning of the input to + * the end. + * + * Non-zero return means than a (non-fatal) error occurred. + */ +int +displaylines(FILE *fp, off_t off) +{ + struct { + int blen; + int len; + char *l; + } *lines; + int ch; + char *p; + int blen, cnt, recno, wrap; + char *sp, *n; + + p = NULL; + if ((lines = malloc(off * sizeof(*lines))) == NULL) + xerr(1, "malloc"); + + memset(lines, 0, sizeof(*lines) * off); + + sp = NULL; + blen = cnt = recno = wrap = 0; + + while ((ch = getc(fp)) != EOF) { + if (++cnt > blen) { + if ((n = realloc(sp, blen + 1024)) == NULL) + xerr(1, "realloc"); + sp = n; + blen += 1024; + p = sp + cnt - 1; + } + *p++ = ch; + if (ch == '\n') { + if (lines[recno].blen < cnt) { + if ((n = realloc(lines[recno].l, + cnt + 256)) == NULL) + xerr(1, "realloc"); + lines[recno].l = n; + lines[recno].blen = cnt + 256; + } + memmove(lines[recno].l, sp, lines[recno].len = cnt); + cnt = 0; + p = sp; + if (++recno == off) { + wrap = 1; + recno = 0; + } + } + } + if (ferror(fp)) { + free(lines); + ierr(); + return 1; + } + if (cnt) { + lines[recno].l = sp; + lines[recno].len = cnt; + if (++recno == off) { + wrap = 1; + recno = 0; + } + } + + if (rflag) { + for (cnt = recno - 1; cnt >= 0; --cnt) + WR(lines[cnt].l, lines[cnt].len); + if (wrap) + for (cnt = off - 1; cnt >= recno; --cnt) + WR(lines[cnt].l, lines[cnt].len); + } else { + if (wrap) + for (cnt = recno; cnt < off; ++cnt) + WR(lines[cnt].l, lines[cnt].len); + for (cnt = 0; cnt < recno; ++cnt) + WR(lines[cnt].l, lines[cnt].len); + } + free(lines); + return 0; +} diff --git a/usr.bin/tail/reverse.c b/usr.bin/tail/reverse.c new file mode 100644 index 000000000..bb8bcd98a --- /dev/null +++ b/usr.bin/tail/reverse.c @@ -0,0 +1,269 @@ +/* $NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. 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. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)reverse.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "extern.h" + +static void r_buf(FILE *); +static void r_reg(FILE *, enum STYLE, off_t, struct stat *); + +/* + * reverse -- display input in reverse order by line. + * + * There are six separate cases for this -- regular and non-regular + * files by bytes, lines or the whole file. + * + * BYTES display N bytes + * REG mmap the file and display the lines + * NOREG cyclically read characters into a wrap-around buffer + * + * LINES display N lines + * REG mmap the file and display the lines + * NOREG cyclically read lines into a wrap-around array of buffers + * + * FILE display the entire file + * REG mmap the file and display the lines + * NOREG cyclically read input into a linked list of buffers + */ +void +reverse(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) +{ + if (style != REVERSE && off == 0) + return; + + if (S_ISREG(sbp->st_mode)) + r_reg(fp, style, off, sbp); + else + switch(style) { + case FBYTES: + case RBYTES: + (void)displaybytes(fp, off); + break; + case FLINES: + case RLINES: + (void)displaylines(fp, off); + break; + case REVERSE: + r_buf(fp); + break; + default: + break; + } +} + +/* + * r_reg -- display a regular file in reverse order by line. + */ +static void +r_reg(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) +{ + off_t size; + int llen; + char *p; + char *start; + + if (!(size = sbp->st_size)) + return; + + if ((uint64_t)size > SIZE_T_MAX) { + /* XXX: need a cleaner way to check this on amd64 */ + errno = EFBIG; + xerr(0, "%s", fname); + return; + } + + if ((start = mmap(NULL, (size_t)size, PROT_READ, + MAP_FILE|MAP_SHARED, fileno(fp), (off_t)0)) == MAP_FAILED) { + xerr(0, "%s", fname); + return; + } + p = start + size - 1; + + if (style == RBYTES && off < size) + size = off; + + /* Last char is special, ignore whether newline or not. */ + for (llen = 1; --size; ++llen) + if (*--p == '\n') { + WR(p + 1, llen); + llen = 0; + if (style == RLINES && !--off) { + ++p; + break; + } + } + if (llen) + WR(p, llen); + if (munmap(start, (size_t)sbp->st_size)) + xerr(0, "%s", fname); +} + +typedef struct bf { + struct bf *next; + struct bf *prev; + int len; + char *l; +} BF; + +/* + * r_buf -- display a non-regular file in reverse order by line. + * + * This is the function that saves the entire input, storing the data in a + * doubly linked list of buffers and then displays them in reverse order. + * It has the usual nastiness of trying to find the newlines, as there's no + * guarantee that a newline occurs anywhere in the file, let alone in any + * particular buffer. If we run out of memory, input is discarded (and the + * user warned). + */ +static void +r_buf(FILE *fp) +{ + BF *mark, *tl, *tr; + int ch, len, llen; + char *p; + off_t enomem; + +#define BSZ (128 * 1024) + tl = NULL; + for (mark = NULL, enomem = 0;;) { + /* + * Allocate a new block and link it into place in a doubly + * linked list. If out of memory, toss the LRU block and + * keep going. + */ + if (enomem) { + if (!mark) { + errno = ENOMEM; + xerr(1, NULL); + } + tl = tl->next; + enomem += tl->len; + } else if ((tl = malloc(sizeof(*tl))) == NULL || + (tl->l = malloc(BSZ)) == NULL) { + if (tl) + free(tl); + if (!mark) { + errno = ENOMEM; + xerr(1, NULL); + } + tl = mark; + enomem += tl->len; + } else if (mark) { + tl->next = mark; + tl->prev = mark->prev; + mark->prev->next = tl; + mark->prev = tl; + } else { + mark = tl; + mark->next = mark->prev = mark; + } + + /* Fill the block with input data. */ + ch = 0; + for (p = tl->l, len = 0; + len < BSZ && (ch = getc(fp)) != EOF; ++len) + *p++ = ch; + + /* + * If no input data for this block and we tossed some data, + * recover it. + */ + if (!len) { + if (enomem) + enomem -= tl->len; + tl = tl->prev; + break; + } + + tl->len = len; + if (ch == EOF) + break; + } + + if (enomem) { + xerrx(0, "Warning: %lld bytes discarded", (long long)enomem); + } + + /* + * Step through the blocks in the reverse order read. The last char + * is special, ignore whether newline or not. + */ + for (mark = tl;;) { + for (p = tl->l + (len = tl->len) - 1, llen = 0; len--; + --p, ++llen) + if (*p == '\n') { + if (llen) { + WR(p + 1, llen); + llen = 0; + } + if (tl == mark) + continue; + for (tr = tl->next; tr->len; tr = tr->next) { + WR(tr->l, tr->len); + tr->len = 0; + if (tr == mark) + break; + } + } + tl->len = llen; + if ((tl = tl->prev) == mark) + break; + } + tl = tl->next; + if (tl->len) { + WR(tl->l, tl->len); + tl->len = 0; + } + while ((tl = tl->next)->len) { + WR(tl->l, tl->len); + tl->len = 0; + } +} diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1 new file mode 100644 index 000000000..4a32a3b2e --- /dev/null +++ b/usr.bin/tail/tail.1 @@ -0,0 +1,192 @@ +.\" $NetBSD: tail.1,v 1.14 2013/01/31 23:09:06 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" 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. 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. +.\" +.\" @(#)tail.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt TAIL 1 +.Os +.Sh NAME +.Nm tail +.Nd display the last part of a file +.Sh SYNOPSIS +.Nm +.Oo +.Fl f | +.Fl F | +.Fl r +.Oc +.Oo +.Fl b Ar number | +.Fl c Ar number | +.Fl n Ar number +.Oc +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +utility displays the contents of +.Ar file +or, by default, its standard input, to the standard output. +.Pp +The display begins at a byte, line or 512-byte block location in the +input. +Numbers having a leading plus (``+'') sign are relative to the beginning +of the input, for example, +.Dq -c +2 +starts the display at the second +byte of the input. +Numbers having a leading minus (``-'') sign or no explicit sign are +relative to the end of the input, for example, +.Dq -n 2 +displays the last two lines of the input. +The default starting location is +.Dq -n 10 , +or the last 10 lines of the input. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl b Ar number +The location is +.Ar number +512-byte blocks. +.It Fl c Ar number +The location is +.Ar number +bytes. +.It Fl f +The +.Fl f +option causes +.Nm +to not stop when end of file is reached, but rather to wait for additional +data to be appended to the input. +The +.Fl f +option is ignored if the standard input is a pipe, but not if it is a FIFO. +.It Fl F +The +.Fl F +option is the same as the +.Fl f +option, except that every five seconds +.Nm +will check to see if the file named on the command line has been +shortened or moved (it is considered moved if the inode or device +number changes) and, if so, it will close +the current file, open the filename given, print out the entire +contents, and continue to wait for more data to be appended. +This option is used to follow log files though rotation by +.Xr newsyslog 8 +or similar programs. +.It Fl n Ar number +The location is +.Ar number +lines. +.It Fl r +The +.Fl r +option causes the input to be displayed in reverse order, by line. +Additionally, this option changes the meaning of the +.Fl b , +.Fl c +and +.Fl n +options. +When the +.Fl r +option is specified, these options specify the number of bytes, lines +or 512-byte blocks to display, instead of the bytes, lines or blocks +from the beginning or end of the input from which to begin the display. +The default for the +.Fl r +option is to display all of the input. +.El +.Pp +If more than a single file is specified, each file is preceded by a +header consisting of the string +.Dq ==\*[Gt] XXX \*[Le]= +where +.Dq XXX +is the name of the file. +.Pp +The +.Nm +utility exits 0 on success, and \*[Gt]0 if an error occurs. +.Sh SEE ALSO +.Xr cat 1 , +.Xr head 1 , +.Xr sed 1 +.Sh STANDARDS +The +.Nm +utility is expected to be a superset of the +.St -p1003.2-92 +specification. +In particular, the +.Fl b , +.Fl r +and +.Fl F +options are extensions to that standard. +.Pp +The historic command line syntax of +.Nm +is supported by this implementation. +The only difference between this implementation and historic versions +of +.Nm , +once the command line syntax translation has been done, is that the +.Fl b , +.Fl c +and +.Fl n +options modify the +.Fl r +option, i.e., ``-r -c 4'' displays the last 4 characters of the last line +of the input, while the historic tail (using the historic syntax ``-4cr'') +would ignore the +.Fl c +option and display the last 4 lines of the input. +.Sh HISTORY +A +.Nm +command appeared in +.At v7 . +.Sh BUGS +When using the +.Fl F +option, +.Nm +will not detect a file truncation if, between the truncation +and the next check of the file size, data written to the file make +it larger than the last known file size. diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c new file mode 100644 index 000000000..75c7b2bcd --- /dev/null +++ b/usr.bin/tail/tail.c @@ -0,0 +1,305 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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. 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. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: tail.c,v 1.17 2013/01/31 23:09:06 wiz Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include "extern.h" + +int fflag, rflag, rval; +const char *fname; + +static void obsolete(char **); +static void usage(void) __dead; + +int +main(int argc, char *argv[]) +{ + struct stat sb; + FILE *fp; + off_t off; + enum STYLE style; + int ch, first; + char *p; + + setprogname(argv[0]); + off = 0; + /* + * 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 = strtoll(optarg, &p, 10) * (units); \ + if (*p) \ + xerrx(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, "Fb:c:fn:r")) != -1) + switch(ch) { + case 'F': + fflag = 2; + break; + 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) + xerrx(1, + "-f and -F options 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; + } + + /* + * 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; + } + } + if (*argv) + for (first = 1; (fname = *argv++) != NULL;) { + if ((fp = fopen(fname, "r")) == NULL || + fstat(fileno(fp), &sb)) { + ierr(); + continue; + } + if (argc > 1) { + (void)printf("%s==> %s <==\n", + first ? "" : "\n", fname); + first = 0; + (void)fflush(stdout); + } + + if (rflag) + reverse(fp, style, off, &sb); + else + forward(fp, style, off, &sb); + (void)fclose(fp); + } + else { + 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); + } + exit(rval); +} + +/* + * 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(char *argv[]) +{ + char *ap, *p, *t; + int len; + char *start; + + while ((ap = *++argv) != NULL) { + /* 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) + xerr(1, "malloc"); + *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: + xerrx(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) +{ + (void)fprintf(stderr, + "Usage: %s [-f | -F | -r] [-b # | -c # | -n #] [file ...]\n", + getprogname()); + exit(1); +}