xref: /netbsd/usr.sbin/puffs/mount_9p/node.c (revision 85291faa)
1 /*	$NetBSD: node.c,v 1.31 2022/03/02 07:48:20 ozaki-r Exp $	*/
2 
3 /*
4  * Copyright (c) 2007  Antti Kantee.  All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #ifndef lint
30 __RCSID("$NetBSD: node.c,v 1.31 2022/03/02 07:48:20 ozaki-r Exp $");
31 #endif /* !lint */
32 
33 #include <assert.h>
34 #include <errno.h>
35 #include <puffs.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 
39 #include "ninepuffs.h"
40 #include "nineproto.h"
41 
42 static void *
nodecmp(struct puffs_usermount * pu,struct puffs_node * pn,void * arg)43 nodecmp(struct puffs_usermount *pu, struct puffs_node *pn, void *arg)
44 {
45 	struct vattr *vap = &pn->pn_va;
46 	struct qid9p *qid = arg;
47 
48 	if (vap->va_fileid == qid->qidpath && vap->va_gen == qid->qidvers)
49 		return pn;
50 
51 	return NULL;
52 }
53 
54 static int
do_getattr(struct puffs_usermount * pu,struct puffs_node * pn,struct vattr * vap)55 do_getattr(struct puffs_usermount *pu, struct puffs_node *pn, struct vattr *vap)
56 {
57 	AUTOVAR(pu);
58 	struct p9pnode *p9n = pn->pn_data;
59 
60 	tag = NEXTTAG(p9p);
61 	p9pbuf_put_1(pb, P9PROTO_T_STAT);
62 	p9pbuf_put_2(pb, tag);
63 	p9pbuf_put_4(pb, p9n->fid_base);
64 	GETRESPONSE(pb);
65 
66 	if (p9pbuf_get_type(pb) != P9PROTO_R_STAT) {
67 		rv = proto_handle_rerror(pu, pb);
68 		goto out;
69 	}
70 	rv = proto_expect_stat(pu, pb, vap);
71 
72  out:
73 	RETURN(rv);
74 }
75 
76 int
puffs9p_node_getattr(struct puffs_usermount * pu,void * opc,struct vattr * vap,const struct puffs_cred * pcr)77 puffs9p_node_getattr(struct puffs_usermount *pu, void *opc, struct vattr *vap,
78 	const struct puffs_cred *pcr)
79 {
80 	struct puffs_node *pn = opc;
81 	int rv;
82 
83 	rv = do_getattr(pu, pn, &pn->pn_va);
84 	if (rv == 0)
85 		memcpy(vap, &pn->pn_va, sizeof(struct vattr));
86 	return rv;
87 }
88 
89 int
puffs9p_node_lookup(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn)90 puffs9p_node_lookup(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
91 	const struct puffs_cn *pcn)
92 {
93 	AUTOVAR(pu);
94 	struct vattr va;
95 	struct puffs_node *pn, *pn_dir = opc;
96 	struct p9pnode *p9n_dir = pn_dir->pn_data;
97 	p9pfid_t tfid = NEXTFID(p9p);
98 	struct qid9p newqid;
99 	uint16_t nqid;
100 
101 	tag = NEXTTAG(p9p);
102 	p9pbuf_put_1(pb, P9PROTO_T_WALK);
103 	p9pbuf_put_2(pb, tag);
104 	p9pbuf_put_4(pb, p9n_dir->fid_base);
105 	p9pbuf_put_4(pb, tfid);
106 	p9pbuf_put_2(pb, 1);
107 	p9pbuf_put_str(pb, pcn->pcn_name);
108 	GETRESPONSE(pb);
109 
110 	rv = proto_expect_walk_nqids(pu, pb, &nqid);
111 	if (rv) {
112 		rv = ENOENT;
113 		goto out;
114 	}
115 	if (nqid != 1) {
116 		rv = EPROTO;
117 		goto out;
118 	}
119 	if ((rv = proto_getqid(pb, &newqid)))
120 		goto out;
121 
122 	/* we get the parent vers in walk(?)  compensate */
123 	p9pbuf_recycleout(pb);
124 	tag = NEXTTAG(p9p);
125 	p9pbuf_put_1(pb, P9PROTO_T_STAT);
126 	p9pbuf_put_2(pb, tag);
127 	p9pbuf_put_4(pb, tfid);
128 	GETRESPONSE(pb);
129 	if ((rv = proto_expect_stat(pu, pb, &va)) != 0) {
130 		proto_cc_clunkfid(pu, tfid, 0);
131 		rv = ENOENT;
132 		goto out;
133 	}
134 	if (newqid.qidpath != va.va_fileid) {
135 		proto_cc_clunkfid(pu, tfid, 0);
136 		rv = EPROTO;
137 		goto out;
138 	}
139 	newqid.qidvers = va.va_gen;
140 
141 	pn = puffs_pn_nodewalk(pu, nodecmp, &newqid);
142 	if (pn == NULL)
143 		pn = newp9pnode_qid(pu, &newqid, tfid);
144 	else
145 		proto_cc_clunkfid(pu, tfid, 0);
146 	/* assert pn */
147 	memcpy(&pn->pn_va, &va, sizeof(va));
148 
149 	puffs_newinfo_setcookie(pni, pn);
150 	puffs_newinfo_setvtype(pni, pn->pn_va.va_type);
151 	puffs_newinfo_setsize(pni, pn->pn_va.va_size);
152 	puffs_newinfo_setrdev(pni, pn->pn_va.va_rdev);
153 
154  out:
155 	RETURN(rv);
156 }
157 
158 /*
159  * Problem is that 9P doesn't allow seeking into a directory.  So we
160  * maintain a list of active fids for any given directory.  They
161  * start living at the first read and exist either until the directory
162  * is closed or until they reach the end.
163  */
164 int
puffs9p_node_readdir(struct puffs_usermount * pu,void * opc,struct dirent * dent,off_t * readoff,size_t * reslen,const struct puffs_cred * pcr,int * eofflag,off_t * cookies,size_t * ncookies)165 puffs9p_node_readdir(struct puffs_usermount *pu, void *opc, struct dirent *dent,
166 	off_t *readoff, size_t *reslen, const struct puffs_cred *pcr,
167 	int *eofflag, off_t *cookies, size_t *ncookies)
168 {
169 	AUTOVAR(pu);
170 	struct puffs_node *pn = opc;
171 	struct p9pnode *p9n = pn->pn_data;
172 	struct vattr va;
173 	struct dirfid *dfp;
174 	char *name;
175 	uint32_t count;
176 	uint16_t statsize;
177 
178 	rv = getdfwithoffset(pu, p9n, *readoff, &dfp);
179 	if (rv)
180 		goto out;
181 
182 	tag = NEXTTAG(p9p);
183 	p9pbuf_put_1(pb, P9PROTO_T_READ);
184 	p9pbuf_put_2(pb, tag);
185 	p9pbuf_put_4(pb, dfp->fid);
186 	p9pbuf_put_8(pb, *readoff);
187 	p9pbuf_put_4(pb, *reslen); /* XXX */
188 	GETRESPONSE(pb);
189 
190 	if (p9pbuf_get_type(pb) != P9PROTO_R_READ) {
191 		rv = proto_handle_rerror(pu, pb);
192 		goto out;
193 	}
194 
195 	p9pbuf_get_4(pb, &count);
196 
197 	/*
198 	 * if count is 0, assume we at end-of-dir.  dfp is no longer
199 	 * useful, so nuke it
200 	 */
201 	if (count == 0) {
202 		*eofflag = 1;
203 		releasedf(pu, dfp);
204 		goto out;
205 	}
206 
207 	while (count > 0) {
208 		if ((rv = proto_getstat(pu, pb, &va, &name, &statsize))) {
209 			/*
210 			 * If there was an error, it's unlikely we'll be
211 			 * coming back, so just nuke the dfp.  If we do
212 			 * come back for some strange reason, we'll just
213 			 * regen it.
214 			 */
215 			releasedf(pu, dfp);
216 			goto out;
217 		}
218 
219 		puffs_nextdent(&dent, name, va.va_fileid,
220 		    puffs_vtype2dt(va.va_type), reslen);
221 
222 		count -= statsize;
223 		*readoff += statsize;
224 		dfp->seekoff += statsize;
225 		free(name);
226 	}
227 
228 	storedf(p9n, dfp);
229 
230  out:
231 	RETURN(rv);
232 }
233 
234 int
puffs9p_node_setattr(struct puffs_usermount * pu,void * opc,const struct vattr * va,const struct puffs_cred * pcr)235 puffs9p_node_setattr(struct puffs_usermount *pu, void *opc,
236 	const struct vattr *va, const struct puffs_cred *pcr)
237 {
238 	AUTOVAR(pu);
239 	struct puffs_node *pn = opc;
240 	struct p9pnode *p9n = pn->pn_data;
241 
242 	tag = NEXTTAG(p9p);
243 	p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
244 	p9pbuf_put_2(pb, tag);
245 	p9pbuf_put_4(pb, p9n->fid_base);
246 	proto_make_stat(pu, pb, va, NULL, pn->pn_va.va_type);
247 	GETRESPONSE(pb);
248 
249 	if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT) {
250 		rv = proto_handle_rerror(pu, pb);
251 	}
252 
253  out:
254 	RETURN(rv);
255 }
256 
257 /*
258  * Ok, time to get clever.  There are two possible cases: we are
259  * opening a file or we are opening a directory.
260  *
261  * If it's a directory, don't bother opening it here, but rather
262  * wait until readdir, since it's probable we need to be able to
263  * open a directory there in any case.
264  *
265  * If it's a regular file, open it here with whatever credentials
266  * we happen to have.   Let the upper layers of the kernel worry
267  * about permission control.
268  */
269 int
puffs9p_node_open(struct puffs_usermount * pu,void * opc,int mode,const struct puffs_cred * pcr)270 puffs9p_node_open(struct puffs_usermount *pu, void *opc, int mode,
271 	const struct puffs_cred *pcr)
272 {
273 	struct puffs_cc *pcc = puffs_cc_getcc(pu);
274 	struct puffs9p *p9p = puffs_getspecific(pu);
275 	struct puffs_node *pn = opc;
276 	struct p9pnode *p9n = pn->pn_data;
277 	p9pfid_t nfid;
278 	int error = 0;
279 
280 	puffs_setback(pcc, PUFFS_SETBACK_INACT_N1);
281 	if (pn->pn_va.va_type != VDIR) {
282 		/* Always require read access for page cache */
283 		mode |= FREAD;
284 		if (mode & FREAD && p9n->fid_read == P9P_INVALFID) {
285 			nfid = NEXTFID(p9p);
286 			error = proto_cc_open(pu, p9n->fid_base, nfid,
287 			    P9PROTO_OMODE_READ);
288 			if (error)
289 				return error;
290 			p9n->fid_read = nfid;
291 		}
292 		if (mode & FWRITE && p9n->fid_write == P9P_INVALFID) {
293 			nfid = NEXTFID(p9p);
294 			error = proto_cc_open(pu, p9n->fid_base, nfid,
295 			    P9PROTO_OMODE_WRITE);
296 			if (error)
297 				return error;
298 			p9n->fid_write = nfid;
299 		}
300 	}
301 
302 	return 0;
303 }
304 
305 int
puffs9p_node_inactive(struct puffs_usermount * pu,void * opc)306 puffs9p_node_inactive(struct puffs_usermount *pu, void *opc)
307 {
308 	struct puffs_node *pn = opc;
309 	struct p9pnode *p9n = pn->pn_data;
310 
311 	if (pn->pn_va.va_type == VDIR) {
312 		nukealldf(pu, p9n);
313 	} else  {
314 		if (p9n->fid_read != P9P_INVALFID) {
315 			proto_cc_clunkfid(pu, p9n->fid_read, 0);
316 			p9n->fid_read = P9P_INVALFID;
317 		}
318 		if (p9n->fid_write != P9P_INVALFID) {
319 			proto_cc_clunkfid(pu, p9n->fid_write, 0);
320 			p9n->fid_write = P9P_INVALFID;
321 		}
322 	}
323 
324 	return 0;
325 }
326 
327 int
puffs9p_node_read(struct puffs_usermount * pu,void * opc,uint8_t * buf,off_t offset,size_t * resid,const struct puffs_cred * pcr,int ioflag)328 puffs9p_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
329 	off_t offset, size_t *resid, const struct puffs_cred *pcr,
330 	int ioflag)
331 {
332 	AUTOVAR(pu);
333 	struct puffs_node *pn = opc;
334 	struct p9pnode *p9n = pn->pn_data;
335 	uint32_t count;
336 	size_t nread;
337 
338 	nread = 0;
339 	while (*resid > 0 && (uint64_t)(offset+nread) < pn->pn_va.va_size) {
340 		tag = NEXTTAG(p9p);
341 		p9pbuf_put_1(pb, P9PROTO_T_READ);
342 		p9pbuf_put_2(pb, tag);
343 		p9pbuf_put_4(pb, p9n->fid_read);
344 		p9pbuf_put_8(pb, offset+nread);
345 		p9pbuf_put_4(pb, MIN((uint32_t)*resid,p9p->maxreq-24));
346 		GETRESPONSE(pb);
347 
348 		if (p9pbuf_get_type(pb) != P9PROTO_R_READ) {
349 			rv = proto_handle_rerror(pu, pb);
350 			break;
351 		}
352 
353 		p9pbuf_get_4(pb, &count);
354 		if ((rv = p9pbuf_read_data(pb, buf + nread, count)))
355 			break;
356 
357 		if (count == 0)
358 			break;
359 
360 		*resid -= count;
361 		nread += count;
362 
363 		p9pbuf_recycleout(pb);
364 	}
365 
366  out:
367 	RETURN(rv);
368 }
369 
370 int
puffs9p_node_write(struct puffs_usermount * pu,void * opc,uint8_t * buf,off_t offset,size_t * resid,const struct puffs_cred * cred,int ioflag)371 puffs9p_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
372 	off_t offset, size_t *resid, const struct puffs_cred *cred,
373 	int ioflag)
374 {
375 	AUTOVAR(pu);
376 	struct puffs_node *pn = opc;
377 	struct p9pnode *p9n = pn->pn_data;
378 	uint32_t chunk, count;
379 	size_t nwrite;
380 
381 	if (ioflag & PUFFS_IO_APPEND)
382 		offset = pn->pn_va.va_size;
383 
384 	nwrite = 0;
385 	while (*resid > 0) {
386 		chunk = MIN(*resid, p9p->maxreq-32);
387 
388 		tag = NEXTTAG(p9p);
389 		p9pbuf_put_1(pb, P9PROTO_T_WRITE);
390 		p9pbuf_put_2(pb, tag);
391 		p9pbuf_put_4(pb, p9n->fid_write);
392 		p9pbuf_put_8(pb, offset+nwrite);
393 		p9pbuf_put_4(pb, chunk);
394 		p9pbuf_write_data(pb, buf+nwrite, chunk);
395 		GETRESPONSE(pb);
396 
397 		if (p9pbuf_get_type(pb) != P9PROTO_R_WRITE) {
398 			rv = proto_handle_rerror(pu, pb);
399 			break;
400 		}
401 
402 		p9pbuf_get_4(pb, &count);
403 		*resid -= count;
404 		nwrite += count;
405 
406 		if (count != chunk) {
407 			rv = EPROTO;
408 			break;
409 		}
410 
411 		p9pbuf_recycleout(pb);
412 	}
413 
414  out:
415 	RETURN(rv);
416 }
417 
418 static int
nodecreate(struct puffs_usermount * pu,struct puffs_node * pn,struct puffs_newinfo * pni,const char * name,const struct vattr * vap,uint32_t dirbit)419 nodecreate(struct puffs_usermount *pu, struct puffs_node *pn,
420 	struct puffs_newinfo *pni, const char *name,
421 	const struct vattr *vap, uint32_t dirbit)
422 {
423 	AUTOVAR(pu);
424 	struct puffs_node *pn_new;
425 	struct p9pnode *p9n = pn->pn_data;
426 	p9pfid_t nfid = NEXTFID(p9p);
427 	struct qid9p nqid;
428 	int tries = 0;
429 
430  again:
431 	if (++tries > 5) {
432 		rv = EPROTO;
433 		goto out;
434 	}
435 
436 	rv = proto_cc_dupfid(pu, p9n->fid_base, nfid);
437 	if (rv)
438 		goto out;
439 
440 	tag = NEXTTAG(p9p);
441 	p9pbuf_put_1(pb, P9PROTO_T_CREATE);
442 	p9pbuf_put_2(pb, tag);
443 	p9pbuf_put_4(pb, nfid);
444 	p9pbuf_put_str(pb, name);
445 	p9pbuf_put_4(pb, dirbit | (vap->va_mode & 0777));
446 	p9pbuf_put_1(pb, 0);
447 	if (p9p->protover == P9PROTO_VERSION_U)
448 		p9pbuf_put_str(pb, ""); /* extension[s] */
449 	GETRESPONSE(pb);
450 
451 	rv = proto_expect_qid(pu, pb, P9PROTO_R_CREATE, &nqid);
452 	if (rv)
453 		goto out;
454 
455 	/*
456 	 * Now, little problem here: create returns an *open* fid.
457 	 * So, clunk it and walk the parent directory to get a fid
458 	 * which is not open for I/O yet.
459 	 */
460 	proto_cc_clunkfid(pu, nfid, 0);
461 	nfid = NEXTFID(p9p);
462 
463 	p9pbuf_recycleout(pb);
464 	tag = NEXTTAG(p9p);
465 	p9pbuf_put_1(pb, P9PROTO_T_WALK);
466 	p9pbuf_put_2(pb, tag);
467 	p9pbuf_put_4(pb, p9n->fid_base);
468 	p9pbuf_put_4(pb, nfid);
469 	p9pbuf_put_2(pb, 1);
470 	p9pbuf_put_str(pb, name);
471 	GETRESPONSE(pb);
472 
473 	/*
474 	 * someone removed it already? try again
475 	 * note: this is kind of lose/lose
476 	 */
477 	if (p9pbuf_get_type(pb) != P9PROTO_R_WALK)
478 		goto again;
479 
480 	pn_new = newp9pnode_va(pu, vap, nfid);
481 	qid2vattr(&pn_new->pn_va, &nqid);
482 	puffs_newinfo_setcookie(pni, pn_new);
483 
484  out:
485 	RETURN(rv);
486 }
487 
488 int
puffs9p_node_create(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn,const struct vattr * va)489 puffs9p_node_create(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
490 	const struct puffs_cn *pcn, const struct vattr *va)
491 {
492 
493 	return nodecreate(pu, opc, pni, pcn->pcn_name, va, 0);
494 }
495 
496 int
puffs9p_node_mkdir(struct puffs_usermount * pu,void * opc,struct puffs_newinfo * pni,const struct puffs_cn * pcn,const struct vattr * va)497 puffs9p_node_mkdir(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
498 	const struct puffs_cn *pcn, const struct vattr *va)
499 {
500 
501 	return nodecreate(pu, opc, pni, pcn->pcn_name,
502 	    va, P9PROTO_CPERM_DIR);
503 }
504 
505 /*
506  * Need to be a bit clever again: the fid is clunked no matter if
507  * the remove succeeds or not.  Re-getting a fid would be way too
508  * difficult in case the remove failed for a valid reason (directory
509  * not empty etcetc.).  So walk ourselves another fid to prod the
510  * ice with.
511  */
512 static int
noderemove(struct puffs_usermount * pu,struct puffs_node * pn)513 noderemove(struct puffs_usermount *pu, struct puffs_node *pn)
514 {
515 	AUTOVAR(pu);
516 	struct p9pnode *p9n = pn->pn_data;
517 	p9pfid_t testfid = NEXTFID(p9p);
518 
519 	rv = proto_cc_dupfid(pu, p9n->fid_base, testfid);
520 	if (rv)
521 		goto out;
522 
523 	tag = NEXTTAG(p9p);
524 	p9pbuf_put_1(pb, P9PROTO_T_REMOVE);
525 	p9pbuf_put_2(pb, tag);
526 	p9pbuf_put_4(pb, testfid);
527 
528 	/*
529 	 * XXX: error handling isn't very robust, but doom is impending
530 	 * anyway, so just accept we're going belly up and play dead
531 	 */
532 	GETRESPONSE(pb);
533 
534 	if (p9pbuf_get_type(pb) != P9PROTO_R_REMOVE) {
535 		rv = proto_handle_rerror(pu, pb);
536 	} else {
537 		proto_cc_clunkfid(pu, p9n->fid_base, 0);
538 		p9n->fid_base = P9P_INVALFID;
539 		puffs_pn_remove(pn);
540 	}
541 
542  out:
543 	RETURN(rv);
544 }
545 
546 int
puffs9p_node_remove(struct puffs_usermount * pu,void * opc,void * targ,const struct puffs_cn * pcn)547 puffs9p_node_remove(struct puffs_usermount *pu, void *opc, void *targ,
548 	const struct puffs_cn *pcn)
549 {
550 	struct puffs_cc *pcc = puffs_cc_getcc(pu);
551 	struct puffs_node *pn = targ;
552 	int rv;
553 
554 	if (pn->pn_va.va_type == VDIR)
555 		return EISDIR;
556 
557 	rv = noderemove(pu, pn);
558 	if (rv == 0)
559 		puffs_setback(pcc, PUFFS_SETBACK_NOREF_N2);
560 
561 	return rv;
562 }
563 
564 int
puffs9p_node_rmdir(struct puffs_usermount * pu,void * opc,void * targ,const struct puffs_cn * pcn)565 puffs9p_node_rmdir(struct puffs_usermount *pu, void *opc, void *targ,
566 	const struct puffs_cn *pcn)
567 {
568 	struct puffs_cc *pcc = puffs_cc_getcc(pu);
569 	struct puffs_node *pn = targ;
570 	int rv;
571 
572 	if (pn->pn_va.va_type != VDIR)
573 		return ENOTDIR;
574 
575 	rv = noderemove(pu, pn);
576 	if (rv == 0)
577 		puffs_setback(pcc, PUFFS_SETBACK_NOREF_N2);
578 
579 	return rv;
580 }
581 
582 int
puffs9p_node_rename(struct puffs_usermount * pu,void * src_dir,void * src,const struct puffs_cn * pcn_src,void * targ_dir,void * targ,const struct puffs_cn * pcn_targ)583 puffs9p_node_rename(struct puffs_usermount *pu,
584 		    void *src_dir, void *src, const struct puffs_cn *pcn_src,
585 		    void *targ_dir, void *targ, const struct puffs_cn *pcn_targ)
586 {
587 	AUTOVAR(pu);
588 	struct puffs_node *pn_src = src;
589 	struct p9pnode *p9n_src = pn_src->pn_data;
590 
591 	/*
592 	 * 9P rename can only change the last pathname component.
593 	 * Return EXDEV for attempts to move a file to a different
594 	 * directory to make mv(1) fall back to copying.
595 	 */
596 	if (src_dir != targ_dir) {
597 		rv = EXDEV;
598 		goto out;
599 	}
600 
601 	/* 9P doesn't allow to overwrite in rename */
602 	if (targ) {
603 		struct puffs_node *pn_targ = targ;
604 
605 		rv = noderemove(pu, pn_targ);
606 		if (rv)
607 			goto out;
608 	}
609 
610 	tag = NEXTTAG(p9p);
611 	p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
612 	p9pbuf_put_2(pb, tag);
613 	p9pbuf_put_4(pb, p9n_src->fid_base);
614 	proto_make_stat(pu, pb, NULL, pcn_targ->pcn_name, pn_src->pn_va.va_type);
615 	GETRESPONSE(pb);
616 
617 	if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT)
618 		rv = proto_handle_rerror(pu, pb);
619 
620  out:
621 	RETURN(rv);
622 }
623 
624 /*
625  * - "here's one"
626  * - "9P"
627  * ~ "i'm not dead"
628  * - "you're not fooling anyone you know, you'll be stone dead in a minute
629  * - "he says he's not quite dead"
630  * - "isn't there anything you could do?"
631  * - *clunk*!
632  * - "thanks"
633  */
634 int
puffs9p_node_reclaim(struct puffs_usermount * pu,void * opc)635 puffs9p_node_reclaim(struct puffs_usermount *pu, void *opc)
636 {
637 	struct puffs_node *pn = opc;
638 	struct p9pnode *p9n = pn->pn_data;
639 
640 	assert(LIST_EMPTY(&p9n->dir_openlist));
641 	assert(p9n->fid_read == P9P_INVALFID && p9n->fid_write == P9P_INVALFID);
642 
643 	proto_cc_clunkfid(pu, p9n->fid_base, 0);
644 	free(p9n);
645 	puffs_pn_put(pn);
646 
647 	return 0;
648 }
649