xref: /netbsd/usr.sbin/puffs/mount_psshfs/subr.c (revision 6550d01e)
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