Files
Arquivotheca.Solaris-2.5/cmd/tail/tail.c
seta75D 7c4988eac0 Init
2021-10-11 19:38:01 -03:00

868 lines
17 KiB
C
Executable File

/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
/* The copyright notice above does not evidence any */
/* actual or intended publication of such source code. */
/* Parts of this product may be derived from OSF/1 1.0 and */
/* Berkeley 4.3 BSD systems licensed from */
/* OPEN SOFTWARE FOUNDATION, INC. and the University of California. */
/* Parts of this product may be derived from the systems licensed */
/* from International Business Machines Corp. and */
/* Bell Telephone Laboratories, Inc. */
/* Portions Copyright (c) 1988, 1994, Sun Microsystems, Inc. */
/* All Rights Reserved. */
#pragma ident "@(#)tail.c 1.25 95/08/31 SMI" /* SVr4.0 1.14 */
/*
* XCU4 compiant tail command
* NEW STYLE
* tail [-fr] [-c number | -n number] [file]
* option 'c' means tail number bytes
* option 'n' means tail number lines
* The number must be a decimal integer whose sign affects the location in
* the file, measured in bytes/lines, to begin the copying:
* Sign Copying Starts
* + Relative to the beginning of the file.
* - Relative to the end of the file.
* none Relative to the end of the file.
* The origin for couting is 1; that is, -c +1 represents the first byte
* of the file, -n -1 the last line.
* option 'f' means loop endlessly trying to read more bytes after the
* end of file, on the assumption that the file is growing.
* option 'r' means inlines in reverse order from end
* (for -r, default is entire buffer)
* OLD STYLE
* tail -[number][b|c|l|r][f] [file]
* tail +[number][b|c|l|r][f] [file]
* - means n lines before end
* + means nth line from beginning
* type 'b' means tail n blocks, not lines
* type 'c' means tail n characters(bytes?)
* type 'l' means tail n lines
* type 'r' means in lines in reverse order from end
* (for -r, default is entire buffer)
* option 'f' means loop endlessly trying to read more bytes after the
* end of file, on the assumption that the file is growing.
*
* Sun-traditional tail command
* tail where [file]
* where is [+|-]n[type]
* - means n lines before end
* + means nth line from beginning
* type 'b' means tail n blocks, not lines
* type 'c' means tail n bytes
* type 'r' means in lines in reverse order from end
* (for -r, default is entire buffer)
* option 'f' means loop endlessly trying to read more
* characters after the end of file, on the assumption
* that the file is growing
*
* Comments regarding possible (future) cleanup:
*
* XXX: In some places, the input file is accessed through the decsriptor
* variable "fd"; in others, it's accessed through the constant "0". The
* way the descriptor is named should be made uniform throughout.
*
* XXX: Too many global variables!
*/
#include <stdio.h>
#include <ctype.h>
#include <locale.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h> /* for LINE_MAX and PATH_MAX */
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/param.h> /* for DEV_BSHIFT and DEV_BSIZE */
#include <sys/mman.h>
#include <sys/uio.h>
/*
* Amount of file buffered when it's not possible to mmap it or determine its
* size in advance; the value reflects a somewhat arbitrary tradeoff between
* memory consumption and providing a big enough window into the file to
* accommodate the command line arguments without truncation (it could
* reasonably be doubled or quadrupled).
*/
#ifdef XPG4
#define LBIN (LINE_MAX * 10)
#else /* !XPG4 */
#define LBIN 65537 /* 64K + 1 for trailing NUL */
#endif /* XPG4 */
#ifdef XPG4
#define NO_FLAG 0
#define FLAG_C 1
#define FLAG_N 2
#endif /* XPG4 */
char staticbin[LBIN]; /* just in case malloc fails */
struct stat statb;
struct statvfs vstatb;
int fcount; /* size of the input file */
char *mapp; /* mmap'ped pointer to the input file */
char *bin;
/* amount to read from file at a crack */
u_long blocksize = DEV_BSIZE; /* fallback value */
int follow; /* -f flag */
int piped;
int bkwds; /* -r flag */
#ifdef XPG4
int fromend, frombegin;
int bylines;
long num;
#endif /* XPG4 */
static char *readbackline(char *, char *);
static void readfromstart(int, int);
static void readfromend(int, int);
static void fexit(void);
static void docopy(void);
static void usage(void);
#ifdef XPG4
static void setnum(char *, int);
#endif /* XPG4 */
main(int argc, char **argv)
{
register i, j, k;
long di;
int partial, lastnl, filefound;
int fsize = LBIN;
char *p, *arg;
char filename[PATH_MAX];
#ifdef XPG4
int cflag, nflag, bflag;
char c;
#else /* !XPG4 */
long num;
int fromend, frombegin;
int bylines;
#endif /* XPG4 */
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
#endif
(void) textdomain(TEXT_DOMAIN);
(void) lseek(0, (long)0, SEEK_CUR);
piped = (errno == ESPIPE);
num = -1;
bylines = -1;
follow = 0;
bkwds = 0;
fromend = 1;
frombegin = 0;
filefound = 0;
#ifdef XPG4
if (argc == 1) { /* just 'tail' */
num = 10;
bylines = 1;
filefound = 0;
goto file;
}
arg = *(argv + 1);
if (*arg != '-' && *arg != '+') { /* 'tail filename' */
if (argc > 2) {
usage();
} else {
num = 10;
bylines = 1;
(void) strcpy(filename, arg);
filefound = 1;
goto file;
}
}
cflag = 0;
nflag = 0;
bflag = 0;
while (--argc > 0) {
arg = *++argv;
switch (*arg) {
case '+':
fromend = 0;
frombegin = 1;
if (nflag == 1) {
setnum(arg, FLAG_N);
nflag = 2;
} else if (cflag == 1) {
setnum(arg, FLAG_C);
cflag = 2;
} else {
num = 10;
setnum(arg, NO_FLAG);
}
break;
case '-':
nextarg:
c = *(arg + 1);
if (c == 'f') {
if (bkwds == 1) {
usage();
}
follow = 1;
if (cflag == 1) { /* OLD STYLE: 'tail -cf' */
bylines = 0;
num = 10;
cflag = 2;
}
} else if (c == 'r') {
if (follow == 1) {
usage();
}
if (num == -1)
num = fsize;
else if (!fromend)
num++;
if (bylines == 0) {
usage();
}
if (cflag == 1) { /* 'tail -cr' */
usage();
}
bkwds = 1;
fromend = 1;
bylines = 1;
} else if (c == 'c') {
if (!cflag && !nflag && !bflag) {
cflag = 1;
} else {
usage();
}
} else if (c == 'n') {
if (!cflag && !nflag && !bflag) {
nflag = 1;
} else {
usage();
}
} else if (c == 'b') {
if (!cflag && !nflag && !bflag) {
bflag = 1;
} else {
usage();
}
} else if ((c == '-') && (*(arg + 2) == '\0')) {
arg = *++argv;
argc--;
goto fileset;
} else {
frombegin = 0;
fromend = 1;
if (nflag == 1) {
setnum(arg, FLAG_N);
nflag = 2;
} else if (cflag == 1) {
setnum(arg, FLAG_C);
cflag = 2;
} else {
setnum(arg, NO_FLAG);
}
break;
}
if (cflag || bflag) {
if (bkwds) {
usage();
}
}
if (*(arg + 2) != '\0') {
*(arg + 1) = '-';
arg++;
goto nextarg;
}
break;
default:
fileset:
if (nflag == 1) {
fromend = 1;
frombegin = 0;
setnum(arg, FLAG_N);
nflag = 2;
} else if (cflag == 1) {
fromend = 1;
frombegin = 0;
setnum(arg, FLAG_C);
cflag = 2;
} else {
if (argc == 0) {
filefound = 0;
} else if (argc > 1) {
usage();
} else {
(void) strcpy(filename, arg);
filefound = 1;
}
}
break;
}
}
#else /* !XPG4 */
arg = *(argv + 1);
if (argc <= 1 || *arg != '-' && *arg != '+') {
arg = "-10l";
argc++;
argv--;
}
fromend = (*arg == '-');
frombegin = (*arg == '+');
arg++;
if (isdigit((int)*arg)) {
num = 0;
while (isdigit((int)*arg)) {
num = num * 10 + *arg++ - '0';
}
} else if (frombegin) { /* option was '+' without an integer */
num = 10;
} else {
num = -1;
}
if (argc > 2) {
(void) strcpy(filename, *(argv + 2));
filefound = 1;
} else {
filefound = 0;
}
bylines = -1;
bkwds = 0;
follow = 0;
while (*arg) {
switch (*arg++) {
case 'b':
if (num == -1) {
num = 10;
}
num <<= DEV_BSHIFT;
if (bylines != -1 || bkwds == 1) {
usage();
}
bylines = 0;
break;
case 'c':
if (bylines != -1 || bkwds == 1) {
usage();
}
bylines = 0;
break;
case 'f':
if (bkwds == 1) {
usage();
}
follow = 1;
break;
case 'r':
if (follow == 1) {
usage();
}
if (num == -1)
num = fsize;
else if (!fromend)
num++;
if (bylines == 0) {
usage();
}
bkwds = 1;
fromend = 1;
bylines = 1;
break;
case 'l':
if (bylines != -1 && bylines == 1) {
usage();
}
bylines = 1;
break;
default:
usage();
break;
}
}
#endif /* XPG4 */
file:
if (filefound) {
int fd;
(void) close(0); /* fd 0 will be used for next open */
piped = 0;
if ((fd = open(filename, 0)) == -1) { /* fd must be 0 */
(void) fprintf(stderr,
gettext("tail: cannot open input\n"));
exit(2);
}
if (fstat(fd, &statb) == -1) {
(void) fprintf(stderr, gettext(
"tail: cannot determine length of %s\n"), filename);
exit(2);
}
fcount = statb.st_size;
fsize = fcount + 1;
/* Get optimal read chunk size. */
if (fstatvfs(fd, &vstatb) == 0 && vstatb.f_bsize != 0) {
blocksize = vstatb.f_bsize;
}
if (!follow) {
if ((mapp = (char *)mmap((caddr_t)NULL,
fcount, PROT_READ,
MAP_PRIVATE, fd, (off_t)0)) == MAP_FAILED) {
/*
* Fall back to use read instead of mmap.
*/
mapp = NULL;
}
}
if (bkwds) {
if (num == -1) {
num = fsize;
}
}
}
if (follow || (mapp == NULL && (bin = (char *)malloc(fsize)) == NULL)) {
bin = staticbin;
if (fsize > LBIN)
fsize = LBIN;
}
if (!fromend && num > 0) {
num--;
}
if (num == -1) {
num = 10;
}
if (bylines == -1) {
bylines = 1;
}
if (bkwds) {
follow = 0;
}
if (fromend) {
if (mapp) {
readfromend(bylines, num);
/* NOTREACHED */
}
goto keep;
}
/*
* seek from beginning
*/
if (mapp) {
readfromstart(bylines, num);
/* NOTREACHED */
}
if (bylines) {
/*
* Read and discard num lines from the beginning of the file,
* and then transcribe the residual amount read.
*/
j = 0;
while (num-- > 0) {
do {
if (j-- <= 0) {
p = bin;
j = read(0, p, blocksize);
if (j-- <= 0) {
fexit();
}
}
} while (*p++ != '\n');
}
(void) write(1, p, j);
} else if (num > 0) {
/*
* See whether it's possible to seek directly to the desired
* position; if not, read up to that point (discarding the
* data).
*/
if (!piped) {
(void) fstat(0, &statb);
}
if (piped || (statb.st_mode & S_IFMT) == S_IFCHR) {
while (num > 0) {
i = MIN(num, blocksize);
i = read(0, bin, i);
if (i <= 0) {
fexit();
}
num -= i;
}
} else {
(void) lseek(0, num, SEEK_SET);
}
}
docopy();
/* NOTREACHED */
/* seek from end */
keep:
if (num <= 0) {
fexit();
}
if (!piped) {
/*
* Figure out how far back to seek; if the file's at least
* that big, do the seek (otherwise the seek pointer is
* already where it should be).
*/
(void) fstat(0, &statb);
di = !bylines && num < fsize ? num : fsize - 1;
if (statb.st_size > di) {
(void) lseek(0, -di, SEEK_END);
}
/*
* If it's not necessary to find line boundaries, it suffices
* to transcribe the rest of the file.
*/
if (!bylines) {
docopy();
}
}
/*
* Suck up data until there's no more available, wrapping circularly
* through the buffer. Set partial to indicate whether or not it was
* possible to fill the buffer.
*/
partial = 1;
for (;;) {
i = 0;
do {
j = read(0, &bin[i], fsize - i);
if (j <= 0) {
goto brka;
}
i += j;
} while (i < fsize);
partial = 0;
}
brka:
/*
* i now is the offset of the "oldest" data in the (circular) buffer.
*
* Set k to the offset of the beginning of the tail data within the
* circular buffer.
*/
if (!bylines) {
k =
num <= i ? i - num :
partial ? 0 :
num >= fsize ? i + 1 :
i - num + fsize;
k--;
} else {
/*
* Scan the buffer for the indicated number of line breaks; j
* counts the number found so far.
*/
if (bkwds && bin[i == 0 ? fsize - 1 : i - 1] != '\n') {
/*
* Force a trailing newline, resetting the boundary
* between the two halves of the buffer if necessary.
*/
bin[i] = '\n';
if (++i >= fsize) {
i = 0;
partial = 0;
}
}
k = i;
j = 0;
do {
lastnl = k;
do {
if (--k < 0) {
if (partial) {
if (bkwds) {
(void) write(1, bin,
lastnl + 1);
}
goto brkb;
}
k = fsize - 1;
}
} while (bin[k] != '\n' && k != i);
if (bkwds && j > 0) {
if (k < lastnl) {
(void) write(1, &bin[k + 1],
lastnl - k);
} else {
(void) write(1, &bin[k + 1],
fsize - k - 1);
(void) write(1, bin, lastnl + 1);
}
}
} while (j++ < num && k != i);
brkb:
if (bkwds) {
exit(0);
}
if (k == i) {
do {
if (++k >= fsize)
k = 0;
} while (bin[k] != '\n' && k != i);
}
}
if (k < i) {
(void) write(1, &bin[k + 1], i - k - 1);
} else {
(void) write(1, &bin[k + 1], fsize - k - 1);
(void) write(1, bin, i);
}
fexit();
}
/*
* Not used when input file is mmap'ped.
*/
static void
fexit()
{
register int n;
if (!follow || piped) {
exit(0);
}
for (;;) {
(void) sleep(1);
while ((n = read(0, bin, blocksize)) > 0) {
(void) write(1, bin, n);
}
}
}
/*
* Not used when input file is mmap'ped.
*/
static void
docopy()
{
int bytes;
while ((bytes = read(0, bin, blocksize)) > 0) {
(void) write(1, bin, bytes);
}
fexit();
/* NOT REACHED */
}
/*
* Used only when the input file is mmap'ped.
*
* Skips over nunits (lines or characters depending on "bylines")
* of the input file from the beginning and writes out
* the remainder.
*/
static void
readfromstart(int bylines, int nunits)
{
if (bylines) {
for (; nunits > 0 && fcount > 0; fcount--) {
if (*mapp++ == '\n') {
nunits--;
}
}
} else {
/*
* skip nunits characters
*/
mapp += nunits;
fcount -= nunits;
}
if (fcount > 0) {
(void) write(1, mapp, fcount);
}
exit(0);
}
/*
* Used only when the input file is mmap'ped.
*
* Writes out nunits (depending on bylines) of the input file counting
* from the end.
*/
static void
readfromend(int bylines, int nunits)
{
int cnt;
char *end, *start, *mark;
if (fcount <= 0 || nunits <= 0) {
exit(0);
}
start = mapp; /* save start of file */
mapp += fcount; /* move mapp to the end */
end = mapp; /* mark the end */
if (bylines) {
while (nunits-- > 0) { /* for each line */
mark = mapp--; /* set mark & move back one */
mapp = readbackline(mapp, start);
cnt = mark - mapp;
if (cnt == 0) { /* done? */
break;
}
if (bkwds) {
(void) write(1, mapp, cnt);
end = mapp;
continue;
}
}
} else {
/*
* bump pointer up to end - nunits.
*/
mapp = end - nunits;
if (mapp < start) {
mapp = start;
}
}
cnt = end - mapp;
if (cnt > 0) {
(void) write(1, mapp, cnt);
}
exit(0);
}
/*
* Reads a line backward (or up to limit) and returns the pointer to the
* beginning of the line.
*/
static char *
readbackline(char *mapp, char *limit)
{
if (mapp <= limit) {
return (limit);
}
while (*--mapp != '\n') {
if (mapp == limit) {
return (limit);
}
}
return (mapp + 1);
}
static void
usage()
{
#ifdef XPG4
(void) fprintf(stderr,
gettext("usage: tail [-f|-r] [-c number | -n number] [file]\n"
" tail [+/-[number][lbc][f]] [file]\n"
" tail [+/-[number][l][r|f]] [file]\n"));
#else /* !XPG4 */
(void) fprintf(stderr, gettext("usage: tail [+/-[n][lbc][f]] [file]\n"
" tail [+/-[n][l][r|f]] [file]\n"));
#endif /* XPG4 */
exit(2);
}
#ifdef XPG4
static void
setnum(char *arg, int flags)
{
if ((*arg == '-') || (*arg == '+')) {
arg++;
}
if (flags != NO_FLAG) { /* '-c num' or '-n num' */
num = 0;
while (*arg) {
if (!isdigit((int)*arg)) {
/* 'num' must be a decimal integer */
usage();
} else {
num = num * 10 + *arg++ - '0';
}
}
if (flags == FLAG_N) {
bylines = 1;
} else {
bylines = 0;
}
} else {
/* '-[num][bcfrl]' or '+[num][bcfrl]' */
if (isdigit((int)*arg)) {
num = 0;
while (isdigit((int)*arg)) {
num = num * 10 + *arg++ - '0';
}
}
bylines = -1;
while (*arg) {
switch (*arg++) {
case 'l':
if (bylines != -1) {
usage();
}
bylines = 1;
break;
case 'b':
if (bylines != -1) {
usage();
}
bylines = 0;
if (num == -1) {
num = 10;
}
num <<= DEV_BSHIFT;
break;
case 'c':
if (bylines != -1) {
usage();
}
bylines = 0;
break;
case 'f':
if (bkwds == 1) {
usage();
}
follow = 1;
break;
case 'r':
if (follow == 1) {
usage();
}
if (num != -1 && !fromend) {
num++;
}
if (bylines == 0) {
usage();
}
bkwds = 1;
fromend = 1;
bylines = 1;
break;
default:
usage();
break;
}
}
}
}
#endif /* XPG4 */