Merge branch 'vendor/NCURSES'
[dragonfly.git] / usr.sbin / puffs / mount_psshfs / subr.c
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 <assert.h>
29 #include <err.h>
30 #include <errno.h>
31 #include <inttypes.h>
32 #include <puffs.h>
33 #include <stdlib.h>
34
35 #include "psshfs.h"
36 #include "sftp_proto.h"
37 #include "util_compat.h"
38
39 static void
40 freedircache(struct psshfs_dir *base, size_t count)
41 {
42         size_t i;
43
44         for (i = 0; i < count; i++) {
45                 free(base[i].entryname);
46                 base[i].entryname = NULL;
47         }
48
49         free(base);
50 }
51
52 #define ENTRYCHUNK 16
53 static void
54 allocdirs(struct psshfs_node *psn)
55 {
56         size_t oldtot = psn->denttot;
57
58         psn->denttot += ENTRYCHUNK;
59         psn->dir = erealloc(psn->dir,
60             psn->denttot * sizeof(struct psshfs_dir));
61         memset(psn->dir + oldtot, 0, ENTRYCHUNK * sizeof(struct psshfs_dir));
62 }
63
64 static void
65 setpnva(struct puffs_usermount *pu, struct puffs_node *pn,
66         const struct vattr *vap)
67 {
68         struct psshfs_ctx *pctx = puffs_getspecific(pu);
69         struct psshfs_node *psn = pn->pn_data;
70         struct vattr modva;
71
72         /*
73          * Check if the file was modified from below us.
74          * If so, invalidate page cache.  This is the only
75          * sensible place we can do this in.
76          */
77         if (pn->pn_va.va_mtime.tv_sec != PUFFS_VNOVAL)
78                 if (pn->pn_va.va_mtime.tv_sec != vap->va_mtime.tv_sec
79                     && pn->pn_va.va_type == VREG)
80                         puffs_inval_pagecache_node(pu, pn);
81
82         modva = *vap;
83         if (pctx->domangleuid && modva.va_uid == pctx->mangleuid)
84                 modva.va_uid = pctx->myuid;
85         if (pctx->domanglegid && modva.va_gid == pctx->manglegid)
86                 modva.va_gid = pctx->mygid;
87
88         puffs_setvattr(&pn->pn_va, &modva);
89         psn->attrread = time(NULL);
90 }
91
92 struct psshfs_dir *
93 lookup(struct psshfs_dir *bdir, size_t ndir, const char *name)
94 {
95         struct psshfs_dir *test;
96         size_t i;
97
98         for (i = 0; i < ndir; i++) {
99                 test = &bdir[i];
100                 if (test->valid != 1)
101                         continue;
102                 if (strcmp(test->entryname, name) == 0)
103                         return test;
104         }
105
106         return NULL;
107 }
108
109 static struct psshfs_dir *
110 lookup_by_entry(struct psshfs_dir *bdir, size_t ndir, struct puffs_node *entry)
111 {
112         struct psshfs_dir *test;
113         size_t i;
114
115         for (i = 0; i < ndir; i++) {
116                 test = &bdir[i];
117                 if (test->valid != 1)
118                         continue;
119                 if (test->entry == entry)
120                         return test;
121         }
122
123         return NULL;
124 }
125
126
127 void
128 closehandles(struct puffs_usermount *pu, struct psshfs_node *psn, int which)
129 {
130         struct psshfs_ctx *pctx = puffs_getspecific(pu);
131         struct puffs_framebuf *pb1, *pb2;
132         uint32_t reqid;
133
134         if (psn->fhand_r && (which & HANDLE_READ)) {
135                 assert(psn->lazyopen_r == NULL);
136
137                 pb1 = psbuf_makeout();
138                 reqid = NEXTREQ(pctx);
139                 psbuf_req_data(pb1, SSH_FXP_CLOSE, reqid,
140                     psn->fhand_r, psn->fhand_r_len);
141                 puffs_framev_enqueue_justsend(pu, pctx->sshfd_data, pb1, 1, 0);
142                 free(psn->fhand_r);
143                 psn->fhand_r = NULL;
144         }
145
146         if (psn->fhand_w && (which & HANDLE_WRITE)) {
147                 assert(psn->lazyopen_w == NULL);
148
149                 pb2 = psbuf_makeout();
150                 reqid = NEXTREQ(pctx);
151                 psbuf_req_data(pb2, SSH_FXP_CLOSE, reqid,
152                     psn->fhand_w, psn->fhand_w_len);
153                 puffs_framev_enqueue_justsend(pu, pctx->sshfd_data, pb2, 1, 0);
154                 free(psn->fhand_w);
155                 psn->fhand_w = NULL;
156         }
157
158         psn->stat |= PSN_HANDLECLOSE;
159 }
160
161 void
162 lazyopen_rresp(struct puffs_usermount *pu, struct puffs_framebuf *pb,
163         void *arg, int error)
164 {
165         struct psshfs_node *psn = arg;
166
167         /* XXX: this is not enough */
168         if (psn->stat & PSN_RECLAIMED) {
169                 error = ENOENT;
170                 goto moreout;
171         }
172         if (error)
173                 goto out;
174
175         error = psbuf_expect_handle(pb, &psn->fhand_r, &psn->fhand_r_len);
176
177  out:
178         psn->lazyopen_err_r = error;
179         psn->lazyopen_r = NULL;
180         if (error)
181                 psn->stat &= ~PSN_DOLAZY_R;
182         if (psn->stat & PSN_HANDLECLOSE && (psn->stat & PSN_LAZYWAIT_R) == 0)
183                 closehandles(pu, psn, HANDLE_READ);
184  moreout:
185         puffs_framebuf_destroy(pb);
186 }
187
188 void
189 lazyopen_wresp(struct puffs_usermount *pu, struct puffs_framebuf *pb,
190         void *arg, int error)
191 {
192         struct psshfs_node *psn = arg;
193
194         /* XXX: this is not enough */
195         if (psn->stat & PSN_RECLAIMED) {
196                 error = ENOENT;
197                 goto moreout;
198         }
199         if (error)
200                 goto out;
201
202         error = psbuf_expect_handle(pb, &psn->fhand_w, &psn->fhand_w_len);
203
204  out:
205         psn->lazyopen_err_w = error;
206         psn->lazyopen_w = NULL;
207         if (error)
208                 psn->stat &= ~PSN_DOLAZY_W;
209         if (psn->stat & PSN_HANDLECLOSE && (psn->stat & PSN_LAZYWAIT_W) == 0)
210                 closehandles(pu, psn, HANDLE_WRITE);
211  moreout:
212         puffs_framebuf_destroy(pb);
213 }
214
215 struct readdirattr {
216         struct psshfs_node *psn;
217         int idx;
218         char entryname[MAXPATHLEN+1];
219 };
220
221 int
222 getpathattr(struct puffs_usermount *pu, const char *path, struct vattr *vap)
223 {
224         PSSHFSAUTOVAR(pu);
225
226         psbuf_req_str(pb, SSH_FXP_LSTAT, reqid, path);
227         GETRESPONSE(pb, pctx->sshfd);
228
229         rv = psbuf_expect_attrs(pb, vap);
230
231  out:
232         PSSHFSRETURN(rv);
233 }
234
235 int
236 getnodeattr(struct puffs_usermount *pu, struct puffs_node *pn, const char *path)
237 {
238         struct psshfs_ctx *pctx = puffs_getspecific(pu);
239         struct psshfs_node *psn = pn->pn_data;
240         struct vattr va;
241         int rv;
242
243         if (!psn->attrread || REFRESHTIMEOUT(pctx, time(NULL)-psn->attrread)) {
244                 rv = getpathattr(pu, path ? path : PNPATH(pn), &va);
245                 if (rv)
246                         return rv;
247
248                 setpnva(pu, pn, &va);
249         }
250
251         return 0;
252 }
253
254 int
255 sftp_readdir(struct puffs_usermount *pu, struct psshfs_ctx *pctx,
256         struct puffs_node *pn)
257 {
258         struct puffs_cc *pcc = puffs_cc_getcc(pu);
259         struct psshfs_node *psn = pn->pn_data;
260         struct psshfs_dir *olddir, *testd;
261         struct puffs_framebuf *pb;
262         uint32_t reqid = NEXTREQ(pctx);
263         uint32_t count, dhandlen;
264         int tmpval;
265         char *dhand = NULL;
266         size_t nent;
267         char *longname = NULL;
268         size_t idx;
269         int rv;
270
271         assert(pn->pn_va.va_type == VDIR);
272         idx = 0;
273         olddir = psn->dir;
274         nent = psn->dentnext;
275
276         if (psn->dir && psn->dentread
277             && !REFRESHTIMEOUT(pctx, time(NULL) - psn->dentread))
278                 return 0;
279
280         if (psn->dentread) {
281                 if ((rv = puffs_inval_namecache_dir(pu, pn)))
282                         warn("readdir: dcache inval fail %p", pn);
283         }
284
285         pb = psbuf_makeout();
286         psbuf_req_str(pb, SSH_FXP_OPENDIR, reqid, PNPATH(pn));
287         if (puffs_framev_enqueue_cc(pcc, pctx->sshfd, pb, 0) == -1) {
288                 rv = errno;
289                 goto wayout;
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
312         for (;;) {
313                 reqid = NEXTREQ(pctx);
314                 psbuf_recycleout(pb);
315                 psbuf_req_data(pb, SSH_FXP_READDIR, reqid, dhand, dhandlen);
316                 GETRESPONSE(pb, pctx->sshfd);
317
318                 /* check for EOF */
319                 if (psbuf_get_type(pb) == SSH_FXP_STATUS) {
320                         rv = psbuf_expect_status(pb);
321                         goto out;
322                 }
323                 rv = psbuf_expect_name(pb, &count);
324                 if (rv)
325                         goto out;
326
327                 for (; count--; idx++) {
328                         if (idx == psn->denttot)
329                                 allocdirs(psn);
330                         if ((rv = psbuf_get_str(pb,
331                             &psn->dir[idx].entryname, NULL)))
332                                 goto out;
333                         if ((rv = psbuf_get_str(pb, &longname, NULL)) != 0)
334                                 goto out;
335                         if ((rv = psbuf_get_vattr(pb, &psn->dir[idx].va)) != 0)
336                                 goto out;
337                         if (sscanf(longname, "%*s%d",
338                             &tmpval) != 1) {
339                                 rv = EPROTO;
340                                 goto out;
341                         }
342                         psn->dir[idx].va.va_nlink = tmpval;
343                         free(longname);
344                         longname = NULL;
345
346                         /*
347                          * In case of DOT, copy the attributes (mostly
348                          * because we want the link count for the root dir).
349                          */
350                         if (strcmp(psn->dir[idx].entryname, ".") == 0) {
351                                 setpnva(pu, pn, &psn->dir[idx].va);
352                         }
353
354                         /*
355                          * Check if we already have a psshfs_dir for the
356                          * name we are processing.  If so, use the old one.
357                          * If not, create a new one
358                          */
359                         testd = lookup(olddir, nent, psn->dir[idx].entryname);
360                         if (testd) {
361                                 psn->dir[idx].entry = testd->entry;
362                                 /*
363                                  * Has entry.  Update attributes to what
364                                  * we just got from the server.
365                                  */
366                                 if (testd->entry) {
367                                         setpnva(pu, testd->entry,
368                                             &psn->dir[idx].va);
369                                         psn->dir[idx].va.va_fileid
370                                             = testd->entry->pn_va.va_fileid;
371
372                                 /*
373                                  * No entry.  This can happen in two cases:
374                                  * 1) the file was created "behind our back"
375                                  *    on the server
376                                  * 2) we do two readdirs before we instantiate
377                                  *    the node (or run with -t 0).
378                                  *
379                                  * Cache attributes from the server in
380                                  * case we want to instantiate this node
381                                  * soon.  Also preserve the old inode number
382                                  * which was given when the dirent was created.
383                                  */
384                                 } else {
385                                         psn->dir[idx].va.va_fileid
386                                             = testd->va.va_fileid;
387                                         testd->va = psn->dir[idx].va;
388                                 }
389
390                         /* No previous entry?  Initialize this one. */
391                         } else {
392                                 psn->dir[idx].entry = NULL;
393                                 psn->dir[idx].va.va_fileid = pctx->nextino++;
394                         }
395                         psn->dir[idx].attrread = psn->dentread;
396                         psn->dir[idx].valid = 1;
397                 }
398         }
399
400  out:
401         /* XXX: rv */
402         psn->dentnext = idx;
403         freedircache(olddir, nent);
404
405         reqid = NEXTREQ(pctx);
406         psbuf_recycleout(pb);
407         psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, dhand, dhandlen);
408         puffs_framev_enqueue_justsend(pu, pctx->sshfd, pb, 1, 0);
409         free(dhand);
410         free(longname);
411
412         return rv;
413
414  wayout:
415         free(dhand);
416         PSSHFSRETURN(rv);
417 }
418
419 struct puffs_node *
420 makenode(struct puffs_usermount *pu, struct puffs_node *parent,
421         const struct psshfs_dir *pd, const struct vattr *vap)
422 {
423         struct psshfs_node *psn_parent = parent->pn_data;
424         struct psshfs_node *psn;
425         struct puffs_node *pn;
426
427         psn = emalloc(sizeof(struct psshfs_node));
428         memset(psn, 0, sizeof(struct psshfs_node));
429
430         pn = puffs_pn_new(pu, psn);
431         if (!pn) {
432                 free(psn);
433                 return NULL;
434         }
435         setpnva(pu, pn, &pd->va);
436         setpnva(pu, pn, vap);
437         psn->attrread = pd->attrread;
438
439         psn->parent = parent;
440         psn_parent->childcount++;
441
442         TAILQ_INIT(&psn->pw);
443
444         return pn;
445 }
446
447 struct puffs_node *
448 allocnode(struct puffs_usermount *pu, struct puffs_node *parent,
449         const char *entryname, const struct vattr *vap)
450 {
451         struct psshfs_ctx *pctx = puffs_getspecific(pu);
452         struct psshfs_dir *pd;
453         struct puffs_node *pn;
454
455         pd = direnter(parent, entryname);
456
457         pd->va.va_fileid = pctx->nextino++;
458         if (vap->va_type == VDIR) {
459                 pd->va.va_nlink = 2;
460                 parent->pn_va.va_nlink++;
461         } else {
462                 pd->va.va_nlink = 1;
463         }
464
465         pn = makenode(pu, parent, pd, vap);
466         if (pn) {
467                 pd->va.va_fileid = pn->pn_va.va_fileid;
468                 pd->entry = pn;
469         }
470
471         return pn;
472 }
473
474 struct psshfs_dir *
475 direnter(struct puffs_node *parent, const char *entryname)
476 {
477         struct psshfs_node *psn_parent = parent->pn_data;
478         struct psshfs_dir *pd;
479         int i;
480
481         /* create directory entry */
482         if (psn_parent->denttot == psn_parent->dentnext)
483                 allocdirs(psn_parent);
484
485         i = psn_parent->dentnext;
486         pd = &psn_parent->dir[i];
487         pd->entryname = estrdup(entryname);
488         pd->valid = 1;
489         pd->attrread = 0;
490         puffs_vattr_null(&pd->va);
491         psn_parent->dentnext++;
492
493         return pd;
494 }
495
496 void
497 doreclaim(struct puffs_node *pn)
498 {
499         struct psshfs_node *psn = pn->pn_data;
500         struct psshfs_node *psn_parent;
501         struct psshfs_dir *dent;
502
503         psn_parent = psn->parent->pn_data;
504         psn_parent->childcount--;
505
506         /*
507          * Null out entry from directory.  Do not treat a missing entry
508          * as an invariant error, since the node might be removed from
509          * under us, and we might do a readdir before the reclaim resulting
510          * in no directory entry in the parent directory.
511          */
512         dent = lookup_by_entry(psn_parent->dir, psn_parent->dentnext, pn);
513         if (dent)
514                 dent->entry = NULL;
515
516         if (pn->pn_va.va_type == VDIR) {
517                 freedircache(psn->dir, psn->dentnext);
518                 psn->denttot = psn->dentnext = 0;
519         }
520         if (psn->symlink)
521                 free(psn->symlink);
522
523         puffs_pn_put(pn);
524 }
525
526 void
527 nukenode(struct puffs_node *node, const char *entryname, int reclaim)
528 {
529         struct psshfs_node *psn, *psn_parent;
530         struct psshfs_dir *pd;
531
532         psn = node->pn_data;
533         psn_parent = psn->parent->pn_data;
534         pd = lookup(psn_parent->dir, psn_parent->dentnext, entryname);
535         assert(pd != NULL);
536         pd->valid = 0;
537         free(pd->entryname);
538         pd->entryname = NULL;
539
540         if (node->pn_va.va_type == VDIR)
541                 psn->parent->pn_va.va_nlink--;
542
543         if (reclaim)
544                 doreclaim(node);
545 }