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