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