1 /* $NetBSD: subr.c,v 1.50 2010/04/01 02:34:09 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.50 2010/04/01 02:34:09 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 size_t 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 68 static void 69 setpnva(struct puffs_usermount *pu, struct puffs_node *pn, 70 const struct vattr *vap) 71 { 72 struct psshfs_ctx *pctx = puffs_getspecific(pu); 73 struct psshfs_node *psn = pn->pn_data; 74 struct vattr modva; 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 modva = *vap; 87 if (pctx->domangleuid && modva.va_uid == pctx->mangleuid) 88 modva.va_uid = pctx->myuid; 89 if (pctx->domanglegid && modva.va_gid == pctx->manglegid) 90 modva.va_gid = pctx->mygid; 91 92 puffs_setvattr(&pn->pn_va, &modva); 93 psn->attrread = time(NULL); 94 } 95 96 struct psshfs_dir * 97 lookup(struct psshfs_dir *bdir, size_t ndir, const char *name) 98 { 99 struct psshfs_dir *test; 100 size_t i; 101 102 for (i = 0; i < ndir; i++) { 103 test = &bdir[i]; 104 if (test->valid != 1) 105 continue; 106 if (strcmp(test->entryname, name) == 0) 107 return test; 108 } 109 110 return NULL; 111 } 112 113 static struct psshfs_dir * 114 lookup_by_entry(struct psshfs_dir *bdir, size_t ndir, struct puffs_node *entry) 115 { 116 struct psshfs_dir *test; 117 size_t i; 118 119 for (i = 0; i < ndir; i++) { 120 test = &bdir[i]; 121 if (test->valid != 1) 122 continue; 123 if (test->entry == entry) 124 return test; 125 } 126 127 return NULL; 128 } 129 130 131 void 132 closehandles(struct puffs_usermount *pu, struct psshfs_node *psn, int which) 133 { 134 struct psshfs_ctx *pctx = puffs_getspecific(pu); 135 struct puffs_framebuf *pb1, *pb2; 136 uint32_t reqid; 137 138 if (psn->fhand_r && (which & HANDLE_READ)) { 139 assert(psn->lazyopen_r == NULL); 140 141 pb1 = psbuf_makeout(); 142 reqid = NEXTREQ(pctx); 143 psbuf_req_data(pb1, SSH_FXP_CLOSE, reqid, 144 psn->fhand_r, psn->fhand_r_len); 145 puffs_framev_enqueue_justsend(pu, pctx->sshfd_data, pb1, 1, 0); 146 free(psn->fhand_r); 147 psn->fhand_r = NULL; 148 } 149 150 if (psn->fhand_w && (which & HANDLE_WRITE)) { 151 assert(psn->lazyopen_w == NULL); 152 153 pb2 = psbuf_makeout(); 154 reqid = NEXTREQ(pctx); 155 psbuf_req_data(pb2, SSH_FXP_CLOSE, reqid, 156 psn->fhand_w, psn->fhand_w_len); 157 puffs_framev_enqueue_justsend(pu, pctx->sshfd_data, pb2, 1, 0); 158 free(psn->fhand_w); 159 psn->fhand_w = NULL; 160 } 161 162 psn->stat |= PSN_HANDLECLOSE; 163 } 164 165 void 166 lazyopen_rresp(struct puffs_usermount *pu, struct puffs_framebuf *pb, 167 void *arg, int error) 168 { 169 struct psshfs_node *psn = arg; 170 171 /* XXX: this is not enough */ 172 if (psn->stat & PSN_RECLAIMED) { 173 error = ENOENT; 174 goto moreout; 175 } 176 if (error) 177 goto out; 178 179 error = psbuf_expect_handle(pb, &psn->fhand_r, &psn->fhand_r_len); 180 181 out: 182 psn->lazyopen_err_r = error; 183 psn->lazyopen_r = NULL; 184 if (error) 185 psn->stat &= ~PSN_DOLAZY_R; 186 if (psn->stat & PSN_HANDLECLOSE && (psn->stat & PSN_LAZYWAIT_R) == 0) 187 closehandles(pu, psn, HANDLE_READ); 188 moreout: 189 puffs_framebuf_destroy(pb); 190 } 191 192 void 193 lazyopen_wresp(struct puffs_usermount *pu, struct puffs_framebuf *pb, 194 void *arg, int error) 195 { 196 struct psshfs_node *psn = arg; 197 198 /* XXX: this is not enough */ 199 if (psn->stat & PSN_RECLAIMED) { 200 error = ENOENT; 201 goto moreout; 202 } 203 if (error) 204 goto out; 205 206 error = psbuf_expect_handle(pb, &psn->fhand_w, &psn->fhand_w_len); 207 208 out: 209 psn->lazyopen_err_w = error; 210 psn->lazyopen_w = NULL; 211 if (error) 212 psn->stat &= ~PSN_DOLAZY_W; 213 if (psn->stat & PSN_HANDLECLOSE && (psn->stat & PSN_LAZYWAIT_W) == 0) 214 closehandles(pu, psn, HANDLE_WRITE); 215 moreout: 216 puffs_framebuf_destroy(pb); 217 } 218 219 struct readdirattr { 220 struct psshfs_node *psn; 221 int idx; 222 char entryname[MAXPATHLEN+1]; 223 }; 224 225 int 226 getpathattr(struct puffs_usermount *pu, const char *path, struct vattr *vap) 227 { 228 PSSHFSAUTOVAR(pu); 229 230 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path); 231 GETRESPONSE(pb, pctx->sshfd); 232 233 rv = psbuf_expect_attrs(pb, vap); 234 235 out: 236 PSSHFSRETURN(rv); 237 } 238 239 int 240 getnodeattr(struct puffs_usermount *pu, struct puffs_node *pn, const char *path) 241 { 242 struct psshfs_ctx *pctx = puffs_getspecific(pu); 243 struct psshfs_node *psn = pn->pn_data; 244 struct vattr va; 245 int rv; 246 247 if (!psn->attrread || REFRESHTIMEOUT(pctx, time(NULL)-psn->attrread)) { 248 rv = getpathattr(pu, path ? path : PNPATH(pn), &va); 249 if (rv) 250 return rv; 251 252 setpnva(pu, pn, &va); 253 } 254 255 return 0; 256 } 257 258 int 259 sftp_readdir(struct puffs_usermount *pu, struct psshfs_ctx *pctx, 260 struct puffs_node *pn) 261 { 262 struct puffs_cc *pcc = puffs_cc_getcc(pu); 263 struct psshfs_node *psn = pn->pn_data; 264 struct psshfs_dir *olddir, *testd; 265 struct puffs_framebuf *pb; 266 uint32_t reqid = NEXTREQ(pctx); 267 uint32_t count, dhandlen; 268 char *dhand = NULL; 269 size_t nent; 270 char *longname = NULL; 271 size_t idx; 272 int rv; 273 274 assert(pn->pn_va.va_type == VDIR); 275 idx = 0; 276 olddir = psn->dir; 277 nent = psn->dentnext; 278 279 if (psn->dir && psn->dentread 280 && !REFRESHTIMEOUT(pctx, time(NULL) - psn->dentread)) 281 return 0; 282 283 if (psn->dentread) { 284 if ((rv = puffs_inval_namecache_dir(pu, pn))) 285 warn("readdir: dcache inval fail %p", pn); 286 } 287 288 pb = psbuf_makeout(); 289 psbuf_req_str(pb, SSH_FXP_OPENDIR, reqid, PNPATH(pn)); 290 if (puffs_framev_enqueue_cc(pcc, pctx->sshfd, pb, 0) == -1) { 291 rv = errno; 292 goto wayout; 293 } 294 rv = psbuf_expect_handle(pb, &dhand, &dhandlen); 295 if (rv) 296 goto wayout; 297 298 /* 299 * Well, the following is O(n^2), so feel free to improve if it 300 * gets too taxing on your system. 301 */ 302 303 /* 304 * note: for the "getattr in batch" to work, this must be before 305 * the attribute-getting. Otherwise times for first entries in 306 * large directories might expire before the directory itself and 307 * result in one-by-one attribute fetching. 308 */ 309 psn->dentread = time(NULL); 310 311 psn->dentnext = 0; 312 psn->denttot = 0; 313 psn->dir = NULL; 314 315 for (;;) { 316 reqid = NEXTREQ(pctx); 317 psbuf_recycleout(pb); 318 psbuf_req_data(pb, SSH_FXP_READDIR, reqid, dhand, dhandlen); 319 GETRESPONSE(pb, pctx->sshfd); 320 321 /* check for EOF */ 322 if (psbuf_get_type(pb) == SSH_FXP_STATUS) { 323 rv = psbuf_expect_status(pb); 324 goto out; 325 } 326 rv = psbuf_expect_name(pb, &count); 327 if (rv) 328 goto out; 329 330 for (; count--; idx++) { 331 if (idx == psn->denttot) 332 allocdirs(psn); 333 if ((rv = psbuf_get_str(pb, 334 &psn->dir[idx].entryname, NULL))) 335 goto out; 336 if ((rv = psbuf_get_str(pb, &longname, NULL)) != 0) 337 goto out; 338 if ((rv = psbuf_get_vattr(pb, &psn->dir[idx].va)) != 0) 339 goto out; 340 if (sscanf(longname, "%*s%d", 341 &psn->dir[idx].va.va_nlink) != 1) { 342 rv = EPROTO; 343 goto out; 344 } 345 free(longname); 346 longname = NULL; 347 348 /* 349 * In case of DOT, copy the attributes (mostly 350 * because we want the link count for the root dir). 351 */ 352 if (strcmp(psn->dir[idx].entryname, ".") == 0) { 353 setpnva(pu, pn, &psn->dir[idx].va); 354 } 355 356 /* 357 * Check if we already have a psshfs_dir for the 358 * name we are processing. If so, use the old one. 359 * If not, create a new one 360 */ 361 testd = lookup(olddir, nent, psn->dir[idx].entryname); 362 if (testd) { 363 psn->dir[idx].entry = testd->entry; 364 /* 365 * Has entry. Update attributes to what 366 * we just got from the server. 367 */ 368 if (testd->entry) { 369 setpnva(pu, testd->entry, 370 &psn->dir[idx].va); 371 psn->dir[idx].va.va_fileid 372 = testd->entry->pn_va.va_fileid; 373 374 /* 375 * No entry. This can happen in two cases: 376 * 1) the file was created "behind our back" 377 * on the server 378 * 2) we do two readdirs before we instantiate 379 * the node (or run with -t 0). 380 * 381 * Cache attributes from the server in 382 * case we want to instantiate this node 383 * soon. Also preserve the old inode number 384 * which was given when the dirent was created. 385 */ 386 } else { 387 psn->dir[idx].va.va_fileid 388 = testd->va.va_fileid; 389 testd->va = psn->dir[idx].va; 390 } 391 392 /* No previous entry? Initialize this one. */ 393 } else { 394 psn->dir[idx].entry = NULL; 395 psn->dir[idx].va.va_fileid = pctx->nextino++; 396 } 397 psn->dir[idx].attrread = psn->dentread; 398 psn->dir[idx].valid = 1; 399 } 400 } 401 402 out: 403 /* XXX: rv */ 404 psn->dentnext = idx; 405 freedircache(olddir, nent); 406 407 reqid = NEXTREQ(pctx); 408 psbuf_recycleout(pb); 409 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, dhand, dhandlen); 410 puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb, 1, 0); 411 free(dhand); 412 free(longname); 413 414 return rv; 415 416 wayout: 417 free(dhand); 418 PSSHFSRETURN(rv); 419 } 420 421 struct puffs_node * 422 makenode(struct puffs_usermount *pu, struct puffs_node *parent, 423 const struct psshfs_dir *pd, const struct vattr *vap) 424 { 425 struct psshfs_node *psn_parent = parent->pn_data; 426 struct psshfs_node *psn; 427 struct puffs_node *pn; 428 429 psn = emalloc(sizeof(struct psshfs_node)); 430 memset(psn, 0, sizeof(struct psshfs_node)); 431 432 pn = puffs_pn_new(pu, psn); 433 if (!pn) { 434 free(psn); 435 return NULL; 436 } 437 setpnva(pu, pn, &pd->va); 438 setpnva(pu, pn, vap); 439 psn->attrread = pd->attrread; 440 441 psn->parent = parent; 442 psn_parent->childcount++; 443 444 TAILQ_INIT(&psn->pw); 445 446 return pn; 447 } 448 449 struct puffs_node * 450 allocnode(struct puffs_usermount *pu, struct puffs_node *parent, 451 const char *entryname, const struct vattr *vap) 452 { 453 struct psshfs_ctx *pctx = puffs_getspecific(pu); 454 struct psshfs_dir *pd; 455 struct puffs_node *pn; 456 457 pd = direnter(parent, entryname); 458 459 pd->va.va_fileid = pctx->nextino++; 460 if (vap->va_type == VDIR) { 461 pd->va.va_nlink = 2; 462 parent->pn_va.va_nlink++; 463 } else { 464 pd->va.va_nlink = 1; 465 } 466 467 pn = makenode(pu, parent, pd, vap); 468 if (pn) { 469 pd->va.va_fileid = pn->pn_va.va_fileid; 470 pd->entry = pn; 471 } 472 473 return pn; 474 } 475 476 struct psshfs_dir * 477 direnter(struct puffs_node *parent, const char *entryname) 478 { 479 struct psshfs_node *psn_parent = parent->pn_data; 480 struct psshfs_dir *pd; 481 int i; 482 483 /* create directory entry */ 484 if (psn_parent->denttot == psn_parent->dentnext) 485 allocdirs(psn_parent); 486 487 i = psn_parent->dentnext; 488 pd = &psn_parent->dir[i]; 489 pd->entryname = estrdup(entryname); 490 pd->valid = 1; 491 pd->attrread = 0; 492 puffs_vattr_null(&pd->va); 493 psn_parent->dentnext++; 494 495 return pd; 496 } 497 498 void 499 doreclaim(struct puffs_node *pn) 500 { 501 struct psshfs_node *psn = pn->pn_data; 502 struct psshfs_node *psn_parent; 503 struct psshfs_dir *dent; 504 505 psn_parent = psn->parent->pn_data; 506 psn_parent->childcount--; 507 508 /* 509 * Null out entry from directory. Do not treat a missing entry 510 * as an invariant error, since the node might be removed from 511 * under us, and we might do a readdir before the reclaim resulting 512 * in no directory entry in the parent directory. 513 */ 514 dent = lookup_by_entry(psn_parent->dir, psn_parent->dentnext, pn); 515 if (dent) 516 dent->entry = NULL; 517 518 if (pn->pn_va.va_type == VDIR) { 519 freedircache(psn->dir, psn->dentnext); 520 psn->denttot = psn->dentnext = 0; 521 } 522 if (psn->symlink) 523 free(psn->symlink); 524 525 puffs_pn_put(pn); 526 } 527 528 void 529 nukenode(struct puffs_node *node, const char *entryname, int reclaim) 530 { 531 struct psshfs_node *psn, *psn_parent; 532 struct psshfs_dir *pd; 533 534 psn = node->pn_data; 535 psn_parent = psn->parent->pn_data; 536 pd = lookup(psn_parent->dir, psn_parent->dentnext, entryname); 537 assert(pd != NULL); 538 pd->valid = 0; 539 free(pd->entryname); 540 pd->entryname = NULL; 541 542 if (node->pn_va.va_type == VDIR) 543 psn->parent->pn_va.va_nlink--; 544 545 if (reclaim) 546 doreclaim(node); 547 } 548