1/*	$NetBSD: node.c,v 1.20 2008/08/22 17:44:14 pooka 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.20 2008/08/22 17:44:14 pooka 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
42static void *
43nodecmp(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
54static int
55do_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	p9pbuf_put_1(pb, P9PROTO_T_STAT);
61	p9pbuf_put_2(pb, tag);
62	p9pbuf_put_4(pb, p9n->fid_base);
63	GETRESPONSE(pb);
64
65	rv = proto_expect_stat(pb, vap);
66
67 out:
68	RETURN(rv);
69}
70
71int
72puffs9p_node_getattr(struct puffs_usermount *pu, void *opc, struct vattr *vap,
73	const struct puffs_cred *pcr)
74{
75	struct puffs_node *pn = opc;
76	int rv;
77
78	rv = do_getattr(pu, pn, &pn->pn_va);
79	if (rv == 0)
80		memcpy(vap, &pn->pn_va, sizeof(struct vattr));
81	return rv;
82}
83
84int
85puffs9p_node_lookup(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
86	const struct puffs_cn *pcn)
87{
88	AUTOVAR(pu);
89	struct vattr va;
90	struct puffs_node *pn, *pn_dir = opc;
91	struct p9pnode *p9n_dir = pn_dir->pn_data;
92	p9ptag_t tfid = NEXTFID(p9p);
93	struct qid9p newqid;
94	uint16_t nqid;
95
96	p9pbuf_put_1(pb, P9PROTO_T_WALK);
97	p9pbuf_put_2(pb, tag);
98	p9pbuf_put_4(pb, p9n_dir->fid_base);
99	p9pbuf_put_4(pb, tfid);
100	p9pbuf_put_2(pb, 1);
101	p9pbuf_put_str(pb, pcn->pcn_name);
102	GETRESPONSE(pb);
103
104	rv = proto_expect_walk_nqids(pb, &nqid);
105	if (rv) {
106		rv = ENOENT;
107		goto out;
108	}
109	if (nqid != 1) {
110		rv = EPROTO;
111		goto out;
112	}
113	if ((rv = proto_getqid(pb, &newqid)))
114		goto out;
115
116	/* we get the parent vers in walk(?)  compensate */
117	p9pbuf_recycleout(pb);
118	tag = NEXTTAG(p9p);
119	p9pbuf_put_1(pb, P9PROTO_T_STAT);
120	p9pbuf_put_2(pb, tag);
121	p9pbuf_put_4(pb, tfid);
122	GETRESPONSE(pb);
123	if ((rv = proto_expect_stat(pb, &va)) != 0) {
124		proto_cc_clunkfid(pu, tfid, 0);
125		rv = ENOENT;
126		goto out;
127	}
128	if (newqid.qidpath != va.va_fileid) {
129		proto_cc_clunkfid(pu, tfid, 0);
130		rv = EPROTO;
131		goto out;
132	}
133	newqid.qidvers = va.va_gen;
134
135	pn = puffs_pn_nodewalk(pu, nodecmp, &newqid);
136	if (pn == NULL)
137		pn = newp9pnode_qid(pu, &newqid, tfid);
138	else
139		proto_cc_clunkfid(pu, tfid, 0);
140	/* assert pn */
141	memcpy(&pn->pn_va, &va, sizeof(va));
142
143	puffs_newinfo_setcookie(pni, pn);
144	puffs_newinfo_setvtype(pni, pn->pn_va.va_type);
145	puffs_newinfo_setsize(pni, pn->pn_va.va_size);
146	puffs_newinfo_setrdev(pni, pn->pn_va.va_rdev);
147
148 out:
149	RETURN(rv);
150}
151
152/*
153 * Problem is that 9P doesn't allow seeking into a directory.  So we
154 * maintain a list of active fids for any given directory.  They
155 * start living at the first read and exist either until the directory
156 * is closed or until they reach the end.
157 */
158int
159puffs9p_node_readdir(struct puffs_usermount *pu, void *opc, struct dirent *dent,
160	off_t *readoff, size_t *reslen, const struct puffs_cred *pcr,
161	int *eofflag, off_t *cookies, size_t *ncookies)
162{
163	AUTOVAR(pu);
164	struct puffs_node *pn = opc;
165	struct p9pnode *p9n = pn->pn_data;
166	struct vattr va;
167	struct dirfid *dfp;
168	char *name;
169	uint32_t count;
170	uint16_t statsize;
171
172	rv = getdfwithoffset(pu, p9n, *readoff, &dfp);
173	if (rv)
174		goto out;
175
176	tag = NEXTTAG(p9p);
177	p9pbuf_put_1(pb, P9PROTO_T_READ);
178	p9pbuf_put_2(pb, tag);
179	p9pbuf_put_4(pb, dfp->fid);
180	p9pbuf_put_8(pb, *readoff);
181	p9pbuf_put_4(pb, *reslen); /* XXX */
182	GETRESPONSE(pb);
183
184	p9pbuf_get_4(pb, &count);
185
186	/*
187	 * if count is 0, assume we at end-of-dir.  dfp is no longer
188	 * useful, so nuke it
189	 */
190	if (count == 0) {
191		*eofflag = 1;
192		releasedf(pu, dfp);
193		goto out;
194	}
195
196	while (count > 0) {
197		if ((rv = proto_getstat(pb, &va, &name, &statsize))) {
198			/*
199			 * If there was an error, it's unlikely we'll be
200			 * coming back, so just nuke the dfp.  If we do
201			 * come back for some strange reason, we'll just
202			 * regen it.
203			 */
204			releasedf(pu, dfp);
205			goto out;
206		}
207
208		puffs_nextdent(&dent, name, va.va_fileid,
209		    puffs_vtype2dt(va.va_type), reslen);
210
211		count -= statsize;
212		*readoff += statsize;
213		dfp->seekoff += statsize;
214		free(name);
215	}
216
217	storedf(p9n, dfp);
218
219 out:
220	RETURN(rv);
221}
222
223int
224puffs9p_node_setattr(struct puffs_usermount *pu, void *opc,
225	const struct vattr *va, const struct puffs_cred *pcr)
226{
227	AUTOVAR(pu);
228	struct puffs_node *pn = opc;
229	struct p9pnode *p9n = pn->pn_data;
230
231	p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
232	p9pbuf_put_2(pb, tag);
233	p9pbuf_put_4(pb, p9n->fid_base);
234	proto_make_stat(pb, va, NULL, pn->pn_va.va_type);
235	GETRESPONSE(pb);
236
237	if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT)
238		rv = EPROTO;
239
240 out:
241	RETURN(rv);
242}
243
244/*
245 * Ok, time to get clever.  There are two possible cases: we are
246 * opening a file or we are opening a directory.
247 *
248 * If it's a directory, don't bother opening it here, but rather
249 * wait until readdir, since it's probable we need to be able to
250 * open a directory there in any case.
251 *
252 * If it's a regular file, open it here with whatever credentials
253 * we happen to have.   Let the upper layers of the kernel worry
254 * about permission control.
255 */
256int
257puffs9p_node_open(struct puffs_usermount *pu, void *opc, int mode,
258	const struct puffs_cred *pcr)
259{
260	struct puffs_cc *pcc = puffs_cc_getcc(pu);
261	struct puffs9p *p9p = puffs_getspecific(pu);
262	struct puffs_node *pn = opc;
263	struct p9pnode *p9n = pn->pn_data;
264	p9pfid_t nfid;
265	int error = 0;
266
267	puffs_setback(pcc, PUFFS_SETBACK_INACT_N1);
268	if (pn->pn_va.va_type != VDIR) {
269		if (mode & FREAD && p9n->fid_read == P9P_INVALFID) {
270			nfid = NEXTFID(p9p);
271			error = proto_cc_open(pu, p9n->fid_base, nfid,
272			    P9PROTO_OMODE_READ);
273			if (error)
274				return error;
275			p9n->fid_read = nfid;
276		}
277		if (mode & FWRITE && p9n->fid_write == P9P_INVALFID) {
278			nfid = NEXTFID(p9p);
279			error = proto_cc_open(pu, p9n->fid_base, nfid,
280			    P9PROTO_OMODE_WRITE);
281			if (error)
282				return error;
283			p9n->fid_write = nfid;
284		}
285	}
286
287	return 0;
288}
289
290int
291puffs9p_node_inactive(struct puffs_usermount *pu, void *opc)
292{
293	struct puffs_node *pn = opc;
294	struct p9pnode *p9n = pn->pn_data;
295
296	if (pn->pn_va.va_type == VDIR) {
297		nukealldf(pu, p9n);
298	} else  {
299		if (p9n->fid_read != P9P_INVALFID) {
300			proto_cc_clunkfid(pu, p9n->fid_read, 0);
301			p9n->fid_read = P9P_INVALFID;
302		}
303		if (p9n->fid_write != P9P_INVALFID) {
304			proto_cc_clunkfid(pu, p9n->fid_write, 0);
305			p9n->fid_write = P9P_INVALFID;
306		}
307	}
308
309	return 0;
310}
311
312int
313puffs9p_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
314	off_t offset, size_t *resid, const struct puffs_cred *pcr,
315	int ioflag)
316{
317	AUTOVAR(pu);
318	struct puffs_node *pn = opc;
319	struct p9pnode *p9n = pn->pn_data;
320	uint32_t count;
321	size_t nread;
322
323	nread = 0;
324	while (*resid > 0 && (uint64_t)(offset+nread) < pn->pn_va.va_size) {
325		p9pbuf_put_1(pb, P9PROTO_T_READ);
326		p9pbuf_put_2(pb, tag);
327		p9pbuf_put_4(pb, p9n->fid_read);
328		p9pbuf_put_8(pb, offset+nread);
329		p9pbuf_put_4(pb, MIN((uint32_t)*resid,p9p->maxreq-24));
330		GETRESPONSE(pb);
331
332		if (p9pbuf_get_type(pb) != P9PROTO_R_READ) {
333			rv = EPROTO;
334			break;
335		}
336
337		p9pbuf_get_4(pb, &count);
338		if ((rv = p9pbuf_read_data(pb, buf + nread, count)))
339			break;
340
341		if (count == 0)
342			break;
343
344		*resid -= count;
345		nread += count;
346
347		p9pbuf_recycleout(pb);
348	}
349
350 out:
351	RETURN(rv);
352}
353
354int
355puffs9p_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
356	off_t offset, size_t *resid, const struct puffs_cred *cred,
357	int ioflag)
358{
359	AUTOVAR(pu);
360	struct puffs_node *pn = opc;
361	struct p9pnode *p9n = pn->pn_data;
362	uint32_t chunk, count;
363	size_t nwrite;
364
365	if (ioflag & PUFFS_IO_APPEND)
366		offset = pn->pn_va.va_size;
367
368	nwrite = 0;
369	while (*resid > 0) {
370		chunk = MIN(*resid, p9p->maxreq-32);
371
372		p9pbuf_put_1(pb, P9PROTO_T_WRITE);
373		p9pbuf_put_2(pb, tag);
374		p9pbuf_put_4(pb, p9n->fid_write);
375		p9pbuf_put_8(pb, offset+nwrite);
376		p9pbuf_put_4(pb, chunk);
377		p9pbuf_write_data(pb, buf+nwrite, chunk);
378		GETRESPONSE(pb);
379
380		if (p9pbuf_get_type(pb) != P9PROTO_R_WRITE) {
381			rv = EPROTO;
382			break;
383		}
384
385		p9pbuf_get_4(pb, &count);
386		*resid -= count;
387		nwrite += count;
388
389		if (count != chunk) {
390			rv = EPROTO;
391			break;
392		}
393
394		p9pbuf_recycleout(pb);
395	}
396
397 out:
398	RETURN(rv);
399}
400
401static int
402nodecreate(struct puffs_usermount *pu, struct puffs_node *pn,
403	struct puffs_newinfo *pni, const char *name,
404	const struct vattr *vap, uint32_t dirbit)
405{
406	AUTOVAR(pu);
407	struct puffs_node *pn_new;
408	struct p9pnode *p9n = pn->pn_data;
409	p9pfid_t nfid = NEXTFID(p9p);
410	struct qid9p nqid;
411	int tries = 0;
412
413 again:
414	if (++tries > 5) {
415		rv = EPROTO;
416		goto out;
417	}
418
419	rv = proto_cc_dupfid(pu, p9n->fid_base, nfid);
420	if (rv)
421		goto out;
422
423	p9pbuf_put_1(pb, P9PROTO_T_CREATE);
424	p9pbuf_put_2(pb, tag);
425	p9pbuf_put_4(pb, nfid);
426	p9pbuf_put_str(pb, name);
427	p9pbuf_put_4(pb, dirbit | (vap->va_mode & 0777));
428	p9pbuf_put_1(pb, 0);
429	GETRESPONSE(pb);
430
431	rv = proto_expect_qid(pb, P9PROTO_R_CREATE, &nqid);
432	if (rv)
433		goto out;
434
435	/*
436	 * Now, little problem here: create returns an *open* fid.
437	 * So, clunk it and walk the parent directory to get a fid
438	 * which is not open for I/O yet.
439	 */
440	proto_cc_clunkfid(pu, nfid, 0);
441	nfid = NEXTFID(p9p);
442
443	p9pbuf_recycleout(pb);
444	p9pbuf_put_1(pb, P9PROTO_T_WALK);
445	p9pbuf_put_2(pb, tag);
446	p9pbuf_put_4(pb, p9n->fid_base);
447	p9pbuf_put_4(pb, nfid);
448	p9pbuf_put_2(pb, 1);
449	p9pbuf_put_str(pb, name);
450	GETRESPONSE(pb);
451
452	/*
453	 * someone removed it already? try again
454	 * note: this is kind of lose/lose
455	 */
456	if (p9pbuf_get_type(pb) != P9PROTO_R_WALK)
457		goto again;
458
459	pn_new = newp9pnode_va(pu, vap, nfid);
460	qid2vattr(&pn_new->pn_va, &nqid);
461	puffs_newinfo_setcookie(pni, pn_new);
462
463 out:
464	RETURN(rv);
465}
466
467int
468puffs9p_node_create(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
469	const struct puffs_cn *pcn, const struct vattr *va)
470{
471
472	return nodecreate(pu, opc, pni, pcn->pcn_name, va, 0);
473}
474
475int
476puffs9p_node_mkdir(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
477	const struct puffs_cn *pcn, const struct vattr *va)
478{
479
480	return nodecreate(pu, opc, pni, pcn->pcn_name,
481	    va, P9PROTO_CPERM_DIR);
482}
483
484/*
485 * Need to be a bit clever again: the fid is clunked no matter if
486 * the remove succeeds or not.  Re-getting a fid would be way too
487 * difficult in case the remove failed for a valid reason (directory
488 * not empty etcetc.).  So walk ourselves another fid to prod the
489 * ice with.
490 */
491static int
492noderemove(struct puffs_usermount *pu, struct puffs_node *pn)
493{
494	AUTOVAR(pu);
495	struct p9pnode *p9n = pn->pn_data;
496	p9pfid_t testfid = NEXTFID(p9p);
497
498	rv = proto_cc_dupfid(pu, p9n->fid_base, testfid);
499	if (rv)
500		goto out;
501
502	p9pbuf_put_1(pb, P9PROTO_T_REMOVE);
503	p9pbuf_put_2(pb, tag);
504	p9pbuf_put_4(pb, testfid);
505
506	/*
507	 * XXX: error handling isn't very robust, but doom is impending
508	 * anyway, so just accept we're going belly up and play dead
509	 */
510	GETRESPONSE(pb);
511
512	if (p9pbuf_get_type(pb) != P9PROTO_R_REMOVE) {
513		rv = EPROTO;
514	} else {
515		proto_cc_clunkfid(pu, p9n->fid_base, 0);
516		p9n->fid_base = P9P_INVALFID;
517		puffs_pn_remove(pn);
518	}
519
520 out:
521	if (rv == 0)
522		puffs_setback(pcc, PUFFS_SETBACK_NOREF_N2);
523
524	RETURN(rv);
525}
526
527int
528puffs9p_node_remove(struct puffs_usermount *pu, void *opc, void *targ,
529	const struct puffs_cn *pcn)
530{
531	struct puffs_node *pn = targ;
532
533	if (pn->pn_va.va_type == VDIR)
534		return EISDIR;
535
536	return noderemove(pu, pn);
537}
538
539int
540puffs9p_node_rmdir(struct puffs_usermount *pu, void *opc, void *targ,
541	const struct puffs_cn *pcn)
542{
543	struct puffs_node *pn = targ;
544
545	if (pn->pn_va.va_type != VDIR)
546		return ENOTDIR;
547
548	return noderemove(pu, pn);
549}
550
551/*
552 * 9P supports renames only for files within a directory
553 * from what I could tell.  So just support in-directory renames
554 * for now.
555 */
556int
557puffs9p_node_rename(struct puffs_usermount *pu, void *opc, void *src,
558	const struct puffs_cn *pcn_src, void *targ_dir, void *targ,
559	const struct puffs_cn *pcn_targ)
560{
561	AUTOVAR(pu);
562	struct puffs_node *pn_src = src;
563	struct p9pnode *p9n_src = pn_src->pn_data;
564
565	if (opc != targ_dir) {
566		rv = EOPNOTSUPP;
567		goto out;
568	}
569
570	/* 9P doesn't allow to overwrite in rename */
571	if (targ) {
572		struct puffs_node *pn_targ = targ;
573
574		rv = noderemove(pu, pn_targ->pn_data);
575		if (rv)
576			goto out;
577	}
578
579	p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
580	p9pbuf_put_2(pb, tag);
581	p9pbuf_put_4(pb, p9n_src->fid_base);
582	proto_make_stat(pb, NULL, pcn_targ->pcn_name, pn_src->pn_va.va_type);
583	GETRESPONSE(pb);
584
585	if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT)
586		rv = EPROTO;
587
588 out:
589	RETURN(rv);
590}
591
592/*
593 * - "here's one"
594 * - "9P"
595 * ~ "i'm not dead"
596 * - "you're not fooling anyone you know, you'll be stone dead in a minute
597 * - "he says he's not quite dead"
598 * - "isn't there anything you could do?"
599 * - *clunk*!
600 * - "thanks"
601 */
602int
603puffs9p_node_reclaim(struct puffs_usermount *pu, void *opc)
604{
605	struct puffs_node *pn = opc;
606	struct p9pnode *p9n = pn->pn_data;
607
608	assert(LIST_EMPTY(&p9n->dir_openlist));
609	assert(p9n->fid_read == P9P_INVALFID && p9n->fid_write == P9P_INVALFID);
610
611	proto_cc_clunkfid(pu, p9n->fid_base, 0);
612	free(p9n);
613	puffs_pn_put(pn);
614
615	return 0;
616}
617