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