ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/cmd/tail.b/
implement Tail;
include "sys.m";
sys: Sys;
include "draw.m";
include "bufio.m";
bufmod : Bufio;
Iobuf : import bufmod;
include "string.m";
str : String;
count, anycount, follow : int;
file : ref sys->FD;
bout : ref Iobuf;
BSize : con 8*1024;
BEG, END, CHARS, LINES , FWD, REV : con iota;
origin := END;
units := LINES;
dir := FWD;
Tail: module
{
init: fn(nil: ref Draw->Context, argv: list of string);
};
init(nil: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
str = load String String->PATH;
bufmod = load Bufio Bufio->PATH;
seekable : int;
bout = bufmod->fopen(sys->fildes(1),bufmod->OWRITE);
argv=parse(tl argv);
if(dir==REV && (units==CHARS || follow || origin==BEG))
fail("incompatible options");
if(!anycount){
if (dir==REV)
count= 16r7fffffff;
else
count = 10;
}
if(origin==BEG && units==LINES && count>0)
count--;
if(len argv > 1)
usage();
if(argv == nil || hd argv == "-") {
file = sys->fildes(0);
seekable = 0;
}
else {
if((file=sys->open(hd argv,sys->OREAD)) == nil )
fatal(hd argv);
(nil, stat) := sys->fstat(file);
seekable = sys->seek(file,big 0,sys->SEEKSTART) == big 0 && stat.length > big 0;
}
if(!seekable && origin==END)
keep();
else if(!seekable && origin==BEG)
skip();
else if(units==CHARS && origin==END){
tseek(big -count, Sys->SEEKEND);
copy();
}
else if(units==CHARS && origin==BEG){
tseek(big count, Sys->SEEKSTART);
copy();
}
else if(units==LINES && origin==END)
reverse();
else if(units==LINES && origin==BEG)
skip();
if(follow){
if(seekable){
d : sys->Dir;
d.length=big -1;
for(;;){
d=trunc(d.length);
copy();
sys->sleep(5000);
}
}else{
for(;;){
copy();
sys->sleep(5000);
}
}
}
exit;
}
trunc(length : big) : sys->Dir
{
(nil,d):=sys->fstat(file);
if(d.length < length)
d.length = tseek(big 0, sys->SEEKSTART);
return d;
}
skip() # read past head of the file to find tail
{
n : int;
buf := array[BSize] of byte;
if(units == CHARS) {
for( ; count>0; count -=n) {
if (count<BSize)
n=count;
else
n=BSize;
n = tread(buf, n);
if(n == 0)
return;
}
} else { # units == LINES
i:=0;
n=0;
while(count > 0) {
n = tread(buf, BSize);
if(n == 0)
return;
for(i=0; i<n && count>0; i++)
if(buf[i]==byte '\n')
count--;
}
twrite(buf[i:n]);
}
copy();
}
copy()
{
buf := array[BSize] of byte;
while((n := tread(buf, BSize)) > 0){
twrite(buf[0:n]);
}
bout.flush();
}
keep() # read whole file, keeping the tail
{ # complexity=length(file)*length(tail). could be linear
j, k : int;
length:=0;
buf : array of byte;
tbuf : array of byte;
bufsize := 0;
for(n:=1; n;) {
if(length+BSize > bufsize ) {
bufsize += 2*BSize;
tbuf = array[bufsize+1] of byte;
tbuf[0:]=buf[0:];
buf = tbuf;
}
for( ; n && length<bufsize; length+=n)
n = tread(buf[length:], bufsize-length);
if(count >= length)
continue;
if(units == CHARS)
j = length - count;
else{ # units == LINES
if (int buf[length-1]=='\n')
j = length-1;
else
j=length;
for(k=0; j>0; j--)
if(int buf[j-1] == '\n')
if(++k >= count)
break;
}
length-=j;
buf[0:]=buf[j:j+length];
}
if(dir == REV) {
if(length>0 && buf[length-1]!= byte '\n')
buf[length++] = byte '\n';
for(j=length-1 ; j>0; j--)
if(buf[j-1] == byte '\n') {
twrite(buf[j:length]);
if(--count <= 0)
return;
length = j;
}
}
if(count > 0 && length > 0)
twrite(buf[0:length]);
bout.flush();
}
reverse() # count backward and print tail of file
{
length := 0;
n := 0;
buf : array of byte;
pos := tseek(big 0, sys->SEEKEND);
bufsize := 0;
for(first:=1; pos>big 0 && count>0; first=0) {
if (pos>big BSize)
n = BSize;
else
n = int pos;
pos -= big n;
if(length+2*n > bufsize) {
bufsize += BSize*((length+2*n-bufsize+BSize-1)/BSize);
tbuf := array[bufsize+1] of byte;
tbuf[0:] = buf;
buf = tbuf;
}
length += n;
abuf := array[length] of byte;
abuf[0:] = buf[0:length];
buf[n:] = abuf;
tseek(pos, sys->SEEKSTART);
if(tread(buf, n) != n)
fatal("length error");
if(first && buf[length-1]!= byte '\n')
buf[length++] = byte '\n';
for(n=length-1 ; n>0 && count>0; n--)
if(buf[n-1] == byte '\n') {
count--;
if(dir == REV){
twrite(buf[n:length]);
bout.flush();
}
length = n;
}
}
if(dir == FWD) {
if (n==0)
tseek(big 0 , sys->SEEKSTART);
else
tseek(pos+big n+big 1, sys->SEEKSTART);
copy();
} else if(count > 0)
twrite(buf[0:length]);
bout.flush();
}
tseek(o : big, p: int) : big
{
o = sys->seek(file, o, p);
if(o == big -1)
fatal("");
return o;
}
tread(buf: array of byte, n: int): int
{
r := sys->read(file, buf, n);
if(r == -1)
fatal("");
return r;
}
twrite(buf:array of byte)
{
str1:= string buf;
if(bout.puts(str1)!=len str1)
fatal("");
}
fatal(s : string)
{
sys->fprint(sys->fildes(2), "tail: %s: %r\n", s);
exit;
}
fail(s : string)
{
sys->fprint(sys->fildes(2), "tail: %s\n", s);
exit;
}
usage()
{
sys->fprint(sys->fildes(2), "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]\n");
exit;
}
getnumber(s: string) : int
{
i:=0;
if (len s == 0) return 0;
if(s[i]=='-' || s[i]=='+') {
if (len s == 1)
return 0;
i++;
}
if(!(s[i]>='0' && s[i]<='9'))
return 0;
if(s[0] == '+')
origin = BEG;
if(anycount++)
fail("excess option");
if (s[0]=='-')
s=s[1:];
(count,nil) = str->toint(s,10);
if(count < 0){ # protect int args (read, fwrite)
fail("too big");
}
return 1;
}
parse(args : list of string) : list of string
{
for(; args!=nil ; args = tl args ) {
hdarg := hd args;
if(getnumber(hdarg))
suffix(hdarg);
else if(len hdarg > 1 && hdarg[0] == '-')
case (hdarg[1]) {
'c' or 'n'=>
if (hdarg[1]=='c')
units = CHARS;
if(len hdarg>2 && getnumber(hdarg[2:]))
;
else if(tl args != nil && getnumber(hd tl args)) {
args = tl args;
} else
usage();
'r' =>
dir = REV;
'f' =>
follow++;
'-' =>
args = tl args;
}
else
break;
}
return args;
}
suffix(s : string)
{
i:=0;
while(i < len s && str->in(s[i],"0123456789+-"))
i++;
if (i==len s)
return;
if (s[i]=='b')
if((count*=1024) < 0)
fail("too big");
if (s[i]=='c' || s[i]=='b')
units = CHARS;
if (s[i]=='l' || s[i]=='c' || s[i]=='b')
i++;
if (i<len s){
case s[i] {
'r'=>
dir = REV;
return;
'f'=>
follow++;
return;
}
}
i++;
if (i<len s)
usage();
}