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