1 /* $NetBSD: node.c,v 1.61 2010/04/01 02:34:09 pooka Exp $ */ 2 3 /* 4 * Copyright (c) 2006-2009 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.61 2010/04/01 02:34:09 pooka Exp $"); 31 #endif /* !lint */ 32 33 #include <assert.h> 34 #include <errno.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 38 #include "psshfs.h" 39 #include "sftp_proto.h" 40 41 int 42 psshfs_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc, 43 struct puffs_newinfo *pni, const struct puffs_cn *pcn) 44 { 45 struct psshfs_ctx *pctx = puffs_getspecific(pu); 46 struct puffs_node *pn_dir = opc; 47 struct psshfs_node *psn, *psn_dir = pn_dir->pn_data; 48 struct puffs_node *pn; 49 struct psshfs_dir *pd; 50 struct vattr va; 51 int rv; 52 53 if (PCNISDOTDOT(pcn)) { 54 psn = psn_dir->parent->pn_data; 55 psn->stat &= ~PSN_RECLAIMED; 56 57 puffs_newinfo_setcookie(pni, psn_dir->parent); 58 puffs_newinfo_setvtype(pni, VDIR); 59 return 0; 60 } 61 62 rv = sftp_readdir(pu, pctx, pn_dir); 63 if (rv) { 64 if (rv != EPERM) 65 return rv; 66 67 /* 68 * Can't read the directory. We still might be 69 * able to find the node with getattr in -r+x dirs 70 */ 71 rv = getpathattr(pu, PCNPATH(pcn), &va); 72 if (rv) 73 return rv; 74 75 /* guess */ 76 if (va.va_type == VDIR) 77 va.va_nlink = 2; 78 else 79 va.va_nlink = 1; 80 81 pn = allocnode(pu, pn_dir, pcn->pcn_name, &va); 82 psn = pn->pn_data; 83 psn->attrread = time(NULL); 84 } else { 85 pd = lookup(psn_dir->dir, psn_dir->dentnext, pcn->pcn_name); 86 if (!pd) { 87 return ENOENT; 88 } 89 90 if (pd->entry) 91 pn = pd->entry; 92 else 93 pd->entry = pn = makenode(pu, pn_dir, pd, &pd->va); 94 95 /* 96 * sure sure we have fresh attributes. most likely we will 97 * have them cached. we might not if we go through: 98 * create - reclaim - lookup (this). 99 */ 100 rv = getnodeattr(pu, pn, PCNPATH(pcn)); 101 if (rv) 102 return rv; 103 104 psn = pn->pn_data; 105 } 106 107 psn->stat &= ~PSN_RECLAIMED; 108 109 puffs_newinfo_setcookie(pni, pn); 110 puffs_newinfo_setvtype(pni, pn->pn_va.va_type); 111 puffs_newinfo_setsize(pni, pn->pn_va.va_size); 112 113 return 0; 114 } 115 116 int 117 psshfs_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc, 118 struct vattr *vap, const struct puffs_cred *pcr) 119 { 120 struct puffs_node *pn = opc; 121 int rv; 122 123 rv = getnodeattr(pu, pn, NULL); 124 if (rv) 125 return rv; 126 127 memcpy(vap, &pn->pn_va, sizeof(struct vattr)); 128 129 return 0; 130 } 131 132 int 133 psshfs_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc, 134 const struct vattr *va, const struct puffs_cred *pcr) 135 { 136 PSSHFSAUTOVAR(pu); 137 struct vattr kludgeva; 138 struct puffs_node *pn = opc; 139 140 psbuf_req_str(pb, SSH_FXP_SETSTAT, reqid, PNPATH(pn)); 141 142 memcpy(&kludgeva, va, sizeof(struct vattr)); 143 144 /* XXX: kludge due to openssh server implementation */ 145 if (va->va_atime.tv_sec != PUFFS_VNOVAL 146 && va->va_mtime.tv_sec == PUFFS_VNOVAL) { 147 if (pn->pn_va.va_mtime.tv_sec != PUFFS_VNOVAL) 148 kludgeva.va_mtime.tv_sec = pn->pn_va.va_mtime.tv_sec; 149 else 150 kludgeva.va_mtime.tv_sec = va->va_atime.tv_sec; 151 } 152 if (va->va_mtime.tv_sec != PUFFS_VNOVAL 153 && va->va_atime.tv_sec == PUFFS_VNOVAL) { 154 if (pn->pn_va.va_atime.tv_sec != PUFFS_VNOVAL) 155 kludgeva.va_atime.tv_sec = pn->pn_va.va_atime.tv_sec; 156 else 157 kludgeva.va_atime.tv_sec = va->va_mtime.tv_sec; 158 } 159 160 psbuf_put_vattr(pb, &kludgeva, pctx); 161 GETRESPONSE(pb, pctx->sshfd); 162 163 rv = psbuf_expect_status(pb); 164 if (rv == 0) 165 puffs_setvattr(&pn->pn_va, &kludgeva); 166 167 out: 168 PSSHFSRETURN(rv); 169 } 170 171 int 172 psshfs_node_create(struct puffs_usermount *pu, puffs_cookie_t opc, 173 struct puffs_newinfo *pni, const struct puffs_cn *pcn, 174 const struct vattr *va) 175 { 176 PSSHFSAUTOVAR(pu); 177 struct puffs_node *pn = opc; 178 struct puffs_node *pn_new; 179 char *fhand = NULL; 180 uint32_t fhandlen; 181 182 /* Create node on server first */ 183 psbuf_req_str(pb, SSH_FXP_OPEN, reqid, PCNPATH(pcn)); 184 psbuf_put_4(pb, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC); 185 psbuf_put_vattr(pb, va, pctx); 186 GETRESPONSE(pb, pctx->sshfd); 187 rv = psbuf_expect_handle(pb, &fhand, &fhandlen); 188 if (rv) 189 goto out; 190 191 /* 192 * Do *not* create the local node before getting a response 193 * from the server. Otherwise we might screw up consistency, 194 * namely that the node can be looked up before create has 195 * returned (mind you, the kernel will unlock the directory 196 * before the create call from userspace returns). 197 */ 198 pn_new = allocnode(pu, pn, pcn->pcn_name, va); 199 if (!pn_new) { 200 struct puffs_framebuf *pb2 = psbuf_makeout(); 201 reqid = NEXTREQ(pctx); 202 psbuf_req_str(pb2, SSH_FXP_REMOVE, reqid, PCNPATH(pcn)); 203 JUSTSEND(pb2, pctx->sshfd); 204 rv = ENOMEM; 205 } 206 207 if (pn_new) 208 puffs_newinfo_setcookie(pni, pn_new); 209 210 reqid = NEXTREQ(pctx); 211 psbuf_recycleout(pb); 212 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, fhand, fhandlen); 213 JUSTSEND(pb, pctx->sshfd); 214 free(fhand); 215 return rv; 216 217 out: 218 free(fhand); 219 PSSHFSRETURN(rv); 220 } 221 222 /* 223 * Open a file handle. This is used for read and write. We do not 224 * wait here for the success or failure of this operation. This is 225 * because otherwise opening and closing file handles would block 226 * reading potentially cached information. Rather, we defer the wait 227 * to read/write and therefore allow cached access without a wait. 228 * 229 * If we have not yet succesfully opened a type of handle, we do wait 230 * here. Also, if a lazy open fails, we revert back to the same 231 * state of waiting. 232 */ 233 int 234 psshfs_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode, 235 const struct puffs_cred *pcr) 236 { 237 struct puffs_cc *pcc = puffs_cc_getcc(pu); 238 struct psshfs_ctx *pctx = puffs_getspecific(pu); 239 struct puffs_framebuf *pb, *pb2; 240 struct vattr va; 241 struct puffs_node *pn = opc; 242 struct psshfs_node *psn = pn->pn_data; 243 uint32_t reqid; 244 int didread, didwrite; 245 int rv = 0; 246 247 if (pn->pn_va.va_type == VDIR) 248 return 0; 249 250 puffs_setback(pcc, PUFFS_SETBACK_INACT_N1); 251 puffs_vattr_null(&va); 252 didread = didwrite = 0; 253 if (mode & FREAD && psn->fhand_r == NULL && psn->lazyopen_r == NULL) { 254 pb = psbuf_makeout(); 255 256 reqid = NEXTREQ(pctx); 257 psbuf_req_str(pb, SSH_FXP_OPEN, reqid, PNPATH(pn)); 258 psbuf_put_4(pb, SSH_FXF_READ); 259 psbuf_put_vattr(pb, &va, pctx); 260 261 if (puffs_framev_enqueue_cb(pu, pctx->sshfd_data, pb, 262 lazyopen_rresp, psn, 0) == -1) { 263 rv = errno; 264 puffs_framebuf_destroy(pb); 265 goto out; 266 } 267 268 psn->lazyopen_r = pb; 269 didread = 1; 270 } 271 if (mode & FWRITE && psn->fhand_w == NULL && psn->lazyopen_w == NULL) { 272 pb2 = psbuf_makeout(); 273 274 reqid = NEXTREQ(pctx); 275 psbuf_req_str(pb2, SSH_FXP_OPEN, reqid, PNPATH(pn)); 276 psbuf_put_4(pb2, SSH_FXF_WRITE); 277 psbuf_put_vattr(pb2, &va, pctx); 278 279 if (puffs_framev_enqueue_cb(pu, pctx->sshfd_data, pb2, 280 lazyopen_wresp, psn, 0) == -1) { 281 rv = errno; 282 puffs_framebuf_destroy(pb2); 283 goto out; 284 } 285 286 psn->lazyopen_w = pb2; 287 didwrite = 1; 288 } 289 psn->stat &= ~PSN_HANDLECLOSE; 290 291 out: 292 /* wait? */ 293 if (didread && (psn->stat & PSN_DOLAZY_R) == 0) { 294 assert(psn->lazyopen_r); 295 296 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_r, pcc); 297 lazyopen_rresp(pu, psn->lazyopen_r, psn, rv); 298 if (psn->fhand_r) { 299 psn->stat |= PSN_DOLAZY_R; 300 } else { 301 if (psn->lazyopen_err_r) 302 return psn->lazyopen_err_r; 303 return EINVAL; 304 } 305 } 306 307 /* wait? */ 308 if (didwrite && (psn->stat & PSN_DOLAZY_W) == 0) { 309 assert(psn->lazyopen_w); 310 311 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_w, pcc); 312 lazyopen_wresp(pu, psn->lazyopen_w, psn, rv); 313 if (psn->fhand_w) { 314 psn->stat |= PSN_DOLAZY_W; 315 } else { 316 if (psn->lazyopen_err_w) 317 return psn->lazyopen_err_w; 318 return EINVAL; 319 } 320 } 321 322 return rv; 323 } 324 325 int 326 psshfs_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc) 327 { 328 struct puffs_node *pn = opc; 329 330 closehandles(pu, pn->pn_data, HANDLE_READ | HANDLE_WRITE); 331 return 0; 332 } 333 334 int 335 psshfs_node_readdir(struct puffs_usermount *pu, puffs_cookie_t opc, 336 struct dirent *dent, off_t *readoff, size_t *reslen, 337 const struct puffs_cred *pcr, int *eofflag, 338 off_t *cookies, size_t *ncookies) 339 { 340 struct puffs_cc *pcc = puffs_cc_getcc(pu); 341 struct psshfs_ctx *pctx = puffs_getspecific(pu); 342 struct puffs_node *pn = opc; 343 struct psshfs_node *psn = pn->pn_data; 344 struct psshfs_dir *pd; 345 size_t i; 346 int rv, set_readdir; 347 348 restart: 349 if (psn->stat & PSN_READDIR) { 350 struct psshfs_wait pw; 351 352 set_readdir = 0; 353 pw.pw_cc = pcc; 354 pw.pw_type = PWTYPE_READDIR; 355 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 356 puffs_cc_yield(pcc); 357 goto restart; 358 } else { 359 psn->stat |= PSN_READDIR; 360 set_readdir = 1; 361 } 362 363 *ncookies = 0; 364 rv = sftp_readdir(pu, pctx, pn); 365 if (rv) 366 goto out; 367 368 /* find next dirent */ 369 for (i = *readoff;;i++) { 370 if (i >= psn->dentnext) 371 goto out; 372 pd = &psn->dir[i]; 373 if (pd->valid) 374 break; 375 } 376 377 for (;;) { 378 *readoff = i; 379 if (!puffs_nextdent(&dent, pd->entryname, 380 pd->va.va_fileid, puffs_vtype2dt(pd->va.va_type), reslen)) { 381 rv = 0; 382 goto out; 383 } 384 385 /* find next entry, store possible nfs key */ 386 do { 387 if (++i >= psn->dentnext) 388 goto out; 389 pd = &psn->dir[i]; 390 } while (pd->valid == 0); 391 PUFFS_STORE_DCOOKIE(cookies, ncookies, (off_t)i); 392 } 393 394 out: 395 if (rv == 0) { 396 if (i >= psn->dentnext) 397 *eofflag = 1; 398 399 *readoff = i; 400 } 401 402 if (set_readdir) { 403 struct psshfs_wait *pw; 404 405 /* all will likely run to completion because of cache */ 406 TAILQ_FOREACH(pw, &psn->pw, pw_entries) { 407 assert(pw->pw_type == PWTYPE_READDIR); 408 puffs_cc_schedule(pw->pw_cc); 409 TAILQ_REMOVE(&psn->pw, pw, pw_entries); 410 } 411 412 psn->stat &= ~PSN_READDIR; 413 } 414 415 return rv; 416 } 417 418 int 419 psshfs_node_read(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, 420 off_t offset, size_t *resid, const struct puffs_cred *pcr, 421 int ioflag) 422 { 423 PSSHFSAUTOVAR(pu); 424 struct puffs_node *pn = opc; 425 struct psshfs_node *psn = pn->pn_data; 426 struct psshfs_wait *pwp; 427 uint32_t readlen; 428 429 if (pn->pn_va.va_type == VDIR) { 430 rv = EISDIR; 431 goto farout; 432 } 433 434 /* check that a lazyopen didn't fail */ 435 if (!psn->fhand_r && !psn->lazyopen_r) { 436 rv = psn->lazyopen_err_r; 437 goto farout; 438 } 439 440 /* if someone is already waiting for the lazyopen, "just" wait */ 441 if (psn->stat & PSN_LAZYWAIT_R) { 442 struct psshfs_wait pw; 443 444 assert(psn->lazyopen_r); 445 446 pw.pw_cc = pcc; 447 pw.pw_type = PWTYPE_READ1; 448 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 449 puffs_cc_yield(pcc); 450 } 451 452 /* if lazyopening, wait for the result */ 453 if (psn->lazyopen_r) { 454 psn->stat |= PSN_LAZYWAIT_R; 455 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_r, pcc); 456 lazyopen_rresp(pu, psn->lazyopen_r, psn, rv); 457 458 /* schedule extra waiters */ 459 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 460 if (pwp->pw_type == PWTYPE_READ1) { 461 puffs_cc_schedule(pwp->pw_cc); 462 TAILQ_REMOVE(&psn->pw, pwp, pw_entries); 463 } 464 psn->stat &= ~PSN_LAZYWAIT_R; 465 466 if ((rv = psn->lazyopen_err_r) != 0) 467 goto farout; 468 } 469 470 /* if there is still no handle, just refuse to live with this */ 471 if (!psn->fhand_r) { 472 rv = EINVAL; 473 goto farout; 474 } 475 476 readlen = *resid; 477 psbuf_req_data(pb, SSH_FXP_READ, reqid, psn->fhand_r, psn->fhand_r_len); 478 psbuf_put_8(pb, offset); 479 psbuf_put_4(pb, readlen); 480 481 /* 482 * Do this *after* accessing the file, the handle might not 483 * exist after blocking. 484 */ 485 if (max_reads && ++psn->readcount > max_reads) { 486 struct psshfs_wait pw; 487 488 pw.pw_cc = pcc; 489 pw.pw_type = PWTYPE_READ2; 490 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 491 puffs_cc_yield(pcc); 492 } 493 494 GETRESPONSE(pb, pctx->sshfd_data); 495 496 rv = psbuf_do_data(pb, buf, &readlen); 497 if (rv == 0) 498 *resid -= readlen; 499 500 out: 501 if (max_reads && --psn->readcount >= max_reads) { 502 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 503 if (pwp->pw_type == PWTYPE_READ2) 504 break; 505 assert(pwp != NULL); 506 puffs_cc_schedule(pwp->pw_cc); 507 TAILQ_REMOVE(&psn->pw, pwp, pw_entries); 508 } 509 510 farout: 511 /* check if we need a lazyclose */ 512 if (psn->stat & PSN_HANDLECLOSE && psn->fhand_r) { 513 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 514 if (pwp->pw_type == PWTYPE_READ1) 515 break; 516 if (pwp == NULL) 517 closehandles(pu, psn, HANDLE_READ); 518 } 519 PSSHFSRETURN(rv); 520 } 521 522 /* XXX: we should getattr for size */ 523 int 524 psshfs_node_write(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, 525 off_t offset, size_t *resid, const struct puffs_cred *cred, 526 int ioflag) 527 { 528 PSSHFSAUTOVAR(pu); 529 struct psshfs_wait *pwp; 530 struct puffs_node *pn = opc; 531 struct psshfs_node *psn = pn->pn_data; 532 uint32_t writelen; 533 534 if (pn->pn_va.va_type == VDIR) { 535 rv = EISDIR; 536 goto out; 537 } 538 539 /* check that a lazyopen didn't fail */ 540 if (!psn->fhand_w && !psn->lazyopen_w) { 541 rv = psn->lazyopen_err_w; 542 goto out; 543 } 544 545 if (psn->stat & PSN_LAZYWAIT_W) { 546 struct psshfs_wait pw; 547 548 assert(psn->lazyopen_w); 549 550 pw.pw_cc = pcc; 551 pw.pw_type = PWTYPE_WRITE; 552 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 553 puffs_cc_yield(pcc); 554 } 555 556 /* 557 * If lazyopening, wait for the result. 558 * There can still be more than oen writer at a time in case 559 * the kernel issues write FAFs. 560 */ 561 if (psn->lazyopen_w) { 562 psn->stat |= PSN_LAZYWAIT_W; 563 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_w, pcc); 564 lazyopen_wresp(pu, psn->lazyopen_w, psn, rv); 565 566 /* schedule extra waiters */ 567 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 568 if (pwp->pw_type == PWTYPE_WRITE) { 569 puffs_cc_schedule(pwp->pw_cc); 570 TAILQ_REMOVE(&psn->pw, pwp, pw_entries); 571 } 572 psn->stat &= ~PSN_LAZYWAIT_W; 573 574 if ((rv = psn->lazyopen_err_w) != 0) 575 goto out; 576 } 577 578 if (!psn->fhand_w) { 579 abort(); 580 rv = EINVAL; 581 goto out; 582 } 583 584 writelen = *resid; 585 psbuf_req_data(pb, SSH_FXP_WRITE, reqid, psn->fhand_w,psn->fhand_w_len); 586 psbuf_put_8(pb, offset); 587 psbuf_put_data(pb, buf, writelen); 588 GETRESPONSE(pb, pctx->sshfd_data); 589 590 rv = psbuf_expect_status(pb); 591 if (rv == 0) 592 *resid = 0; 593 594 if (pn->pn_va.va_size < (uint64_t)offset + writelen) 595 pn->pn_va.va_size = offset + writelen; 596 597 out: 598 /* check if we need a lazyclose */ 599 if (psn->stat & PSN_HANDLECLOSE && psn->fhand_w) { 600 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 601 if (pwp->pw_type == PWTYPE_WRITE) 602 break; 603 if (pwp == NULL) 604 closehandles(pu, psn, HANDLE_WRITE); 605 } 606 PSSHFSRETURN(rv); 607 } 608 609 int 610 psshfs_node_readlink(struct puffs_usermount *pu, puffs_cookie_t opc, 611 const struct puffs_cred *cred, char *linkvalue, size_t *linklen) 612 { 613 PSSHFSAUTOVAR(pu); 614 struct puffs_node *pn = opc; 615 struct psshfs_node *psn = pn->pn_data; 616 uint32_t count; 617 618 if (pctx->protover < 3) { 619 rv = EOPNOTSUPP; 620 goto out; 621 } 622 623 /* 624 * check if we can use a cached version 625 * 626 * XXX: we might end up reading the same link multiple times 627 * from the server if we get many requests at once, but that's 628 * quite harmless as this routine is reentrant. 629 */ 630 if (psn->symlink && !REFRESHTIMEOUT(pctx, time(NULL) - psn->slread)) 631 goto copy; 632 633 if (psn->symlink) { 634 free(psn->symlink); 635 psn->symlink = NULL; 636 psn->slread = 0; 637 } 638 639 psbuf_req_str(pb, SSH_FXP_READLINK, reqid, PNPATH(pn)); 640 GETRESPONSE(pb, pctx->sshfd); 641 642 rv = psbuf_expect_name(pb, &count); 643 if (rv) 644 goto out; 645 if (count != 1) { 646 rv = EPROTO; 647 goto out; 648 } 649 650 rv = psbuf_get_str(pb, &psn->symlink, NULL); 651 if (rv) 652 goto out; 653 psn->slread = time(NULL); 654 655 copy: 656 *linklen = strlen(psn->symlink); 657 (void) memcpy(linkvalue, psn->symlink, *linklen); 658 659 out: 660 PSSHFSRETURN(rv); 661 } 662 663 static int 664 doremove(struct puffs_usermount *pu, struct puffs_node *pn_dir, 665 struct puffs_node *pn, const char *name) 666 { 667 PSSHFSAUTOVAR(pu); 668 int op; 669 670 if (pn->pn_va.va_type == VDIR) 671 op = SSH_FXP_RMDIR; 672 else 673 op = SSH_FXP_REMOVE; 674 675 psbuf_req_str(pb, op, reqid, PNPATH(pn)); 676 GETRESPONSE(pb, pctx->sshfd); 677 678 rv = psbuf_expect_status(pb); 679 if (rv == 0) 680 nukenode(pn, name, 0); 681 682 out: 683 PSSHFSRETURN(rv); 684 } 685 686 int 687 psshfs_node_remove(struct puffs_usermount *pu, puffs_cookie_t opc, 688 puffs_cookie_t targ, const struct puffs_cn *pcn) 689 { 690 struct puffs_node *pn_targ = targ; 691 int rv; 692 693 assert(pn_targ->pn_va.va_type != VDIR); 694 695 rv = doremove(pu, opc, targ, pcn->pcn_name); 696 if (rv == 0) 697 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2); 698 699 return rv; 700 } 701 702 int 703 psshfs_node_rmdir(struct puffs_usermount *pu, puffs_cookie_t opc, 704 puffs_cookie_t targ, const struct puffs_cn *pcn) 705 { 706 struct puffs_node *pn_targ = targ; 707 int rv; 708 709 assert(pn_targ->pn_va.va_type == VDIR); 710 711 rv = doremove(pu, opc, targ, pcn->pcn_name); 712 if (rv == 0) 713 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2); 714 715 return rv; 716 } 717 718 int 719 psshfs_node_mkdir(struct puffs_usermount *pu, puffs_cookie_t opc, 720 struct puffs_newinfo *pni, const struct puffs_cn *pcn, 721 const struct vattr *va) 722 { 723 PSSHFSAUTOVAR(pu); 724 struct puffs_node *pn = opc; 725 struct puffs_node *pn_new; 726 727 psbuf_req_str(pb, SSH_FXP_MKDIR, reqid, PCNPATH(pcn)); 728 psbuf_put_vattr(pb, va, pctx); 729 GETRESPONSE(pb, pctx->sshfd); 730 731 rv = psbuf_expect_status(pb); 732 if (rv) 733 goto out; 734 735 pn_new = allocnode(pu, pn, pcn->pcn_name, va); 736 if (pn_new) { 737 puffs_newinfo_setcookie(pni, pn_new); 738 } else { 739 struct puffs_framebuf *pb2 = psbuf_makeout(); 740 reqid = NEXTREQ(pctx); 741 psbuf_recycleout(pb2); 742 psbuf_req_str(pb2, SSH_FXP_RMDIR, reqid, PCNPATH(pcn)); 743 JUSTSEND(pb2, pctx->sshfd); 744 rv = ENOMEM; 745 } 746 747 out: 748 PSSHFSRETURN(rv); 749 } 750 751 int 752 psshfs_node_symlink(struct puffs_usermount *pu, puffs_cookie_t opc, 753 struct puffs_newinfo *pni, const struct puffs_cn *pcn, 754 const struct vattr *va, const char *link_target) 755 { 756 PSSHFSAUTOVAR(pu); 757 struct puffs_node *pn = opc; 758 struct puffs_node *pn_new; 759 760 if (pctx->protover < 3) { 761 rv = EOPNOTSUPP; 762 goto out; 763 } 764 765 /* 766 * XXX: ietf says: source, target. openssh says: ietf who? 767 * Let's go with openssh and build quirk tables later if we care 768 */ 769 psbuf_req_str(pb, SSH_FXP_SYMLINK, reqid, link_target); 770 psbuf_put_str(pb, PCNPATH(pcn)); 771 GETRESPONSE(pb, pctx->sshfd); 772 773 rv = psbuf_expect_status(pb); 774 if (rv) 775 goto out; 776 777 pn_new = allocnode(pu, pn, pcn->pcn_name, va); 778 if (pn_new) { 779 puffs_newinfo_setcookie(pni, pn_new); 780 } else { 781 struct puffs_framebuf *pb2 = psbuf_makeout(); 782 reqid = NEXTREQ(pctx); 783 psbuf_recycleout(pb2); 784 psbuf_req_str(pb2, SSH_FXP_REMOVE, reqid, PCNPATH(pcn)); 785 JUSTSEND(pb2, pctx->sshfd); 786 rv = ENOMEM; 787 } 788 789 out: 790 PSSHFSRETURN(rv); 791 } 792 793 int 794 psshfs_node_rename(struct puffs_usermount *pu, puffs_cookie_t opc, 795 puffs_cookie_t src, const struct puffs_cn *pcn_src, 796 puffs_cookie_t targ_dir, puffs_cookie_t targ, 797 const struct puffs_cn *pcn_targ) 798 { 799 PSSHFSAUTOVAR(pu); 800 struct puffs_node *pn_sf = src; 801 struct puffs_node *pn_td = targ_dir, *pn_tf = targ; 802 struct psshfs_node *psn_src = pn_sf->pn_data; 803 struct psshfs_node *psn_targdir = pn_td->pn_data; 804 805 if (pctx->protover < 2) { 806 rv = EOPNOTSUPP; 807 goto out; 808 } 809 810 if (pn_tf) { 811 rv = doremove(pu, targ_dir, pn_tf, pcn_targ->pcn_name); 812 if (rv) 813 goto out; 814 } 815 816 psbuf_req_str(pb, SSH_FXP_RENAME, reqid, PCNPATH(pcn_src)); 817 psbuf_put_str(pb, PCNPATH(pcn_targ)); 818 GETRESPONSE(pb, pctx->sshfd); 819 820 rv = psbuf_expect_status(pb); 821 if (rv == 0) { 822 struct psshfs_dir *pd; 823 824 /* 825 * XXX: interfaces didn't quite work with rename.. 826 * the song remains the same. go figure .. ;) 827 */ 828 nukenode(pn_sf, pcn_src->pcn_name, 0); 829 pd = direnter(pn_td, pcn_targ->pcn_name); 830 pd->entry = pn_sf; 831 puffs_setvattr(&pd->va, &pn_sf->pn_va); 832 833 if (opc != targ_dir) { 834 psn_targdir->childcount++; 835 psn_src->parent = pn_td; 836 if (pn_sf->pn_va.va_type == VDIR) 837 pn_td->pn_va.va_nlink++; 838 } 839 } 840 841 out: 842 PSSHFSRETURN(rv); 843 } 844 845 /* 846 * So this file system happened to be written in such a way that 847 * lookup for ".." is hard if we lose the in-memory node. We'd 848 * need to recreate the entire directory structure from the root 849 * node up to the ".." node we're looking up. 850 * 851 * And since our entire fs structure is purely fictional (i.e. it's 852 * only in-memory, not fetchable from the server), the easiest way 853 * to deal with it is to not allow nodes with children to be 854 * reclaimed. 855 * 856 * If a node with children is being attempted to be reclaimed, we 857 * just mark it "reclaimed" but leave it as is until all its children 858 * have been reclaimed. If a lookup for that node is done meanwhile, 859 * it will be found by lookup() and we just remove the "reclaimed" 860 * bit. 861 */ 862 int 863 psshfs_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc) 864 { 865 struct puffs_node *pn = opc, *pn_next, *pn_root; 866 struct psshfs_node *psn = pn->pn_data; 867 868 /* 869 * don't reclaim if we have file handle issued, otherwise 870 * we can't do fhtonode 871 */ 872 if (psn->stat & PSN_HASFH) 873 return 0; 874 875 psn->stat |= PSN_RECLAIMED; 876 pn_root = puffs_getroot(pu); 877 for (; pn != pn_root; pn = pn_next) { 878 psn = pn->pn_data; 879 if ((psn->stat & PSN_RECLAIMED) == 0 || psn->childcount != 0) 880 break; 881 882 pn_next = psn->parent; 883 doreclaim(pn); 884 } 885 886 return 0; 887 } 888