blob: b40552bfd98cb6fe699e82e68d77e4cd82a016de [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
/*
* tail command, posix plus v10 option -r.
* the simple command tail -c, legal in v10, is illegal
*/
vlong count;
int anycount;
int follow;
int file = 0;
char* umsg = "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]";
Biobuf bout;
enum
{
BEG,
END
} origin = END;
enum
{
CHARS,
LINES
} units = LINES;
enum
{
FWD,
REV
} dir = FWD;
extern void copy(void);
extern void fatal(char*);
extern int getnumber(char*);
extern void keep(void);
extern void reverse(void);
extern void skip(void);
extern void suffix(char*);
extern long tread(char*, long);
#define trunc tailtrunc
extern void trunc(Dir*, Dir**);
extern vlong tseek(vlong, int);
extern void twrite(char*, long);
extern void usage(void);
#define JUMP(o,p) tseek(o,p), copy()
void
main(int argc, char **argv)
{
int seekable, c;
Binit(&bout, 1, OWRITE);
for(; argc > 1 && ((c=*argv[1])=='-'||c=='+'); argc--,argv++ ) {
if(getnumber(argv[1])) {
suffix(argv[1]);
continue;
} else
if(c == '-')
switch(argv[1][1]) {
case 'c':
units = CHARS;
case 'n':
if(getnumber(argv[1]+2))
continue;
else
if(argc > 2 && getnumber(argv[2])) {
argc--, argv++;
continue;
} else
usage();
case 'r':
dir = REV;
continue;
case 'f':
follow++;
continue;
case '-':
argc--, argv++;
}
break;
}
if(dir==REV && (units==CHARS || follow || origin==BEG))
fatal("incompatible options");
if(!anycount)
count = dir==REV? ~0ULL>>1: 10;
if(origin==BEG && units==LINES && count>0)
count--;
if(argc > 2)
usage();
if(argc > 1 && (file=open(argv[1],0)) < 0)
fatal(argv[1]);
seekable = seek(file,0L,0) == 0;
if(!seekable && origin==END)
keep();
else
if(!seekable && origin==BEG)
skip();
else
if(units==CHARS && origin==END)
JUMP(-count, 2);
else
if(units==CHARS && origin==BEG)
JUMP(count, 0);
else
if(units==LINES && origin==END)
reverse();
else
if(units==LINES && origin==BEG)
skip();
if(follow && seekable)
for(;;) {
static Dir *sb0, *sb1;
trunc(sb1, &sb0);
copy();
trunc(sb0, &sb1);
sleep(5000);
}
exits(0);
}
void
trunc(Dir *old, Dir **new)
{
Dir *d;
vlong olength;
d = dirfstat(file);
if(d == nil)
return;
olength = 0;
if(old)
olength = old->length;
if(d->length < olength)
d->length = tseek(0L, 0);
free(*new);
*new = d;
}
void
suffix(char *s)
{
while(*s && strchr("0123456789+-", *s))
s++;
switch(*s) {
case 'b':
if((count *= 1024) < 0)
fatal("too big");
case 'c':
units = CHARS;
case 'l':
s++;
}
switch(*s) {
case 'r':
dir = REV;
return;
case 'f':
follow++;
return;
case 0:
return;
}
usage();
}
/*
* read past head of the file to find tail
*/
void
skip(void)
{
int i;
long n;
char buf[Bsize];
if(units == CHARS) {
for( ; count>0; count -=n) {
n = count<Bsize? count: Bsize;
if(!(n = tread(buf, n)))
return;
}
} else /*units == LINES*/ {
n = i = 0;
while(count > 0) {
if(!(n = tread(buf, Bsize)))
return;
for(i=0; i<n && count>0; i++)
if(buf[i]=='\n')
count--;
}
twrite(buf+i, n-i);
}
copy();
}
void
copy(void)
{
long n;
char buf[Bsize];
while((n=tread(buf, Bsize)) > 0) {
twrite(buf, n);
Bflush(&bout); /* for FWD on pipe; else harmless */
}
}
/*
* read whole file, keeping the tail
* complexity is length(file)*length(tail).
* could be linear.
*/
void
keep(void)
{
int len = 0;
long bufsiz = 0;
char *buf = 0;
int j, k, n;
for(n=1; n;) {
if(len+Bsize > bufsiz) {
bufsiz += 2*Bsize;
if(!(buf = realloc(buf, bufsiz+1)))
fatal("out of space");
}
for(; n && len<bufsiz; len+=n)
n = tread(buf+len, bufsiz-len);
if(count >= len)
continue;
if(units == CHARS)
j = len - count;
else {
/* units == LINES */
j = buf[len-1]=='\n'? len-1: len;
for(k=0; j>0; j--)
if(buf[j-1] == '\n')
if(++k >= count)
break;
}
memmove(buf, buf+j, len-=j);
}
if(dir == REV) {
if(len>0 && buf[len-1]!='\n')
buf[len++] = '\n';
for(j=len-1 ; j>0; j--)
if(buf[j-1] == '\n') {
twrite(buf+j, len-j);
if(--count <= 0)
return;
len = j;
}
}
if(count > 0)
twrite(buf, len);
}
/*
* count backward and print tail of file
*/
void
reverse(void)
{
int first;
long len = 0;
long n = 0;
long bufsiz = 0;
char *buf = 0;
vlong pos = tseek(0L, 2);
for(first=1; pos>0 && count>0; first=0) {
n = pos>Bsize? Bsize: (int)pos;
pos -= n;
if(len+n > bufsiz) {
bufsiz += 2*Bsize;
if(!(buf = realloc(buf, bufsiz+1)))
fatal("out of space");
}
memmove(buf+n, buf, len);
len += n;
tseek(pos, 0);
if(tread(buf, n) != n)
fatal("length error");
if(first && buf[len-1]!='\n')
buf[len++] = '\n';
for(n=len-1 ; n>0 && count>0; n--)
if(buf[n-1] == '\n') {
count--;
if(dir == REV)
twrite(buf+n, len-n);
len = n;
}
}
if(dir == FWD) {
tseek(n==0? 0 : pos+n+1, 0);
copy();
} else
if(count > 0)
twrite(buf, len);
}
vlong
tseek(vlong o, int p)
{
o = seek(file, o, p);
if(o == -1)
fatal("");
return o;
}
long
tread(char *buf, long n)
{
int r = read(file, buf, n);
if(r == -1)
fatal("");
return r;
}
void
twrite(char *s, long n)
{
if(Bwrite(&bout, s, n) != n)
fatal("");
}
int
getnumber(char *s)
{
if(*s=='-' || *s=='+')
s++;
if(!isdigit((uchar)*s))
return 0;
if(s[-1] == '+')
origin = BEG;
if(anycount++)
fatal("excess option");
count = atol(s);
/* check range of count */
if(count < 0 || (int)count != count)
fatal("too big");
return 1;
}
void
fatal(char *s)
{
char buf[ERRMAX];
errstr(buf, sizeof buf);
fprint(2, "tail: %s: %s\n", s, buf);
exits(s);
}
void
usage(void)
{
fprint(2, "%s\n", umsg);
exits("usage");
}