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