xref: /netbsd-src/usr.sbin/puffs/mount_psshfs/subr.c (revision 3816d47b2c42fcd6e549e3407f842a5b1a1d23ad)
1 /*      $NetBSD: subr.c,v 1.48 2010/01/07 21:05:50 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.48 2010/01/07 21:05:50 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)
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, 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 			 * Check if we already have a psshfs_dir for the
350 			 * name we are processing.  If so, use the old one.
351 			 * If not, create a new one
352 			 */
353 			testd = lookup(olddir, nent, psn->dir[idx].entryname);
354 			if (testd) {
355 				psn->dir[idx].entry = testd->entry;
356 				/*
357 				 * Has entry.  Update attributes to what
358 				 * we just got from the server.
359 				 */
360 				if (testd->entry) {
361 					setpnva(pu, testd->entry,
362 					    &psn->dir[idx].va);
363 					psn->dir[idx].va.va_fileid
364 					    = testd->entry->pn_va.va_fileid;
365 
366 				/*
367 				 * No entry.  This can happen in two cases:
368 				 * 1) the file was created "behind our back"
369 				 *    on the server
370 				 * 2) we do two readdirs before we instantiate
371 				 *    the node (or run with -t 0).
372 				 *
373 				 * Cache attributes from the server in
374 				 * case we want to instantiate this node
375 				 * soon.  Also preserve the old inode number
376 				 * which was given when the dirent was created.
377 				 */
378 				} else {
379 					psn->dir[idx].va.va_fileid
380 					    = testd->va.va_fileid;
381 					testd->va = psn->dir[idx].va;
382 				}
383 
384 			/* No previous entry?  Initialize this one. */
385 			} else {
386 				psn->dir[idx].entry = NULL;
387 				psn->dir[idx].va.va_fileid = pctx->nextino++;
388 			}
389 			psn->dir[idx].attrread = psn->dentread;
390 			psn->dir[idx].valid = 1;
391 		}
392 	}
393 
394  out:
395 	/* XXX: rv */
396 	psn->dentnext = idx;
397 	freedircache(olddir, nent);
398 
399 	reqid = NEXTREQ(pctx);
400 	psbuf_recycleout(pb);
401 	psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, dhand, dhandlen);
402 	puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb, 1, 0);
403 	free(dhand);
404 	free(longname);
405 
406 	return rv;
407 
408  wayout:
409 	free(dhand);
410 	PSSHFSRETURN(rv);
411 }
412 
413 struct puffs_node *
414 makenode(struct puffs_usermount *pu, struct puffs_node *parent,
415 	struct psshfs_dir *pd, const struct vattr *vap)
416 {
417 	struct psshfs_node *psn_parent = parent->pn_data;
418 	struct psshfs_node *psn;
419 	struct puffs_node *pn;
420 
421 	psn = emalloc(sizeof(struct psshfs_node));
422 	memset(psn, 0, sizeof(struct psshfs_node));
423 
424 	pn = puffs_pn_new(pu, psn);
425 	if (!pn) {
426 		free(psn);
427 		return NULL;
428 	}
429 	setpnva(pu, pn, &pd->va);
430 	setpnva(pu, pn, vap);
431 	psn->attrread = pd->attrread;
432 
433 	pd->entry = pn;
434 	psn->parent = parent;
435 	psn_parent->childcount++;
436 
437 	TAILQ_INIT(&psn->pw);
438 
439 	return pn;
440 }
441 
442 struct puffs_node *
443 allocnode(struct puffs_usermount *pu, struct puffs_node *parent,
444 	const char *entryname, const struct vattr *vap)
445 {
446 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
447 	struct psshfs_dir *pd;
448 	struct puffs_node *pn;
449 
450 	pd = direnter(parent, entryname);
451 
452 	pd->va.va_fileid = pctx->nextino++;
453 	if (vap->va_type == VDIR) {
454 		pd->va.va_nlink = 2;
455 		parent->pn_va.va_nlink++;
456 	} else {
457 		pd->va.va_nlink = 1;
458 	}
459 
460 	pn = makenode(pu, parent, pd, vap);
461 	if (pn)
462 		pd->va.va_fileid = pn->pn_va.va_fileid;
463 
464 	return pn;
465 }
466 
467 struct psshfs_dir *
468 direnter(struct puffs_node *parent, const char *entryname)
469 {
470 	struct psshfs_node *psn_parent = parent->pn_data;
471 	struct psshfs_dir *pd;
472 	int i;
473 
474 	/* create directory entry */
475 	if (psn_parent->denttot == psn_parent->dentnext)
476 		allocdirs(psn_parent);
477 
478 	i = psn_parent->dentnext;
479 	pd = &psn_parent->dir[i];
480 	pd->entryname = estrdup(entryname);
481 	pd->valid = 1;
482 	pd->attrread = 0;
483 	puffs_vattr_null(&pd->va);
484 	psn_parent->dentnext++;
485 
486 	return pd;
487 }
488 
489 void
490 doreclaim(struct puffs_node *pn)
491 {
492 	struct psshfs_node *psn = pn->pn_data;
493 	struct psshfs_node *psn_parent;
494 	struct psshfs_dir *dent;
495 
496 	psn_parent = psn->parent->pn_data;
497 	psn_parent->childcount--;
498 
499 	/*
500 	 * Null out entry from directory.  Do not treat a missing entry
501 	 * as an invariant error, since the node might be removed from
502 	 * under us, and we might do a readdir before the reclaim resulting
503 	 * in no directory entry in the parent directory.
504 	 */
505 	dent = lookup_by_entry(psn_parent->dir, psn_parent->dentnext, pn);
506 	if (dent)
507 		dent->entry = NULL;
508 
509 	if (pn->pn_va.va_type == VDIR) {
510 		freedircache(psn->dir, psn->dentnext);
511 		psn->denttot = psn->dentnext = 0;
512 	}
513 	if (psn->symlink)
514 		free(psn->symlink);
515 
516 	puffs_pn_put(pn);
517 }
518 
519 void
520 nukenode(struct puffs_node *node, const char *entryname, int reclaim)
521 {
522 	struct psshfs_node *psn, *psn_parent;
523 	struct psshfs_dir *pd;
524 
525 	psn = node->pn_data;
526 	psn_parent = psn->parent->pn_data;
527 	pd = lookup(psn_parent->dir, psn_parent->dentnext, entryname);
528 	assert(pd != NULL);
529 	pd->valid = 0;
530 	free(pd->entryname);
531 	pd->entryname = NULL;
532 
533 	if (node->pn_va.va_type == VDIR)
534 		psn->parent->pn_va.va_nlink--;
535 
536 	if (reclaim)
537 		doreclaim(node);
538 }
539