Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/feature_unix/sys/src/cmd/wikifs/fs.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include <u.h>
2
#include <libc.h>
3
#include <bio.h>
4
#include <String.h>
5
#include <thread.h>
6
#include "wiki.h"
7
 
8
#include <auth.h>
9
#include <fcall.h>
10
#include <9p.h>
11
 
12
enum {
13
	Qindexhtml,
14
	Qindextxt,
15
	Qraw,
16
	Qhistoryhtml,
17
	Qhistorytxt,
18
	Qdiffhtml,
19
	Qedithtml,
20
	Qwerrorhtml,
21
	Qwerrortxt,
22
	Qhttplogin,
23
	Nfile,
24
};
25
 
26
static char *filelist[] = {
27
	"index.html",
28
	"index.txt",
29
	"current",
30
	"history.html",
31
	"history.txt",
32
	"diff.html",
33
	"edit.html",
34
	"werror.html",
35
	"werror.txt",
36
	".httplogin",
37
};
38
 
39
static int needhist[Nfile] = {
40
[Qhistoryhtml] 1,
41
[Qhistorytxt] 1,
42
[Qdiffhtml] 1,
43
};
44
 
45
/*
46
 * The qids are <8-bit type><16-bit page number><16-bit page version><8-bit file index>.
47
 */
48
enum {		/* <8-bit type> */
49
	Droot = 1,
50
	D1st,
51
	D2nd,
52
	Fnew,
53
	Fmap,
54
	F1st,
55
	F2nd,
56
};
57
 
58
uvlong
59
mkqid(int type, int num, int vers, int file)
60
{
61
	return ((uvlong)type<<40) | ((uvlong)num<<24) | (vers<<8) | file;
62
}
63
 
64
int
65
qidtype(uvlong path)
66
{
67
	return (path>>40)&0xFF;
68
}
69
 
70
int
71
qidnum(uvlong path)
72
{
73
	return (path>>24)&0xFFFF;
74
}
75
 
76
int
77
qidvers(uvlong path)
78
{
79
	return (path>>8)&0xFFFF;
80
}
81
 
82
int
83
qidfile(uvlong path)
84
{
85
	return path&0xFF;
86
}
87
 
88
typedef struct Aux Aux;
89
struct Aux {
90
	String *name;
91
	Whist *w;
92
	int n;
93
	ulong t;
94
	String *s;
95
	Map *map;
96
};
97
 
98
static void
99
fsattach(Req *r)
100
{
101
	Aux *a;
102
 
103
	if(r->ifcall.aname && r->ifcall.aname[0]){
104
		respond(r, "invalid attach specifier");
105
		return;
106
	}
107
 
108
	a = emalloc(sizeof(Aux));
109
	r->fid->aux = a;
110
	a->name = s_copy(r->ifcall.uname);
111
 
112
	r->ofcall.qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR};
113
	r->fid->qid = r->ofcall.qid;
114
	respond(r, nil);
115
}
116
 
117
static String *
118
httplogin(void)
119
{
120
	String *s=s_new();
121
	Biobuf *b;
122
 
123
	if((b = wBopen(".httplogin", OREAD)) == nil)
124
		goto Return;
125
 
126
	while(s_read(b, s, Bsize) > 0)
127
		;
128
	Bterm(b);
129
 
130
Return:
131
	return s;
132
}
133
 
134
static char*
135
fswalk1(Fid *fid, char *name, Qid *qid)
136
{
137
	char *q;
138
	int i, isdotdot, n, t;
139
	uvlong path;
140
	Aux *a;
141
	Whist *wh;
142
	String *s;
143
 
144
	isdotdot = strcmp(name, "..")==0;
145
	n = strtoul(name, &q, 10);
146
	path = fid->qid.path;
147
	a = fid->aux;
148
 
149
	switch(qidtype(path)){
150
	case 0:
151
		return "wikifs: bad path in server (bug)";
152
 
153
	case Droot:
154
		if(isdotdot){
155
			*qid = fid->qid;
156
			return nil;
157
		}
158
		if(strcmp(name, "new")==0){
159
			*qid = (Qid){mkqid(Fnew, 0, 0, 0), 0, 0};
160
			return nil;
161
		}
162
		if(strcmp(name, "map")==0){
163
			*qid = (Qid){mkqid(Fmap, 0, 0, 0), 0, 0};
164
			return nil;
165
		}
166
		if((*q!='\0' || (wh=getcurrent(n))==nil)
167
		&& (wh=getcurrentbyname(name))==nil)
168
			return "file does not exist";
169
		*qid = (Qid){mkqid(D1st, wh->n, 0, 0), wh->doc->time, QTDIR};
170
		a->w = wh;
171
		return nil;
172
 
173
	case D1st:
174
		if(isdotdot){
175
			*qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR};
176
			return nil;
177
		}
178
 
179
		/* handle history directories */
180
		if(*q == '\0'){
181
			if((wh = gethistory(qidnum(path))) == nil)
182
				return "file does not exist";
183
			for(i=0; i<wh->ndoc; i++)
184
				if(wh->doc[i].time == n)
185
					break;
186
			if(i==wh->ndoc){
187
				closewhist(wh);
188
				return "file does not exist";
189
			}
190
			closewhist(a->w);
191
			a->w = wh;
192
			a->n = i;
193
			*qid = (Qid){mkqid(D2nd, qidnum(path), i, 0), wh->doc[i].time, QTDIR};
194
			return nil;
195
		}
196
 
197
		/* handle files other than index */
198
		for(i=0; i<nelem(filelist); i++){
199
			if(strcmp(name, filelist[i])==0){
200
				if(needhist[i]){
201
					if((wh = gethistory(qidnum(path))) == nil)
202
						return "file does not exist";
203
					closewhist(a->w);
204
					a->w = wh;
205
				}
206
				*qid = (Qid){mkqid(F1st, qidnum(path), 0, i), a->w->doc->time, 0};
207
				goto Gotfile;
208
			}
209
		}
210
		return "file does not exist";
211
 
212
	case D2nd:
213
		if(isdotdot){
214
			/*
215
			 * Can't use a->w[a->ndoc-1] because that
216
			 * might be a failed write rather than the real one.
217
			 */
218
			*qid = (Qid){mkqid(D1st, qidnum(path), 0, 0), 0, QTDIR};
219
			if((wh = getcurrent(qidnum(path))) == nil)
220
				return "file does not exist";
221
			closewhist(a->w);
222
			a->w = wh;
223
			a->n = 0;
224
			return nil;
225
		}
226
		for(i=0; i<=Qraw; i++){
227
			if(strcmp(name, filelist[i])==0){
228
				*qid = (Qid){mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc->time, 0};
229
				goto Gotfile;
230
			}
231
		}
232
		return "file does not exist";
233
 
234
	default:
235
		return "bad programming";
236
	}
237
	/* not reached */
238
 
239
Gotfile:
240
	t = qidtype(qid->path);
241
	switch(qidfile(qid->path)){
242
	case Qindexhtml:
243
		s = tohtml(a->w, a->w->doc+a->n,
244
			t==F1st? Tpage : Toldpage);
245
		break;
246
	case Qindextxt:
247
		s = totext(a->w, a->w->doc+a->n,
248
			t==F1st? Tpage : Toldpage);
249
		break;
250
	case Qraw:
251
		s = s_copy(a->w->title);
252
		s = s_append(s, "\n");
253
		s = doctext(s, &a->w->doc[a->n]);
254
		break;
255
	case Qhistoryhtml:
256
		s = tohtml(a->w, a->w->doc+a->n, Thistory);
257
		break;
258
	case Qhistorytxt:
259
		s = totext(a->w, a->w->doc+a->n, Thistory);
260
		break;
261
	case Qdiffhtml:
262
		s = tohtml(a->w, a->w->doc+a->n, Tdiff);
263
		break;
264
	case Qedithtml:
265
		s = tohtml(a->w, a->w->doc+a->n, Tedit);
266
		break;
267
	case Qwerrorhtml:
268
		s = tohtml(a->w, a->w->doc+a->n, Twerror);
269
		break;
270
	case Qwerrortxt:
271
		s = totext(a->w, a->w->doc+a->n, Twerror);
272
		break;
273
	case Qhttplogin:
274
		s = httplogin();
275
		break;
276
	default:
277
		return "internal error";
278
	}
279
	a->s = s;
280
	return nil;
281
}
282
 
283
static void
284
fsopen(Req *r)
285
{
286
	int t;
287
	uvlong path;
288
	Aux *a;
289
	Fid *fid;
290
	Whist *wh;
291
 
292
	fid = r->fid;
293
	path = fid->qid.path;
294
	t = qidtype(fid->qid.path);
295
	if((r->ifcall.mode != OREAD && t != Fnew && t != Fmap)
296
	|| (r->ifcall.mode&ORCLOSE)){
297
		respond(r, "permission denied");
298
		return;
299
	}
300
 
301
	a = fid->aux;
302
	switch(t){
303
	case Droot:
304
		currentmap(0);
305
		rlock(&maplock);
306
		a->map = map;
307
		incref(map);
308
		runlock(&maplock);
309
		respond(r, nil);
310
		break;
311
 
312
	case D1st:
313
		if((wh = gethistory(qidnum(path))) == nil){
314
			respond(r, "file does not exist");
315
			return;
316
		}
317
		closewhist(a->w);
318
		a->w = wh;
319
		a->n = a->w->ndoc-1;
320
		r->ofcall.qid.vers = wh->doc[a->n].time;
321
		r->fid->qid = r->ofcall.qid;
322
		respond(r, nil);
323
		break;
324
 
325
	case D2nd:
326
		respond(r, nil);
327
		break;
328
 
329
	case Fnew:
330
		a->s = s_copy("");
331
		respond(r, nil);
332
		break;
333
 
334
	case Fmap:
335
	case F1st:
336
	case F2nd:
337
		respond(r, nil);
338
		break;
339
 
340
	default:
341
		respond(r, "programmer error");
342
		break;
343
	}
344
}
345
 
346
static char*
347
fsclone(Fid *old, Fid *new)
348
{
349
	Aux *a;
350
 
351
	a = emalloc(sizeof(*a));
352
	*a = *(Aux*)old->aux;
353
	if(a->s)
354
		s_incref(a->s);
355
	if(a->w)
356
		incref(a->w);
357
	if(a->map)
358
		incref(a->map);
359
	if(a->name)
360
		s_incref(a->name);
361
	new->aux = a;
362
	new->qid = old->qid;
363
 
364
	return nil;
365
}
366
 
367
static void
368
fsdestroyfid(Fid *fid)
369
{
370
	Aux *a;
371
 
372
	a = fid->aux;
373
	if(a==nil)
374
		return;
375
 
376
	if(a->name)
377
		s_free(a->name);
378
	if(a->map)
379
		closemap(a->map);
380
	if(a->s)
381
		s_free(a->s);
382
	if(a->w)
383
		closewhist(a->w);
384
	free(a);
385
	fid->aux = nil;
386
}
387
 
388
static void
389
fillstat(Dir *d, uvlong path, ulong tm, ulong length)
390
{
391
	char tmp[32], *p;
392
	int type;
393
 
394
	memset(d, 0, sizeof(Dir));
395
	d->uid = estrdup9p("wiki");
396
	d->gid = estrdup9p("wiki");
397
 
398
	switch(qidtype(path)){
399
	case Droot:
400
	case D1st:
401
	case D2nd:
402
		type = QTDIR;
403
		break;
404
	default:
405
		type = 0;
406
		break;
407
	}
408
	d->qid = (Qid){path, tm, type};
409
 
410
	d->atime = d->mtime = tm;
411
	d->length = length;
412
	if(qidfile(path) == Qedithtml)
413
		d->atime = d->mtime = time(0);
414
 
415
	switch(qidtype(path)){
416
	case Droot:
417
		d->name = estrdup("/");
418
		d->mode = DMDIR|0555;
419
		break;
420
 
421
	case D1st:
422
		d->name = numtoname(qidnum(path));
423
		if(d->name == nil)
424
			d->name = estrdup("<dead>");
425
		for(p=d->name; *p; p++)
426
			if(*p==' ')
427
				*p = '_';
428
		d->mode = DMDIR|0555;
429
		break;
430
 
431
	case D2nd:
432
		snprint(tmp, sizeof tmp, "%lud", tm);
433
		d->name = estrdup(tmp);
434
		d->mode = DMDIR|0555;
435
		break;
436
 
437
	case Fmap:
438
		d->name = estrdup("map");
439
		d->mode = 0666;
440
		break;
441
 
442
	case Fnew:
443
		d->name = estrdup("new");
444
		d->mode = 0666;
445
		break;
446
 
447
	case F1st:
448
		d->name = estrdup(filelist[qidfile(path)]);
449
		d->mode = 0444;
450
		break;
451
 
452
	case F2nd:
453
		d->name = estrdup(filelist[qidfile(path)]);
454
		d->mode = 0444;
455
		break;
456
 
457
	default:
458
		print("bad qid path 0x%.8llux\n", path);
459
		break;
460
	}
461
}
462
 
463
static void
464
fsstat(Req *r)
465
{
466
	Aux *a;
467
	Fid *fid;
468
	ulong t;
469
 
470
	t = 0;
471
	fid = r->fid;
472
	if((a = fid->aux) && a->w)
473
		t = a->w->doc[a->n].time;
474
 
475
	fillstat(&r->d, fid->qid.path, t, a->s ? s_len(a->s) : 0);
476
	respond(r, nil);
477
}
478
 
479
typedef struct Bogus Bogus;
480
struct Bogus {
481
	uvlong path;
482
	Aux *a;
483
};
484
 
485
static int
486
rootgen(int i, Dir *d, void *aux)
487
{
488
	Aux *a;
489
	Bogus *b;
490
 
491
	b = aux;
492
	a = b->a;
493
	switch(i){
494
	case 0:	/* new */
495
		fillstat(d, mkqid(Fnew, 0, 0, 0), a->map->t, 0);
496
		return 0;
497
	case 1:	/* map */
498
		fillstat(d, mkqid(Fmap, 0, 0, 0), a->map->t, 0);
499
		return 0;
500
	default:	/* first-level directory */
501
		i -= 2;
502
		if(i >= a->map->nel)
503
			return -1;
504
		fillstat(d, mkqid(D1st, a->map->el[i].n, 0, 0), a->map->t, 0);
505
		return 0;
506
	}
507
}
508
 
509
static int
510
firstgen(int i, Dir *d, void *aux)
511
{
512
	ulong t;
513
	Bogus *b;
514
	int num;
515
	Aux *a;
516
 
517
	b = aux;
518
	num = qidnum(b->path);
519
	a = b->a;
520
	t = a->w->doc[a->n].time;
521
 
522
	if(i < Nfile){	/* file in first-level directory */
523
		fillstat(d, mkqid(F1st, num, 0, i), t, 0);
524
		return 0;
525
	}
526
	i -= Nfile;
527
 
528
	if(i < a->w->ndoc){	/* second-level (history) directory */
529
		fillstat(d, mkqid(D2nd, num, i, 0), a->w->doc[i].time, 0);
530
		return 0;
531
	}
532
	//i -= a->w->ndoc;
533
 
534
	return -1;
535
}
536
 
537
static int
538
secondgen(int i, Dir *d, void *aux)
539
{
540
	Bogus *b;
541
	uvlong path;
542
	Aux *a;
543
 
544
	b = aux;
545
	path = b->path;
546
	a = b->a;
547
 
548
	if(i <= Qraw){	/* index.html, index.txt, raw */
549
		fillstat(d, mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc[a->n].time, 0);
550
		return 0;
551
	}
552
	//i -= Qraw;
553
 
554
	return -1;
555
}
556
 
557
static void
558
fsread(Req *r)
559
{
560
	char *t, *s;
561
	uvlong path;
562
	Aux *a;
563
	Bogus b;
564
 
565
	a = r->fid->aux;
566
	path = r->fid->qid.path;
567
	b.a = a;
568
	b.path = path;
569
	switch(qidtype(path)){
570
	default:
571
		respond(r, "cannot happen (bad qid)");
572
		return;
573
 
574
	case Droot:
575
		if(a == nil || a->map == nil){
576
			respond(r, "cannot happen (no map)");
577
			return;
578
		}
579
		dirread9p(r, rootgen, &b);
580
		respond(r, nil);
581
		return;
582
 
583
	case D1st:
584
		if(a == nil || a->w == nil){
585
			respond(r, "cannot happen (no wh)");
586
			return;
587
		}
588
		dirread9p(r, firstgen, &b);
589
		respond(r, nil);
590
		return;
591
 
592
	case D2nd:
593
		dirread9p(r, secondgen, &b);
594
		respond(r, nil);
595
		return;
596
 
597
	case Fnew:
598
		if(a->s){
599
			respond(r, "protocol botch");
600
			return;
601
		}
602
		/* fall through */
603
	case Fmap:
604
		t = numtoname(a->n);
605
		if(t == nil){
606
			respond(r, "unknown name");
607
			return;
608
		}
609
		for(s=t; *s; s++)
610
			if(*s == ' ')
611
				*s = '_';
612
		readstr(r, t);
613
		free(t);
614
		respond(r, nil);
615
		return;
616
 
617
	case F1st:
618
	case F2nd:
619
		if(a == nil || a->s == nil){
620
			respond(r, "cannot happen (no s)");
621
			return;
622
		}
623
		readbuf(r, s_to_c(a->s), s_len(a->s));
624
		respond(r, nil);
625
		return;
626
	}
627
}
628
 
629
typedef struct Sread Sread;
630
struct Sread {
631
	char *rp;
632
};
633
 
634
static char*
635
Srdline(void *v, int c)
636
{
637
	char *p, *rv;
638
	Sread *s;
639
 
640
	s = v;
641
	if(s->rp == nil)
642
		rv = nil;
643
	else if(p = strchr(s->rp, c)){
644
		*p = '\0';
645
		rv = s->rp;
646
		s->rp = p+1;
647
	}else{
648
		rv = s->rp;
649
		s->rp = nil;
650
	}
651
	return rv;
652
}
653
 
654
static void
655
responderrstr(Req *r)
656
{
657
	char buf[ERRMAX];
658
 
659
	rerrstr(buf, sizeof buf);
660
	if(buf[0] == '\0')
661
		strcpy(buf, "unknown error");
662
	respond(r, buf);
663
}
664
 
665
static void
666
fswrite(Req *r)
667
{
668
	char *author, *comment, *net, *err, *p, *title, tmp[40];
669
	int rv, n;
670
	ulong t;
671
	Aux *a;
672
	Fid *fid;
673
	Sread s;
674
	String *stmp;
675
	Whist *w;
676
 
677
	fid = r->fid;
678
	a = fid->aux;
679
	switch(qidtype(fid->qid.path)){
680
	case Fmap:
681
		stmp = s_nappend(s_reset(nil), r->ifcall.data, r->ifcall.count);
682
		a->n = nametonum(s_to_c(stmp));
683
		s_free(stmp);
684
		if(a->n < 0)
685
			respond(r, "name not found");
686
		else
687
			respond(r, nil);
688
		return;
689
	case Fnew:
690
		break;
691
	default:
692
		respond(r, "cannot happen");
693
		return;
694
	}
695
 
696
	if(a->s == nil){
697
		respond(r, "protocol botch");
698
		return;
699
	}
700
	if(r->ifcall.count==0){	/* do final processing */
701
		s.rp = s_to_c(a->s);
702
		w = nil;
703
		err = "bad format";
704
		if((title = Srdline(&s, '\n')) == nil){
705
		Error:
706
			if(w)
707
				closewhist(w);
708
			s_free(a->s);
709
			a->s = nil;
710
			respond(r, err);
711
			return;
712
		}
713
 
714
		w = emalloc(sizeof(*w));
715
		incref(w);
716
		w->title = estrdup(title);
717
 
718
		t = 0;
719
		author = estrdup(s_to_c(a->name));
720
 
721
		comment = nil;
722
		while(s.rp && *s.rp && *s.rp != '\n'){
723
			p = Srdline(&s, '\n');
724
			assert(p != nil);
725
			switch(p[0]){
726
			case 'A':
727
				free(author);
728
				author = estrdup(p+1);
729
				break;
730
			case 'D':
731
				t = strtoul(p+1, &p, 10);
732
				if(*p != '\0')
733
					goto Error;
734
				break;
735
			case 'C':
736
				free(comment);
737
				comment = estrdup(p+1);
738
				break;
739
			}
740
		}
741
 
742
		w->doc = emalloc(sizeof(w->doc[0]));
743
		w->doc->time = time(0);
744
		w->doc->comment = comment;
745
 
746
		if(net = r->pool->srv->aux){
747
			p = emalloc(strlen(author)+10+strlen(net));
748
			strcpy(p, author);
749
			strcat(p, " (");
750
			strcat(p, net);
751
			strcat(p, ")");
752
			free(author);
753
			author = p;
754
		}
755
		w->doc->author = author;
756
 
757
		if((w->doc->wtxt = Brdpage(Srdline, &s)) == nil){
758
			err = "empty document";
759
			goto Error;
760
		}
761
 
762
		w->ndoc = 1;
763
		if((n = allocnum(w->title, 0)) < 0)
764
			goto Error;
765
		sprint(tmp, "D%lud\n", w->doc->time);
766
		a->s = s_reset(a->s);
767
		a->s = doctext(a->s, w->doc);
768
		rv = writepage(n, t, a->s, w->title);
769
		s_free(a->s);
770
		a->s = nil;
771
		a->n = n;
772
		closewhist(w);
773
		if(rv < 0)
774
			responderrstr(r);
775
		else
776
			respond(r, nil);
777
		return;
778
	}
779
 
780
	if(s_len(a->s)+r->ifcall.count > Maxfile){
781
		respond(r, "file too large");
782
		s_free(a->s);
783
		a->s = nil;
784
		return;
785
	}
786
	a->s = s_nappend(a->s, r->ifcall.data, r->ifcall.count);
787
	r->ofcall.count = r->ifcall.count;
788
	respond(r, nil);
789
}
790
 
791
Srv wikisrv = {
792
.attach=	fsattach,
793
.destroyfid=	fsdestroyfid,
794
.clone=	fsclone,
795
.walk1=	fswalk1,
796
.open=	fsopen,
797
.read=	fsread,
798
.write=	fswrite,
799
.stat=	fsstat,
800
};
801
 
802
void
803
usage(void)
804
{
805
	fprint(2, "usage: wikifs [-D] [-a addr]... [-m mtpt] [-p perm] [-s service] dir\n");
806
	exits("usage");
807
}
808
 
809
void
810
main(int argc, char **argv)
811
{
812
	char **addr;
813
	int i, naddr;
814
	char *buf;
815
	char *service, *mtpt;
816
	ulong perm;
817
	Dir d, *dp;
818
	Srv *s;
819
 
820
	naddr = 0;
821
	addr = nil;
822
	perm = 0;
823
	service = nil;
824
	mtpt = "/mnt/wiki";
825
	ARGBEGIN{
826
	case 'D':
827
		chatty9p++;
828
		break;
829
	case 'a':
830
		if(naddr%8 == 0)
831
			addr = erealloc(addr, (naddr+8)*sizeof(addr[0]));
832
		addr[naddr++] = EARGF(usage());
833
		break;
834
	case 'm':
835
		mtpt = EARGF(usage());
836
		break;
837
	case 'M':
838
		mtpt = nil;
839
		break;
840
	case 'p':
841
		perm = strtoul(EARGF(usage()), nil, 8);
842
		break;
843
	case 's':
844
		service = EARGF(usage());
845
		break;
846
	default:
847
		usage();
848
		break;
849
	}ARGEND
850
 
851
	if(argc != 1)
852
		usage();
853
 
854
	if((dp = dirstat(argv[0])) == nil)
855
		sysfatal("dirstat %s: %r", argv[0]);
856
	if((dp->mode&DMDIR) == 0)
857
		sysfatal("%s: not a directory", argv[0]);
858
	free(dp);
859
	wikidir = argv[0];
860
 
861
	currentmap(0);
862
 
863
	for(i=0; i<naddr; i++)
864
		listensrv(&wikisrv, addr[i]);
865
 
866
	s = emalloc(sizeof *s);
867
	*s = wikisrv;
868
	postmountsrv(s, service, mtpt, MREPL|MCREATE);
869
	if(perm){
870
		buf = emalloc9p(5+strlen(service)+1);
871
		strcpy(buf, "/srv/");
872
		strcat(buf, service);
873
		nulldir(&d);
874
		d.mode = perm;
875
		if(dirwstat(buf, &d) < 0)
876
			fprint(2, "wstat: %r\n");
877
		free(buf);
878
	}
879
	exits(nil);
880
}