1 /* $NetBSD: subr.c,v 1.38 2007/11/30 19:02:41 pooka Exp $ */ 2 3 /* 4 * Copyright (c) 2006 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: subr.c,v 1.38 2007/11/30 19:02:41 pooka Exp $"); 31 #endif /* !lint */ 32 33 #include <assert.h> 34 #include <err.h> 35 #include <errno.h> 36 #include <puffs.h> 37 #include <stdlib.h> 38 #include <util.h> 39 40 #include "psshfs.h" 41 #include "sftp_proto.h" 42 43 static void 44 freedircache(struct psshfs_dir *base, size_t count) 45 { 46 int i; 47 48 for (i = 0; i < count; i++) { 49 free(base[i].entryname); 50 base[i].entryname = NULL; 51 } 52 53 free(base); 54 } 55 56 #define ENTRYCHUNK 16 57 static void 58 allocdirs(struct psshfs_node *psn) 59 { 60 size_t oldtot = psn->denttot; 61 62 psn->denttot += ENTRYCHUNK; 63 psn->dir = erealloc(psn->dir, 64 psn->denttot * sizeof(struct psshfs_dir)); 65 memset(psn->dir + oldtot, 0, ENTRYCHUNK * sizeof(struct psshfs_dir)); 66 67 psn->da = erealloc(psn->da, psn->denttot * sizeof(struct delayattr)); 68 } 69 70 static void 71 setpnva(struct puffs_usermount *pu, struct puffs_node *pn, 72 const struct vattr *vap) 73 { 74 struct psshfs_node *psn = pn->pn_data; 75 76 /* 77 * Check if the file was modified from below us. 78 * If so, invalidate page cache. This is the only 79 * sensible place we can do this in. 80 */ 81 if (pn->pn_va.va_mtime.tv_sec != PUFFS_VNOVAL) 82 if (pn->pn_va.va_mtime.tv_sec != vap->va_mtime.tv_sec 83 && pn->pn_va.va_type == VREG) 84 puffs_inval_pagecache_node(pu, pn); 85 86 puffs_setvattr(&pn->pn_va, vap); 87 psn->attrread = time(NULL); 88 } 89 90 struct psshfs_dir * 91 lookup(struct psshfs_dir *bdir, size_t ndir, const char *name) 92 { 93 struct psshfs_dir *test; 94 int i; 95 96 for (i = 0; i < ndir; i++) { 97 test = &bdir[i]; 98 if (test->valid != 1) 99 continue; 100 if (strcmp(test->entryname, name) == 0) 101 return test; 102 } 103 104 return NULL; 105 } 106 107 static struct psshfs_dir * 108 lookup_by_entry(struct psshfs_dir *bdir, size_t ndir, struct puffs_node *entry) 109 { 110 struct psshfs_dir *test; 111 int i; 112 113 for (i = 0; i < ndir; i++) { 114 test = &bdir[i]; 115 if (test->valid != 1) 116 continue; 117 if (test->entry == entry) 118 return test; 119 } 120 121 return NULL; 122 } 123 124 125 void 126 closehandles(struct puffs_usermount *pu, struct psshfs_node *psn, int which) 127 { 128 struct psshfs_ctx *pctx = puffs_getspecific(pu); 129 struct puffs_framebuf *pb1, *pb2; 130 uint32_t reqid; 131 132 if (psn->fhand_r && (which & HANDLE_READ)) { 133 assert(psn->lazyopen_r == NULL); 134 135 pb1 = psbuf_makeout(); 136 reqid = NEXTREQ(pctx); 137 psbuf_req_data(pb1, SSH_FXP_CLOSE, reqid, 138 psn->fhand_r, psn->fhand_r_len); 139 puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb1, 1, 0); 140 free(psn->fhand_r); 141 psn->fhand_r = NULL; 142 } 143 144 if (psn->fhand_w && (which & HANDLE_WRITE)) { 145 assert(psn->lazyopen_w == NULL); 146 147 pb2 = psbuf_makeout(); 148 reqid = NEXTREQ(pctx); 149 psbuf_req_data(pb2, SSH_FXP_CLOSE, reqid, 150 psn->fhand_w, psn->fhand_w_len); 151 puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb2, 1, 0); 152 free(psn->fhand_w); 153 psn->fhand_w = NULL; 154 } 155 156 psn->stat |= PSN_HANDLECLOSE; 157 } 158 159 void 160 lazyopen_rresp(struct puffs_usermount *pu, struct puffs_framebuf *pb, 161 void *arg, int error) 162 { 163 struct psshfs_node *psn = arg; 164 165 /* XXX: this is not enough */ 166 if (psn->stat & PSN_RECLAIMED) { 167 error = ENOENT; 168 goto moreout; 169 } 170 if (error) 171 goto out; 172 173 error = psbuf_expect_handle(pb, &psn->fhand_r, &psn->fhand_r_len); 174 175 out: 176 psn->lazyopen_err_r = error; 177 psn->lazyopen_r = NULL; 178 if (error) 179 psn->stat &= ~PSN_DOLAZY_R; 180 if (psn->stat & PSN_HANDLECLOSE && (psn->stat & PSN_LAZYWAIT_R) == 0) 181 closehandles(pu, psn, HANDLE_READ); 182 moreout: 183 puffs_framebuf_destroy(pb); 184 } 185 186 void 187 lazyopen_wresp(struct puffs_usermount *pu, struct puffs_framebuf *pb, 188 void *arg, int error) 189 { 190 struct psshfs_node *psn = arg; 191 192 /* XXX: this is not enough */ 193 if (psn->stat & PSN_RECLAIMED) { 194 error = ENOENT; 195 goto moreout; 196 } 197 if (error) 198 goto out; 199 200 error = psbuf_expect_handle(pb, &psn->fhand_w, &psn->fhand_w_len); 201 202 out: 203 psn->lazyopen_err_w = error; 204 psn->lazyopen_w = NULL; 205 if (error) 206 psn->stat &= ~PSN_DOLAZY_W; 207 if (psn->stat & PSN_HANDLECLOSE && (psn->stat & PSN_LAZYWAIT_W) == 0) 208 closehandles(pu, psn, HANDLE_WRITE); 209 moreout: 210 puffs_framebuf_destroy(pb); 211 } 212 213 struct readdirattr { 214 struct psshfs_node *psn; 215 int idx; 216 char entryname[MAXPATHLEN+1]; 217 }; 218 219 static void 220 readdir_getattr_resp(struct puffs_usermount *pu, 221 struct puffs_framebuf *pb, void *arg, int error) 222 { 223 struct readdirattr *rda = arg; 224 struct psshfs_node *psn = rda->psn; 225 struct psshfs_node *psn_targ = NULL; 226 struct psshfs_dir *pdir = NULL; 227 struct vattr va; 228 229 /* XXX: this is not enough */ 230 if (psn->stat & PSN_RECLAIMED) 231 goto out; 232 233 if (error) 234 goto out; 235 236 pdir = lookup(psn->dir, psn->denttot, rda->entryname); 237 if (!pdir) 238 goto out; 239 240 if (psbuf_expect_attrs(pb, &va)) 241 goto out; 242 243 if (pdir->entry) { 244 psn_targ = pdir->entry->pn_data; 245 246 setpnva(pu, pdir->entry, &va); 247 } else { 248 puffs_setvattr(&pdir->va, &va); 249 pdir->attrread = time(NULL); 250 } 251 252 out: 253 if (psn_targ) { 254 psn_targ->getattr_pb = NULL; 255 assert(pdir->getattr_pb == NULL); 256 } else if (pdir) { 257 pdir->getattr_pb = NULL; 258 } 259 260 free(rda); 261 puffs_framebuf_destroy(pb); 262 } 263 264 static void 265 readdir_getattr(struct puffs_usermount *pu, struct psshfs_node *psn, 266 const char *basepath, int idx) 267 { 268 char path[MAXPATHLEN+1]; 269 struct psshfs_ctx *pctx = puffs_getspecific(pu); 270 struct psshfs_dir *pdir = psn->dir; 271 struct puffs_framebuf *pb; 272 struct readdirattr *rda; 273 const char *entryname = pdir[idx].entryname; 274 uint32_t reqid = NEXTREQ(pctx); 275 276 rda = emalloc(sizeof(struct readdirattr)); 277 rda->psn = psn; 278 rda->idx = idx; 279 strlcpy(rda->entryname, entryname, sizeof(rda->entryname)); 280 281 strcpy(path, basepath); 282 strcat(path, "/"); 283 strlcat(path, entryname, sizeof(path)); 284 285 pb = psbuf_makeout(); 286 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path); 287 psn->da[psn->nextda].pufbuf = pb; 288 psn->da[psn->nextda].rda = rda; 289 psn->nextda++; 290 } 291 292 static void 293 readdir_getattr_send(struct puffs_usermount *pu, struct psshfs_node *psn) 294 { 295 struct psshfs_ctx *pctx = puffs_getspecific(pu); 296 struct psshfs_dir *pdir = psn->dir; 297 struct psshfs_node *psn_targ; 298 struct readdirattr *rda; 299 size_t i; 300 int rv = 0; 301 302 for (i = 0; i < psn->nextda; i++) { 303 rda = psn->da[i].rda; 304 if (pdir[rda->idx].entry) { 305 psn_targ = pdir[rda->idx].entry->pn_data; 306 psn_targ->getattr_pb = psn->da[i].pufbuf; 307 } else { 308 pdir[rda->idx].getattr_pb = psn->da[i].pufbuf; 309 } 310 SENDCB(psn->da[i].pufbuf, readdir_getattr_resp, rda); 311 } 312 313 out: 314 return; 315 } 316 317 int 318 getpathattr(struct puffs_usermount *pu, const char *path, struct vattr *vap) 319 { 320 PSSHFSAUTOVAR(pu); 321 322 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path); 323 GETRESPONSE(pb); 324 325 rv = psbuf_expect_attrs(pb, vap); 326 327 out: 328 PSSHFSRETURN(rv); 329 } 330 331 int 332 getnodeattr(struct puffs_usermount *pu, struct puffs_node *pn) 333 { 334 struct puffs_cc *pcc = puffs_cc_getcc(pu); 335 struct psshfs_ctx *pctx = puffs_getspecific(pu); 336 struct psshfs_node *psn = pn->pn_data; 337 struct vattr va; 338 int rv, dohardway; 339 340 if (!psn->attrread || REFRESHTIMEOUT(pctx, time(NULL)-psn->attrread)) { 341 dohardway = 1; 342 if (psn->getattr_pb) { 343 rv=puffs_framev_framebuf_ccpromote(psn->getattr_pb,pcc); 344 if (rv == 0) { 345 rv = psbuf_expect_attrs(psn->getattr_pb, &va); 346 puffs_framebuf_destroy(psn->getattr_pb); 347 psn->getattr_pb = NULL; 348 if (rv == 0) 349 dohardway = 0; 350 } 351 } 352 353 if (dohardway) { 354 rv = getpathattr(pu, PNPATH(pn), &va); 355 if (rv) 356 return rv; 357 } 358 359 setpnva(pu, pn, &va); 360 } 361 362 return 0; 363 } 364 365 int 366 sftp_readdir(struct puffs_cc *pcc, struct psshfs_ctx *pctx, 367 struct puffs_node *pn) 368 { 369 struct puffs_usermount *pu = puffs_cc_getusermount(pcc); 370 struct psshfs_node *psn = pn->pn_data; 371 struct psshfs_dir *olddir, *testd; 372 struct puffs_framebuf *pb; 373 uint32_t reqid = NEXTREQ(pctx); 374 uint32_t count, dhandlen; 375 char *dhand = NULL; 376 size_t nent; 377 char *longname = NULL; 378 int idx, rv; 379 380 assert(pn->pn_va.va_type == VDIR); 381 idx = 0; 382 olddir = psn->dir; 383 nent = psn->dentnext; 384 385 if (psn->dir && psn->dentread 386 && !REFRESHTIMEOUT(pctx, time(NULL) - psn->dentread)) 387 return 0; 388 389 if ((rv = puffs_inval_namecache_dir(pu, pn))) 390 warn("readdir: dcache inval fail %p", pn); 391 392 pb = psbuf_makeout(); 393 psbuf_req_str(pb, SSH_FXP_OPENDIR, reqid, PNPATH(pn)); 394 if (puffs_framev_enqueue_cc(pcc, pctx->sshfd, pb, 0) == -1) { 395 rv = errno; 396 goto wayout; 397 } 398 rv = psbuf_expect_handle(pb, &dhand, &dhandlen); 399 if (rv) 400 goto wayout; 401 402 /* 403 * Well, the following is O(n^2), so feel free to improve if it 404 * gets too taxing on your system. 405 */ 406 407 /* 408 * note: for the "getattr in batch" to work, this must be before 409 * the attribute-getting. Otherwise times for first entries in 410 * large directories might expire before the directory itself and 411 * result in one-by-one attribute fetching. 412 */ 413 psn->dentread = time(NULL); 414 415 psn->dentnext = 0; 416 psn->denttot = 0; 417 psn->dir = NULL; 418 psn->nextda = 0; 419 420 for (;;) { 421 reqid = NEXTREQ(pctx); 422 psbuf_recycleout(pb); 423 psbuf_req_data(pb, SSH_FXP_READDIR, reqid, dhand, dhandlen); 424 GETRESPONSE(pb); 425 426 /* check for EOF */ 427 if (psbuf_get_type(pb) == SSH_FXP_STATUS) { 428 rv = psbuf_expect_status(pb); 429 goto out; 430 } 431 rv = psbuf_expect_name(pb, &count); 432 if (rv) 433 goto out; 434 435 for (; count--; idx++) { 436 if (idx == psn->denttot) 437 allocdirs(psn); 438 if ((rv = psbuf_get_str(pb, 439 &psn->dir[idx].entryname, NULL))) 440 goto out; 441 if ((rv = psbuf_get_str(pb, &longname, NULL)) != 0) 442 goto out; 443 if ((rv = psbuf_get_vattr(pb, &psn->dir[idx].va)) != 0) 444 goto out; 445 if (sscanf(longname, "%*s%d", 446 &psn->dir[idx].va.va_nlink) != 1) { 447 rv = EPROTO; 448 goto out; 449 } 450 free(longname); 451 longname = NULL; 452 453 testd = lookup(olddir, nent, psn->dir[idx].entryname); 454 if (testd) { 455 psn->dir[idx].entry = testd->entry; 456 psn->dir[idx].va = testd->va; 457 } else { 458 psn->dir[idx].entry = NULL; 459 psn->dir[idx].va.va_fileid = pctx->nextino++; 460 } 461 /* 462 * XXX: there's a dangling pointer race here if 463 * the server responds to our queries out-of-order. 464 * fixxxme some day 465 */ 466 readdir_getattr(puffs_cc_getusermount(pcc), 467 psn, PNPATH(pn), idx); 468 469 psn->dir[idx].valid = 1; 470 } 471 } 472 473 out: 474 /* fire off getattr requests */ 475 readdir_getattr_send(pu, psn); 476 477 /* XXX: rv */ 478 psn->dentnext = idx; 479 freedircache(olddir, nent); 480 481 reqid = NEXTREQ(pctx); 482 psbuf_recycleout(pb); 483 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, dhand, dhandlen); 484 puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb, 1, 0); 485 free(dhand); 486 free(longname); 487 488 return rv; 489 490 wayout: 491 free(dhand); 492 PSSHFSRETURN(rv); 493 } 494 495 struct puffs_node * 496 makenode(struct puffs_usermount *pu, struct puffs_node *parent, 497 struct psshfs_dir *pd, const struct vattr *vap) 498 { 499 struct psshfs_node *psn_parent = parent->pn_data; 500 struct psshfs_node *psn; 501 struct puffs_node *pn; 502 503 psn = emalloc(sizeof(struct psshfs_node)); 504 memset(psn, 0, sizeof(struct psshfs_node)); 505 506 pn = puffs_pn_new(pu, psn); 507 if (!pn) { 508 free(psn); 509 return NULL; 510 } 511 setpnva(pu, pn, &pd->va); 512 setpnva(pu, pn, vap); 513 514 pd->entry = pn; 515 psn->parent = parent; 516 psn_parent->childcount++; 517 518 TAILQ_INIT(&psn->pw); 519 520 if (pd->getattr_pb) { 521 psn->getattr_pb = pd->getattr_pb; 522 pd->getattr_pb = NULL; 523 } 524 525 return pn; 526 } 527 528 struct puffs_node * 529 allocnode(struct puffs_usermount *pu, struct puffs_node *parent, 530 const char *entryname, const struct vattr *vap) 531 { 532 struct psshfs_ctx *pctx = puffs_getspecific(pu); 533 struct psshfs_dir *pd; 534 struct puffs_node *pn; 535 536 pd = direnter(parent, entryname); 537 538 pd->va.va_fileid = pctx->nextino++; 539 if (vap->va_type == VDIR) { 540 pd->va.va_nlink = 2; 541 parent->pn_va.va_nlink++; 542 } else { 543 pd->va.va_nlink = 1; 544 } 545 546 pn = makenode(pu, parent, pd, vap); 547 if (pn) 548 pd->va.va_fileid = pn->pn_va.va_fileid; 549 550 return pn; 551 } 552 553 struct psshfs_dir * 554 direnter(struct puffs_node *parent, const char *entryname) 555 { 556 struct psshfs_node *psn_parent = parent->pn_data; 557 struct psshfs_dir *pd; 558 int i; 559 560 /* create directory entry */ 561 if (psn_parent->denttot == psn_parent->dentnext) 562 allocdirs(psn_parent); 563 564 i = psn_parent->dentnext; 565 pd = &psn_parent->dir[i]; 566 pd->entryname = estrdup(entryname); 567 pd->valid = 1; 568 pd->attrread = 0; 569 puffs_vattr_null(&pd->va); 570 psn_parent->dentnext++; 571 572 return pd; 573 } 574 575 void 576 doreclaim(struct puffs_node *pn) 577 { 578 struct psshfs_node *psn = pn->pn_data; 579 struct psshfs_node *psn_parent; 580 struct psshfs_dir *dent; 581 582 psn_parent = psn->parent->pn_data; 583 psn_parent->childcount--; 584 585 /* 586 * Null out entry from directory. Do not treat a missing entry 587 * as an invariant error, since the node might be removed from 588 * under us, and we might do a readdir before the reclaim resulting 589 * in no directory entry in the parent directory. 590 */ 591 dent = lookup_by_entry(psn_parent->dir, psn_parent->dentnext, pn); 592 if (dent) 593 dent->entry = NULL; 594 595 if (pn->pn_va.va_type == VDIR) { 596 freedircache(psn->dir, psn->dentnext); 597 psn->denttot = psn->dentnext = 0; 598 free(psn->da); 599 } 600 if (psn->symlink) 601 free(psn->symlink); 602 603 puffs_pn_put(pn); 604 } 605 606 void 607 nukenode(struct puffs_node *node, const char *entryname, int reclaim) 608 { 609 struct psshfs_node *psn, *psn_parent; 610 struct psshfs_dir *pd; 611 612 psn = node->pn_data; 613 psn_parent = psn->parent->pn_data; 614 pd = lookup(psn_parent->dir, psn_parent->dentnext, entryname); 615 assert(pd != NULL); 616 pd->valid = 0; 617 free(pd->entryname); 618 pd->entryname = NULL; 619 620 if (node->pn_va.va_type == VDIR) 621 psn->parent->pn_va.va_nlink--; 622 623 if (reclaim) 624 doreclaim(node); 625 } 626