subr.c revision 1.22 1 /* $NetBSD: subr.c,v 1.22 2007/05/20 20:06:23 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 * 3. The name of the company nor the name of the author may be used to
15 * endorse or promote products derived from this software without specific
16 * prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/cdefs.h>
32 #ifndef lint
33 __RCSID("$NetBSD: subr.c,v 1.22 2007/05/20 20:06:23 pooka Exp $");
34 #endif /* !lint */
35
36 #include <assert.h>
37 #include <errno.h>
38 #include <puffs.h>
39 #include <stdlib.h>
40 #include <util.h>
41
42 #include "psshfs.h"
43 #include "sftp_proto.h"
44
45 static void
46 freedircache(struct psshfs_dir *base, size_t count)
47 {
48 int i;
49
50 for (i = 0; i < count; i++) {
51 free(base[i].entryname);
52 base[i].entryname = NULL;
53 }
54
55 free(base);
56 }
57
58 #define ENTRYCHUNK 16
59 static void
60 allocdirs(struct psshfs_node *psn)
61 {
62 size_t oldtot = psn->denttot;
63
64 psn->denttot += ENTRYCHUNK;
65 psn->dir = erealloc(psn->dir,
66 psn->denttot * sizeof(struct psshfs_dir));
67 memset(psn->dir + oldtot, 0, ENTRYCHUNK * sizeof(struct psshfs_dir));
68
69 psn->da = erealloc(psn->da, psn->denttot * sizeof(struct delayattr));
70 }
71
72 struct psshfs_dir *
73 lookup(struct psshfs_dir *bdir, size_t ndir, const char *name)
74 {
75 struct psshfs_dir *test;
76 int i;
77
78 for (i = 0; i < ndir; i++) {
79 test = &bdir[i];
80 if (test->valid != 1)
81 continue;
82 if (strcmp(test->entryname, name) == 0)
83 return test;
84 }
85
86 return NULL;
87 }
88
89 static struct psshfs_dir *
90 lookup_by_entry(struct psshfs_dir *bdir, size_t ndir, struct puffs_node *entry)
91 {
92 struct psshfs_dir *test;
93 int i;
94
95 for (i = 0; i < ndir; i++) {
96 test = &bdir[i];
97 if (test->valid != 1)
98 continue;
99 if (test->entry == entry)
100 return test;
101 }
102
103 return NULL;
104 }
105
106 struct readdirattr {
107 struct psshfs_node *psn;
108 int idx;
109 char entryname[MAXPATHLEN+1];
110 };
111
112 static void
113 readdir_getattr_resp(struct puffs_usermount *pu,
114 struct puffs_framebuf *pb, void *arg, int error)
115 {
116 struct readdirattr *rda = arg;
117 struct psshfs_node *psn = rda->psn;
118 struct psshfs_node *psn_targ;
119 struct psshfs_dir *pdir;
120 struct vattr va;
121
122 if (error)
123 goto out;
124
125 pdir = lookup(psn->dir, psn->denttot, rda->entryname);
126 if (!pdir)
127 goto out;
128
129 if (psbuf_expect_attrs(pb, &va))
130 goto out;
131
132 psn_targ = NULL;
133 if (pdir->entry) {
134 psn_targ = pdir->entry->pn_data;
135
136 puffs_setvattr(&pdir->entry->pn_va, &va);
137 psn_targ->attrread = time(NULL);
138 } else {
139 puffs_setvattr(&pdir->va, &va);
140 pdir->attrread = time(NULL);
141 }
142
143 out:
144 if (psn_targ) {
145 psn_targ->getattr_pb = NULL;
146 assert(pdir->getattr_pb == NULL);
147 } else {
148 pdir->getattr_pb = NULL;
149 }
150
151 free(rda);
152 puffs_framebuf_destroy(pb);
153 }
154
155 static void
156 readdir_getattr(struct puffs_usermount *pu, struct psshfs_node *psn,
157 const char *basepath, int idx)
158 {
159 char path[MAXPATHLEN+1];
160 struct psshfs_ctx *pctx = puffs_getspecific(pu);
161 struct psshfs_dir *pdir = psn->dir;
162 struct puffs_framebuf *pb;
163 struct readdirattr *rda;
164 const char *entryname = pdir[idx].entryname;
165 uint32_t reqid = NEXTREQ(pctx);
166
167 rda = emalloc(sizeof(struct readdirattr));
168 rda->psn = psn;
169 rda->idx = idx;
170 strlcpy(rda->entryname, entryname, sizeof(rda->entryname));
171
172 strcpy(path, basepath);
173 strcat(path, "/");
174 strlcat(path, entryname, sizeof(path));
175
176 pb = psbuf_makeout();
177 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path);
178 psn->da[psn->nextda].pufbuf = pb;
179 psn->da[psn->nextda].rda = rda;
180 psn->nextda++;
181 }
182
183 static void
184 readdir_getattr_send(struct puffs_usermount *pu, struct psshfs_node *psn)
185 {
186 struct psshfs_ctx *pctx = puffs_getspecific(pu);
187 struct psshfs_dir *pdir = psn->dir;
188 struct psshfs_node *psn_targ;
189 struct readdirattr *rda;
190 size_t i;
191 int rv = 0;
192
193 for (i = 0; i < psn->nextda; i++) {
194 rda = psn->da[i].rda;
195 if (pdir[rda->idx].entry) {
196 psn_targ = pdir[rda->idx].entry->pn_data;
197 psn_targ->getattr_pb = psn->da[i].pufbuf;
198 } else {
199 pdir[rda->idx].getattr_pb = psn->da[i].pufbuf;
200 }
201 SENDCB(psn->da[i].pufbuf, readdir_getattr_resp, rda);
202 }
203
204 out:
205 return;
206 }
207
208 int
209 getpathattr(struct puffs_cc *pcc, const char *path, struct vattr *vap)
210 {
211 PSSHFSAUTOVAR(pcc);
212
213 psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path);
214 GETRESPONSE(pb);
215
216 rv = psbuf_expect_attrs(pb, vap);
217
218 out:
219 PSSHFSRETURN(rv);
220 }
221
222 int
223 getnodeattr(struct puffs_cc *pcc, struct puffs_node *pn)
224 {
225 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
226 struct psshfs_node *psn = pn->pn_data;
227 struct vattr va;
228 int rv;
229
230 if ((time(NULL) - psn->attrread) >= PSSHFS_REFRESHIVAL) {
231 if (psn->getattr_pb) {
232 rv=puffs_framev_framebuf_ccpromote(psn->getattr_pb,pcc);
233 assert(rv == 0);
234
235 rv = psbuf_expect_attrs(psn->getattr_pb, &va);
236 puffs_framebuf_destroy(psn->getattr_pb);
237 psn->getattr_pb = NULL;
238 if (rv)
239 return rv;
240 } else {
241 rv = getpathattr(pcc, PNPATH(pn), &va);
242 if (rv)
243 return rv;
244 }
245
246 /*
247 * Check if the file was modified from below us.
248 * If so, invalidate page cache. This is the only
249 * sensible place we can do this in.
250 */
251 if (psn->attrread)
252 if (pn->pn_va.va_mtime.tv_sec != va.va_mtime.tv_sec)
253 puffs_inval_pagecache_node(pu, pn);
254
255 puffs_setvattr(&pn->pn_va, &va);
256 psn->attrread = time(NULL);
257 }
258
259 return 0;
260 }
261
262 int
263 sftp_readdir(struct puffs_cc *pcc, struct psshfs_ctx *pctx,
264 struct puffs_node *pn)
265 {
266 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
267 struct psshfs_node *psn = pn->pn_data;
268 struct psshfs_dir *olddir, *testd;
269 struct puffs_framebuf *pb;
270 uint32_t reqid = NEXTREQ(pctx);
271 uint32_t count, dhandlen;
272 char *dhand = NULL;
273 size_t nent;
274 char *longname;
275 int idx, rv;
276
277 assert(pn->pn_va.va_type == VDIR);
278 idx = 0;
279 olddir = psn->dir;
280 nent = psn->dentnext;
281
282 if (psn->dir && (time(NULL) - psn->dentread) < PSSHFS_REFRESHIVAL)
283 return 0;
284
285 puffs_inval_namecache_dir(puffs_cc_getusermount(pcc), pn);
286
287 pb = psbuf_makeout();
288 psbuf_req_str(pb, SSH_FXP_OPENDIR, reqid, PNPATH(pn));
289 GETRESPONSE(pb);
290
291 rv = psbuf_expect_handle(pb, &dhand, &dhandlen);
292 if (rv)
293 goto wayout;
294
295 /*
296 * Well, the following is O(n^2), so feel free to improve if it
297 * gets too taxing on your system.
298 */
299
300 /*
301 * note: for the "getattr in batch" to work, this must be before
302 * the attribute-getting. Otherwise times for first entries in
303 * large directories might expire before the directory itself and
304 * result in one-by-one attribute fetching.
305 */
306 psn->dentread = time(NULL);
307
308 psn->dentnext = 0;
309 psn->denttot = 0;
310 psn->dir = NULL;
311 psn->nextda = 0;
312
313 for (;;) {
314 reqid = NEXTREQ(pctx);
315 psbuf_recycleout(pb);
316 psbuf_req_data(pb, SSH_FXP_READDIR, reqid, dhand, dhandlen);
317 GETRESPONSE(pb);
318
319 /* check for EOF */
320 if (psbuf_get_type(pb) == SSH_FXP_STATUS) {
321 rv = psbuf_expect_status(pb);
322 goto out;
323 }
324 rv = psbuf_expect_name(pb, &count);
325 if (rv)
326 goto out;
327
328 for (; count--; idx++) {
329 if (idx == psn->denttot)
330 allocdirs(psn);
331 if ((rv = psbuf_get_str(pb,
332 &psn->dir[idx].entryname, NULL)))
333 goto out;
334 if ((rv = psbuf_get_str(pb, &longname, NULL)))
335 goto out;
336 if ((rv = psbuf_get_vattr(pb, &psn->dir[idx].va))) {
337 free(longname);
338 goto out;
339 }
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
347 testd = lookup(olddir, nent, psn->dir[idx].entryname);
348 if (testd) {
349 psn->dir[idx].entry = testd->entry;
350 psn->dir[idx].va = testd->va;
351 } else {
352 psn->dir[idx].entry = NULL;
353 psn->dir[idx].va.va_fileid = pctx->nextino++;
354 }
355 /*
356 * XXX: there's a dangling pointer race here if
357 * the server responds to our queries out-of-order.
358 * fixxxme some day
359 */
360 readdir_getattr(puffs_cc_getusermount(pcc),
361 psn, PNPATH(pn), idx);
362
363 psn->dir[idx].valid = 1;
364 }
365 }
366
367 out:
368 /* fire off getattr requests */
369 readdir_getattr_send(pu, psn);
370
371 /* XXX: rv */
372 psn->dentnext = idx;
373 freedircache(olddir, nent);
374
375 reqid = NEXTREQ(pctx);
376 psbuf_recycleout(pb);
377 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, dhand, dhandlen);
378 JUSTSEND(pb);
379 free(dhand);
380
381 return rv;
382
383 wayout:
384 free(dhand);
385 PSSHFSRETURN(rv);
386 }
387
388 struct puffs_node *
389 makenode(struct puffs_usermount *pu, struct puffs_node *parent,
390 struct psshfs_dir *pd, const struct vattr *vap)
391 {
392 struct psshfs_node *psn_parent = parent->pn_data;
393 struct psshfs_node *psn;
394 struct puffs_node *pn;
395
396 psn = emalloc(sizeof(struct psshfs_node));
397 memset(psn, 0, sizeof(struct psshfs_node));
398
399 pn = puffs_pn_new(pu, psn);
400 if (!pn) {
401 free(psn);
402 return NULL;
403 }
404 puffs_setvattr(&pn->pn_va, &pd->va);
405 psn->attrread = pd->attrread;
406 puffs_setvattr(&pn->pn_va, vap);
407
408 pd->entry = pn;
409 psn->parent = parent;
410 psn_parent->childcount++;
411
412 if (pd->getattr_pb) {
413 psn->getattr_pb = pd->getattr_pb;
414 pd->getattr_pb = NULL;
415 }
416
417 return pn;
418 }
419
420 struct puffs_node *
421 allocnode(struct puffs_usermount *pu, struct puffs_node *parent,
422 const char *entryname, const struct vattr *vap)
423 {
424 struct psshfs_ctx *pctx = puffs_getspecific(pu);
425 struct psshfs_dir *pd;
426 struct puffs_node *pn;
427
428 pd = direnter(parent, entryname);
429
430 pd->va.va_fileid = pctx->nextino++;
431 if (vap->va_type == VDIR) {
432 pd->va.va_nlink = 2;
433 parent->pn_va.va_nlink++;
434 } else {
435 pd->va.va_nlink = 1;
436 }
437
438 pn = makenode(pu, parent, pd, vap);
439 if (pn)
440 pd->va.va_fileid = pn->pn_va.va_fileid;
441
442 return pn;
443 }
444
445 struct psshfs_dir *
446 direnter(struct puffs_node *parent, const char *entryname)
447 {
448 struct psshfs_node *psn_parent = parent->pn_data;
449 struct psshfs_dir *pd;
450 int i;
451
452 /* create directory entry */
453 if (psn_parent->denttot == psn_parent->dentnext)
454 allocdirs(psn_parent);
455
456 i = psn_parent->dentnext;
457 pd = &psn_parent->dir[i];
458 pd->entryname = estrdup(entryname);
459 pd->valid = 1;
460 pd->attrread = 0;
461 puffs_vattr_null(&pd->va);
462 psn_parent->dentnext++;
463
464 return pd;
465 }
466
467 void
468 doreclaim(struct puffs_node *pn)
469 {
470 struct psshfs_node *psn = pn->pn_data;
471 struct psshfs_node *psn_parent;
472 struct psshfs_dir *dent;
473
474 psn_parent = psn->parent->pn_data;
475 psn_parent->childcount--;
476
477 /*
478 * Null out entry from directory. Do not treat a missing entry
479 * as an invariant error, since the node might be removed from
480 * under us, and we might do a readdir before the reclaim resulting
481 * in no directory entry in the parent directory.
482 */
483 dent = lookup_by_entry(psn_parent->dir, psn_parent->dentnext, pn);
484 if (dent)
485 dent->entry = NULL;
486
487 if (pn->pn_va.va_type == VDIR) {
488 freedircache(psn->dir, psn->dentnext);
489 free(psn->da);
490 }
491
492 puffs_pn_put(pn);
493 }
494
495 void
496 nukenode(struct puffs_node *node, const char *entryname, int reclaim)
497 {
498 struct psshfs_node *psn, *psn_parent;
499 struct psshfs_dir *pd;
500
501 psn = node->pn_data;
502 psn_parent = psn->parent->pn_data;
503 pd = lookup(psn_parent->dir, psn_parent->dentnext, entryname);
504 assert(pd != NULL);
505 pd->valid = 0;
506 free(pd->entryname);
507 pd->entryname = NULL;
508
509 if (node->pn_va.va_type == VDIR)
510 psn->parent->pn_va.va_nlink--;
511
512 if (reclaim)
513 doreclaim(node);
514 }
515