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