importing tail

Change-Id: Ieda9690195ccd78f081b383cb530702d45537122
This commit is contained in:
Claudio Martella 2014-03-31 22:02:03 +02:00 committed by Lionel Sambuc
parent 10b1980b6e
commit a967d739ab
14 changed files with 1494 additions and 416 deletions

View file

@ -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 \

View file

@ -1,4 +0,0 @@
PROG= tail
MAN=
.include <bsd.prog.mk>

View file

@ -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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
/* External interfaces that should have been standardized into <getopt.h> */
extern char *optarg;
extern int optind;
/* We expect this constant to be defined in <limits.h> 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);
}

View file

@ -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 \

View file

@ -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).

View file

@ -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 \

7
usr.bin/tail/Makefile Normal file
View file

@ -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 <bsd.prog.mk>

52
usr.bin/tail/extern.h Normal file
View file

@ -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;

367
usr.bin/tail/forward.c Normal file
View file

@ -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 <sys/cdefs.h>
#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 <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/event.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

89
usr.bin/tail/misc.c Normal file
View file

@ -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 <sys/cdefs.h>
#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 <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#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;
}

210
usr.bin/tail/read.c Normal file
View file

@ -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 <sys/cdefs.h>
#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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

269
usr.bin/tail/reverse.c Normal file
View file

@ -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 <sys/cdefs.h>
#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 <sys/param.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
}

192
usr.bin/tail/tail.1 Normal file
View file

@ -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.

305
usr.bin/tail/tail.c Normal file
View file

@ -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 <sys/cdefs.h>
#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 <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;
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);
}