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