node.c revision 1.26 1 /* $NetBSD: node.c,v 1.26 2020/05/27 00:05:22 uwe 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.26 2020/05/27 00:05:22 uwe 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 *
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
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
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
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
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 p9pbuf_get_4(pb, &count);
191
192 /*
193 * if count is 0, assume we at end-of-dir. dfp is no longer
194 * useful, so nuke it
195 */
196 if (count == 0) {
197 *eofflag = 1;
198 releasedf(pu, dfp);
199 goto out;
200 }
201
202 while (count > 0) {
203 if ((rv = proto_getstat(pu, pb, &va, &name, &statsize))) {
204 /*
205 * If there was an error, it's unlikely we'll be
206 * coming back, so just nuke the dfp. If we do
207 * come back for some strange reason, we'll just
208 * regen it.
209 */
210 releasedf(pu, dfp);
211 goto out;
212 }
213
214 puffs_nextdent(&dent, name, va.va_fileid,
215 puffs_vtype2dt(va.va_type), reslen);
216
217 count -= statsize;
218 *readoff += statsize;
219 dfp->seekoff += statsize;
220 free(name);
221 }
222
223 storedf(p9n, dfp);
224
225 out:
226 RETURN(rv);
227 }
228
229 int
230 puffs9p_node_setattr(struct puffs_usermount *pu, void *opc,
231 const struct vattr *va, const struct puffs_cred *pcr)
232 {
233 AUTOVAR(pu);
234 struct puffs_node *pn = opc;
235 struct p9pnode *p9n = pn->pn_data;
236
237 tag = NEXTTAG(p9p);
238 p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
239 p9pbuf_put_2(pb, tag);
240 p9pbuf_put_4(pb, p9n->fid_base);
241 proto_make_stat(pu, pb, va, NULL, pn->pn_va.va_type);
242 GETRESPONSE(pb);
243
244 if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT) {
245 rv = proto_handle_rerror(pu, pb);
246 }
247
248 out:
249 RETURN(rv);
250 }
251
252 /*
253 * Ok, time to get clever. There are two possible cases: we are
254 * opening a file or we are opening a directory.
255 *
256 * If it's a directory, don't bother opening it here, but rather
257 * wait until readdir, since it's probable we need to be able to
258 * open a directory there in any case.
259 *
260 * If it's a regular file, open it here with whatever credentials
261 * we happen to have. Let the upper layers of the kernel worry
262 * about permission control.
263 */
264 int
265 puffs9p_node_open(struct puffs_usermount *pu, void *opc, int mode,
266 const struct puffs_cred *pcr)
267 {
268 struct puffs_cc *pcc = puffs_cc_getcc(pu);
269 struct puffs9p *p9p = puffs_getspecific(pu);
270 struct puffs_node *pn = opc;
271 struct p9pnode *p9n = pn->pn_data;
272 p9pfid_t nfid;
273 int error = 0;
274
275 puffs_setback(pcc, PUFFS_SETBACK_INACT_N1);
276 if (pn->pn_va.va_type != VDIR) {
277 if (mode & FREAD && p9n->fid_read == P9P_INVALFID) {
278 nfid = NEXTFID(p9p);
279 error = proto_cc_open(pu, p9n->fid_base, nfid,
280 P9PROTO_OMODE_READ);
281 if (error)
282 return error;
283 p9n->fid_read = nfid;
284 }
285 if (mode & FWRITE && p9n->fid_write == P9P_INVALFID) {
286 nfid = NEXTFID(p9p);
287 error = proto_cc_open(pu, p9n->fid_base, nfid,
288 P9PROTO_OMODE_WRITE);
289 if (error)
290 return error;
291 p9n->fid_write = nfid;
292 }
293 }
294
295 return 0;
296 }
297
298 int
299 puffs9p_node_inactive(struct puffs_usermount *pu, void *opc)
300 {
301 struct puffs_node *pn = opc;
302 struct p9pnode *p9n = pn->pn_data;
303
304 if (pn->pn_va.va_type == VDIR) {
305 nukealldf(pu, p9n);
306 } else {
307 if (p9n->fid_read != P9P_INVALFID) {
308 proto_cc_clunkfid(pu, p9n->fid_read, 0);
309 p9n->fid_read = P9P_INVALFID;
310 }
311 if (p9n->fid_write != P9P_INVALFID) {
312 proto_cc_clunkfid(pu, p9n->fid_write, 0);
313 p9n->fid_write = P9P_INVALFID;
314 }
315 }
316
317 return 0;
318 }
319
320 int
321 puffs9p_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
322 off_t offset, size_t *resid, const struct puffs_cred *pcr,
323 int ioflag)
324 {
325 AUTOVAR(pu);
326 struct puffs_node *pn = opc;
327 struct p9pnode *p9n = pn->pn_data;
328 uint32_t count;
329 size_t nread;
330
331 nread = 0;
332 while (*resid > 0 && (uint64_t)(offset+nread) < pn->pn_va.va_size) {
333 tag = NEXTTAG(p9p);
334 p9pbuf_put_1(pb, P9PROTO_T_READ);
335 p9pbuf_put_2(pb, tag);
336 p9pbuf_put_4(pb, p9n->fid_read);
337 p9pbuf_put_8(pb, offset+nread);
338 p9pbuf_put_4(pb, MIN((uint32_t)*resid,p9p->maxreq-24));
339 GETRESPONSE(pb);
340
341 if (p9pbuf_get_type(pb) != P9PROTO_R_READ) {
342 rv = proto_handle_rerror(pu, pb);
343 break;
344 }
345
346 p9pbuf_get_4(pb, &count);
347 if ((rv = p9pbuf_read_data(pb, buf + nread, count)))
348 break;
349
350 if (count == 0)
351 break;
352
353 *resid -= count;
354 nread += count;
355
356 p9pbuf_recycleout(pb);
357 }
358
359 out:
360 RETURN(rv);
361 }
362
363 int
364 puffs9p_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
365 off_t offset, size_t *resid, const struct puffs_cred *cred,
366 int ioflag)
367 {
368 AUTOVAR(pu);
369 struct puffs_node *pn = opc;
370 struct p9pnode *p9n = pn->pn_data;
371 uint32_t chunk, count;
372 size_t nwrite;
373
374 if (ioflag & PUFFS_IO_APPEND)
375 offset = pn->pn_va.va_size;
376
377 nwrite = 0;
378 while (*resid > 0) {
379 chunk = MIN(*resid, p9p->maxreq-32);
380
381 tag = NEXTTAG(p9p);
382 p9pbuf_put_1(pb, P9PROTO_T_WRITE);
383 p9pbuf_put_2(pb, tag);
384 p9pbuf_put_4(pb, p9n->fid_write);
385 p9pbuf_put_8(pb, offset+nwrite);
386 p9pbuf_put_4(pb, chunk);
387 p9pbuf_write_data(pb, buf+nwrite, chunk);
388 GETRESPONSE(pb);
389
390 if (p9pbuf_get_type(pb) != P9PROTO_R_WRITE) {
391 rv = proto_handle_rerror(pu, pb);
392 break;
393 }
394
395 p9pbuf_get_4(pb, &count);
396 *resid -= count;
397 nwrite += count;
398
399 if (count != chunk) {
400 rv = EPROTO;
401 break;
402 }
403
404 p9pbuf_recycleout(pb);
405 }
406
407 out:
408 RETURN(rv);
409 }
410
411 static int
412 nodecreate(struct puffs_usermount *pu, struct puffs_node *pn,
413 struct puffs_newinfo *pni, const char *name,
414 const struct vattr *vap, uint32_t dirbit)
415 {
416 AUTOVAR(pu);
417 struct puffs_node *pn_new;
418 struct p9pnode *p9n = pn->pn_data;
419 p9pfid_t nfid = NEXTFID(p9p);
420 struct qid9p nqid;
421 int tries = 0;
422
423 again:
424 if (++tries > 5) {
425 rv = EPROTO;
426 goto out;
427 }
428
429 rv = proto_cc_dupfid(pu, p9n->fid_base, nfid);
430 if (rv)
431 goto out;
432
433 tag = NEXTTAG(p9p);
434 p9pbuf_put_1(pb, P9PROTO_T_CREATE);
435 p9pbuf_put_2(pb, tag);
436 p9pbuf_put_4(pb, nfid);
437 p9pbuf_put_str(pb, name);
438 p9pbuf_put_4(pb, dirbit | (vap->va_mode & 0777));
439 p9pbuf_put_1(pb, 0);
440 if (p9p->protover == P9PROTO_VERSION_U)
441 p9pbuf_put_str(pb, ""); /* extension[s] */
442 GETRESPONSE(pb);
443
444 rv = proto_expect_qid(pu, pb, P9PROTO_R_CREATE, &nqid);
445 if (rv)
446 goto out;
447
448 /*
449 * Now, little problem here: create returns an *open* fid.
450 * So, clunk it and walk the parent directory to get a fid
451 * which is not open for I/O yet.
452 */
453 proto_cc_clunkfid(pu, nfid, 0);
454 nfid = NEXTFID(p9p);
455
456 p9pbuf_recycleout(pb);
457 tag = NEXTTAG(p9p);
458 p9pbuf_put_1(pb, P9PROTO_T_WALK);
459 p9pbuf_put_2(pb, tag);
460 p9pbuf_put_4(pb, p9n->fid_base);
461 p9pbuf_put_4(pb, nfid);
462 p9pbuf_put_2(pb, 1);
463 p9pbuf_put_str(pb, name);
464 GETRESPONSE(pb);
465
466 /*
467 * someone removed it already? try again
468 * note: this is kind of lose/lose
469 */
470 if (p9pbuf_get_type(pb) != P9PROTO_R_WALK)
471 goto again;
472
473 pn_new = newp9pnode_va(pu, vap, nfid);
474 qid2vattr(&pn_new->pn_va, &nqid);
475 puffs_newinfo_setcookie(pni, pn_new);
476
477 out:
478 RETURN(rv);
479 }
480
481 int
482 puffs9p_node_create(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
483 const struct puffs_cn *pcn, const struct vattr *va)
484 {
485
486 return nodecreate(pu, opc, pni, pcn->pcn_name, va, 0);
487 }
488
489 int
490 puffs9p_node_mkdir(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
491 const struct puffs_cn *pcn, const struct vattr *va)
492 {
493
494 return nodecreate(pu, opc, pni, pcn->pcn_name,
495 va, P9PROTO_CPERM_DIR);
496 }
497
498 /*
499 * Need to be a bit clever again: the fid is clunked no matter if
500 * the remove succeeds or not. Re-getting a fid would be way too
501 * difficult in case the remove failed for a valid reason (directory
502 * not empty etcetc.). So walk ourselves another fid to prod the
503 * ice with.
504 */
505 static int
506 noderemove(struct puffs_usermount *pu, struct puffs_node *pn)
507 {
508 AUTOVAR(pu);
509 struct p9pnode *p9n = pn->pn_data;
510 p9pfid_t testfid = NEXTFID(p9p);
511
512 rv = proto_cc_dupfid(pu, p9n->fid_base, testfid);
513 if (rv)
514 goto out;
515
516 tag = NEXTTAG(p9p);
517 p9pbuf_put_1(pb, P9PROTO_T_REMOVE);
518 p9pbuf_put_2(pb, tag);
519 p9pbuf_put_4(pb, testfid);
520
521 /*
522 * XXX: error handling isn't very robust, but doom is impending
523 * anyway, so just accept we're going belly up and play dead
524 */
525 GETRESPONSE(pb);
526
527 if (p9pbuf_get_type(pb) != P9PROTO_R_REMOVE) {
528 rv = proto_handle_rerror(pu, pb);
529 } else {
530 proto_cc_clunkfid(pu, p9n->fid_base, 0);
531 p9n->fid_base = P9P_INVALFID;
532 puffs_pn_remove(pn);
533 }
534
535 out:
536 if (rv == 0)
537 puffs_setback(pcc, PUFFS_SETBACK_NOREF_N2);
538
539 RETURN(rv);
540 }
541
542 int
543 puffs9p_node_remove(struct puffs_usermount *pu, void *opc, void *targ,
544 const struct puffs_cn *pcn)
545 {
546 struct puffs_node *pn = targ;
547
548 if (pn->pn_va.va_type == VDIR)
549 return EISDIR;
550
551 return noderemove(pu, pn);
552 }
553
554 int
555 puffs9p_node_rmdir(struct puffs_usermount *pu, void *opc, void *targ,
556 const struct puffs_cn *pcn)
557 {
558 struct puffs_node *pn = targ;
559
560 if (pn->pn_va.va_type != VDIR)
561 return ENOTDIR;
562
563 return noderemove(pu, pn);
564 }
565
566 /*
567 * 9P supports renames only for files within a directory
568 * from what I could tell. So just support in-directory renames
569 * for now.
570 */
571 int
572 puffs9p_node_rename(struct puffs_usermount *pu, void *opc, void *src,
573 const struct puffs_cn *pcn_src, void *targ_dir, void *targ,
574 const struct puffs_cn *pcn_targ)
575 {
576 AUTOVAR(pu);
577 struct puffs_node *pn_src = src;
578 struct p9pnode *p9n_src = pn_src->pn_data;
579
580 if (opc != targ_dir) {
581 rv = EOPNOTSUPP;
582 goto out;
583 }
584
585 /* 9P doesn't allow to overwrite in rename */
586 if (targ) {
587 struct puffs_node *pn_targ = targ;
588
589 rv = noderemove(pu, pn_targ);
590 if (rv)
591 goto out;
592 }
593
594 tag = NEXTTAG(p9p);
595 p9pbuf_put_1(pb, P9PROTO_T_WSTAT);
596 p9pbuf_put_2(pb, tag);
597 p9pbuf_put_4(pb, p9n_src->fid_base);
598 proto_make_stat(pu, pb, NULL, pcn_targ->pcn_name, pn_src->pn_va.va_type);
599 GETRESPONSE(pb);
600
601 if (p9pbuf_get_type(pb) != P9PROTO_R_WSTAT)
602 rv = proto_handle_rerror(pu, pb);
603
604 out:
605 RETURN(rv);
606 }
607
608 /*
609 * - "here's one"
610 * - "9P"
611 * ~ "i'm not dead"
612 * - "you're not fooling anyone you know, you'll be stone dead in a minute
613 * - "he says he's not quite dead"
614 * - "isn't there anything you could do?"
615 * - *clunk*!
616 * - "thanks"
617 */
618 int
619 puffs9p_node_reclaim(struct puffs_usermount *pu, void *opc)
620 {
621 struct puffs_node *pn = opc;
622 struct p9pnode *p9n = pn->pn_data;
623
624 assert(LIST_EMPTY(&p9n->dir_openlist));
625 assert(p9n->fid_read == P9P_INVALFID && p9n->fid_write == P9P_INVALFID);
626
627 proto_cc_clunkfid(pu, p9n->fid_base, 0);
628 free(p9n);
629 puffs_pn_put(pn);
630
631 return 0;
632 }
633