mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-12 11:10:07 +00:00
363 lines
5.6 KiB
C
363 lines
5.6 KiB
C
#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(*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");
|
|
}
|