1implement Tail; 2 3include "sys.m"; 4sys: Sys; 5 6include "draw.m"; 7 8include "bufio.m"; 9bufmod : Bufio; 10Iobuf : import bufmod; 11 12include "string.m"; 13 str : String; 14 15count, anycount, follow : int; 16file : ref sys->FD; 17bout : ref Iobuf; 18BSize : con 8*1024; 19 20BEG, END, CHARS, LINES , FWD, REV : con iota; 21 22origin := END; 23units := LINES; 24dir := FWD; 25 26 27Tail: module 28{ 29 init: fn(nil: ref Draw->Context, argv: list of string); 30}; 31 32 33init(nil: ref Draw->Context, argv: list of string) 34{ 35 sys = load Sys Sys->PATH; 36 str = load String String->PATH; 37 bufmod = load Bufio Bufio->PATH; 38 seekable : int; 39 bout = bufmod->fopen(sys->fildes(1),bufmod->OWRITE); 40 argv=parse(tl argv); 41 if(dir==REV && (units==CHARS || follow || origin==BEG)) 42 fail("incompatible options"); 43 if(!anycount){ 44 if (dir==REV) 45 count= 16r7fffffff; 46 else 47 count = 10; 48 } 49 if(origin==BEG && units==LINES && count>0) 50 count--; 51 if(len argv > 1) 52 usage(); 53 if(argv == nil || hd argv == "-") { 54 file = sys->fildes(0); 55 seekable = 0; 56 } 57 else { 58 if((file=sys->open(hd argv,sys->OREAD)) == nil ) 59 fatal(hd argv); 60 (nil, stat) := sys->fstat(file); 61 seekable = sys->seek(file,big 0,sys->SEEKSTART) == big 0 && stat.length > big 0; 62 } 63 64 if(!seekable && origin==END) 65 keep(); 66 else if(!seekable && origin==BEG) 67 skip(); 68 else if(units==CHARS && origin==END){ 69 tseek(big -count, Sys->SEEKEND); 70 copy(); 71 } 72 else if(units==CHARS && origin==BEG){ 73 tseek(big count, Sys->SEEKSTART); 74 copy(); 75 } 76 else if(units==LINES && origin==END) 77 reverse(); 78 else if(units==LINES && origin==BEG) 79 skip(); 80 if(follow){ 81 if(seekable){ 82 d : sys->Dir; 83 d.length=big -1; 84 for(;;){ 85 d=trunc(d.length); 86 copy(); 87 sys->sleep(5000); 88 } 89 }else{ 90 for(;;){ 91 copy(); 92 sys->sleep(5000); 93 } 94 } 95 } 96 exit; 97} 98 99 100trunc(length : big) : sys->Dir 101{ 102 (nil,d):=sys->fstat(file); 103 if(d.length < length) 104 d.length = tseek(big 0, sys->SEEKSTART); 105 return d; 106} 107 108 109skip() # read past head of the file to find tail 110{ 111 n : int; 112 buf := array[BSize] of byte; 113 if(units == CHARS) { 114 for( ; count>0; count -=n) { 115 if (count<BSize) 116 n=count; 117 else 118 n=BSize; 119 n = tread(buf, n); 120 if(n == 0) 121 return; 122 } 123 } else { # units == LINES 124 i:=0; 125 n=0; 126 while(count > 0) { 127 n = tread(buf, BSize); 128 if(n == 0) 129 return; 130 for(i=0; i<n && count>0; i++) 131 if(buf[i]==byte '\n') 132 count--; 133 } 134 twrite(buf[i:n]); 135 } 136 copy(); 137} 138 139 140copy() 141{ 142 buf := array[BSize] of byte; 143 while((n := tread(buf, BSize)) > 0){ 144 twrite(buf[0:n]); 145 } 146 bout.flush(); 147} 148 149 150keep() # read whole file, keeping the tail 151{ # complexity=length(file)*length(tail). could be linear 152 j, k : int; 153 length:=0; 154 buf : array of byte; 155 tbuf : array of byte; 156 bufsize := 0; 157 for(n:=1; n;) { 158 if(length+BSize > bufsize ) { 159 bufsize += 2*BSize; 160 tbuf = array[bufsize+1] of byte; 161 tbuf[0:]=buf[0:]; 162 buf = tbuf; 163 } 164 for( ; n && length<bufsize; length+=n) 165 n = tread(buf[length:], bufsize-length); 166 if(count >= length) 167 continue; 168 if(units == CHARS) 169 j = length - count; 170 else{ # units == LINES 171 if (int buf[length-1]=='\n') 172 j = length-1; 173 else 174 j=length; 175 for(k=0; j>0; j--) 176 if(int buf[j-1] == '\n') 177 if(++k >= count) 178 break; 179 } 180 length-=j; 181 buf[0:]=buf[j:j+length]; 182 } 183 if(dir == REV) { 184 if(length>0 && buf[length-1]!= byte '\n') 185 buf[length++] = byte '\n'; 186 for(j=length-1 ; j>0; j--) 187 if(buf[j-1] == byte '\n') { 188 twrite(buf[j:length]); 189 if(--count <= 0) 190 return; 191 length = j; 192 } 193 } 194 if(count > 0 && length > 0) 195 twrite(buf[0:length]); 196 bout.flush(); 197} 198 199reverse() # count backward and print tail of file 200{ 201 length := 0; 202 n := 0; 203 buf : array of byte; 204 pos := tseek(big 0, sys->SEEKEND); 205 bufsize := 0; 206 for(first:=1; pos>big 0 && count>0; first=0) { 207 if (pos>big BSize) 208 n = BSize; 209 else 210 n = int pos; 211 pos -= big n; 212 if(length+2*n > bufsize) { 213 bufsize += BSize*((length+2*n-bufsize+BSize-1)/BSize); 214 tbuf := array[bufsize+1] of byte; 215 tbuf[0:] = buf; 216 buf = tbuf; 217 } 218 length += n; 219 abuf := array[length] of byte; 220 abuf[0:] = buf[0:length]; 221 buf[n:] = abuf; 222 tseek(pos, sys->SEEKSTART); 223 if(tread(buf, n) != n) 224 fatal("length error"); 225 if(first && buf[length-1]!= byte '\n') 226 buf[length++] = byte '\n'; 227 for(n=length-1 ; n>0 && count>0; n--) 228 if(buf[n-1] == byte '\n') { 229 count--; 230 if(dir == REV){ 231 twrite(buf[n:length]); 232 bout.flush(); 233 } 234 length = n; 235 } 236 } 237 if(dir == FWD) { 238 if (n==0) 239 tseek(big 0 , sys->SEEKSTART); 240 else 241 tseek(pos+big n+big 1, sys->SEEKSTART); 242 243 copy(); 244 } else if(count > 0) 245 twrite(buf[0:length]); 246 bout.flush(); 247} 248 249 250tseek(o : big, p: int) : big 251{ 252 o = sys->seek(file, o, p); 253 if(o == big -1) 254 fatal(""); 255 return o; 256} 257 258 259tread(buf: array of byte, n: int): int 260{ 261 r := sys->read(file, buf, n); 262 if(r == -1) 263 fatal(""); 264 return r; 265} 266 267 268twrite(buf:array of byte) 269{ 270 str1:= string buf; 271 if(bout.puts(str1)!=len str1) 272 fatal(""); 273} 274 275 276 277fatal(s : string) 278{ 279 sys->fprint(sys->fildes(2), "tail: %s: %r\n", s); 280 exit; 281} 282 283fail(s : string) 284{ 285 sys->fprint(sys->fildes(2), "tail: %s\n", s); 286 exit; 287} 288 289 290usage() 291{ 292 sys->fprint(sys->fildes(2), "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]\n"); 293 exit; 294} 295 296 297getnumber(s: string) : int 298{ 299 i:=0; 300 if (len s == 0) return 0; 301 if(s[i]=='-' || s[i]=='+') { 302 if (len s == 1) 303 return 0; 304 i++; 305 } 306 if(!(s[i]>='0' && s[i]<='9')) 307 return 0; 308 if(s[0] == '+') 309 origin = BEG; 310 if(anycount++) 311 fail("excess option"); 312 if (s[0]=='-') 313 s=s[1:]; 314 (count,nil) = str->toint(s,10); 315 if(count < 0){ # protect int args (read, fwrite) 316 fail("too big"); 317 } 318 return 1; 319} 320 321parse(args : list of string) : list of string 322{ 323 for(; args!=nil ; args = tl args ) { 324 hdarg := hd args; 325 if(getnumber(hdarg)) 326 suffix(hdarg); 327 else if(len hdarg > 1 && hdarg[0] == '-') 328 case (hdarg[1]) { 329 'c' or 'n'=> 330 if (hdarg[1]=='c') 331 units = CHARS; 332 if(len hdarg>2 && getnumber(hdarg[2:])) 333 ; 334 else if(tl args != nil && getnumber(hd tl args)) { 335 args = tl args; 336 } else 337 usage(); 338 'r' => 339 dir = REV; 340 'f' => 341 follow++; 342 '-' => 343 args = tl args; 344 } 345 else 346 break; 347 } 348 return args; 349} 350 351 352suffix(s : string) 353{ 354 i:=0; 355 while(i < len s && str->in(s[i],"0123456789+-")) 356 i++; 357 if (i==len s) 358 return; 359 if (s[i]=='b') 360 if((count*=1024) < 0) 361 fail("too big"); 362 if (s[i]=='c' || s[i]=='b') 363 units = CHARS; 364 if (s[i]=='l' || s[i]=='c' || s[i]=='b') 365 i++; 366 if (i<len s){ 367 case s[i] { 368 'r'=> 369 dir = REV; 370 return; 371 'f'=> 372 follow++; 373 return; 374 } 375 } 376 i++; 377 if (i<len s) 378 usage(); 379} 380