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();}
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();
}