kernel: Hide the sysctl.debug sysctl in the SYSCTL_DEBUG kernel option.
[dragonfly.git] / usr.sbin / puffs / mount_psshfs / node.c
1 /*      $NetBSD: node.c,v 1.62 2010/10/29 16:13:51 pooka Exp $  */
2
3 /*
4  * Copyright (c) 2006-2009  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 <errno.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include "psshfs.h"
34 #include "sftp_proto.h"
35
36 int
37 psshfs_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc,
38         struct puffs_newinfo *pni, const struct puffs_cn *pcn)
39 {
40         struct psshfs_ctx *pctx = puffs_getspecific(pu);
41         struct puffs_node *pn_dir = opc;
42         struct psshfs_node *psn, *psn_dir = pn_dir->pn_data;
43         struct puffs_node *pn;
44         struct psshfs_dir *pd;
45         struct vattr va;
46         int rv;
47
48         rv = sftp_readdir(pu, pctx, pn_dir);
49         if (rv) {
50                 if (rv != EPERM)
51                         return rv;
52
53                 /*
54                  * Can't read the directory.  We still might be
55                  * able to find the node with getattr in -r+x dirs
56                  */
57                 rv = getpathattr(pu, PCNPATH(pcn), &va);
58                 if (rv)
59                         return rv;
60
61                 /* guess */
62                 if (va.va_type == VDIR)
63                         va.va_nlink = 2;
64                 else
65                         va.va_nlink = 1;
66
67                 pn = allocnode(pu, pn_dir, pcn->pcn_name, &va);
68                 psn = pn->pn_data;
69                 psn->attrread = time(NULL);
70         } else {
71                 pd = lookup(psn_dir->dir, psn_dir->dentnext, pcn->pcn_name);
72                 if (!pd) {
73                         return ENOENT;
74                 }
75
76                 if (pd->entry)
77                         pn = pd->entry;
78                 else
79                         pd->entry = pn = makenode(pu, pn_dir, pd, &pd->va);
80
81                 /*
82                  * sure sure we have fresh attributes.  most likely we will
83                  * have them cached.  we might not if we go through:
84                  * create - reclaim - lookup (this).
85                  */
86                 rv = getnodeattr(pu, pn, PCNPATH(pcn));
87                 if (rv)
88                         return rv;
89
90                 psn = pn->pn_data;
91         }
92
93         psn->stat &= ~PSN_RECLAIMED;
94
95         puffs_newinfo_setcookie(pni, pn);
96         puffs_newinfo_setvtype(pni, pn->pn_va.va_type);
97         puffs_newinfo_setsize(pni, pn->pn_va.va_size);
98
99         return 0;
100 }
101
102 int
103 psshfs_node_lookupdotdot(struct puffs_usermount *pu, puffs_cookie_t opc,
104         struct puffs_newinfo *pni, const struct puffs_cn *pcn)
105 {
106         struct puffs_node *pn_dir = opc;
107         struct psshfs_node *psn, *psn_dir = pn_dir->pn_data;
108
109         psn = psn_dir->parent->pn_data;
110         psn->stat &= ~PSN_RECLAIMED;
111
112         puffs_newinfo_setcookie(pni, psn_dir->parent);
113         puffs_newinfo_setvtype(pni, VDIR);
114         return 0;
115 }
116
117 int
118 psshfs_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc,
119         struct vattr *vap, const struct puffs_cred *pcr)
120 {
121         struct puffs_node *pn = opc;
122         int rv;
123
124         rv = getnodeattr(pu, pn, NULL);
125         if (rv)
126                 return rv;
127
128         memcpy(vap, &pn->pn_va, sizeof(struct vattr));
129
130         return 0;
131 }
132
133 int
134 psshfs_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc,
135         const struct vattr *va, const struct puffs_cred *pcr)
136 {
137         PSSHFSAUTOVAR(pu);
138         struct vattr kludgeva;
139         struct puffs_node *pn = opc;
140
141         psbuf_req_str(pb, SSH_FXP_SETSTAT, reqid, PNPATH(pn));
142
143         memcpy(&kludgeva, va, sizeof(struct vattr));
144
145         /* XXX: kludge due to openssh server implementation */
146         if (va->va_atime.tv_sec != PUFFS_VNOVAL
147             && va->va_mtime.tv_sec == PUFFS_VNOVAL) {
148                 if (pn->pn_va.va_mtime.tv_sec != PUFFS_VNOVAL)
149                         kludgeva.va_mtime.tv_sec = pn->pn_va.va_mtime.tv_sec;
150                 else
151                         kludgeva.va_mtime.tv_sec = va->va_atime.tv_sec;
152         }
153         if (va->va_mtime.tv_sec != PUFFS_VNOVAL
154             && va->va_atime.tv_sec == PUFFS_VNOVAL) {
155                 if (pn->pn_va.va_atime.tv_sec != PUFFS_VNOVAL)
156                         kludgeva.va_atime.tv_sec = pn->pn_va.va_atime.tv_sec;
157                 else
158                         kludgeva.va_atime.tv_sec = va->va_mtime.tv_sec;
159         }
160
161         psbuf_put_vattr(pb, &kludgeva, pctx);
162         GETRESPONSE(pb, pctx->sshfd);
163
164         rv = psbuf_expect_status(pb);
165         if (rv == 0)
166                 puffs_setvattr(&pn->pn_va, &kludgeva);
167
168  out:
169         PSSHFSRETURN(rv);
170 }
171
172 int
173 psshfs_node_create(struct puffs_usermount *pu, puffs_cookie_t opc,
174         struct puffs_newinfo *pni, const struct puffs_cn *pcn,
175         const struct vattr *va)
176 {
177         PSSHFSAUTOVAR(pu);
178         struct puffs_node *pn = opc;
179         struct puffs_node *pn_new;
180         char *fhand = NULL;
181         uint32_t fhandlen;
182
183         /* Create node on server first */
184         psbuf_req_str(pb, SSH_FXP_OPEN, reqid, PCNPATH(pcn));
185         psbuf_put_4(pb, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
186         psbuf_put_vattr(pb, va, pctx);
187         GETRESPONSE(pb, pctx->sshfd);
188         rv = psbuf_expect_handle(pb, &fhand, &fhandlen);
189         if (rv)
190                 goto out;
191
192         /*
193          * Do *not* create the local node before getting a response
194          * from the server.  Otherwise we might screw up consistency,
195          * namely that the node can be looked up before create has
196          * returned (mind you, the kernel will unlock the directory
197          * before the create call from userspace returns).
198          */
199         pn_new = allocnode(pu, pn, pcn->pcn_name, va);
200         if (!pn_new) {
201                 struct puffs_framebuf *pb2 = psbuf_makeout();
202                 reqid = NEXTREQ(pctx);
203                 psbuf_req_str(pb2, SSH_FXP_REMOVE, reqid, PCNPATH(pcn));
204                 JUSTSEND(pb2, pctx->sshfd);
205                 rv = ENOMEM;
206         }
207
208         if (pn_new)
209                 puffs_newinfo_setcookie(pni, pn_new);
210
211         reqid = NEXTREQ(pctx);
212         psbuf_recycleout(pb);
213         psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, fhand, fhandlen);
214         JUSTSEND(pb, pctx->sshfd);
215         free(fhand);
216         return rv;
217
218  out:
219         free(fhand);
220         PSSHFSRETURN(rv);
221 }
222
223 /*
224  * Open a file handle.  This is used for read and write.  We do not
225  * wait here for the success or failure of this operation.  This is
226  * because otherwise opening and closing file handles would block
227  * reading potentially cached information.  Rather, we defer the wait
228  * to read/write and therefore allow cached access without a wait.
229  *
230  * If we have not yet succesfully opened a type of handle, we do wait
231  * here.  Also, if a lazy open fails, we revert back to the same
232  * state of waiting.
233  */
234 int
235 psshfs_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode,
236         const struct puffs_cred *pcr)
237 {
238         struct puffs_cc *pcc = puffs_cc_getcc(pu);
239         struct psshfs_ctx *pctx = puffs_getspecific(pu);
240         struct puffs_framebuf *pb, *pb2;
241         struct vattr va;
242         struct puffs_node *pn = opc;
243         struct psshfs_node *psn = pn->pn_data;
244         uint32_t reqid;
245         int didread, didwrite;
246         int rv = 0;
247
248         if (pn->pn_va.va_type == VDIR)
249                 return 0;
250
251         puffs_setback(pcc, PUFFS_SETBACK_INACT_N1);
252         puffs_vattr_null(&va);
253         didread = didwrite = 0;
254         if (mode & FREAD && psn->fhand_r == NULL && psn->lazyopen_r == NULL) {
255                 pb = psbuf_makeout();
256
257                 reqid = NEXTREQ(pctx);
258                 psbuf_req_str(pb, SSH_FXP_OPEN, reqid, PNPATH(pn));
259                 psbuf_put_4(pb, SSH_FXF_READ);
260                 psbuf_put_vattr(pb, &va, pctx);
261
262                 if (puffs_framev_enqueue_cb(pu, pctx->sshfd_data, pb,
263                     lazyopen_rresp, psn, 0) == -1) {
264                         rv = errno;
265                         puffs_framebuf_destroy(pb);
266                         goto out;
267                 }
268
269                 psn->lazyopen_r = pb;
270                 didread = 1;
271         }
272         if (mode & FWRITE && psn->fhand_w == NULL && psn->lazyopen_w == NULL) {
273                 pb2 = psbuf_makeout();
274
275                 reqid = NEXTREQ(pctx);
276                 psbuf_req_str(pb2, SSH_FXP_OPEN, reqid, PNPATH(pn));
277                 psbuf_put_4(pb2, SSH_FXF_WRITE);
278                 psbuf_put_vattr(pb2, &va, pctx);
279
280                 if (puffs_framev_enqueue_cb(pu, pctx->sshfd_data, pb2,
281                     lazyopen_wresp, psn, 0) == -1) {
282                         rv = errno;
283                         puffs_framebuf_destroy(pb2);
284                         goto out;
285                 }
286
287                 psn->lazyopen_w = pb2;
288                 didwrite = 1;
289         }
290         psn->stat &= ~PSN_HANDLECLOSE;
291
292  out:
293         /* wait? */
294         if (didread && (psn->stat & PSN_DOLAZY_R) == 0) {
295                 assert(psn->lazyopen_r);
296
297                 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_r, pcc);
298                 lazyopen_rresp(pu, psn->lazyopen_r, psn, rv);
299                 if (psn->fhand_r) {
300                         psn->stat |= PSN_DOLAZY_R;
301                 } else {
302                         if (psn->lazyopen_err_r)
303                                 return psn->lazyopen_err_r;
304                         return EINVAL;
305                 }
306         }
307
308         /* wait? */
309         if (didwrite && (psn->stat & PSN_DOLAZY_W) == 0) {
310                 assert(psn->lazyopen_w);
311
312                 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_w, pcc);
313                 lazyopen_wresp(pu, psn->lazyopen_w, psn, rv);
314                 if (psn->fhand_w) {
315                         psn->stat |= PSN_DOLAZY_W;
316                 } else {
317                         if (psn->lazyopen_err_w)
318                                 return psn->lazyopen_err_w;
319                         return EINVAL;
320                 }
321         }
322
323         return rv;
324 }
325
326 int
327 psshfs_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc)
328 {
329         struct puffs_node *pn = opc;
330
331         closehandles(pu, pn->pn_data, HANDLE_READ | HANDLE_WRITE);
332         return 0;
333 }
334
335 int
336 psshfs_node_readdir(struct puffs_usermount *pu, puffs_cookie_t opc,
337         struct dirent *dent, off_t *readoff, size_t *reslen,
338         const struct puffs_cred *pcr, int *eofflag,
339         off_t *cookies, size_t *ncookies)
340 {
341         struct puffs_cc *pcc = puffs_cc_getcc(pu);
342         struct psshfs_ctx *pctx = puffs_getspecific(pu);
343         struct puffs_node *pn = opc;
344         struct psshfs_node *psn = pn->pn_data;
345         struct psshfs_dir *pd;
346         size_t i;
347         int rv, set_readdir;
348
349  restart:
350         if (psn->stat & PSN_READDIR) {
351                 struct psshfs_wait pw;
352
353                 set_readdir = 0;
354                 pw.pw_cc = pcc;
355                 pw.pw_type = PWTYPE_READDIR;
356                 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
357                 puffs_cc_yield(pcc);
358                 goto restart;
359         } else {
360                 psn->stat |= PSN_READDIR;
361                 set_readdir = 1;
362         }
363
364         *ncookies = 0;
365         rv = sftp_readdir(pu, pctx, pn);
366         if (rv) {
367                 goto out;
368         }
369
370         /* find next dirent */
371         for (i = *readoff;;i++) {
372                 if (i >= psn->dentnext)
373                         goto out;
374                 pd = &psn->dir[i];
375                 if (pd->valid)
376                         break;
377         }
378
379         for (;;) {
380                 *readoff = i;
381                 if (!puffs_nextdent(&dent, pd->entryname,
382                     pd->va.va_fileid, puffs_vtype2dt(pd->va.va_type), reslen)) {
383                         rv = 0;
384                         goto out;
385                 }
386
387                 /* find next entry, store possible nfs key */
388                 do {
389                         if (++i >= psn->dentnext)
390                                 goto out;
391                         pd = &psn->dir[i];
392                 } while (pd->valid == 0);
393                 PUFFS_STORE_DCOOKIE(cookies, ncookies, (off_t)i);
394         }
395
396  out:
397         if (rv == 0) {
398                 if (i >= psn->dentnext)
399                         *eofflag = 1;
400
401                 *readoff = i;
402         }
403
404         if (set_readdir) {
405                 struct psshfs_wait *pw;
406
407                 /* all will likely run to completion because of cache */
408                 TAILQ_FOREACH(pw, &psn->pw, pw_entries) {
409                         assert(pw->pw_type == PWTYPE_READDIR);
410                         puffs_cc_schedule(pw->pw_cc);
411                         TAILQ_REMOVE(&psn->pw, pw, pw_entries);
412                 }
413
414                 psn->stat &= ~PSN_READDIR;
415         }
416
417         return rv;
418 }
419
420 int
421 psshfs_node_read(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf,
422         off_t offset, size_t *resid, const struct puffs_cred *pcr,
423         int ioflag)
424 {
425         PSSHFSAUTOVAR(pu);
426         struct puffs_node *pn = opc;
427         struct psshfs_node *psn = pn->pn_data;
428         struct psshfs_wait *pwp;
429         uint32_t readlen;
430
431         if (pn->pn_va.va_type == VDIR) {
432                 rv = EISDIR;
433                 goto farout;
434         }
435
436         /* check that a lazyopen didn't fail */
437         if (!psn->fhand_r && !psn->lazyopen_r) {
438                 rv = psn->lazyopen_err_r;
439                 goto farout;
440         }
441
442         /* if someone is already waiting for the lazyopen, "just" wait */
443         if (psn->stat & PSN_LAZYWAIT_R) {
444                 struct psshfs_wait pw;
445
446                 assert(psn->lazyopen_r);
447
448                 pw.pw_cc = pcc;
449                 pw.pw_type = PWTYPE_READ1;
450                 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
451                 puffs_cc_yield(pcc);
452         }
453
454         /* if lazyopening, wait for the result */
455         if (psn->lazyopen_r) {
456                 psn->stat |= PSN_LAZYWAIT_R;
457                 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_r, pcc);
458                 lazyopen_rresp(pu, psn->lazyopen_r, psn, rv);
459
460                 /* schedule extra waiters */
461                 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
462                         if (pwp->pw_type == PWTYPE_READ1) {
463                                 puffs_cc_schedule(pwp->pw_cc);
464                                 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
465                         }
466                 psn->stat &= ~PSN_LAZYWAIT_R;
467
468                 if ((rv = psn->lazyopen_err_r) != 0)
469                         goto farout;
470         }
471
472         /* if there is still no handle, just refuse to live with this */
473         if (!psn->fhand_r) {
474                 rv = EINVAL;
475                 goto farout;
476         }
477
478         readlen = *resid;
479         psbuf_req_data(pb, SSH_FXP_READ, reqid, psn->fhand_r, psn->fhand_r_len);
480         psbuf_put_8(pb, offset);
481         psbuf_put_4(pb, readlen);
482
483         /*
484          * Do this *after* accessing the file, the handle might not
485          * exist after blocking.
486          */
487         if (max_reads && ++psn->readcount > max_reads) {
488                 struct psshfs_wait pw;
489
490                 pw.pw_cc = pcc;
491                 pw.pw_type = PWTYPE_READ2;
492                 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
493                 puffs_cc_yield(pcc);
494         }
495
496         GETRESPONSE(pb, pctx->sshfd_data);
497
498         rv = psbuf_do_data(pb, buf, &readlen);
499         if (rv == 0)
500                 *resid -= readlen;
501
502  out:
503         if (max_reads && --psn->readcount >= max_reads) {
504                 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
505                         if (pwp->pw_type == PWTYPE_READ2)
506                                 break;
507                 assert(pwp != NULL);
508                 puffs_cc_schedule(pwp->pw_cc);
509                 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
510         }
511
512  farout:
513         /* check if we need a lazyclose */
514         if (psn->stat & PSN_HANDLECLOSE && psn->fhand_r) {
515                 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
516                         if (pwp->pw_type == PWTYPE_READ1)
517                                 break;
518                 if (pwp == NULL)
519                         closehandles(pu, psn, HANDLE_READ);
520         }
521         PSSHFSRETURN(rv);
522 }
523
524 /* XXX: we should getattr for size */
525 int
526 psshfs_node_write(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf,
527         off_t offset, size_t *resid, const struct puffs_cred *cred,
528         int ioflag)
529 {
530         PSSHFSAUTOVAR(pu);
531         struct psshfs_wait *pwp;
532         struct puffs_node *pn = opc;
533         struct psshfs_node *psn = pn->pn_data;
534         uint32_t writelen;
535
536         if (pn->pn_va.va_type == VDIR) {
537                 rv = EISDIR;
538                 goto out;
539         }
540
541         /* check that a lazyopen didn't fail */
542         if (!psn->fhand_w && !psn->lazyopen_w) {
543                 rv = psn->lazyopen_err_w;
544                 goto out;
545         }
546
547         if (psn->stat & PSN_LAZYWAIT_W) {
548                 struct psshfs_wait pw;
549
550                 assert(psn->lazyopen_w);
551
552                 pw.pw_cc = pcc;
553                 pw.pw_type = PWTYPE_WRITE;
554                 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
555                 puffs_cc_yield(pcc);
556         }
557
558         /*
559          * If lazyopening, wait for the result.
560          * There can still be more than oen writer at a time in case
561          * the kernel issues write FAFs.
562          */
563         if (psn->lazyopen_w) {
564                 psn->stat |= PSN_LAZYWAIT_W;
565                 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_w, pcc);
566                 lazyopen_wresp(pu, psn->lazyopen_w, psn, rv);
567
568                 /* schedule extra waiters */
569                 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
570                         if (pwp->pw_type == PWTYPE_WRITE) {
571                                 puffs_cc_schedule(pwp->pw_cc);
572                                 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
573                         }
574                 psn->stat &= ~PSN_LAZYWAIT_W;
575
576                 if ((rv = psn->lazyopen_err_w) != 0)
577                         goto out;
578         }
579
580         if (!psn->fhand_w) {
581                 abort();
582                 rv = EINVAL;
583                 goto out;
584         }
585
586         writelen = *resid;
587         psbuf_req_data(pb, SSH_FXP_WRITE, reqid, psn->fhand_w,psn->fhand_w_len);
588         psbuf_put_8(pb, offset);
589         psbuf_put_data(pb, buf, writelen);
590         GETRESPONSE(pb, pctx->sshfd_data);
591
592         rv = psbuf_expect_status(pb);
593         if (rv == 0)
594                 *resid = 0;
595
596         if (pn->pn_va.va_size < (uint64_t)offset + writelen)
597                 pn->pn_va.va_size = offset + writelen;
598
599  out:
600         /* check if we need a lazyclose */
601         if (psn->stat & PSN_HANDLECLOSE && psn->fhand_w) {
602                 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
603                         if (pwp->pw_type == PWTYPE_WRITE)
604                                 break;
605                 if (pwp == NULL)
606                         closehandles(pu, psn, HANDLE_WRITE);
607         }
608         PSSHFSRETURN(rv);
609 }
610
611 int
612 psshfs_node_readlink(struct puffs_usermount *pu, puffs_cookie_t opc,
613         const struct puffs_cred *cred, char *linkvalue, size_t *linklen)
614 {
615         PSSHFSAUTOVAR(pu);
616         struct puffs_node *pn = opc;
617         struct psshfs_node *psn = pn->pn_data;
618         uint32_t count;
619
620         if (pctx->protover < 3) {
621                 rv = EOPNOTSUPP;
622                 goto out;
623         }
624
625         /*
626          * check if we can use a cached version
627          *
628          * XXX: we might end up reading the same link multiple times
629          * from the server if we get many requests at once, but that's
630          * quite harmless as this routine is reentrant.
631          */
632         if (psn->symlink && !REFRESHTIMEOUT(pctx, time(NULL) - psn->slread))
633                 goto copy;
634
635         if (psn->symlink) {
636                 free(psn->symlink);
637                 psn->symlink = NULL;
638                 psn->slread = 0;
639         }
640
641         psbuf_req_str(pb, SSH_FXP_READLINK, reqid, PNPATH(pn));
642         GETRESPONSE(pb, pctx->sshfd);
643
644         rv = psbuf_expect_name(pb, &count);
645         if (rv)
646                 goto out;
647         if (count != 1) {
648                 rv = EPROTO;
649                 goto out;
650         }
651
652         rv = psbuf_get_str(pb, &psn->symlink, NULL);
653         if (rv)
654                 goto out;
655         psn->slread = time(NULL);
656
657  copy:
658         *linklen = strlen(psn->symlink);
659         (void) memcpy(linkvalue, psn->symlink, *linklen);
660
661  out:
662         PSSHFSRETURN(rv);
663 }
664
665 static int
666 doremove(struct puffs_usermount *pu, struct puffs_node *pn_dir,
667         struct puffs_node *pn, const char *name)
668 {
669         PSSHFSAUTOVAR(pu);
670         int op;
671
672         if (pn->pn_va.va_type == VDIR)
673                 op = SSH_FXP_RMDIR;
674         else
675                 op = SSH_FXP_REMOVE;
676
677         psbuf_req_str(pb, op, reqid, PNPATH(pn));
678         GETRESPONSE(pb, pctx->sshfd);
679
680         rv = psbuf_expect_status(pb);
681         if (rv == 0)
682                 nukenode(pn, name, 0);
683
684  out:
685         PSSHFSRETURN(rv);
686 }
687
688 int
689 psshfs_node_remove(struct puffs_usermount *pu, puffs_cookie_t opc,
690         puffs_cookie_t targ, const struct puffs_cn *pcn)
691 {
692         struct puffs_node *pn_targ = targ;
693         int rv;
694
695         assert(pn_targ->pn_va.va_type != VDIR);
696
697         rv = doremove(pu, opc, targ, pcn->pcn_name);
698         if (rv == 0)
699                 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
700
701         return rv;
702 }
703
704 int
705 psshfs_node_rmdir(struct puffs_usermount *pu, puffs_cookie_t opc,
706         puffs_cookie_t targ, const struct puffs_cn *pcn)
707 {
708         struct puffs_node *pn_targ = targ;
709         int rv;
710
711         assert(pn_targ->pn_va.va_type == VDIR);
712
713         rv = doremove(pu, opc, targ, pcn->pcn_name);
714         if (rv == 0)
715                 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
716
717         return rv;
718 }
719
720 int
721 psshfs_node_mkdir(struct puffs_usermount *pu, puffs_cookie_t opc,
722         struct puffs_newinfo *pni, const struct puffs_cn *pcn,
723         const struct vattr *va)
724 {
725         PSSHFSAUTOVAR(pu);
726         struct puffs_node *pn = opc;
727         struct puffs_node *pn_new;
728
729         psbuf_req_str(pb, SSH_FXP_MKDIR, reqid, PCNPATH(pcn));
730         psbuf_put_vattr(pb, va, pctx);
731         GETRESPONSE(pb, pctx->sshfd);
732
733         rv = psbuf_expect_status(pb);
734         if (rv)
735                 goto out;
736
737         pn_new = allocnode(pu, pn, pcn->pcn_name, va);
738         if (pn_new) {
739                 puffs_newinfo_setcookie(pni, pn_new);
740         } else {
741                 struct puffs_framebuf *pb2 = psbuf_makeout();
742                 reqid = NEXTREQ(pctx);
743                 psbuf_recycleout(pb2);
744                 psbuf_req_str(pb2, SSH_FXP_RMDIR, reqid, PCNPATH(pcn));
745                 JUSTSEND(pb2, pctx->sshfd);
746                 rv = ENOMEM;
747         }
748
749  out:
750         PSSHFSRETURN(rv);
751 }
752
753 int
754 psshfs_node_link(struct puffs_usermount *pu, puffs_cookie_t opc,
755         puffs_cookie_t targ, const struct puffs_cn *pcn)
756 {
757         PSSHFSAUTOVAR(pu);
758         struct puffs_node *pn = opc;
759         struct puffs_node *pn_targ = targ;
760
761         if (pctx->protover < 3) {
762                 rv = EOPNOTSUPP;
763                 goto out;
764         }
765
766         psbuf_req_str(pb, SSH_FXP_EXTENDED, reqid, "hardlink@openssh.com");
767         psbuf_put_str(pb, PNPATH(pn_targ));
768         psbuf_put_str(pb, PCNPATH(pcn));
769
770         GETRESPONSE(pb, pctx->sshfd);
771         rv = psbuf_expect_status(pb);
772
773         if (rv == 0) {
774                 struct psshfs_node *psn;
775
776                 /* Force refresh of attributes */
777                 psn = pn_targ->pn_data;
778                 psn->attrread = 0;
779
780                 /* Force refresh of dirents */
781                 psn = pn->pn_data;
782                 if (psn->dir)
783                         psn->dentread = 0;
784         }
785
786  out:
787         PSSHFSRETURN(rv);
788 }
789
790 int
791 psshfs_node_symlink(struct puffs_usermount *pu, puffs_cookie_t opc,
792         struct puffs_newinfo *pni, const struct puffs_cn *pcn,
793         const struct vattr *va, const char *link_target)
794 {
795         PSSHFSAUTOVAR(pu);
796         struct puffs_node *pn = opc;
797         struct puffs_node *pn_new;
798
799         if (pctx->protover < 3) {
800                 rv = EOPNOTSUPP;
801                 goto out;
802         }
803
804         /*
805          * XXX: ietf says: source, target.  openssh says: ietf who?
806          * Let's go with openssh and build quirk tables later if we care
807          */
808         psbuf_req_str(pb, SSH_FXP_SYMLINK, reqid, link_target);
809         psbuf_put_str(pb, PCNPATH(pcn));
810         GETRESPONSE(pb, pctx->sshfd);
811
812         rv = psbuf_expect_status(pb);
813         if (rv)
814                 goto out;
815
816         pn_new = allocnode(pu, pn, pcn->pcn_name, va);
817         if (pn_new) {
818                 puffs_newinfo_setcookie(pni, pn_new);
819         } else {
820                 struct puffs_framebuf *pb2 = psbuf_makeout();
821                 reqid = NEXTREQ(pctx);
822                 psbuf_recycleout(pb2);
823                 psbuf_req_str(pb2, SSH_FXP_REMOVE, reqid, PCNPATH(pcn));
824                 JUSTSEND(pb2, pctx->sshfd);
825                 rv = ENOMEM;
826         }
827
828  out:
829         PSSHFSRETURN(rv);
830 }
831
832 int
833 psshfs_node_rename(struct puffs_usermount *pu, puffs_cookie_t opc,
834         puffs_cookie_t src, const struct puffs_cn *pcn_src,
835         puffs_cookie_t targ_dir, puffs_cookie_t targ,
836         const struct puffs_cn *pcn_targ)
837 {
838         PSSHFSAUTOVAR(pu);
839         struct puffs_node *pn_sf = src;
840         struct puffs_node *pn_td = targ_dir, *pn_tf = targ;
841         struct psshfs_node *psn_src = pn_sf->pn_data;
842         struct psshfs_node *psn_targdir = pn_td->pn_data;
843
844         if (pctx->protover < 2) {
845                 rv = EOPNOTSUPP;
846                 goto out;
847         }
848
849         if (pn_tf) {
850                 rv = doremove(pu, targ_dir, pn_tf, pcn_targ->pcn_name);
851                 if (rv)
852                         goto out;
853         }
854
855         psbuf_req_str(pb, SSH_FXP_RENAME, reqid, PCNPATH(pcn_src));
856         psbuf_put_str(pb, PCNPATH(pcn_targ));
857         GETRESPONSE(pb, pctx->sshfd);
858
859         rv = psbuf_expect_status(pb);
860         if (rv == 0) {
861                 struct psshfs_dir *pd;
862
863                 /*
864                  * XXX: interfaces didn't quite work with rename..
865                  * the song remains the same.  go figure .. ;)
866                  */
867                 nukenode(pn_sf, pcn_src->pcn_name, 0);
868                 pd = direnter(pn_td, pcn_targ->pcn_name);
869                 pd->entry = pn_sf;
870                 puffs_setvattr(&pd->va, &pn_sf->pn_va);
871
872                 if (opc != targ_dir) {
873                         psn_targdir->childcount++;
874                         psn_src->parent = pn_td;
875                         if (pn_sf->pn_va.va_type == VDIR)
876                                 pn_td->pn_va.va_nlink++;
877                 }
878         }
879
880  out:
881         PSSHFSRETURN(rv);
882 }
883
884 /*
885  * So this file system happened to be written in such a way that
886  * lookup for ".." is hard if we lose the in-memory node.  We'd
887  * need to recreate the entire directory structure from the root
888  * node up to the ".." node we're looking up.
889  *
890  * And since our entire fs structure is purely fictional (i.e. it's
891  * only in-memory, not fetchable from the server), the easiest way
892  * to deal with it is to not allow nodes with children to be
893  * reclaimed.
894  *
895  * If a node with children is being attempted to be reclaimed, we
896  * just mark it "reclaimed" but leave it as is until all its children
897  * have been reclaimed.  If a lookup for that node is done meanwhile,
898  * it will be found by lookup() and we just remove the "reclaimed"
899  * bit.
900  */
901 int
902 psshfs_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc)
903 {
904         struct puffs_node *pn = opc, *pn_next, *pn_root;
905         struct psshfs_node *psn = pn->pn_data;
906
907         /*
908          * don't reclaim if we have file handle issued, otherwise
909          * we can't do fhtonode
910          */
911         if (psn->stat & PSN_HASFH)
912                 return 0;
913
914         psn->stat |= PSN_RECLAIMED;
915         pn_root = puffs_getroot(pu);
916         for (; pn != pn_root; pn = pn_next) {
917                 psn = pn->pn_data;
918                 if ((psn->stat & PSN_RECLAIMED) == 0 || psn->childcount != 0)
919                         break;
920
921                 pn_next = psn->parent;
922                 doreclaim(pn);
923         }
924
925         return 0;
926 }