1 /* $NetBSD: subr.c,v 1.33 2007/11/10 18:36:06 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.33 2007/11/10 18:36:06 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 struct psshfs_dir * 71 lookup(struct psshfs_dir *bdir, size_t ndir, const char *name) 72 { 73 struct psshfs_dir *test; 74 int i; 75 76 for (i = 0; i < ndir; i++) { 77 test = &bdir[i]; 78 if (test->valid != 1) 79 continue; 80 if (strcmp(test->entryname, name) == 0) 81 return test; 82 } 83 84 return NULL; 85 } 86 87 static struct psshfs_dir * 88 lookup_by_entry(struct psshfs_dir *bdir, size_t ndir, struct puffs_node *entry) 89 { 90 struct psshfs_dir *test; 91 int i; 92 93 for (i = 0; i < ndir; i++) { 94 test = &bdir[i]; 95 if (test->valid != 1) 96 continue; 97 if (test->entry == entry) 98 return test; 99 } 100 101 return NULL; 102 } 103 104 struct readdirattr { 105 struct psshfs_node *psn; 106 int idx; 107 char entryname[MAXPATHLEN+1]; 108 }; 109 110 static void 111 readdir_getattr_resp(struct puffs_usermount *pu, 112 struct puffs_framebuf *pb, void *arg, int error) 113 { 114 struct readdirattr *rda = arg; 115 struct psshfs_node *psn = rda->psn; 116 struct psshfs_node *psn_targ = NULL; 117 struct psshfs_dir *pdir = NULL; 118 struct vattr va; 119 120 /* XXX: this is not enough */ 121 if (psn->stat & PSN_RECLAIMED) 122 goto out; 123 124 if (error) 125 goto out; 126 127 pdir = lookup(psn->dir, psn->denttot, rda->entryname); 128 if (!pdir) 129 goto out; 130 131 if (psbuf_expect_attrs(pb, &va)) 132 goto out; 133 134 if (pdir->entry) { 135 psn_targ = pdir->entry->pn_data; 136 137 puffs_setvattr(&pdir->entry->pn_va, &va); 138 psn_targ->attrread = time(NULL); 139 } else { 140 puffs_setvattr(&pdir->va, &va); 141 pdir->attrread = time(NULL); 142 } 143 144 out: 145 if (psn_targ) { 146 psn_targ->getattr_pb = NULL; 147 assert(pdir->getattr_pb == NULL); 148 } else if (pdir) { 149 pdir->getattr_pb = NULL; 150 } 151 152 free(rda); 153 puffs_framebuf_destroy(pb); 154 } 155 156 static void 157 readdir_getattr(struct puffs_usermount *pu, struct psshfs_node *psn, 158 const char *basepath, int idx) 159 { 160 char path[MAXPATHLEN+1]; 161 struct psshfs_ctx *pctx = puffs_getspecific(pu); 162 struct psshfs_dir *pdir = psn->dir; 163 struct puffs_framebuf *pb; 164 struct readdirattr *rda; 165 const char *entryname = pdir[idx].entryname; 166 uint32_t reqid = NEXTREQ(pctx); 167 168 rda = emalloc(sizeof(struct readdirattr)); 169 rda->psn = psn; 170 rda->idx = idx; 171 strlcpy(rda->entryname, entryname, sizeof(rda->entryname)); 172 173 strcpy(path, basepath); 174 strcat(path, "/"); 175 strlcat(path, entryname, sizeof(path)); 176 177 pb = psbuf_makeout(); 178 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path); 179 psn->da[psn->nextda].pufbuf = pb; 180 psn->da[psn->nextda].rda = rda; 181 psn->nextda++; 182 } 183 184 static void 185 readdir_getattr_send(struct puffs_usermount *pu, struct psshfs_node *psn) 186 { 187 struct psshfs_ctx *pctx = puffs_getspecific(pu); 188 struct psshfs_dir *pdir = psn->dir; 189 struct psshfs_node *psn_targ; 190 struct readdirattr *rda; 191 size_t i; 192 int rv = 0; 193 194 for (i = 0; i < psn->nextda; i++) { 195 rda = psn->da[i].rda; 196 if (pdir[rda->idx].entry) { 197 psn_targ = pdir[rda->idx].entry->pn_data; 198 psn_targ->getattr_pb = psn->da[i].pufbuf; 199 } else { 200 pdir[rda->idx].getattr_pb = psn->da[i].pufbuf; 201 } 202 SENDCB(psn->da[i].pufbuf, readdir_getattr_resp, rda); 203 } 204 205 out: 206 return; 207 } 208 209 int 210 getpathattr(struct puffs_cc *pcc, const char *path, struct vattr *vap) 211 { 212 PSSHFSAUTOVAR(pcc); 213 214 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path); 215 GETRESPONSE(pb); 216 217 rv = psbuf_expect_attrs(pb, vap); 218 219 out: 220 PSSHFSRETURN(rv); 221 } 222 223 int 224 getnodeattr(struct puffs_cc *pcc, struct puffs_node *pn) 225 { 226 struct puffs_usermount *pu = puffs_cc_getusermount(pcc); 227 struct psshfs_ctx *pctx = puffs_getspecific(pu); 228 struct psshfs_node *psn = pn->pn_data; 229 struct vattr va; 230 int rv, dohardway; 231 232 if (!psn->attrread || REFRESHTIMEOUT(pctx, time(NULL)-psn->attrread)) { 233 dohardway = 1; 234 if (psn->getattr_pb) { 235 rv=puffs_framev_framebuf_ccpromote(psn->getattr_pb,pcc); 236 if (rv == 0) { 237 rv = psbuf_expect_attrs(psn->getattr_pb, &va); 238 puffs_framebuf_destroy(psn->getattr_pb); 239 psn->getattr_pb = NULL; 240 if (rv == 0) 241 dohardway = 0; 242 } 243 } 244 245 if (dohardway) { 246 rv = getpathattr(pcc, PNPATH(pn), &va); 247 if (rv) 248 return rv; 249 } 250 251 /* 252 * Check if the file was modified from below us. 253 * If so, invalidate page cache. This is the only 254 * sensible place we can do this in. 255 */ 256 if (psn->attrread) 257 if (pn->pn_va.va_mtime.tv_sec != va.va_mtime.tv_sec) 258 puffs_inval_pagecache_node(pu, pn); 259 260 puffs_setvattr(&pn->pn_va, &va); 261 psn->attrread = time(NULL); 262 } 263 264 return 0; 265 } 266 267 int 268 sftp_readdir(struct puffs_cc *pcc, struct psshfs_ctx *pctx, 269 struct puffs_node *pn) 270 { 271 struct puffs_usermount *pu = puffs_cc_getusermount(pcc); 272 struct psshfs_node *psn = pn->pn_data; 273 struct psshfs_dir *olddir, *testd; 274 struct puffs_framebuf *pb; 275 uint32_t reqid = NEXTREQ(pctx); 276 uint32_t count, dhandlen; 277 char *dhand = NULL; 278 size_t nent; 279 char *longname = NULL; 280 int idx, rv; 281 282 assert(pn->pn_va.va_type == VDIR); 283 idx = 0; 284 olddir = psn->dir; 285 nent = psn->dentnext; 286 287 if (psn->dir && psn->dentread 288 && !REFRESHTIMEOUT(pctx, time(NULL) - psn->dentread)) 289 return 0; 290 291 if ((rv = puffs_inval_namecache_dir(pu, pn))) 292 warn("readdir: dcache inval fail %p", pn); 293 294 pb = psbuf_makeout(); 295 psbuf_req_str(pb, SSH_FXP_OPENDIR, reqid, PNPATH(pn)); 296 if (puffs_framev_enqueue_cc(pcc, pctx->sshfd, pb, 0) == -1) { 297 rv = errno; 298 goto wayout; 299 } 300 rv = psbuf_expect_handle(pb, &dhand, &dhandlen); 301 if (rv) 302 goto wayout; 303 304 /* 305 * Well, the following is O(n^2), so feel free to improve if it 306 * gets too taxing on your system. 307 */ 308 309 /* 310 * note: for the "getattr in batch" to work, this must be before 311 * the attribute-getting. Otherwise times for first entries in 312 * large directories might expire before the directory itself and 313 * result in one-by-one attribute fetching. 314 */ 315 psn->dentread = time(NULL); 316 317 psn->dentnext = 0; 318 psn->denttot = 0; 319 psn->dir = NULL; 320 psn->nextda = 0; 321 322 for (;;) { 323 reqid = NEXTREQ(pctx); 324 psbuf_recycleout(pb); 325 psbuf_req_data(pb, SSH_FXP_READDIR, reqid, dhand, dhandlen); 326 GETRESPONSE(pb); 327 328 /* check for EOF */ 329 if (psbuf_get_type(pb) == SSH_FXP_STATUS) { 330 rv = psbuf_expect_status(pb); 331 goto out; 332 } 333 rv = psbuf_expect_name(pb, &count); 334 if (rv) 335 goto out; 336 337 for (; count--; idx++) { 338 if (idx == psn->denttot) 339 allocdirs(psn); 340 if ((rv = psbuf_get_str(pb, 341 &psn->dir[idx].entryname, NULL))) 342 goto out; 343 if ((rv = psbuf_get_str(pb, &longname, NULL)) != 0) 344 goto out; 345 if ((rv = psbuf_get_vattr(pb, &psn->dir[idx].va)) != 0) 346 goto out; 347 if (sscanf(longname, "%*s%d", 348 &psn->dir[idx].va.va_nlink) != 1) { 349 rv = EPROTO; 350 goto out; 351 } 352 free(longname); 353 longname = NULL; 354 355 testd = lookup(olddir, nent, psn->dir[idx].entryname); 356 if (testd) { 357 psn->dir[idx].entry = testd->entry; 358 psn->dir[idx].va = testd->va; 359 } else { 360 psn->dir[idx].entry = NULL; 361 psn->dir[idx].va.va_fileid = pctx->nextino++; 362 } 363 /* 364 * XXX: there's a dangling pointer race here if 365 * the server responds to our queries out-of-order. 366 * fixxxme some day 367 */ 368 readdir_getattr(puffs_cc_getusermount(pcc), 369 psn, PNPATH(pn), idx); 370 371 psn->dir[idx].valid = 1; 372 } 373 } 374 375 out: 376 /* fire off getattr requests */ 377 readdir_getattr_send(pu, psn); 378 379 /* XXX: rv */ 380 psn->dentnext = idx; 381 freedircache(olddir, nent); 382 383 reqid = NEXTREQ(pctx); 384 psbuf_recycleout(pb); 385 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, dhand, dhandlen); 386 puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb, 1, 0); 387 free(dhand); 388 free(longname); 389 390 return rv; 391 392 wayout: 393 free(dhand); 394 PSSHFSRETURN(rv); 395 } 396 397 struct puffs_node * 398 makenode(struct puffs_usermount *pu, struct puffs_node *parent, 399 struct psshfs_dir *pd, const struct vattr *vap) 400 { 401 struct psshfs_node *psn_parent = parent->pn_data; 402 struct psshfs_node *psn; 403 struct puffs_node *pn; 404 405 psn = emalloc(sizeof(struct psshfs_node)); 406 memset(psn, 0, sizeof(struct psshfs_node)); 407 408 pn = puffs_pn_new(pu, psn); 409 if (!pn) { 410 free(psn); 411 return NULL; 412 } 413 puffs_setvattr(&pn->pn_va, &pd->va); 414 psn->attrread = pd->attrread; 415 puffs_setvattr(&pn->pn_va, vap); 416 417 pd->entry = pn; 418 psn->parent = parent; 419 psn_parent->childcount++; 420 421 TAILQ_INIT(&psn->pw); 422 423 if (pd->getattr_pb) { 424 psn->getattr_pb = pd->getattr_pb; 425 pd->getattr_pb = NULL; 426 } 427 428 return pn; 429 } 430 431 struct puffs_node * 432 allocnode(struct puffs_usermount *pu, struct puffs_node *parent, 433 const char *entryname, const struct vattr *vap) 434 { 435 struct psshfs_ctx *pctx = puffs_getspecific(pu); 436 struct psshfs_dir *pd; 437 struct puffs_node *pn; 438 439 pd = direnter(parent, entryname); 440 441 pd->va.va_fileid = pctx->nextino++; 442 if (vap->va_type == VDIR) { 443 pd->va.va_nlink = 2; 444 parent->pn_va.va_nlink++; 445 } else { 446 pd->va.va_nlink = 1; 447 } 448 449 pn = makenode(pu, parent, pd, vap); 450 if (pn) 451 pd->va.va_fileid = pn->pn_va.va_fileid; 452 453 return pn; 454 } 455 456 struct psshfs_dir * 457 direnter(struct puffs_node *parent, const char *entryname) 458 { 459 struct psshfs_node *psn_parent = parent->pn_data; 460 struct psshfs_dir *pd; 461 int i; 462 463 /* create directory entry */ 464 if (psn_parent->denttot == psn_parent->dentnext) 465 allocdirs(psn_parent); 466 467 i = psn_parent->dentnext; 468 pd = &psn_parent->dir[i]; 469 pd->entryname = estrdup(entryname); 470 pd->valid = 1; 471 pd->attrread = 0; 472 puffs_vattr_null(&pd->va); 473 psn_parent->dentnext++; 474 475 return pd; 476 } 477 478 void 479 doreclaim(struct puffs_node *pn) 480 { 481 struct psshfs_node *psn = pn->pn_data; 482 struct psshfs_node *psn_parent; 483 struct psshfs_dir *dent; 484 485 psn_parent = psn->parent->pn_data; 486 psn_parent->childcount--; 487 488 /* 489 * Null out entry from directory. Do not treat a missing entry 490 * as an invariant error, since the node might be removed from 491 * under us, and we might do a readdir before the reclaim resulting 492 * in no directory entry in the parent directory. 493 */ 494 dent = lookup_by_entry(psn_parent->dir, psn_parent->dentnext, pn); 495 if (dent) 496 dent->entry = NULL; 497 498 if (pn->pn_va.va_type == VDIR) { 499 freedircache(psn->dir, psn->dentnext); 500 psn->denttot = psn->dentnext = 0; 501 free(psn->da); 502 } 503 if (psn->symlink) 504 free(psn->symlink); 505 506 puffs_pn_put(pn); 507 } 508 509 void 510 nukenode(struct puffs_node *node, const char *entryname, int reclaim) 511 { 512 struct psshfs_node *psn, *psn_parent; 513 struct psshfs_dir *pd; 514 515 psn = node->pn_data; 516 psn_parent = psn->parent->pn_data; 517 pd = lookup(psn_parent->dir, psn_parent->dentnext, entryname); 518 assert(pd != NULL); 519 pd->valid = 0; 520 free(pd->entryname); 521 pd->entryname = NULL; 522 523 if (node->pn_va.va_type == VDIR) 524 psn->parent->pn_va.va_nlink--; 525 526 if (reclaim) 527 doreclaim(node); 528 } 529