xref: /inferno-os/appl/cmd/logfile.b (revision a3d4017d8d3be485b256b15d2344afac09112204)
1implement Logfile;
2
3#
4# Copyright © 1999 Vita Nuova Limited.  All rights reserved.
5#
6
7include "sys.m";
8	sys: Sys;
9	stderr: ref Sys->FD;
10include "draw.m";
11
12Logfile: module {
13	init: fn(nil: ref Draw->Context, argv: list of string);
14};
15
16Fidrec: adt {
17	fid: 	int;		# fid of read
18	rq: 	list of (int, Sys->Rread);	# outstanding read requests
19	pos:	int;		# current position in the logfile
20};
21
22Circbuf: adt {
23	start: int;
24	data: array of byte;
25	new: fn(size: int): ref Circbuf;
26	put: fn(b: self ref Circbuf, d: array of byte): int;
27	get: fn(b: self ref Circbuf, s, n: int): (int, array of byte);
28};
29
30Fidhash: adt
31{
32	table: array of list of ref Fidrec;
33	get: fn(ht: self ref Fidhash, fid: int): ref Fidrec;
34	put: fn(ht: self ref Fidhash, fidrec: ref Fidrec);
35	del: fn(ht: self ref Fidhash, fidrec: ref Fidrec);
36	new: fn(): ref Fidhash;
37};
38
39usage()
40{
41	sys->fprint(stderr, "usage: logfile [-size] file\n");
42	raise "fail: usage";
43}
44
45init(nil: ref Draw->Context, argv: list of string)
46{
47	sys = load Sys Sys->PATH;
48	stderr = sys->fildes(2);
49
50	bufsize := Sys->ATOMICIO * 4;
51
52	if (argv != nil)
53		argv = tl argv;
54	if (argv != nil && len hd argv && (hd argv)[0] == '-' && len hd argv > 1) {
55		if ((bufsize = int ((hd argv)[1:])) <= 0) {
56			sys->fprint(stderr, "logfile: can't have a zero buffer size\n");
57			usage();
58		}
59		argv = tl argv;
60	}
61	if (argv == nil || tl argv != nil)
62		usage();
63	path := hd argv;
64
65	(dir, f) := pathsplit(path);
66	if (sys->bind("#s", dir, Sys->MBEFORE) == -1) {
67		sys->fprint(stderr, "logfile: bind #s failed: %r\n");
68		return;
69	}
70	fio := sys->file2chan(dir, f);
71	if (fio == nil) {
72		sys->fprint(stderr, "logfile: couldn't make %s: %r\n", path);
73		return;
74	}
75
76	spawn logserver(fio, bufsize);
77}
78
79logserver(fio: ref Sys->FileIO, bufsize: int)
80{
81	waitlist: list of ref Fidrec;
82	readers := Fidhash.new();
83	availcount := 0;
84	availchan := chan of int;
85	workchan := chan of (Sys->Rread, array of byte);
86	buf := Circbuf.new(bufsize);
87	for (;;) alt {
88	<-availchan =>
89		availcount++;
90	(nil, count, fid, rc) := <-fio.read =>
91		r := readers.get(fid);
92		if (rc == nil) {
93			if (r != nil)
94				readers.del(r);
95			continue;
96		}
97		if (r == nil) {
98			r = ref Fidrec(fid, nil, buf.start);
99			if (r.pos < len buf.data)
100				r.pos = len buf.data;		# first buffer's worth is garbage
101			readers.put(r);
102		}
103
104		(s, d) := buf.get(r.pos, count);
105		r.pos = s + len d;
106
107		if (d != nil) {
108			rc <-= (d, nil);
109		} else {
110			if (r.rq == nil)
111				waitlist = r :: waitlist;
112			r.rq = (count, rc) :: r.rq;
113		}
114
115	(nil, data, nil, wc) := <-fio.write =>
116		if (wc == nil)
117			continue;
118		if ((n := buf.put(data)) < len data)
119			wc <-= (n, "write too long for buffer");
120		else
121			wc <-= (n, nil);
122
123		wl := waitlist;
124		for (waitlist = nil; wl != nil; wl = tl wl) {
125			r := hd wl;
126			if (availcount == 0) {
127				spawn worker(workchan, availchan);
128				availcount++;
129			}
130			(count, rc) := hd r.rq;
131			r.rq = tl r.rq;
132
133			# optimisation: if the read request wants exactly the data provided
134			# in the write request, then use the original data buffer.
135			s: int;
136			d: array of byte;
137			if (count >= n && r.pos == buf.start + len buf.data - n)
138				(s, d) = (r.pos, data);
139			else
140				(s, d) = buf.get(r.pos, count);
141			r.pos = s + len d;
142			workchan <-= (rc, d);
143			availcount--;
144			if (r.rq != nil)
145				waitlist = r :: waitlist;
146			d = nil;
147		}
148		data = nil;
149		wl = nil;
150	}
151}
152
153worker(work: chan of (Sys->Rread, array of byte), ready: chan of int)
154{
155	for (;;) {
156		(rc, data) := <-work;	# blocks forever if the reading process is killed
157		rc <-= (data, nil);
158		(rc, data) = (nil, nil);
159		ready <-= 1;
160	}
161}
162
163Circbuf.new(size: int): ref Circbuf
164{
165	return ref Circbuf(0, array[size] of byte);
166}
167
168# return number of bytes actually written
169Circbuf.put(b: self ref Circbuf, d: array of byte): int
170{
171	blen := len b.data;
172	# if too big to fit in buffer, truncate the write.
173	if (len d > blen)
174		d = d[0:blen];
175	dlen := len d;
176
177	offset := b.start % blen;
178	if (offset + dlen <= blen) {
179		b.data[offset:] = d;
180	} else {
181		b.data[offset:] = d[0:blen - offset];
182		b.data[0:] = d[blen - offset:];
183	}
184	b.start += dlen;
185	return dlen;
186}
187
188# return (start, data)
189Circbuf.get(b: self ref Circbuf, s, n: int): (int, array of byte)
190{
191	# if the beginning's been overrun, start from the earliest place we can.
192	# we could put some indication of elided bytes in the buffer.
193	if (s < b.start)
194		s = b.start;
195	blen := len b.data;
196	if (s + n > b.start + blen)
197		n = b.start + blen - s;
198	if (n <= 0)
199		return (s, nil);
200	o := s % blen;
201	d := array[n] of byte;
202	if (o + n <= blen)
203		d[0:] = b.data[o:o+n];
204	else {
205		d[0:] = b.data[o:];
206		d[blen - o:] = b.data[0:o+n-blen];
207	}
208	return (s, d);
209}
210
211FIDHASHSIZE: con 32;
212
213Fidhash.new(): ref Fidhash
214{
215	return ref Fidhash(array[FIDHASHSIZE] of list of ref Fidrec);
216}
217
218# put an entry in the hash table.
219# assumes there is no current entry for the fid.
220Fidhash.put(ht: self ref Fidhash, f: ref Fidrec)
221{
222	slot := f.fid & (FIDHASHSIZE-1);
223	ht.table[slot] = f :: ht.table[slot];
224}
225
226Fidhash.get(ht: self ref Fidhash, fid: int): ref Fidrec
227{
228	for (l := ht.table[fid & (FIDHASHSIZE-1)]; l != nil; l = tl l)
229		if ((hd l).fid == fid)
230			return hd l;
231	return nil;
232}
233
234Fidhash.del(ht: self ref Fidhash, f: ref Fidrec)
235{
236	slot := f.fid & (FIDHASHSIZE-1);
237	nl: list of ref Fidrec;
238	for (l := ht.table[slot]; l != nil; l = tl l)
239		if ((hd l).fid != f.fid)
240			nl = (hd l) :: nl;
241	ht.table[slot] = nl;
242}
243
244pathsplit(p: string): (string, string)
245{
246	for (i := len p - 1; i >= 0; i--)
247		if (p[i] != '/')
248			break;
249	if (i < 0)
250		return (p, nil);
251	p = p[0:i+1];
252	for (i = len p - 1; i >=0; i--)
253		if (p[i] == '/')
254			break;
255	if (i < 0)
256		return (".", p);
257	return (p[0:i+1], p[i+1:]);
258}
259
260