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