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