changelog shortlog tags branches changeset files file revisions raw help

Mercurial > hg > ventivac / annotate appl/cmd/vacfs.b

changeset 122: 84abb5444a76
parent: 9d2a54eb24ce
child: 07b5d02499fd
author: Mechiel Lukkien <mechiel@ueber.net>
date: Fri, 17 Aug 2007 22:28:53 +0200
permissions: -rw-r--r--
description: remove dflag from vac library interface. doesn't belong there anymore.
mjl@0 1
 implement Vacfs;
mjl@0 2
 
mjl@0 3
 include "sys.m";
mjl@0 4
 	sys: Sys;
mjl@0 5
 include "draw.m";
mjl@0 6
 include "arg.m";
mjl@0 7
 include "string.m";
mechiel@33 8
 include "daytime.m";
mjl@0 9
 include "venti.m";
mjl@0 10
 include "vac.m";
mjl@0 11
 include "styx.m";
mjl@0 12
 	styx: Styx;
mjl@0 13
 	Tmsg, Rmsg: import styx;
mjl@0 14
 include "styxservers.m";
mjl@0 15
 
mjl@0 16
 str: String;
mechiel@33 17
 daytime: Daytime;
mjl@0 18
 venti: Venti;
mjl@0 19
 vac: Vac;
mjl@0 20
 styxservers: Styxservers;
mjl@0 21
 
mjl@0 22
 print, sprint, fprint, fildes: import sys;
mjl@0 23
 Score, Session: import venti;
mechiel@78 24
 Direntry, Vacdir, Vacfile, Source: import vac;
mechiel@108 25
 Styxserver, Fid, Navigator, Navop: import styxservers;
mjl@0 26
 
mjl@0 27
 Vacfs: module {
mjl@0 28
 	init:	fn(nil: ref Draw->Context, args: list of string);
mjl@0 29
 };
mjl@0 30
 
mjl@0 31
 addr := "net!$venti!venti";
mjl@0 32
 dflag := pflag := 0;
mjl@0 33
 session: ref Session;
mjl@0 34
 
mechiel@104 35
 srv: ref Styxserver;
mjl@0 36
 
mjl@0 37
 Elem: adt {
mjl@0 38
 	qid:	int;
mjl@0 39
 	de: 	ref Direntry;
mjl@0 40
 	size:	big;
mjl@0 41
 	pick {
mjl@0 42
 	File =>	vf: 	ref Vacfile;
mjl@0 43
 	Dir =>	vd:	ref Vacdir;
mjl@0 44
 		pqid:	int;
mechiel@106 45
 		prev:	(int, ref Sys->Dir);	# last returned Dir to Readdir
mjl@0 46
 	}
mjl@0 47
 
mechiel@48 48
 	mkdir:	fn(qid: int, de: ref Direntry, size: big, vd: ref Vacdir, pqid: int): ref Elem.Dir;
mjl@0 49
 	new:	fn(nqid: int, vd: ref Vacdir, de: ref Direntry, pqid: int): ref Elem;
mjl@0 50
 	stat:	fn(e: self ref Elem): ref Sys->Dir;
mjl@0 51
 };
mjl@0 52
 
mechiel@108 53
 # maps vacfs dir qid to (vac qid, vacfs qid) of files in that dir
mechiel@107 54
 Qidmap: adt {
mjl@0 55
 	qid:	int;
mjl@0 56
 	cqids:	list of (big, int);
mjl@0 57
 };
mjl@0 58
 
mechiel@110 59
 # path that has been walked to
mechiel@110 60
 Path: adt {
mechiel@110 61
 	path, nused:	int;
mechiel@110 62
 	elems:	list of ref Elem;
mechiel@110 63
 };
mechiel@110 64
 
mechiel@108 65
 Qfakeroot:	con 0;
mechiel@108 66
 
mjl@0 67
 lastqid := 0;
mechiel@110 68
 qidmaps := array[512] of list of ref Qidmap;
mechiel@110 69
 scoreelems: list of (string, ref Elem.Dir);
mechiel@110 70
 rootelem: ref Elem;
mjl@0 71
 
mechiel@110 72
 curelems: list of ref Elem;
mjl@0 73
 
mjl@0 74
 init(nil: ref Draw->Context, args: list of string)
mjl@0 75
 {
mjl@0 76
 	sys = load Sys Sys->PATH;
mjl@0 77
 	arg := load Arg Arg->PATH;
mjl@0 78
 	str = load String String->PATH;
mechiel@33 79
 	daytime = load Daytime Daytime->PATH;
mjl@0 80
 	venti = load Venti Venti->PATH;
mjl@0 81
 	styx = load Styx Styx->PATH;
mjl@0 82
 	styxservers = load Styxservers Styxservers->PATH;
mjl@0 83
 	vac = load Vac Vac->PATH;
mechiel@103 84
 
mjl@0 85
 	venti->init();
mechiel@6 86
 	vac->init();
mjl@0 87
 	styx->init();
mjl@0 88
 	styxservers->init(styx);
mjl@0 89
 
mjl@0 90
 	arg->init(args);
mechiel@77 91
 	arg->setusage(arg->progname()+" [-Ddp] [-a addr] [vacfile]");
mjl@0 92
 	while((ch := arg->opt()) != 0)
mjl@0 93
 		case ch {
mjl@0 94
 		'D' =>	styxservers->traceset(1);
mjl@0 95
 		'a' =>	addr = arg->earg();
mjl@0 96
 		'd' =>	dflag++;
mjl@0 97
 		'p' =>	pflag++;
mechiel@103 98
 		* =>	arg->usage();
mjl@0 99
 		}
mjl@0 100
 	args = arg->argv();
mjl@0 101
 	if(len args > 1)
mjl@0 102
 		arg->usage();
mjl@0 103
 
mjl@0 104
 	score: ref Score;
mjl@0 105
 	if(len args == 1) {
mechiel@77 106
 		err: string;
mechiel@77 107
 		(nil, score, err) = vac->readscore(hd args);
mechiel@77 108
 		if(err != nil)
mechiel@77 109
 			error("reading score: "+err);
mjl@0 110
 	}
mjl@0 111
 
mjl@0 112
 	(cok, conn) := sys->dial(addr, nil);
mjl@0 113
 	if(cok < 0)
mjl@0 114
 		error(sprint("dialing %s: %r", addr));
mjl@0 115
 	say("have connection");
mjl@0 116
 
mechiel@103 117
 	session = Session.new(conn.dfd);
mjl@0 118
 	if(session == nil)
mjl@0 119
 		error(sprint("handshake: %r"));
mjl@0 120
 	say("have handshake");
mjl@0 121
 
mjl@0 122
 	if(args == nil) {
mjl@0 123
 		de := Direntry.new();
mechiel@103 124
 		de.uid = de.gid = de.mid = "vacfs";
mechiel@33 125
 		de.ctime = de.atime = de.mtime = daytime->now();
mechiel@103 126
 		de.mode = Vac->Modedir|8r555;
mechiel@103 127
 		de.emode = Sys->DMDIR|8r555;
mechiel@110 128
 		rootelem = Elem.mkdir(Qfakeroot, de, big 0, nil, Qfakeroot);
mjl@0 129
 	} else {
mjl@0 130
 		(vd, de, err) := vac->vdroot(session, *score);
mjl@0 131
 		if(err != nil)
mjl@0 132
 			error(err);
mechiel@110 133
 		qid := ++lastqid;
mechiel@110 134
 		rootelem = Elem.mkdir(qid, de, big 0, vd, qid);
mjl@0 135
 	}
mjl@0 136
 
mjl@0 137
 	navchan := chan of ref Navop;
mjl@0 138
 	nav := Navigator.new(navchan);
mjl@0 139
 	spawn navigator(navchan);
mjl@0 140
 
mechiel@33 141
 	msgc: chan of ref Tmsg;
mechiel@110 142
 	(msgc, srv) = Styxserver.new(sys->fildes(0), nav, big rootelem.qid);
mjl@0 143
 
mechiel@104 144
 serve:
mechiel@104 145
 	while((mm := <-msgc) != nil)
mechiel@104 146
 		pick m := mm {
mjl@0 147
 		Readerror =>
mechiel@104 148
 			fprint(fildes(2), "styx read: %s\n", m.error);
mechiel@104 149
 			break serve;
mjl@0 150
 
mechiel@110 151
 		Attach =>
mechiel@110 152
 			f := srv.attach(m);
mechiel@110 153
 			if(f != nil) {
mechiel@110 154
 				p := getpath(int f.path);
mechiel@110 155
 				if(p == nil)
mechiel@110 156
 					putpath(p = ref Path(int f.path, 0, rootelem::nil));
mechiel@110 157
 				p.nused++;
mechiel@110 158
 			}
mechiel@110 159
 
mjl@0 160
 		Read =>
mechiel@107 161
 			if(dflag) say(sprint("have read, offset=%ubd count=%d", m.offset, m.count));
mechiel@110 162
 			(f, err) := srv.canread(m);
mechiel@110 163
 			if(f == nil){
mechiel@104 164
 				srv.reply(ref Rmsg.Error(m.tag, err));
mechiel@104 165
 				continue;
mjl@0 166
 			}
mechiel@110 167
 			if(f.qtype & Sys->QTDIR){
mechiel@104 168
 				srv.default(m);
mechiel@104 169
 				continue;
mjl@0 170
 			}
mjl@0 171
 
mechiel@110 172
 			p := getpath(int f.path);
mechiel@110 173
 			file: ref Elem.File;
mechiel@110 174
 			pick e := hd p.elems {
mechiel@110 175
 			File =>	file = e;
mechiel@110 176
 			Dir =>	srv.reply(ref Rmsg.Error(m.tag, "internal error"));
mechiel@110 177
 				continue;
mechiel@110 178
 			}
mjl@0 179
 			n := m.count;
mjl@0 180
 			a := array[n] of byte;
mechiel@110 181
 			have := file.vf.pread(a, n, m.offset);
mjl@0 182
 			if(have < 0) {
mechiel@104 183
 				srv.reply(ref Rmsg.Error(m.tag, sprint("%r")));
mechiel@104 184
 				continue;
mjl@0 185
 			}
mechiel@104 186
 			srv.reply(ref Rmsg.Read(m.tag, a[:have]));
mjl@0 187
 
mechiel@110 188
 		Walk =>
mechiel@110 189
 			f := srv.getfid(m.fid);
mechiel@110 190
 			if(f == nil) {
mechiel@110 191
 				srv.reply(ref Rmsg.Error(m.tag, styxservers->Ebadfid));
mechiel@110 192
 				continue;
mechiel@110 193
 			}
mechiel@110 194
 			p := getpath(int f.path);
mechiel@110 195
 			curelems = p.elems;
mechiel@110 196
 			nf := srv.walk(m);
mechiel@110 197
 			if(nf != nil) {
mechiel@110 198
 				if(nf.fid == f.fid) {
mechiel@110 199
 					if(--p.nused <= 0)
mechiel@110 200
 						delpath(p);
mechiel@110 201
 				}
mechiel@110 202
 				putpath(p = ref Path(int nf.path, 0, curelems));
mechiel@110 203
 				p.nused++;
mechiel@110 204
 			}
mechiel@110 205
 			curelems = nil;
mechiel@110 206
 
mechiel@33 207
 		Open =>
mechiel@110 208
 			(f, mode, d, err) := canopen(m);
mechiel@110 209
 			if(f == nil){
mechiel@104 210
 				srv.reply(ref Rmsg.Error(m.tag, err));
mechiel@104 211
 				continue;
mechiel@33 212
 			}
mechiel@110 213
 			f.open(mode, d.qid);
mechiel@110 214
 			srv.reply(ref Rmsg.Open(m.tag, d.qid, srv.iounit()));
mechiel@33 215
 
mechiel@110 216
 		Clunk or Remove =>
mechiel@104 217
 			f := srv.getfid(m.fid);
mechiel@110 218
 			if(f != nil) {
mechiel@110 219
 				p := getpath(int f.path);
mechiel@110 220
 				if(--p.nused <= 0)
mechiel@110 221
 					delpath(p);
mechiel@101 222
 			}
mechiel@110 223
 			if(tagof m == tagof Tmsg.Remove)
mechiel@110 224
 				srv.reply(ref Rmsg.Error(m.tag, styxservers->Eperm));
mechiel@110 225
 			else
mechiel@110 226
 				srv.default(m);
mechiel@33 227
 
mjl@0 228
 		* =>
mechiel@104 229
 			srv.default(m);
mjl@0 230
 		}
mechiel@104 231
 	navchan <-= nil;
mjl@0 232
 }
mjl@0 233
 
mechiel@104 234
 # from appl/lib/styxservers.b
mechiel@33 235
 canopen(m: ref Tmsg.Open): (ref Fid, int, ref Sys->Dir, string)
mechiel@33 236
 {
mechiel@104 237
 	c := srv.getfid(m.fid);
mechiel@33 238
 	if(c == nil)
mechiel@33 239
 		return (nil, 0, nil, Styxservers->Ebadfid);
mechiel@33 240
 	if(c.isopen)
mechiel@33 241
 		return (nil, 0, nil, Styxservers->Eopen);
mechiel@104 242
 	(f, err) := srv.t.stat(c.path);
mechiel@33 243
 	if(f == nil)
mechiel@33 244
 		return (nil, 0, nil, err);
mechiel@33 245
 	mode := styxservers->openmode(m.mode);
mechiel@33 246
 	if(mode == -1)
mechiel@33 247
 		return (nil, 0, nil, Styxservers->Ebadarg);
mechiel@33 248
 	if(mode != Sys->OREAD && f.qid.qtype & Sys->QTDIR)
mechiel@33 249
 		return (nil, 0, nil, Styxservers->Eperm);
mechiel@33 250
 	if(!pflag && !styxservers->openok(c.uname, m.mode, f.mode, f.uid, f.gid))
mechiel@33 251
 		return (nil, 0, nil, Styxservers->Eperm);
mechiel@33 252
 	if(m.mode & Sys->ORCLOSE)
mechiel@33 253
 		return (nil, 0, nil, Styxservers->Eperm);
mechiel@33 254
 	return (c, mode, f, err);
mechiel@33 255
 }
mechiel@33 256
 
mjl@0 257
 navigator(c: chan of ref Navop)
mjl@0 258
 {
mechiel@104 259
 	while((navop := <-c) != nil)
mjl@0 260
 		pick n := navop {
mjl@0 261
 		Stat =>
mechiel@110 262
 			e := rootelem;
mechiel@110 263
 			if(int n.path != rootelem.qid) {
mechiel@110 264
 				p := getpath(int n.path);
mechiel@110 265
 				if(p != nil) {
mechiel@110 266
 					e = hd p.elems;
mechiel@110 267
 				} else if(curelems != nil && (hd curelems).qid == int n.path) {
mechiel@110 268
 					e = hd curelems;
mechiel@110 269
 				} else {
mechiel@110 270
 					n.reply <-= (nil, "internal error");
mechiel@110 271
 					continue;
mechiel@110 272
 				}
mechiel@110 273
 			}
mechiel@110 274
 			n.reply <-= (e.stat(), nil);
mjl@0 275
 
mjl@0 276
 		Walk =>
mechiel@110 277
 			(e, err) := walk(int n.path, n.name);
mjl@0 278
 			if(err != nil) {
mjl@0 279
 				n.reply <-= (nil, err);
mechiel@104 280
 				continue;
mjl@0 281
 			}
mechiel@110 282
 			n.reply <-= (e.stat(), nil);
mjl@0 283
 
mjl@0 284
 		Readdir =>
mechiel@103 285
 			if(dflag) say(sprint("have readdir path=%bd offset=%d count=%d", n.path, n.offset, n.count));
mechiel@108 286
 			if(n.path == big Qfakeroot) {
mjl@0 287
 				n.reply <-= (nil, nil);
mechiel@104 288
 				continue;
mjl@0 289
 			}
mechiel@110 290
 
mechiel@110 291
 			p := getpath(int n.path);
mechiel@110 292
 			e: ref Elem.Dir;
mechiel@110 293
 			pick ee := hd p.elems {
mechiel@110 294
 			Dir =>	e = ee;
mechiel@110 295
 			File =>	n.reply <-= (nil, "internal error");
mechiel@110 296
 				continue;
mechiel@110 297
 			}
mechiel@4 298
 			if(n.offset == 0) {
mechiel@110 299
 				e.vd.rewind();
mechiel@110 300
 				e.prev = (-1, nil);
mechiel@32 301
 			}
mechiel@48 302
 
mechiel@106 303
 			# prev is needed because styxservers can request the previously returned Dir
mechiel@110 304
 			(loffset, d) := e.prev;
mechiel@108 305
 			if(n.offset == loffset+1) {
mechiel@110 306
 				(ok, de) := e.vd.readdir();
mechiel@32 307
 				if(ok < 0) {
mechiel@32 308
 					say(sprint("readdir error: %r"));
mechiel@32 309
 					n.reply <-= (nil, sprint("reading directory: %r"));
mechiel@104 310
 					continue;
mjl@0 311
 				}
mechiel@48 312
 				if(de != nil) {
mechiel@110 313
 					cqid := qidget(e.qid, de.qid);
mechiel@101 314
 					if(cqid < 0)
mechiel@110 315
 						cqid = qidput(e.qid, de.qid);
mechiel@110 316
 					ne := Elem.new(cqid, e.vd, de, e.qid);
mechiel@110 317
 					e.prev = (n.offset, ne.stat());
mechiel@48 318
 				} else {
mechiel@110 319
 					e.prev = (n.offset, nil);
mechiel@32 320
 				}
mechiel@108 321
 			} else if(n.offset != loffset)
mechiel@108 322
 				error("internal error");
mechiel@110 323
 			(nil, d) = e.prev;
mechiel@48 324
 			if(d != nil)
mechiel@32 325
 				n.reply <-= (d, nil);
mjl@0 326
 			n.reply <-= (nil, nil);
mjl@0 327
 		}
mjl@0 328
 }
mjl@0 329
 
mechiel@110 330
 walk(path: int, name: string): (ref Elem, string)
mechiel@110 331
 {
mechiel@110 332
 	if(name == "..") {
mechiel@110 333
 		if(len curelems > 1)
mechiel@110 334
 			curelems = tl curelems;
mechiel@110 335
 		return (hd curelems, nil);
mechiel@110 336
 	}
mechiel@110 337
 
mechiel@110 338
 	if(path == Qfakeroot) {
mechiel@110 339
 		e := scoreget(name);
mechiel@110 340
 		if(e == nil) {
mechiel@110 341
 			(ok, score) := Score.parse(name);
mechiel@110 342
 			if(ok != 0)
mechiel@110 343
 				return (nil, "bad score");
mechiel@110 344
 
mechiel@110 345
 			(vd, de, err) := vac->vdroot(session, score);
mechiel@110 346
 			if(err != nil)
mechiel@110 347
 				return (nil, err);
mechiel@110 348
 
mechiel@110 349
 			e = Elem.mkdir(++lastqid, de, big 0, vd, rootelem.qid);
mechiel@110 350
 			scoreput(name, e);
mechiel@110 351
 		}
mechiel@110 352
 		curelems = e::curelems;
mechiel@110 353
 		return (hd curelems, nil);
mechiel@110 354
 	}
mechiel@110 355
 
mechiel@110 356
 	pick e := hd curelems {
mechiel@110 357
 	File =>
mechiel@110 358
 		return (nil, styxservers->Enotdir);
mechiel@110 359
 	Dir =>
mechiel@110 360
 		de := e.vd.walk(name);
mechiel@110 361
 		if(de == nil)
mechiel@110 362
 			return (nil, sprint("%r"));
mechiel@110 363
 		cqid := qidget(e.qid, de.qid);
mechiel@110 364
 		if(cqid < 0)
mechiel@110 365
 			cqid = qidput(e.qid, de.qid);
mechiel@110 366
 		ne := Elem.new(cqid, e.vd, de, e.qid);
mechiel@110 367
 		curelems = ne::curelems;
mechiel@110 368
 		return (ne, nil);
mechiel@110 369
 	}
mechiel@110 370
 }
mechiel@110 371
 
mechiel@108 372
 qidget(qid: int, vqid: big): int
mechiel@105 373
 {
mechiel@107 374
 	for(l := qidmaps[qid % len qidmaps]; l != nil; l = tl l) {
mechiel@105 375
 		if((hd l).qid != qid)
mechiel@105 376
 			continue;
mechiel@105 377
 		for(m := (hd l).cqids; m != nil; m = tl m) {
mechiel@105 378
 			(vq, cq) := hd m;
mechiel@105 379
 			if(vq == vqid)
mechiel@105 380
 				return cq;
mechiel@105 381
 		}
mechiel@105 382
 	}
mechiel@105 383
 	return -1;
mechiel@105 384
 }
mechiel@105 385
 
mechiel@108 386
 qidput(qid: int, vqid: big): int
mechiel@105 387
 {
mechiel@107 388
 	qd: ref Qidmap;
mechiel@107 389
 	for(l := qidmaps[qid % len qidmaps]; l != nil; l = tl l)
mechiel@105 390
 		if((hd l).qid == qid) {
mechiel@105 391
 			qd = hd l;
mechiel@105 392
 			break;
mechiel@105 393
 		}
mechiel@105 394
 	if(qd == nil) {
mechiel@107 395
 		qd = ref Qidmap(qid, nil);
mechiel@107 396
 		qidmaps[qid % len qidmaps] = qd::nil;
mechiel@105 397
 	}
mechiel@105 398
 	qd.cqids = (vqid, ++lastqid)::qd.cqids;
mechiel@105 399
 	return lastqid;
mechiel@105 400
 }
mechiel@105 401
 
mechiel@110 402
 scoreget(score: string): ref Elem.Dir
mechiel@105 403
 {
mechiel@110 404
 	for(l := scoreelems; l != nil; l = tl l) {
mechiel@110 405
 		(s, e) := hd l;
mechiel@105 406
 		if(s == score)
mechiel@110 407
 			return e;
mechiel@105 408
 	}
mechiel@105 409
 	return nil;
mechiel@105 410
 }
mechiel@105 411
 
mechiel@110 412
 scoreput(score: string, e: ref Elem.Dir)
mechiel@105 413
 {
mechiel@110 414
 	scoreelems = (score, e)::scoreelems;
mechiel@105 415
 }
mechiel@105 416
 
mechiel@105 417
 Elem.mkdir(qid: int, de: ref Direntry, size: big, vd: ref Vacdir, pqid: int): ref Elem.Dir
mechiel@105 418
 {
mechiel@110 419
 	return ref Elem.Dir(qid, de, size, vd, pqid, (-1, nil));
mechiel@105 420
 }
mechiel@105 421
 
mechiel@105 422
 Elem.new(nqid: int, vd: ref Vacdir, de: ref Direntry, pqid: int): ref Elem
mechiel@105 423
 {
mechiel@105 424
 	(e, me) := vd.open(de);
mechiel@105 425
 	if(e == nil)
mechiel@105 426
 		return nil;
mechiel@105 427
 	if(de.mode & Vac->Modedir)
mechiel@105 428
 		return Elem.mkdir(nqid, de, e.size, Vacdir.new(session, e, me), pqid);
mechiel@110 429
 	return ref Elem.File(nqid, de, e.size, Vacfile.new(session, e));
mechiel@105 430
 }
mechiel@105 431
 
mechiel@105 432
 Elem.stat(e: self ref Elem): ref Sys->Dir
mechiel@105 433
 {
mechiel@105 434
 	d := e.de.mkdir();
mechiel@105 435
 	d.qid.path = big e.qid;
mechiel@105 436
 	d.length = e.size;
mechiel@105 437
 	return d;
mechiel@105 438
 }
mechiel@105 439
 
mechiel@105 440
 
mechiel@110 441
 qidpaths := array[512] of list of ref Path;
mechiel@110 442
 nqidpaths := 0;
mechiel@105 443
 
mechiel@110 444
 delpath(p: ref Path)
mechiel@110 445
 {
mechiel@110 446
 	i := p.path % len qidpaths;
mechiel@110 447
 	r: list of ref Path;
mechiel@110 448
 	for(l := qidpaths[i]; l != nil; l = tl l)
mechiel@110 449
 		if(hd l != p)
mechiel@110 450
 			r = hd l::r;
mechiel@110 451
 		else
mechiel@110 452
 			nqidpaths--;
mechiel@110 453
 	qidpaths[i] = r;
mechiel@105 454
 }
mechiel@105 455
 
mechiel@110 456
 putpath(p: ref Path)
mechiel@105 457
 {
mechiel@110 458
 	i := p.path % len qidpaths;
mechiel@110 459
 	qidpaths[i] = p::qidpaths[i];
mechiel@110 460
 	nqidpaths++;
mechiel@105 461
 }
mechiel@105 462
 
mechiel@110 463
 getpath(path: int): ref Path
mechiel@105 464
 {
mechiel@110 465
 	i := path % len qidpaths;
mechiel@110 466
 	for(l := qidpaths[i]; l != nil; l = tl l)
mechiel@110 467
 		if((hd l).path == path)
mechiel@108 468
 			return hd l;
mechiel@105 469
 	return nil;
mechiel@105 470
 }
mechiel@105 471
 
mjl@0 472
 error(s: string)
mjl@0 473
 {
mjl@0 474
 	fprint(fildes(2), "%s\n", s);
mjl@0 475
 	raise "fail:"+s;
mjl@0 476
 }
mjl@0 477
 
mjl@0 478
 say(s: string)
mjl@0 479
 {
mjl@0 480
 	if(dflag)
mechiel@103 481
 		fprint(fildes(2), "%s\n", s);
mjl@0 482
 }