xref: /plan9/sys/src/cmd/fossil/9p.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
1 #include "stdinc.h"
2 
3 #include "9.h"
4 
5 enum {
6 	OMODE		= 0x7,		/* Topen/Tcreate mode */
7 };
8 
9 enum {
10 	PermX		= 1,
11 	PermW		= 2,
12 	PermR		= 4,
13 };
14 
15 static char EPermission[] = "permission denied";
16 
17 static int
18 permFile(File* file, Fid* fid, int perm)
19 {
20 	char *u;
21 	DirEntry de;
22 
23 	if(!fileGetDir(file, &de))
24 		return 0;
25 
26 	/*
27 	 * User none only gets other permissions.
28 	 */
29 	if(strcmp(fid->uname, unamenone) != 0){
30 		/*
31 		 * There is only one uid<->uname mapping
32 		 * and it's already cached in the Fid, but
33 		 * it might have changed during the lifetime
34 		 * if this Fid.
35 		 */
36 		if((u = unameByUid(de.uid)) != nil){
37 			if(strcmp(fid->uname, u) == 0 && ((perm<<6) & de.mode)){
38 				vtMemFree(u);
39 				deCleanup(&de);
40 				return 1;
41 			}
42 			vtMemFree(u);
43 		}
44 		if(groupMember(de.gid, fid->uname) && ((perm<<3) & de.mode)){
45 			deCleanup(&de);
46 			return 1;
47 		}
48 	}
49 	if(perm & de.mode){
50 		if(perm == PermX && (de.mode & ModeDir)){
51 			deCleanup(&de);
52 			return 1;
53 		}
54 		if(!groupMember(uidnoworld, fid->uname)){
55 			deCleanup(&de);
56 			return 1;
57 		}
58 	}
59 	if(fsysNoPermCheck(fid->fsys) || fid->con->noperm){
60 		deCleanup(&de);
61 		return 1;
62 	}
63 	vtSetError(EPermission);
64 
65 	deCleanup(&de);
66 	return 0;
67 }
68 
69 static int
70 permFid(Fid* fid, int p)
71 {
72 	return permFile(fid->file, fid, p);
73 }
74 
75 static int
76 permParent(Fid* fid, int p)
77 {
78 	int r;
79 	File *parent;
80 
81 	parent = fileGetParent(fid->file);
82 	r = permFile(parent, fid, p);
83 	fileDecRef(parent);
84 
85 	return r;
86 }
87 
88 int
89 validFileName(char* name)
90 {
91 	char *p;
92 
93 	if(name == nil || name[0] == '\0'){
94 		vtSetError("no file name");
95 		return 0;
96 	}
97 	if(name[0] == '.'){
98 		if(name[1] == '\0' || (name[1] == '.' && name[2] == '\0')){
99 			vtSetError(". and .. illegal as file name");
100 			return 0;
101 		}
102 	}
103 
104 	for(p = name; *p != '\0'; p++){
105 		if((*p & 0xFF) < 040){
106 			vtSetError("bad character in file name");
107 			return 0;
108 		}
109 	}
110 
111 	return 1;
112 }
113 
114 static int
115 rTwstat(Msg* m)
116 {
117 	Dir dir;
118 	Fid *fid;
119 	ulong mode, oldmode;
120 	DirEntry de;
121 	char *gid, *strs, *uid;
122 	int gl, op, retval, tsync;
123 
124 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
125 		return 0;
126 
127 	gid = uid = nil;
128 	retval = 0;
129 
130 	if(strcmp(fid->uname, unamenone) == 0 || (fid->qid.type & QTAUTH)){
131 		vtSetError(EPermission);
132 		goto error0;
133 	}
134 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
135 		vtSetError("read-only filesystem");
136 		goto error0;
137 	}
138 
139 	if(!fileGetDir(fid->file, &de))
140 		goto error0;
141 
142 	strs = vtMemAlloc(m->t.nstat);
143 	if(convM2D(m->t.stat, m->t.nstat, &dir, strs) == 0){
144 		vtSetError("wstat -- protocol botch");
145 		goto error;
146 	}
147 
148 	/*
149 	 * Run through each of the (sub-)fields in the provided Dir
150 	 * checking for validity and whether it's a default:
151 	 * .type, .dev and .atime are completely ignored and not checked;
152 	 * .qid.path, .qid.vers and .muid are checked for validity but
153 	 * any attempt to change them is an error.
154 	 * .qid.type/.mode, .mtime, .name, .length, .uid and .gid can
155 	 * possibly be changed.
156 	 *
157 	 * 'Op' flags there are changed fields, i.e. it's not a no-op.
158 	 * 'Tsync' flags all fields are defaulted.
159 	 */
160 	tsync = 1;
161 	if(dir.qid.path != ~0){
162 		if(dir.qid.path != de.qid){
163 			vtSetError("wstat -- attempt to change qid.path");
164 			goto error;
165 		}
166 		tsync = 0;
167 	}
168 	if(dir.qid.vers != ~0){
169 		if(dir.qid.vers != de.mcount){
170 			vtSetError("wstat -- attempt to change qid.vers");
171 			goto error;
172 		}
173 		tsync = 0;
174 	}
175 	if(dir.muid != nil && *dir.muid != '\0'){
176 		if((uid = uidByUname(dir.muid)) == nil){
177 			vtSetError("wstat -- unknown muid");
178 			goto error;
179 		}
180 		if(strcmp(uid, de.mid) != 0){
181 			vtSetError("wstat -- attempt to change muid");
182 			goto error;
183 		}
184 		vtMemFree(uid);
185 		uid = nil;
186 		tsync = 0;
187 	}
188 
189 	/*
190 	 * Check .qid.type and .mode agree if neither is defaulted.
191 	 */
192 	if(dir.qid.type != (uchar)~0 && dir.mode != ~0){
193 		if(dir.qid.type != ((dir.mode>>24) & 0xFF)){
194 			vtSetError("wstat -- qid.type/mode mismatch");
195 			goto error;
196 		}
197 	}
198 
199 	op = 0;
200 
201 	oldmode = de.mode;
202 	if(dir.qid.type != (uchar)~0 || dir.mode != ~0){
203 		/*
204 		 * .qid.type or .mode isn't defaulted, check for unknown bits.
205 		 */
206 		if(dir.mode == ~0)
207 			dir.mode = (dir.qid.type<<24)|(de.mode & 0777);
208 		if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|0777)){
209 			vtSetError("wstat -- unknown bits in qid.type/mode");
210 			goto error;
211 		}
212 
213 		/*
214 		 * Synthesise a mode to check against the current settings.
215 		 */
216 		mode = dir.mode & 0777;
217 		if(dir.mode & DMEXCL)
218 			mode |= ModeExclusive;
219 		if(dir.mode & DMAPPEND)
220 			mode |= ModeAppend;
221 		if(dir.mode & DMDIR)
222 			mode |= ModeDir;
223 
224 		if((de.mode^mode) & ModeDir){
225 			vtSetError("wstat -- attempt to change directory bit");
226 			goto error;
227 		}
228 
229 		if((de.mode & (ModeAppend|ModeExclusive|0777)) != mode){
230 			de.mode &= ~(ModeAppend|ModeExclusive|0777);
231 			de.mode |= mode;
232 			op = 1;
233 		}
234 		tsync = 0;
235 	}
236 
237 	if(dir.mtime != ~0){
238 		if(dir.mtime != de.mtime){
239 			de.mtime = dir.mtime;
240 			op = 1;
241 		}
242 		tsync = 0;
243 	}
244 
245 	if(dir.length != ~0){
246 		/*
247 		 * Cannot change length on append-only files.
248 		 * If we're changing the append bit, it's okay.
249 		 */
250 		if(de.mode & oldmode & ModeAppend){
251 			vtSetError("wstat -- attempt to change length of append-only file");
252 			goto error;
253 		}
254 		if(de.mode & ModeDir){
255 			vtSetError("wstat -- attempt to change length of directory");
256 			goto error;
257 		}
258 		if(dir.length != de.size){
259 			de.size = dir.length;
260 			op = 1;
261 		}
262 		tsync = 0;
263 	}
264 
265 	/*
266 	 * Check for permission to change .mode, .mtime or .length,
267 	 * must be owner or leader of either group, for which test gid
268 	 * is needed; permission checks on gid will be done later.
269 	 */
270 	if(dir.gid != nil && *dir.gid != '\0'){
271 		if((gid = uidByUname(dir.gid)) == nil){
272 			vtSetError("wstat -- unknown gid");
273 			goto error;
274 		}
275 		tsync = 0;
276 	}
277 	else
278 		gid = vtStrDup(de.gid);
279 
280 	/*
281 	 * 'Gl' counts whether neither, one or both groups are led.
282 	 */
283 	gl = groupLeader(gid, fid->uname) != 0;
284 	gl += groupLeader(de.gid, fid->uname) != 0;
285 
286 	if(op && !(fsysWstatAllow(fid->fsys) || m->con->wstatallow)){
287 		if(strcmp(fid->uid, de.uid) != 0 && !gl){
288 			vtSetError("wstat -- not owner or group leader");
289 			goto error;
290 		}
291 	}
292 
293 	/*
294 	 * Check for permission to change group, must be
295 	 * either owner and in new group or leader of both groups.
296 	 * If gid is nil here then
297 	 */
298 	if(strcmp(gid, de.gid) != 0){
299 		if(!(fsysWstatAllow(fid->fsys) || m->con->wstatallow)
300 		&& !(strcmp(fid->uid, de.uid) == 0 && groupMember(gid, fid->uname))
301 		&& !(gl == 2)){
302 			vtSetError("wstat -- not owner and not group leaders");
303 			goto error;
304 		}
305 		vtMemFree(de.gid);
306 		de.gid = gid;
307 		gid = nil;
308 		op = 1;
309 		tsync = 0;
310 	}
311 
312 	/*
313 	 * Rename.
314 	 * Check .name is valid and different to the current.
315 	 * If so, check write permission in parent.
316 	 */
317 	if(dir.name != nil && *dir.name != '\0'){
318 		if(!validFileName(dir.name))
319 			goto error;
320 		if(strcmp(dir.name, de.elem) != 0){
321 			if(!permParent(fid, PermW))
322 				goto error;
323 			vtMemFree(de.elem);
324 			de.elem = vtStrDup(dir.name);
325 			op = 1;
326 		}
327 		tsync = 0;
328 	}
329 
330 	/*
331 	 * Check for permission to change owner - must be god.
332 	 */
333 	if(dir.uid != nil && *dir.uid != '\0'){
334 		if((uid = uidByUname(dir.uid)) == nil){
335 			vtSetError("wstat -- unknown uid");
336 			goto error;
337 		}
338 		if(strcmp(uid, de.uid) != 0){
339 			if(!(fsysWstatAllow(fid->fsys) || m->con->wstatallow)){
340 				vtSetError("wstat -- not owner");
341 				goto error;
342 			}
343 			if(strcmp(uid, uidnoworld) == 0){
344 				vtSetError(EPermission);
345 				goto error;
346 			}
347 			vtMemFree(de.uid);
348 			de.uid = uid;
349 			uid = nil;
350 			op = 1;
351 		}
352 		tsync = 0;
353 	}
354 
355 	if(op)
356 		retval = fileSetDir(fid->file, &de, fid->uid);
357 	else
358 		retval = 1;
359 
360 	if(tsync){
361 		/*
362 		 * All values were defaulted,
363 		 * make the state of the file exactly what it
364 		 * claims to be before returning...
365 		 */
366 		USED(tsync);
367 	}
368 
369 error:
370 	deCleanup(&de);
371 	vtMemFree(strs);
372 	if(gid != nil)
373 		vtMemFree(gid);
374 	if(uid != nil)
375 		vtMemFree(uid);
376 error0:
377 	fidPut(fid);
378 	return retval;
379 };
380 
381 static int
382 rTstat(Msg* m)
383 {
384 	Dir dir;
385 	Fid *fid;
386 	DirEntry de;
387 
388 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
389 		return 0;
390 	if(fid->qid.type & QTAUTH){
391 		memset(&dir, 0, sizeof(Dir));
392 		dir.qid = fid->qid;
393 		dir.mode = DMAUTH;
394 		dir.atime = time(0L);
395 		dir.mtime = dir.atime;
396 		dir.length = 0;
397 		dir.name = "#¿";
398 		dir.uid = fid->uname;
399 		dir.gid = fid->uname;
400 		dir.muid = fid->uname;
401 
402 		if((m->r.nstat = convD2M(&dir, m->data, m->con->msize)) == 0){
403 			vtSetError("stat QTAUTH botch");
404 			fidPut(fid);
405 			return 0;
406 		}
407 		m->r.stat = m->data;
408 
409 		fidPut(fid);
410 		return 1;
411 	}
412 	if(!fileGetDir(fid->file, &de)){
413 		fidPut(fid);
414 		return 0;
415 	}
416 	fidPut(fid);
417 
418 	/*
419 	 * TODO: optimise this copy (in convS2M) away somehow.
420 	 * This pettifoggery with m->data will do for the moment.
421 	 */
422 	m->r.nstat = dirDe2M(&de, m->data, m->con->msize);
423 	m->r.stat = m->data;
424 	deCleanup(&de);
425 
426 	return 1;
427 }
428 
429 static int
430 _rTclunk(Fid* fid, int remove)
431 {
432 	int rok;
433 
434 	if(fid->excl)
435 		exclFree(fid);
436 
437 	rok = 1;
438 	if(remove && !(fid->qid.type & QTAUTH)){
439 		if((rok = permParent(fid, PermW)) != 0)
440 			rok = fileRemove(fid->file, fid->uid);
441 	}
442 	fidClunk(fid);
443 
444 	return rok;
445 }
446 
447 static int
448 rTremove(Msg* m)
449 {
450 	Fid *fid;
451 
452 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
453 		return 0;
454 	return _rTclunk(fid, 1);
455 }
456 
457 static int
458 rTclunk(Msg* m)
459 {
460 	Fid *fid;
461 
462 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
463 		return 0;
464 	_rTclunk(fid, (fid->open & FidORclose));
465 
466 	return 1;
467 }
468 
469 static int
470 rTwrite(Msg* m)
471 {
472 	Fid *fid;
473 	int count, n;
474 
475 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
476 		return 0;
477 	if(!(fid->open & FidOWrite)){
478 		vtSetError("fid not open for write");
479 		goto error;
480 	}
481 
482 	count = m->t.count;
483 	if(count < 0 || count > m->con->msize-IOHDRSZ){
484 		vtSetError("write count too big");
485 		goto error;
486 	}
487 	if(m->t.offset < 0){
488 		vtSetError("write offset negative");
489 		goto error;
490 	}
491 	if(fid->excl != nil && !exclUpdate(fid))
492 		goto error;
493 
494 	if(fid->qid.type & QTDIR){
495 		vtSetError("is a directory");
496 		goto error;
497 	}
498 	else if(fid->qid.type & QTAUTH)
499 		n = authWrite(fid, m->t.data, count);
500 	else
501 		n = fileWrite(fid->file, m->t.data, count, m->t.offset, fid->uid);
502 	if(n < 0)
503 		goto error;
504 
505 
506 	m->r.count = n;
507 
508 	fidPut(fid);
509 	return 1;
510 
511 error:
512 	fidPut(fid);
513 	return 0;
514 }
515 
516 static int
517 rTread(Msg* m)
518 {
519 	Fid *fid;
520 	uchar *data;
521 	int count, n;
522 
523 	if((fid = fidGet(m->con, m->t.fid, 0)) == nil)
524 		return 0;
525 	if(!(fid->open & FidORead)){
526 		vtSetError("fid not open for read");
527 		goto error;
528 	}
529 
530 	count = m->t.count;
531 	if(count < 0 || count > m->con->msize-IOHDRSZ){
532 		vtSetError("read count too big");
533 		goto error;
534 	}
535 	if(m->t.offset < 0){
536 		vtSetError("read offset negative");
537 		goto error;
538 	}
539 	if(fid->excl != nil && !exclUpdate(fid))
540 		goto error;
541 
542 	/*
543 	 * TODO: optimise this copy (in convS2M) away somehow.
544 	 * This pettifoggery with m->data will do for the moment.
545 	 */
546 	data = m->data+IOHDRSZ;
547 	if(fid->qid.type & QTDIR)
548 		n = dirRead(fid, data, count, m->t.offset);
549 	else if(fid->qid.type & QTAUTH)
550 		n = authRead(fid, data, count);
551 	else
552 		n = fileRead(fid->file, data, count, m->t.offset);
553 	if(n < 0)
554 		goto error;
555 
556 	m->r.count = n;
557 	m->r.data = (char*)data;
558 
559 	fidPut(fid);
560 	return 1;
561 
562 error:
563 	fidPut(fid);
564 	return 0;
565 }
566 
567 static int
568 rTcreate(Msg* m)
569 {
570 	Fid *fid;
571 	File *file;
572 	ulong mode;
573 	int omode, open, perm;
574 
575 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
576 		return 0;
577 	if(fid->open){
578 		vtSetError("fid open for I/O");
579 		goto error;
580 	}
581 	if(fileIsRoFs(fid->file) || !groupWriteMember(fid->uname)){
582 		vtSetError("read-only filesystem");
583 		goto error;
584 	}
585 	if(!fileIsDir(fid->file)){
586 		vtSetError("not a directory");
587 		goto error;
588 	}
589 	if(!permFid(fid, PermW))
590 		goto error;
591 	if(!validFileName(m->t.name))
592 		goto error;
593 	if(strcmp(fid->uid, uidnoworld) == 0){
594 		vtSetError(EPermission);
595 		goto error;
596 	}
597 
598 	omode = m->t.mode & OMODE;
599 	open = 0;
600 
601 	if(omode == OREAD || omode == ORDWR || omode == OEXEC)
602 		open |= FidORead;
603 	if(omode == OWRITE || omode == ORDWR)
604 		open |= FidOWrite;
605 	if((open & (FidOWrite|FidORead)) == 0){
606 		vtSetError("unknown mode");
607 		goto error;
608 	}
609 	if(m->t.perm & DMDIR){
610 		if((m->t.mode & (ORCLOSE|OTRUNC)) || (open & FidOWrite)){
611 			vtSetError("illegal mode");
612 			goto error;
613 		}
614 		if(m->t.perm & DMAPPEND){
615 			vtSetError("illegal perm");
616 			goto error;
617 		}
618 	}
619 
620 	mode = fileGetMode(fid->file);
621 	perm = m->t.perm;
622 	if(m->t.perm & DMDIR)
623 		perm &= ~0777|(mode & 0777);
624 	else
625 		perm &= ~0666|(mode & 0666);
626 	mode = perm & 0777;
627 	if(m->t.perm & DMDIR)
628 		mode |= ModeDir;
629 	if(m->t.perm & DMAPPEND)
630 		mode |= ModeAppend;
631 	if(m->t.perm & DMEXCL)
632 		mode |= ModeExclusive;
633 
634 	if((file = fileCreate(fid->file, m->t.name, mode, fid->uid)) == nil){
635 		fidPut(fid);
636 		return 0;
637 	}
638 	fileDecRef(fid->file);
639 
640 	fid->qid.vers = fileGetMcount(file);
641 	fid->qid.path = fileGetId(file);
642 	fid->file = file;
643 	mode = fileGetMode(fid->file);
644 	if(mode & ModeDir)
645 		fid->qid.type = QTDIR;
646 	else
647 		fid->qid.type = QTFILE;
648 	if(mode & ModeAppend)
649 		fid->qid.type |= QTAPPEND;
650 	if(mode & ModeExclusive){
651 		fid->qid.type |= QTEXCL;
652 		assert(exclAlloc(fid) != 0);
653 	}
654 	if(m->t.mode & ORCLOSE)
655 		open |= FidORclose;
656 	fid->open = open;
657 
658 	m->r.qid = fid->qid;
659 	m->r.iounit = m->con->msize-IOHDRSZ;
660 
661 	fidPut(fid);
662 	return 1;
663 
664 error:
665 	fidPut(fid);
666 	return 0;
667 }
668 
669 static int
670 rTopen(Msg* m)
671 {
672 	Fid *fid;
673 	int isdir, mode, omode, open, rofs;
674 
675 	if((fid = fidGet(m->con, m->t.fid, FidFWlock)) == nil)
676 		return 0;
677 	if(fid->open){
678 		vtSetError("fid open for I/O");
679 		goto error;
680 	}
681 
682 	isdir = fileIsDir(fid->file);
683 	open = 0;
684 	rofs = fileIsRoFs(fid->file) || !groupWriteMember(fid->uname);
685 
686 	if(m->t.mode & ORCLOSE){
687 		if(isdir){
688 			vtSetError("is a directory");
689 			goto error;
690 		}
691 		if(rofs){
692 			vtSetError("read-only filesystem");
693 			goto error;
694 		}
695 		if(!permParent(fid, PermW))
696 			goto error;
697 
698 		open |= FidORclose;
699 	}
700 
701 	omode = m->t.mode & OMODE;
702 	if(omode == OREAD || omode == ORDWR){
703 		if(!permFid(fid, PermR))
704 			goto error;
705 		open |= FidORead;
706 	}
707 	if(omode == OWRITE || omode == ORDWR || (m->t.mode & OTRUNC)){
708 		if(isdir){
709 			vtSetError("is a directory");
710 			goto error;
711 		}
712 		if(rofs){
713 			vtSetError("read-only filesystem");
714 			goto error;
715 		}
716 		if(!permFid(fid, PermW))
717 			goto error;
718 		open |= FidOWrite;
719 	}
720 	if(omode == OEXEC){
721 		if(isdir){
722 			vtSetError("is a directory");
723 			goto error;
724 		}
725 		if(!permFid(fid, PermX))
726 			goto error;
727 		open |= FidORead;
728 	}
729 	if((open & (FidOWrite|FidORead)) == 0){
730 		vtSetError("unknown mode");
731 		goto error;
732 	}
733 
734 	mode = fileGetMode(fid->file);
735 	if((mode & ModeExclusive) && exclAlloc(fid) == 0)
736 		goto error;
737 
738 	/*
739 	 * Everything checks out, try to commit any changes.
740 	 */
741 	if((m->t.mode & OTRUNC) && !(mode & ModeAppend)){
742 		if(!fileTruncate(fid->file, fid->uid))
743 			goto error;
744 		fid->qid.vers = fileGetMcount(fid->file);
745 	}
746 	if(isdir && fid->db != nil){
747 		dirBufFree(fid->db);
748 		fid->db = nil;
749 	}
750 
751 	m->r.qid = fid->qid;
752 	m->r.iounit = m->con->msize-IOHDRSZ;
753 
754 	fid->open = open;
755 
756 	fidPut(fid);
757 	return 1;
758 
759 error:
760 	if(fid->excl != nil)
761 		exclFree(fid);
762 	fidPut(fid);
763 	return 0;
764 }
765 
766 static int
767 rTwalk(Msg* m)
768 {
769 	Qid qid;
770 	Fcall *r, *t;
771 	int nwname, wlock;
772 	File *file, *nfile;
773 	Fid *fid, *ofid, *nfid;
774 
775 	t = &m->t;
776 	if(t->fid == t->newfid)
777 		wlock = FidFWlock;
778 	else
779 		wlock = 0;
780 
781 	/*
782 	 * The file identified by t->fid must be valid in the
783 	 * current session and must not have been opened for I/O
784 	 * by an open or create message.
785 	 */
786 	if((ofid = fidGet(m->con, t->fid, wlock)) == nil)
787 		return 0;
788 	if(ofid->open){
789 		vtSetError("file open for I/O");
790 		fidPut(ofid);
791 		return 0;
792 	}
793 
794 	/*
795 	 * If newfid is not the same as fid, allocate a new file;
796 	 * a side effect is checking newfid is not already in use (error);
797 	 * if there are no names to walk this will be equivalent to a
798 	 * simple 'clone' operation.
799 	 * It's a no-op if newfid is the same as fid and t->nwname is 0.
800 	 */
801 	nfid = nil;
802 	if(t->fid != t->newfid){
803 		nfid = fidGet(m->con, t->newfid, FidFWlock|FidFCreate);
804 		if(nfid == nil){
805 			vtSetError("fid in use");
806 			fidPut(ofid);
807 			return 0;
808 		}
809 		nfid->open = ofid->open & ~FidORclose;
810 		nfid->file = fileIncRef(ofid->file);
811 		nfid->qid = ofid->qid;
812 		nfid->uid = vtStrDup(ofid->uid);
813 		nfid->uname = vtStrDup(ofid->uname);
814 		nfid->fsys = fsysIncRef(ofid->fsys);
815 		fid = nfid;
816 	}
817 	else
818 		fid = ofid;
819 
820 	r = &m->r;
821 	r->nwqid = 0;
822 
823 	if(t->nwname == 0){
824 		if(nfid != nil)
825 			fidPut(nfid);
826 		fidPut(ofid);
827 
828 		return 1;
829 	}
830 
831 	file = fid->file;
832 	fileIncRef(file);
833 	qid = fid->qid;
834 
835 	for(nwname = 0; nwname < t->nwname; nwname++){
836 		/*
837 		 * Walked elements must represent a directory and
838 		 * the implied user must have permission to search
839 		 * the directory.  Walking .. is always allowed, so that
840 		 * you can't walk into a directory and then not be able
841 		 * to walk out of it.
842 		 */
843 		if(!(qid.type & QTDIR)){
844 			vtSetError("not a directory");
845 			break;
846 		}
847 		if(!permFile(file, fid, PermX) && strcmp(t->wname[nwname], "..") != 0)
848 			break;
849 		if((nfile = fileWalk(file, t->wname[nwname])) == nil)
850 			break;
851 		fileDecRef(file);
852 		file = nfile;
853 		qid.type = QTFILE;
854 		if(fileIsDir(file))
855 			qid.type = QTDIR;
856 		qid.vers = fileGetMcount(file);
857 		qid.path = fileGetId(file);
858 		r->wqid[r->nwqid++] = qid;
859 	}
860 
861 	if(nwname == t->nwname){
862 		/*
863 		 * Walked all elements. Update the target fid
864 		 * from the temporary qid used during the walk,
865 		 * and tidy up.
866 		 */
867 		fid->qid = r->wqid[r->nwqid-1];
868 		fileDecRef(fid->file);
869 		fid->file = file;
870 
871 		if(nfid != nil)
872 			fidPut(nfid);
873 
874 		fidPut(ofid);
875 		return 1;
876 	}
877 
878 	/*
879 	 * Didn't walk all elements, 'clunk' nfid if it exists
880 	 * and leave fid untouched.
881 	 * It's not an error if some of the elements were walked OK.
882 	 */
883 	fileDecRef(file);
884 	if(nfid != nil)
885 		fidClunk(nfid);
886 
887 	fidPut(ofid);
888 	if(nwname == 0)
889 		return 0;
890 	return 1;
891 }
892 
893 static int
894 rTflush(Msg* m)
895 {
896 	if(m->t.oldtag != NOTAG)
897 		msgFlush(m);
898 	return 1;
899 }
900 
901 static void
902 parseAname(char *aname, char **fsname, char **path)
903 {
904 	char *s;
905 
906 	if(aname && aname[0])
907 		s = vtStrDup(aname);
908 	else
909 		s = vtStrDup("main/active");
910 	*fsname = s;
911 	if((*path = strchr(s, '/')) != nil)
912 		*(*path)++ = '\0';
913 	else
914 		*path = "";
915 }
916 
917 static int
918 rTattach(Msg* m)
919 {
920 	Fid *fid;
921 	Fsys *fsys;
922 	char *fsname, *path;
923 
924 	if((fid = fidGet(m->con, m->t.fid, FidFWlock|FidFCreate)) == nil)
925 		return 0;
926 
927 	parseAname(m->t.aname, &fsname, &path);
928 	if((fsys = fsysGet(fsname)) == nil){
929 		fidClunk(fid);
930 		vtMemFree(fsname);
931 		return 0;
932 	}
933 	fid->fsys = fsys;
934 
935 	if(m->t.uname[0] != '\0')
936 		fid->uname = vtStrDup(m->t.uname);
937 	else
938 		fid->uname = vtStrDup(unamenone);
939 
940 	if(fsysNoAuthCheck(fsys) || m->con->noauth){
941 		if((fid->uid = uidByUname(fid->uname)) == nil)
942 			fid->uid = vtStrDup(unamenone);
943 	}
944 	else if(!authCheck(&m->t, fid, fsys)){
945 		fidClunk(fid);
946 		vtMemFree(fsname);
947 		vtSetError("authentication failed");
948 		return 0;
949 	}
950 
951 	fsysFsRlock(fsys);
952 	if((fid->file = fsysGetRoot(fsys, path)) == nil){
953 		fsysFsRUnlock(fsys);
954 		fidClunk(fid);
955 		vtMemFree(fsname);
956 		return 0;
957 	}
958 	fsysFsRUnlock(fsys);
959 	vtMemFree(fsname);
960 
961 	fid->qid = (Qid){fileGetId(fid->file), 0, QTDIR};
962 	m->r.qid = fid->qid;
963 
964 	fidPut(fid);
965 	return 1;
966 }
967 
968 static int
969 rTauth(Msg* m)
970 {
971 	int afd;
972 	Con *con;
973 	Fid *afid;
974 	Fsys *fsys;
975 	char *fsname, *path;
976 
977 	parseAname(m->t.aname, &fsname, &path);
978 	if((fsys = fsysGet(fsname)) == nil){
979 		vtMemFree(fsname);
980 		return 0;
981 	}
982 	vtMemFree(fsname);
983 
984 	if(fsysNoAuthCheck(fsys) || m->con->noauth){
985 		m->con->aok = 1;
986 		vtSetError("authentication disabled");
987 		fsysPut(fsys);
988 		return 0;
989 	}
990 	if(strcmp(m->t.uname, unamenone) == 0){
991 		vtSetError("user 'none' requires no authentication");
992 		fsysPut(fsys);
993 		return 0;
994 	}
995 
996 	con = m->con;
997 	if((afid = fidGet(con, m->t.afid, FidFWlock|FidFCreate)) == nil){
998 		fsysPut(fsys);
999 		return 0;
1000 	}
1001 	afid->fsys = fsys;
1002 
1003 	if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0){
1004 		vtSetError("can't open \"/mnt/factotum/rpc\"");
1005 		fidClunk(afid);
1006 		return 0;
1007 	}
1008 	if((afid->rpc = auth_allocrpc(afd)) == nil){
1009 		close(afd);
1010 		vtSetError("can't auth_allocrpc");
1011 		fidClunk(afid);
1012 		return 0;
1013 	}
1014 	if(auth_rpc(afid->rpc, "start", "proto=p9any role=server", 23) != ARok){
1015 		vtSetError("can't auth_rpc");
1016 		fidClunk(afid);
1017 		return 0;
1018 	}
1019 
1020 	afid->open = FidOWrite|FidORead;
1021 	afid->qid.type = QTAUTH;
1022 	afid->qid.path = m->t.afid;
1023 	afid->uname = vtStrDup(m->t.uname);
1024 
1025 	m->r.qid = afid->qid;
1026 
1027 	fidPut(afid);
1028 	return 1;
1029 }
1030 
1031 static int
1032 rTversion(Msg* m)
1033 {
1034 	int v;
1035 	Con *con;
1036 	Fcall *r, *t;
1037 
1038 	t = &m->t;
1039 	r = &m->r;
1040 	con = m->con;
1041 
1042 	vtLock(con->lock);
1043 	if(con->state != ConInit){
1044 		vtUnlock(con->lock);
1045 		vtSetError("Tversion: down");
1046 		return 0;
1047 	}
1048 	con->state = ConNew;
1049 
1050 	/*
1051 	 * Release the karma of past lives and suffering.
1052 	 * Should this be done before or after checking the
1053 	 * validity of the Tversion?
1054 	 */
1055 	fidClunkAll(con);
1056 
1057 	if(t->tag != NOTAG){
1058 		vtUnlock(con->lock);
1059 		vtSetError("Tversion: invalid tag");
1060 		return 0;
1061 	}
1062 
1063 	if(t->msize < 256){
1064 		vtUnlock(con->lock);
1065 		vtSetError("Tversion: message size too small");
1066 		return 0;
1067 	}
1068 	if(t->msize < con->msize)
1069 		r->msize = t->msize;
1070 	else
1071 		r->msize = con->msize;
1072 
1073 	r->version = "unknown";
1074 	if(t->version[0] == '9' && t->version[1] == 'P'){
1075 		/*
1076 		 * Currently, the only defined version
1077 		 * is "9P2000"; ignore any later versions.
1078           	 */
1079 		v = strtol(&t->version[2], 0, 10);
1080 		if(v >= 2000){
1081 			r->version = VERSION9P;
1082 			con->msize = r->msize;
1083 			con->state = ConUp;
1084 		}
1085 		else if(strcmp(t->version, "9PEoF") == 0){
1086 			r->version = "9PEoF";
1087 			con->msize = r->msize;
1088 			con->state = ConMoribund;
1089 		}
1090 	}
1091 	vtUnlock(con->lock);
1092 
1093 	return 1;
1094 }
1095 
1096 int (*rFcall[Tmax])(Msg*) = {
1097 	[Tversion]	= rTversion,
1098 	[Tauth]		= rTauth,
1099 	[Tattach]	= rTattach,
1100 	[Tflush]	= rTflush,
1101 	[Twalk]		= rTwalk,
1102 	[Topen]		= rTopen,
1103 	[Tcreate]	= rTcreate,
1104 	[Tread]		= rTread,
1105 	[Twrite]	= rTwrite,
1106 	[Tclunk]	= rTclunk,
1107 	[Tremove]	= rTremove,
1108 	[Tstat]		= rTstat,
1109 	[Twstat]	= rTwstat,
1110 };
1111