node.c revision 1.50 1 /* $NetBSD: node.c,v 1.50 2007/12/07 13:09:49 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.50 2007/12/07 13:09:49 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 psn->attrread = psn_dir->dentread;
97 }
98
99 psn->stat &= ~PSN_RECLAIMED;
100
101 puffs_newinfo_setcookie(pni, pn);
102 puffs_newinfo_setvtype(pni, pn->pn_va.va_type);
103 puffs_newinfo_setsize(pni, pn->pn_va.va_size);
104
105 return 0;
106 }
107
108 int
109 psshfs_node_getattr(struct puffs_usermount *pu, void *opc, struct vattr *vap,
110 const struct puffs_cred *pcr)
111 {
112 struct puffs_node *pn = opc;
113 int rv;
114
115 rv = getnodeattr(pu, pn);
116 if (rv)
117 return rv;
118
119 memcpy(vap, &pn->pn_va, sizeof(struct vattr));
120
121 return 0;
122 }
123
124 int
125 psshfs_node_setattr(struct puffs_usermount *pu, void *opc,
126 const struct vattr *va, const struct puffs_cred *pcr)
127 {
128 PSSHFSAUTOVAR(pu);
129 struct vattr kludgeva;
130 struct puffs_node *pn = opc;
131
132 psbuf_req_str(pb, SSH_FXP_SETSTAT, reqid, PNPATH(pn));
133
134 memcpy(&kludgeva, va, sizeof(struct vattr));
135
136 /* XXX: kludge due to openssh server implementation */
137 if (va->va_atime.tv_sec != PUFFS_VNOVAL
138 && va->va_mtime.tv_sec == PUFFS_VNOVAL) {
139 if (pn->pn_va.va_mtime.tv_sec != PUFFS_VNOVAL)
140 kludgeva.va_mtime.tv_sec = pn->pn_va.va_mtime.tv_sec;
141 else
142 kludgeva.va_mtime.tv_sec = va->va_atime.tv_sec;
143 }
144 if (va->va_mtime.tv_sec != PUFFS_VNOVAL
145 && va->va_atime.tv_sec == PUFFS_VNOVAL) {
146 if (pn->pn_va.va_atime.tv_sec != PUFFS_VNOVAL)
147 kludgeva.va_atime.tv_sec = pn->pn_va.va_atime.tv_sec;
148 else
149 kludgeva.va_atime.tv_sec = va->va_mtime.tv_sec;
150 }
151
152 psbuf_put_vattr(pb, &kludgeva);
153 GETRESPONSE(pb);
154
155 rv = psbuf_expect_status(pb);
156 if (rv == 0)
157 puffs_setvattr(&pn->pn_va, &kludgeva);
158
159 out:
160 PSSHFSRETURN(rv);
161 }
162
163 int
164 psshfs_node_create(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
165 const struct puffs_cn *pcn, const struct vattr *va)
166 {
167 PSSHFSAUTOVAR(pu);
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_usermount *pu, void *opc, int mode,
215 const struct puffs_cred *pcr)
216 {
217 struct puffs_cc *pcc = puffs_cc_getcc(pu);
218 struct psshfs_ctx *pctx = puffs_getspecific(pu);
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_usermount *pu, void *opc)
307 {
308 struct puffs_node *pn = opc;
309
310 closehandles(pu, pn->pn_data, HANDLE_READ | HANDLE_WRITE);
311 return 0;
312 }
313
314 int
315 psshfs_node_readdir(struct puffs_usermount *pu, void *opc, struct dirent *dent,
316 off_t *readoff, size_t *reslen, const struct puffs_cred *pcr,
317 int *eofflag, off_t *cookies, size_t *ncookies)
318 {
319 struct puffs_cc *pcc = puffs_cc_getcc(pu);
320 struct psshfs_ctx *pctx = puffs_getspecific(pu);
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 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 TAILQ_REMOVE(&psn->pw, pw, pw_entries);
388 }
389
390 psn->stat &= ~PSN_READDIR;
391 }
392
393 return rv;
394 }
395
396 int
397 psshfs_node_read(struct puffs_usermount *pu, void *opc, uint8_t *buf,
398 off_t offset, size_t *resid, const struct puffs_cred *pcr,
399 int ioflag)
400 {
401 PSSHFSAUTOVAR(pu);
402 struct puffs_node *pn = opc;
403 struct psshfs_node *psn = pn->pn_data;
404 struct psshfs_wait *pwp;
405 uint32_t readlen;
406
407 if (pn->pn_va.va_type == VDIR) {
408 rv = EISDIR;
409 goto farout;
410 }
411
412 /* check that a lazyopen didn't fail */
413 if (!psn->fhand_r && !psn->lazyopen_r) {
414 rv = psn->lazyopen_err_r;
415 goto farout;
416 }
417
418 /* if someone is already waiting for the lazyopen, "just" wait */
419 if (psn->stat & PSN_LAZYWAIT_R) {
420 struct psshfs_wait pw;
421
422 assert(psn->lazyopen_r);
423
424 pw.pw_cc = pcc;
425 pw.pw_type = PWTYPE_READ1;
426 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
427 puffs_cc_yield(pcc);
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 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
441 }
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 puffs_cc_schedule(pwp->pw_cc);
485 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
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_usermount *pu, void *opc, uint8_t *buf,
503 off_t offset, size_t *resid, const struct puffs_cred *cred,
504 int ioflag)
505 {
506 PSSHFSAUTOVAR(pu);
507 struct psshfs_wait *pwp;
508 struct puffs_node *pn = opc;
509 struct psshfs_node *psn = pn->pn_data;
510 uint32_t writelen;
511
512 if (pn->pn_va.va_type == VDIR) {
513 rv = EISDIR;
514 goto out;
515 }
516
517 /* check that a lazyopen didn't fail */
518 if (!psn->fhand_w && !psn->lazyopen_w) {
519 rv = psn->lazyopen_err_w;
520 goto out;
521 }
522
523 if (psn->stat & PSN_LAZYWAIT_W) {
524 struct psshfs_wait pw;
525
526 assert(psn->lazyopen_w);
527
528 pw.pw_cc = pcc;
529 pw.pw_type = PWTYPE_WRITE;
530 TAILQ_INSERT_TAIL(&psn->pw, &pw, pw_entries);
531 puffs_cc_yield(pcc);
532 }
533
534 /*
535 * If lazyopening, wait for the result.
536 * There can still be more than oen writer at a time in case
537 * the kernel issues write FAFs.
538 */
539 if (psn->lazyopen_w) {
540 psn->stat |= PSN_LAZYWAIT_W;
541 rv = puffs_framev_framebuf_ccpromote(psn->lazyopen_w, pcc);
542 lazyopen_wresp(pu, psn->lazyopen_w, psn, rv);
543
544 /* schedule extra waiters */
545 TAILQ_FOREACH(pwp, &psn->pw, pw_entries)
546 if (pwp->pw_type == PWTYPE_WRITE) {
547 puffs_cc_schedule(pwp->pw_cc);
548 TAILQ_REMOVE(&psn->pw, pwp, pw_entries);
549 }
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_usermount *pu, void *opc,
589 const struct puffs_cred *cred, char *linkvalue, size_t *linklen)
590 {
591 PSSHFSAUTOVAR(pu);
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_usermount *pu, struct puffs_node *pn_dir,
643 struct puffs_node *pn, const char *name)
644 {
645 PSSHFSAUTOVAR(pu);
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_usermount *pu, 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(pu, opc, targ, pcn->pcn_name);
674 if (rv == 0)
675 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
676
677 return rv;
678 }
679
680 int
681 psshfs_node_rmdir(struct puffs_usermount *pu, 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(pu, opc, targ, pcn->pcn_name);
690 if (rv == 0)
691 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2);
692
693 return rv;
694 }
695
696 int
697 psshfs_node_mkdir(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
698 const struct puffs_cn *pcn, const struct vattr *va)
699 {
700 PSSHFSAUTOVAR(pu);
701 struct puffs_node *pn = opc;
702 struct puffs_node *pn_new;
703
704 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
705 if (!pn_new) {
706 rv = ENOMEM;
707 goto out;
708 }
709
710 psbuf_req_str(pb, SSH_FXP_MKDIR, reqid, PCNPATH(pcn));
711 psbuf_put_vattr(pb, va);
712 GETRESPONSE(pb);
713
714 rv = psbuf_expect_status(pb);
715
716 if (rv == 0)
717 puffs_newinfo_setcookie(pni, pn_new);
718 else
719 nukenode(pn_new, pcn->pcn_name, 1);
720
721 out:
722 PSSHFSRETURN(rv);
723 }
724
725 int
726 psshfs_node_symlink(struct puffs_usermount *pu, void *opc, struct puffs_newinfo *pni,
727 const struct puffs_cn *pcn, const struct vattr *va,
728 const char *link_target)
729 {
730 PSSHFSAUTOVAR(pu);
731 struct puffs_node *pn = opc;
732 struct puffs_node *pn_new;
733
734 if (pctx->protover < 3) {
735 rv = EOPNOTSUPP;
736 goto out;
737 }
738
739 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
740 if (!pn_new) {
741 rv = ENOMEM;
742 goto out;
743 }
744
745 /*
746 * XXX: ietf says: source, target. openssh says: ietf who?
747 * Let's go with openssh and build quirk tables later if we care
748 */
749 psbuf_req_str(pb, SSH_FXP_SYMLINK, reqid, link_target);
750 psbuf_put_str(pb, PCNPATH(pcn));
751 GETRESPONSE(pb);
752
753 rv = psbuf_expect_status(pb);
754 if (rv == 0)
755 puffs_newinfo_setcookie(pni, pn_new);
756 else
757 nukenode(pn_new, pcn->pcn_name, 1);
758
759 out:
760 PSSHFSRETURN(rv);
761 }
762
763 int
764 psshfs_node_rename(struct puffs_usermount *pu, void *opc, void *src,
765 const struct puffs_cn *pcn_src, void *targ_dir, void *targ,
766 const struct puffs_cn *pcn_targ)
767 {
768 PSSHFSAUTOVAR(pu);
769 struct puffs_node *pn_sf = src;
770 struct puffs_node *pn_td = targ_dir, *pn_tf = targ;
771 struct psshfs_node *psn_targdir = pn_td->pn_data;
772
773 if (pctx->protover < 2) {
774 rv = EOPNOTSUPP;
775 goto out;
776 }
777
778 if (pn_tf) {
779 rv = doremove(pu, targ_dir, pn_tf, pcn_targ->pcn_name);
780 if (rv)
781 goto out;
782 }
783
784 psbuf_req_str(pb, SSH_FXP_RENAME, reqid, PCNPATH(pcn_src));
785 psbuf_put_str(pb, PCNPATH(pcn_targ));
786 GETRESPONSE(pb);
787
788 rv = psbuf_expect_status(pb);
789 if (rv == 0) {
790 struct psshfs_dir *pd;
791
792 /*
793 * XXX: interfaces didn't quite work with rename..
794 * the song remains the same. go figure .. ;)
795 */
796 nukenode(pn_sf, pcn_src->pcn_name, 0);
797 pd = direnter(pn_td, pcn_targ->pcn_name);
798 pd->entry = pn_sf;
799 puffs_setvattr(&pd->va, &pn_sf->pn_va);
800
801 if (opc != targ_dir) {
802 psn_targdir->childcount++;
803 if (pn_sf->pn_va.va_type == VDIR)
804 pn_td->pn_va.va_nlink++;
805 }
806 }
807
808 out:
809 PSSHFSRETURN(rv);
810 }
811
812 /*
813 * So this file system happened to be written in such a way that
814 * lookup for ".." is hard if we lose the in-memory node. We'd
815 * need to recreate the entire directory structure from the root
816 * node up to the ".." node we're looking up.
817 *
818 * And since our entire fs structure is purely fictional (i.e. it's
819 * only in-memory, not fetchable from the server), the easiest way
820 * to deal with it is to not allow nodes with children to be
821 * reclaimed.
822 *
823 * If a node with children is being attempted to be reclaimed, we
824 * just mark it "reclaimed" but leave it as is until all its children
825 * have been reclaimed. If a lookup for that node is done meanwhile,
826 * it will be found by lookup() and we just remove the "reclaimed"
827 * bit.
828 */
829 int
830 psshfs_node_reclaim(struct puffs_usermount *pu, void *opc)
831 {
832 struct puffs_node *pn = opc, *pn_next, *pn_root;
833 struct psshfs_node *psn = pn->pn_data;
834
835 /*
836 * don't reclaim if we have file handle issued, otherwise
837 * we can't do fhtonode
838 */
839 if (psn->stat & PSN_HASFH)
840 return 0;
841
842 psn->stat |= PSN_RECLAIMED;
843 pn_root = puffs_getroot(pu);
844 for (; pn != pn_root; pn = pn_next) {
845 psn = pn->pn_data;
846 if ((psn->stat & PSN_RECLAIMED) == 0 || psn->childcount != 0)
847 break;
848
849 pn_next = psn->parent;
850 doreclaim(pn);
851 }
852
853 return 0;
854 }
855