xref: /netbsd/usr.sbin/puffs/mount_psshfs/subr.c (revision 808903e4)
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
freedircache(struct psshfs_dir * base,size_t count)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
allocdirs(struct psshfs_node * psn)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
setpnva(struct puffs_usermount * pu,struct puffs_node * pn,const struct vattr * vap)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 *
lookup(struct psshfs_dir * bdir,size_t ndir,const char * name)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 *
lookup_by_entry(struct psshfs_dir * bdir,size_t ndir,struct puffs_node * entry)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
closehandles(struct puffs_usermount * pu,struct psshfs_node * psn,int which)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
lazyopen_rresp(struct puffs_usermount * pu,struct puffs_framebuf * pb,void * arg,int error)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
lazyopen_wresp(struct puffs_usermount * pu,struct puffs_framebuf * pb,void * arg,int error)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
getpathattr(struct puffs_usermount * pu,const char * path,struct vattr * vap)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
getnodeattr(struct puffs_usermount * pu,struct puffs_node * pn,const char * path)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
sftp_readdir(struct puffs_usermount * pu,struct psshfs_ctx * pctx,struct puffs_node * pn)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 *
makenode(struct puffs_usermount * pu,struct puffs_node * parent,const struct psshfs_dir * pd,const struct vattr * vap)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 *
allocnode(struct puffs_usermount * pu,struct puffs_node * parent,const char * entryname,const struct vattr * vap)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 *
direnter(struct puffs_node * parent,const char * entryname)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
doreclaim(struct puffs_node * pn)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
nukenode(struct puffs_node * node,const char * entryname,int reclaim)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