1 /* $NetBSD: node.c,v 1.62 2010/10/29 16:13:51 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.62 2010/10/29 16:13:51 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 369 /* find next dirent */ 370 for (i = *readoff;;i++) { 371 if (i >= psn->dentnext) 372 goto out; 373 pd = &psn->dir[i]; 374 if (pd->valid) 375 break; 376 } 377 378 for (;;) { 379 *readoff = i; 380 if (!puffs_nextdent(&dent, pd->entryname, 381 pd->va.va_fileid, puffs_vtype2dt(pd->va.va_type), reslen)) { 382 rv = 0; 383 goto out; 384 } 385 386 /* find next entry, store possible nfs key */ 387 do { 388 if (++i >= psn->dentnext) 389 goto out; 390 pd = &psn->dir[i]; 391 } while (pd->valid == 0); 392 PUFFS_STORE_DCOOKIE(cookies, ncookies, (off_t)i); 393 } 394 395 out: 396 if (rv == 0) { 397 if (i >= psn->dentnext) 398 *eofflag = 1; 399 400 *readoff = i; 401 } 402 403 if (set_readdir) { 404 struct psshfs_wait *pw; 405 406 /* all will likely run to completion because of cache */ 407 TAILQ_FOREACH(pw, &psn->pw, pw_entries) { 408 assert(pw->pw_type == PWTYPE_READDIR); 409 puffs_cc_schedule(pw->pw_cc); 410 TAILQ_REMOVE(&psn->pw, pw, pw_entries); 411 } 412 413 psn->stat &= ~PSN_READDIR; 414 } 415 416 return rv; 417 } 418 419 int 420 psshfs_node_read(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, 421 off_t offset, size_t *resid, const struct puffs_cred *pcr, 422 int ioflag) 423 { 424 PSSHFSAUTOVAR(pu); 425 struct puffs_node *pn = opc; 426 struct psshfs_node *psn = pn->pn_data; 427 struct psshfs_wait *pwp; 428 uint32_t readlen; 429 430 if (pn->pn_va.va_type == VDIR) { 431 rv = EISDIR; 432 goto farout; 433 } 434 435 /* check that a lazyopen didn't fail */ 436 if (!psn->fhand_r && !psn->lazyopen_r) { 437 rv = psn->lazyopen_err_r; 438 goto farout; 439 } 440 441 /* if someone is already waiting for the lazyopen, "just" wait */ 442 if (psn->stat & PSN_LAZYWAIT_R) { 443 struct psshfs_wait pw; 444 445 assert(psn->lazyopen_r); 446 447 pw.pw_cc = pcc; 448 pw.pw_type = PWTYPE_READ1; 449 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 450 puffs_cc_yield(pcc); 451 } 452 453 /* if lazyopening, wait for the result */ 454 if (psn->lazyopen_r) { 455 psn->stat |= PSN_LAZYWAIT_R; 456 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_r, pcc); 457 lazyopen_rresp(pu, psn->lazyopen_r, psn, rv); 458 459 /* schedule extra waiters */ 460 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 461 if (pwp->pw_type == PWTYPE_READ1) { 462 puffs_cc_schedule(pwp->pw_cc); 463 TAILQ_REMOVE(&psn->pw, pwp, pw_entries); 464 } 465 psn->stat &= ~PSN_LAZYWAIT_R; 466 467 if ((rv = psn->lazyopen_err_r) != 0) 468 goto farout; 469 } 470 471 /* if there is still no handle, just refuse to live with this */ 472 if (!psn->fhand_r) { 473 rv = EINVAL; 474 goto farout; 475 } 476 477 readlen = *resid; 478 psbuf_req_data(pb, SSH_FXP_READ, reqid, psn->fhand_r, psn->fhand_r_len); 479 psbuf_put_8(pb, offset); 480 psbuf_put_4(pb, readlen); 481 482 /* 483 * Do this *after* accessing the file, the handle might not 484 * exist after blocking. 485 */ 486 if (max_reads && ++psn->readcount > max_reads) { 487 struct psshfs_wait pw; 488 489 pw.pw_cc = pcc; 490 pw.pw_type = PWTYPE_READ2; 491 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 492 puffs_cc_yield(pcc); 493 } 494 495 GETRESPONSE(pb, pctx->sshfd_data); 496 497 rv = psbuf_do_data(pb, buf, &readlen); 498 if (rv == 0) 499 *resid -= readlen; 500 501 out: 502 if (max_reads && --psn->readcount >= max_reads) { 503 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 504 if (pwp->pw_type == PWTYPE_READ2) 505 break; 506 assert(pwp != NULL); 507 puffs_cc_schedule(pwp->pw_cc); 508 TAILQ_REMOVE(&psn->pw, pwp, pw_entries); 509 } 510 511 farout: 512 /* check if we need a lazyclose */ 513 if (psn->stat & PSN_HANDLECLOSE && psn->fhand_r) { 514 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 515 if (pwp->pw_type == PWTYPE_READ1) 516 break; 517 if (pwp == NULL) 518 closehandles(pu, psn, HANDLE_READ); 519 } 520 PSSHFSRETURN(rv); 521 } 522 523 /* XXX: we should getattr for size */ 524 int 525 psshfs_node_write(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, 526 off_t offset, size_t *resid, const struct puffs_cred *cred, 527 int ioflag) 528 { 529 PSSHFSAUTOVAR(pu); 530 struct psshfs_wait *pwp; 531 struct puffs_node *pn = opc; 532 struct psshfs_node *psn = pn->pn_data; 533 uint32_t writelen; 534 535 if (pn->pn_va.va_type == VDIR) { 536 rv = EISDIR; 537 goto out; 538 } 539 540 /* check that a lazyopen didn't fail */ 541 if (!psn->fhand_w && !psn->lazyopen_w) { 542 rv = psn->lazyopen_err_w; 543 goto out; 544 } 545 546 if (psn->stat & PSN_LAZYWAIT_W) { 547 struct psshfs_wait pw; 548 549 assert(psn->lazyopen_w); 550 551 pw.pw_cc = pcc; 552 pw.pw_type = PWTYPE_WRITE; 553 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries); 554 puffs_cc_yield(pcc); 555 } 556 557 /* 558 * If lazyopening, wait for the result. 559 * There can still be more than oen writer at a time in case 560 * the kernel issues write FAFs. 561 */ 562 if (psn->lazyopen_w) { 563 psn->stat |= PSN_LAZYWAIT_W; 564 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_w, pcc); 565 lazyopen_wresp(pu, psn->lazyopen_w, psn, rv); 566 567 /* schedule extra waiters */ 568 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 569 if (pwp->pw_type == PWTYPE_WRITE) { 570 puffs_cc_schedule(pwp->pw_cc); 571 TAILQ_REMOVE(&psn->pw, pwp, pw_entries); 572 } 573 psn->stat &= ~PSN_LAZYWAIT_W; 574 575 if ((rv = psn->lazyopen_err_w) != 0) 576 goto out; 577 } 578 579 if (!psn->fhand_w) { 580 abort(); 581 rv = EINVAL; 582 goto out; 583 } 584 585 writelen = *resid; 586 psbuf_req_data(pb, SSH_FXP_WRITE, reqid, psn->fhand_w,psn->fhand_w_len); 587 psbuf_put_8(pb, offset); 588 psbuf_put_data(pb, buf, writelen); 589 GETRESPONSE(pb, pctx->sshfd_data); 590 591 rv = psbuf_expect_status(pb); 592 if (rv == 0) 593 *resid = 0; 594 595 if (pn->pn_va.va_size < (uint64_t)offset + writelen) 596 pn->pn_va.va_size = offset + writelen; 597 598 out: 599 /* check if we need a lazyclose */ 600 if (psn->stat & PSN_HANDLECLOSE && psn->fhand_w) { 601 TAILQ_FOREACH(pwp, &psn->pw, pw_entries) 602 if (pwp->pw_type == PWTYPE_WRITE) 603 break; 604 if (pwp == NULL) 605 closehandles(pu, psn, HANDLE_WRITE); 606 } 607 PSSHFSRETURN(rv); 608 } 609 610 int 611 psshfs_node_readlink(struct puffs_usermount *pu, puffs_cookie_t opc, 612 const struct puffs_cred *cred, char *linkvalue, size_t *linklen) 613 { 614 PSSHFSAUTOVAR(pu); 615 struct puffs_node *pn = opc; 616 struct psshfs_node *psn = pn->pn_data; 617 uint32_t count; 618 619 if (pctx->protover < 3) { 620 rv = EOPNOTSUPP; 621 goto out; 622 } 623 624 /* 625 * check if we can use a cached version 626 * 627 * XXX: we might end up reading the same link multiple times 628 * from the server if we get many requests at once, but that's 629 * quite harmless as this routine is reentrant. 630 */ 631 if (psn->symlink && !REFRESHTIMEOUT(pctx, time(NULL) - psn->slread)) 632 goto copy; 633 634 if (psn->symlink) { 635 free(psn->symlink); 636 psn->symlink = NULL; 637 psn->slread = 0; 638 } 639 640 psbuf_req_str(pb, SSH_FXP_READLINK, reqid, PNPATH(pn)); 641 GETRESPONSE(pb, pctx->sshfd); 642 643 rv = psbuf_expect_name(pb, &count); 644 if (rv) 645 goto out; 646 if (count != 1) { 647 rv = EPROTO; 648 goto out; 649 } 650 651 rv = psbuf_get_str(pb, &psn->symlink, NULL); 652 if (rv) 653 goto out; 654 psn->slread = time(NULL); 655 656 copy: 657 *linklen = strlen(psn->symlink); 658 (void) memcpy(linkvalue, psn->symlink, *linklen); 659 660 out: 661 PSSHFSRETURN(rv); 662 } 663 664 static int 665 doremove(struct puffs_usermount *pu, struct puffs_node *pn_dir, 666 struct puffs_node *pn, const char *name) 667 { 668 PSSHFSAUTOVAR(pu); 669 int op; 670 671 if (pn->pn_va.va_type == VDIR) 672 op = SSH_FXP_RMDIR; 673 else 674 op = SSH_FXP_REMOVE; 675 676 psbuf_req_str(pb, op, reqid, PNPATH(pn)); 677 GETRESPONSE(pb, pctx->sshfd); 678 679 rv = psbuf_expect_status(pb); 680 if (rv == 0) 681 nukenode(pn, name, 0); 682 683 out: 684 PSSHFSRETURN(rv); 685 } 686 687 int 688 psshfs_node_remove(struct puffs_usermount *pu, puffs_cookie_t opc, 689 puffs_cookie_t targ, const struct puffs_cn *pcn) 690 { 691 struct puffs_node *pn_targ = targ; 692 int rv; 693 694 assert(pn_targ->pn_va.va_type != VDIR); 695 696 rv = doremove(pu, opc, targ, pcn->pcn_name); 697 if (rv == 0) 698 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2); 699 700 return rv; 701 } 702 703 int 704 psshfs_node_rmdir(struct puffs_usermount *pu, puffs_cookie_t opc, 705 puffs_cookie_t targ, const struct puffs_cn *pcn) 706 { 707 struct puffs_node *pn_targ = targ; 708 int rv; 709 710 assert(pn_targ->pn_va.va_type == VDIR); 711 712 rv = doremove(pu, opc, targ, pcn->pcn_name); 713 if (rv == 0) 714 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2); 715 716 return rv; 717 } 718 719 int 720 psshfs_node_mkdir(struct puffs_usermount *pu, puffs_cookie_t opc, 721 struct puffs_newinfo *pni, const struct puffs_cn *pcn, 722 const struct vattr *va) 723 { 724 PSSHFSAUTOVAR(pu); 725 struct puffs_node *pn = opc; 726 struct puffs_node *pn_new; 727 728 psbuf_req_str(pb, SSH_FXP_MKDIR, reqid, PCNPATH(pcn)); 729 psbuf_put_vattr(pb, va, pctx); 730 GETRESPONSE(pb, pctx->sshfd); 731 732 rv = psbuf_expect_status(pb); 733 if (rv) 734 goto out; 735 736 pn_new = allocnode(pu, pn, pcn->pcn_name, va); 737 if (pn_new) { 738 puffs_newinfo_setcookie(pni, pn_new); 739 } else { 740 struct puffs_framebuf *pb2 = psbuf_makeout(); 741 reqid = NEXTREQ(pctx); 742 psbuf_recycleout(pb2); 743 psbuf_req_str(pb2, SSH_FXP_RMDIR, reqid, PCNPATH(pcn)); 744 JUSTSEND(pb2, pctx->sshfd); 745 rv = ENOMEM; 746 } 747 748 out: 749 PSSHFSRETURN(rv); 750 } 751 752 int 753 psshfs_node_symlink(struct puffs_usermount *pu, puffs_cookie_t opc, 754 struct puffs_newinfo *pni, const struct puffs_cn *pcn, 755 const struct vattr *va, const char *link_target) 756 { 757 PSSHFSAUTOVAR(pu); 758 struct puffs_node *pn = opc; 759 struct puffs_node *pn_new; 760 761 if (pctx->protover < 3) { 762 rv = EOPNOTSUPP; 763 goto out; 764 } 765 766 /* 767 * XXX: ietf says: source, target. openssh says: ietf who? 768 * Let's go with openssh and build quirk tables later if we care 769 */ 770 psbuf_req_str(pb, SSH_FXP_SYMLINK, reqid, link_target); 771 psbuf_put_str(pb, PCNPATH(pcn)); 772 GETRESPONSE(pb, pctx->sshfd); 773 774 rv = psbuf_expect_status(pb); 775 if (rv) 776 goto out; 777 778 pn_new = allocnode(pu, pn, pcn->pcn_name, va); 779 if (pn_new) { 780 puffs_newinfo_setcookie(pni, pn_new); 781 } else { 782 struct puffs_framebuf *pb2 = psbuf_makeout(); 783 reqid = NEXTREQ(pctx); 784 psbuf_recycleout(pb2); 785 psbuf_req_str(pb2, SSH_FXP_REMOVE, reqid, PCNPATH(pcn)); 786 JUSTSEND(pb2, pctx->sshfd); 787 rv = ENOMEM; 788 } 789 790 out: 791 PSSHFSRETURN(rv); 792 } 793 794 int 795 psshfs_node_rename(struct puffs_usermount *pu, puffs_cookie_t opc, 796 puffs_cookie_t src, const struct puffs_cn *pcn_src, 797 puffs_cookie_t targ_dir, puffs_cookie_t targ, 798 const struct puffs_cn *pcn_targ) 799 { 800 PSSHFSAUTOVAR(pu); 801 struct puffs_node *pn_sf = src; 802 struct puffs_node *pn_td = targ_dir, *pn_tf = targ; 803 struct psshfs_node *psn_src = pn_sf->pn_data; 804 struct psshfs_node *psn_targdir = pn_td->pn_data; 805 806 if (pctx->protover < 2) { 807 rv = EOPNOTSUPP; 808 goto out; 809 } 810 811 if (pn_tf) { 812 rv = doremove(pu, targ_dir, pn_tf, pcn_targ->pcn_name); 813 if (rv) 814 goto out; 815 } 816 817 psbuf_req_str(pb, SSH_FXP_RENAME, reqid, PCNPATH(pcn_src)); 818 psbuf_put_str(pb, PCNPATH(pcn_targ)); 819 GETRESPONSE(pb, pctx->sshfd); 820 821 rv = psbuf_expect_status(pb); 822 if (rv == 0) { 823 struct psshfs_dir *pd; 824 825 /* 826 * XXX: interfaces didn't quite work with rename.. 827 * the song remains the same. go figure .. ;) 828 */ 829 nukenode(pn_sf, pcn_src->pcn_name, 0); 830 pd = direnter(pn_td, pcn_targ->pcn_name); 831 pd->entry = pn_sf; 832 puffs_setvattr(&pd->va, &pn_sf->pn_va); 833 834 if (opc != targ_dir) { 835 psn_targdir->childcount++; 836 psn_src->parent = pn_td; 837 if (pn_sf->pn_va.va_type == VDIR) 838 pn_td->pn_va.va_nlink++; 839 } 840 } 841 842 out: 843 PSSHFSRETURN(rv); 844 } 845 846 /* 847 * So this file system happened to be written in such a way that 848 * lookup for ".." is hard if we lose the in-memory node. We'd 849 * need to recreate the entire directory structure from the root 850 * node up to the ".." node we're looking up. 851 * 852 * And since our entire fs structure is purely fictional (i.e. it's 853 * only in-memory, not fetchable from the server), the easiest way 854 * to deal with it is to not allow nodes with children to be 855 * reclaimed. 856 * 857 * If a node with children is being attempted to be reclaimed, we 858 * just mark it "reclaimed" but leave it as is until all its children 859 * have been reclaimed. If a lookup for that node is done meanwhile, 860 * it will be found by lookup() and we just remove the "reclaimed" 861 * bit. 862 */ 863 int 864 psshfs_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc) 865 { 866 struct puffs_node *pn = opc, *pn_next, *pn_root; 867 struct psshfs_node *psn = pn->pn_data; 868 869 /* 870 * don't reclaim if we have file handle issued, otherwise 871 * we can't do fhtonode 872 */ 873 if (psn->stat & PSN_HASFH) 874 return 0; 875 876 psn->stat |= PSN_RECLAIMED; 877 pn_root = puffs_getroot(pu); 878 for (; pn != pn_root; pn = pn_next) { 879 psn = pn->pn_data; 880 if ((psn->stat & PSN_RECLAIMED) == 0 || psn->childcount != 0) 881 break; 882 883 pn_next = psn->parent; 884 doreclaim(pn); 885 } 886 887 return 0; 888 } 889