node.c revision 1.49 1 /* $NetBSD: node.c,v 1.49 2007/12/04 19:31:22 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.49 2007/12/04 19:31:22 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 goto restart;
335 } else {
336 psn->stat |= PSN_READDIR;
337 set_readdir = 1;
338 }
339
340 *ncookies = 0;
341 rv = sftp_readdir(pcc, pctx, pn);
342 if (rv)
343 goto out;
344
345 /* find next dirent */
346 for (i = *readoff;;i++) {
347 if (i >= psn->dentnext)
348 goto out;
349 pd = &psn->dir[i];
350 if (pd->valid)
351 break;
352 }
353
354 for (;;) {
355 *readoff = i;
356 if (!puffs_nextdent(&dent, pd->entryname,
357 pd->va.va_fileid, puffs_vtype2dt(pd->va.va_type), reslen)) {
358 rv = 0;
359 goto out;
360 }
361
362 /* find next entry, store possible nfs key */
363 do {
364 if (++i >= psn->dentnext)
365 goto out;
366 pd = &psn->dir[i];
367 } while (pd->valid == 0);
368 PUFFS_STORE_DCOOKIE(cookies, ncookies, (off_t)i);
369 }
370
371 out:
372 if (rv == 0) {
373 if (i >= psn->dentnext)
374 *eofflag = 1;
375
376 *readoff = i;
377 }
378
379 if (set_readdir) {
380 struct psshfs_wait *pw;
381
382 /* all will likely run to completion because of cache */
383 TAILQ_FOREACH(pw, &psn->pw, pw_entries) {
384 assert(pw->pw_type == PWTYPE_READDIR);
385 puffs_cc_schedule(pw->pw_cc);
386 TAILQ_REMOVE(&psn->pw, pw, pw_entries);
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 }
428
429 /* if lazyopening, wait for the result */
430 if (psn->lazyopen_r) {
431 psn->stat |= PSN_LAZYWAIT_R;
432 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_r, pcc);
433 lazyopen_rresp(pu, psn->lazyopen_r, psn, rv);
434
435 /* schedule extra waiters */
436 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
437 if (pwp->pw_type == PWTYPE_READ1) {
438 puffs_cc_schedule(pwp->pw_cc);
439 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
440 }
441 psn->stat &= ~PSN_LAZYWAIT_R;
442
443 if ((rv = psn->lazyopen_err_r) != 0)
444 goto farout;
445 }
446
447 /* if there is still no handle, just refuse to live with this */
448 if (!psn->fhand_r) {
449 rv = EINVAL;
450 goto farout;
451 }
452
453 readlen = *resid;
454 psbuf_req_data(pb, SSH_FXP_READ, reqid, psn->fhand_r, psn->fhand_r_len);
455 psbuf_put_8(pb, offset);
456 psbuf_put_4(pb, readlen);
457
458 /*
459 * Do this *after* accessing the file, the handle might not
460 * exist after blocking.
461 */
462 if (max_reads && ++psn->readcount > max_reads) {
463 struct psshfs_wait pw;
464
465 pw.pw_cc = pcc;
466 pw.pw_type = PWTYPE_READ2;
467 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
468 puffs_cc_yield(pcc);
469 }
470
471 GETRESPONSE(pb);
472
473 rv = psbuf_do_data(pb, buf, &readlen);
474 if (rv == 0)
475 *resid -= readlen;
476
477 out:
478 if (max_reads && --psn->readcount >= max_reads) {
479 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
480 if (pwp->pw_type == PWTYPE_READ2)
481 break;
482 assert(pwp != NULL);
483 puffs_cc_schedule(pwp->pw_cc);
484 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
485 }
486
487 farout:
488 /* check if we need a lazyclose */
489 if (psn->stat & PSN_HANDLECLOSE && psn->fhand_r) {
490 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
491 if (pwp->pw_type == PWTYPE_READ1)
492 break;
493 if (pwp == NULL)
494 closehandles(pu, psn, HANDLE_READ);
495 }
496 PSSHFSRETURN(rv);
497 }
498
499 /* XXX: we should getattr for size */
500 int
501 psshfs_node_write(struct puffs_usermount *pu, void *opc, uint8_t *buf,
502 off_t offset, size_t *resid, const struct puffs_cred *cred,
503 int ioflag)
504 {
505 PSSHFSAUTOVAR(pu);
506 struct psshfs_wait *pwp;
507 struct puffs_node *pn = opc;
508 struct psshfs_node *psn = pn->pn_data;
509 uint32_t writelen;
510
511 if (pn->pn_va.va_type == VDIR) {
512 rv = EISDIR;
513 goto out;
514 }
515
516 /* check that a lazyopen didn't fail */
517 if (!psn->fhand_w && !psn->lazyopen_w) {
518 rv = psn->lazyopen_err_w;
519 goto out;
520 }
521
522 if (psn->stat & PSN_LAZYWAIT_W) {
523 struct psshfs_wait pw;
524
525 assert(psn->lazyopen_w);
526
527 pw.pw_cc = pcc;
528 pw.pw_type = PWTYPE_WRITE;
529 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
530 puffs_cc_yield(pcc);
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 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
548 }
549 psn->stat &= ~PSN_LAZYWAIT_W;
550
551 if ((rv = psn->lazyopen_err_w) != 0)
552 goto out;
553 }
554
555 if (!psn->fhand_w) {
556 abort();
557 rv = EINVAL;
558 goto out;
559 }
560
561 writelen = *resid;
562 psbuf_req_data(pb, SSH_FXP_WRITE, reqid, psn->fhand_w,psn->fhand_w_len);
563 psbuf_put_8(pb, offset);
564 psbuf_put_data(pb, buf, writelen);
565 GETRESPONSE(pb);
566
567 rv = psbuf_expect_status(pb);
568 if (rv == 0)
569 *resid = 0;
570
571 if (pn->pn_va.va_size < offset + writelen)
572 pn->pn_va.va_size = offset + writelen;
573
574 out:
575 /* check if we need a lazyclose */
576 if (psn->stat & PSN_HANDLECLOSE && psn->fhand_w) {
577 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
578 if (pwp->pw_type == PWTYPE_WRITE)
579 break;
580 if (pwp == NULL)
581 closehandles(pu, psn, HANDLE_WRITE);
582 }
583 PSSHFSRETURN(rv);
584 }
585
586 int
587 psshfs_node_readlink(struct puffs_usermount *pu, void *opc,
588 const struct puffs_cred *cred, char *linkvalue, size_t *linklen)
589 {
590 PSSHFSAUTOVAR(pu);
591 struct puffs_node *pn = opc;
592 struct psshfs_node *psn = pn->pn_data;
593 uint32_t count;
594
595 if (pctx->protover < 3) {
596 rv = EOPNOTSUPP;
597 goto out;
598 }
599
600 /*
601 * check if we can use a cached version
602 *
603 * XXX: we might end up reading the same link multiple times
604 * from the server if we get many requests at once, but that's
605 * quite harmless as this routine is reentrant.
606 */
607 if (psn->symlink && !REFRESHTIMEOUT(pctx, time(NULL) - psn->slread))
608 goto copy;
609
610 if (psn->symlink) {
611 free(psn->symlink);
612 psn->symlink = NULL;
613 psn->slread = 0;
614 }
615
616 psbuf_req_str(pb, SSH_FXP_READLINK, reqid, PNPATH(pn));
617 GETRESPONSE(pb);
618
619 rv = psbuf_expect_name(pb, &count);
620 if (rv)
621 goto out;
622 if (count != 1) {
623 rv = EPROTO;
624 goto out;
625 }
626
627 rv = psbuf_get_str(pb, &psn->symlink, NULL);
628 if (rv)
629 goto out;
630 psn->slread = time(NULL);
631
632 copy:
633 *linklen = strlen(psn->symlink);
634 (void) memcpy(linkvalue, psn->symlink, *linklen);
635
636 out:
637 PSSHFSRETURN(rv);
638 }
639
640 static int
641 doremove(struct puffs_usermount *pu, struct puffs_node *pn_dir,
642 struct puffs_node *pn, const char *name)
643 {
644 PSSHFSAUTOVAR(pu);
645 int op;
646
647 if (pn->pn_va.va_type == VDIR)
648 op = SSH_FXP_RMDIR;
649 else
650 op = SSH_FXP_REMOVE;
651
652 psbuf_req_str(pb, op, reqid, PNPATH(pn));
653 GETRESPONSE(pb);
654
655 rv = psbuf_expect_status(pb);
656 if (rv == 0)
657 nukenode(pn, name, 0);
658
659 out:
660 PSSHFSRETURN(rv);
661 }
662
663 int
664 psshfs_node_remove(struct puffs_usermount *pu, void *opc, void *targ,
665 const struct puffs_cn *pcn)
666 {
667 struct puffs_node *pn_targ = targ;
668 int rv;
669
670 assert(pn_targ->pn_va.va_type != VDIR);
671
672 rv = doremove(pu, opc, targ, pcn->pcn_name);
673 if (rv == 0)
674 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
675
676 return rv;
677 }
678
679 int
680 psshfs_node_rmdir(struct puffs_usermount *pu, void *opc, void *targ,
681 const struct puffs_cn *pcn)
682 {
683 struct puffs_node *pn_targ = targ;
684 int rv;
685
686 assert(pn_targ->pn_va.va_type == VDIR);
687
688 rv = doremove(pu, opc, targ, pcn->pcn_name);
689 if (rv == 0)
690 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
691
692 return rv;
693 }
694
695 int
696 psshfs_node_mkdir(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
697 const struct puffs_cn *pcn, const struct vattr *va)
698 {
699 PSSHFSAUTOVAR(pu);
700 struct puffs_node *pn = opc;
701 struct puffs_node *pn_new;
702
703 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
704 if (!pn_new) {
705 rv = ENOMEM;
706 goto out;
707 }
708
709 psbuf_req_str(pb, SSH_FXP_MKDIR, reqid, PCNPATH(pcn));
710 psbuf_put_vattr(pb, va);
711 GETRESPONSE(pb);
712
713 rv = psbuf_expect_status(pb);
714
715 if (rv == 0)
716 puffs_newinfo_setcookie(pni, pn_new);
717 else
718 nukenode(pn_new, pcn->pcn_name, 1);
719
720 out:
721 PSSHFSRETURN(rv);
722 }
723
724 int
725 psshfs_node_symlink(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
726 const struct puffs_cn *pcn, const struct vattr *va,
727 const char *link_target)
728 {
729 PSSHFSAUTOVAR(pu);
730 struct puffs_node *pn = opc;
731 struct puffs_node *pn_new;
732
733 if (pctx->protover < 3) {
734 rv = EOPNOTSUPP;
735 goto out;
736 }
737
738 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
739 if (!pn_new) {
740 rv = ENOMEM;
741 goto out;
742 }
743
744 /*
745 * XXX: ietf says: source, target. openssh says: ietf who?
746 * Let's go with openssh and build quirk tables later if we care
747 */
748 psbuf_req_str(pb, SSH_FXP_SYMLINK, reqid, link_target);
749 psbuf_put_str(pb, PCNPATH(pcn));
750 GETRESPONSE(pb);
751
752 rv = psbuf_expect_status(pb);
753 if (rv == 0)
754 puffs_newinfo_setcookie(pni, pn_new);
755 else
756 nukenode(pn_new, pcn->pcn_name, 1);
757
758 out:
759 PSSHFSRETURN(rv);
760 }
761
762 int
763 psshfs_node_rename(struct puffs_usermount *pu, void *opc, void *src,
764 const struct puffs_cn *pcn_src, void *targ_dir, void *targ,
765 const struct puffs_cn *pcn_targ)
766 {
767 PSSHFSAUTOVAR(pu);
768 struct puffs_node *pn_sf = src;
769 struct puffs_node *pn_td = targ_dir, *pn_tf = targ;
770 struct psshfs_node *psn_targdir = pn_td->pn_data;
771
772 if (pctx->protover < 2) {
773 rv = EOPNOTSUPP;
774 goto out;
775 }
776
777 if (pn_tf) {
778 rv = doremove(pu, targ_dir, pn_tf, pcn_targ->pcn_name);
779 if (rv)
780 goto out;
781 }
782
783 psbuf_req_str(pb, SSH_FXP_RENAME, reqid, PCNPATH(pcn_src));
784 psbuf_put_str(pb, PCNPATH(pcn_targ));
785 GETRESPONSE(pb);
786
787 rv = psbuf_expect_status(pb);
788 if (rv == 0) {
789 struct psshfs_dir *pd;
790
791 /*
792 * XXX: interfaces didn't quite work with rename..
793 * the song remains the same. go figure .. ;)
794 */
795 nukenode(pn_sf, pcn_src->pcn_name, 0);
796 pd = direnter(pn_td, pcn_targ->pcn_name);
797 pd->entry = pn_sf;
798 puffs_setvattr(&pd->va, &pn_sf->pn_va);
799
800 if (opc != targ_dir) {
801 psn_targdir->childcount++;
802 if (pn_sf->pn_va.va_type == VDIR)
803 pn_td->pn_va.va_nlink++;
804 }
805 }
806
807 out:
808 PSSHFSRETURN(rv);
809 }
810
811 /*
812 * So this file system happened to be written in such a way that
813 * lookup for ".." is hard if we lose the in-memory node. We'd
814 * need to recreate the entire directory structure from the root
815 * node up to the ".." node we're looking up.
816 *
817 * And since our entire fs structure is purely fictional (i.e. it's
818 * only in-memory, not fetchable from the server), the easiest way
819 * to deal with it is to not allow nodes with children to be
820 * reclaimed.
821 *
822 * If a node with children is being attempted to be reclaimed, we
823 * just mark it "reclaimed" but leave it as is until all its children
824 * have been reclaimed. If a lookup for that node is done meanwhile,
825 * it will be found by lookup() and we just remove the "reclaimed"
826 * bit.
827 */
828 int
829 psshfs_node_reclaim(struct puffs_usermount *pu, void *opc)
830 {
831 struct puffs_node *pn = opc, *pn_next, *pn_root;
832 struct psshfs_node *psn = pn->pn_data;
833
834 /*
835 * don't reclaim if we have file handle issued, otherwise
836 * we can't do fhtonode
837 */
838 if (psn->stat & PSN_HASFH)
839 return 0;
840
841 psn->stat |= PSN_RECLAIMED;
842 pn_root = puffs_getroot(pu);
843 for (; pn != pn_root; pn = pn_next) {
844 psn = pn->pn_data;
845 if ((psn->stat & PSN_RECLAIMED) == 0 || psn->childcount != 0)
846 break;
847
848 pn_next = psn->parent;
849 doreclaim(pn);
850 }
851
852 return 0;
853 }
854