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