xref: /inferno-os/appl/cmd/tail.b (revision 3f1f06c5d12b24c4061e5123acabf72348ff45a2)
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