node.c revision 1.21 1 /* $NetBSD: node.c,v 1.21 2007/05/01 20:43:14 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 * 3. The name of the company nor the name of the author may be used to
15 * endorse or promote products derived from this software without specific
16 * prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31 #include <sys/cdefs.h>
32 #ifndef lint
33 __RCSID("$NetBSD: node.c,v 1.21 2007/05/01 20:43:14 pooka Exp $");
34 #endif /* !lint */
35
36 #include <assert.h>
37 #include <errno.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40
41 #include "psshfs.h"
42 #include "sftp_proto.h"
43
44 int
45 psshfs_node_lookup(struct puffs_cc *pcc, void *opc, void **newnode,
46 enum vtype *newtype, voff_t *newsize, dev_t *newrdev,
47 const struct puffs_cn *pcn)
48 {
49 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
50 struct psshfs_ctx *pctx = puffs_getspecific(pu);
51 struct puffs_node *pn_dir = opc;
52 struct psshfs_node *psn, *psn_dir = pn_dir->pn_data;
53 struct puffs_node *pn;
54 struct psshfs_dir *pd;
55 struct vattr va;
56 int rv;
57
58 if (PCNISDOTDOT(pcn)) {
59 psn = psn_dir->parent->pn_data;
60 psn->reclaimed = 0;
61
62 *newnode = psn_dir->parent;
63 *newtype = VDIR;
64 return 0;
65 }
66
67 rv = sftp_readdir(pcc, pctx, pn_dir);
68 if (rv) {
69 if (rv != EPERM)
70 return rv;
71
72 /*
73 * Can't read the directory. We still might be
74 * able to find the node with getattr in -r+x dirs
75 */
76 rv = getpathattr(pcc, PCNPATH(pcn), &va);
77 if (rv)
78 return rv;
79
80 /* guess */
81 if (va.va_type == VDIR)
82 va.va_nlink = 2;
83 else
84 va.va_nlink = 1;
85
86 pn = allocnode(pu, pn_dir, pcn->pcn_name, &va);
87 psn = pn->pn_data;
88 psn->attrread = time(NULL);
89 } else {
90 pd = lookup(psn_dir->dir, psn_dir->dentnext, pcn->pcn_name);
91 if (!pd) {
92 return ENOENT;
93 }
94
95 if (pd->entry)
96 pn = pd->entry;
97 else
98 pn = makenode(pu, pn_dir, pd, &pd->va);
99 psn = pn->pn_data;
100 }
101
102 psn->reclaimed = 0;
103
104 *newnode = pn;
105 *newsize = pn->pn_va.va_size;
106 *newtype = pn->pn_va.va_type;
107
108 return 0;
109 }
110
111 int
112 psshfs_node_getattr(struct puffs_cc *pcc, void *opc, struct vattr *vap,
113 const struct puffs_cred *pcr, pid_t pid)
114 {
115 struct puffs_node *pn = opc;
116 int rv;
117
118 rv = getnodeattr(pcc, pn);
119 if (rv)
120 return rv;
121
122 memcpy(vap, &pn->pn_va, sizeof(struct vattr));
123
124 return 0;
125 }
126
127 int
128 psshfs_node_setattr(struct puffs_cc *pcc, void *opc,
129 const struct vattr *va, const struct puffs_cred *pcr, pid_t pid)
130 {
131 PSSHFSAUTOVAR(pcc);
132 struct vattr kludgeva;
133 struct puffs_node *pn = opc;
134
135 psbuf_req_str(pb, SSH_FXP_SETSTAT, reqid, PNPATH(pn));
136
137 memcpy(&kludgeva, va, sizeof(struct vattr));
138
139 /* XXX: kludge due to openssh server implementation */
140 if (va->va_atime.tv_sec != PUFFS_VNOVAL
141 && va->va_mtime.tv_sec == PUFFS_VNOVAL) {
142 if (pn->pn_va.va_mtime.tv_sec != PUFFS_VNOVAL)
143 kludgeva.va_mtime.tv_sec = pn->pn_va.va_mtime.tv_sec;
144 else
145 kludgeva.va_mtime.tv_sec = va->va_atime.tv_sec;
146 }
147 if (va->va_mtime.tv_sec != PUFFS_VNOVAL
148 && va->va_atime.tv_sec == PUFFS_VNOVAL) {
149 if (pn->pn_va.va_atime.tv_sec != PUFFS_VNOVAL)
150 kludgeva.va_atime.tv_sec = pn->pn_va.va_atime.tv_sec;
151 else
152 kludgeva.va_atime.tv_sec = va->va_mtime.tv_sec;
153 }
154
155 psbuf_put_vattr(pb, &kludgeva);
156 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
157
158 puffs_cc_yield(pcc);
159
160 rv = psbuf_expect_status(pb);
161 if (rv == 0)
162 puffs_setvattr(&pn->pn_va, &kludgeva);
163
164 PSSHFSRETURN(rv);
165 }
166
167 int
168 psshfs_node_create(struct puffs_cc *pcc, void *opc, void **newnode,
169 const struct puffs_cn *pcn, const struct vattr *va)
170 {
171 PSSHFSAUTOVAR(pcc);
172 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
173 struct puffs_node *pn = opc;
174 struct puffs_node *pn_new;
175 char *fhand = NULL;
176 uint32_t fhandlen;
177
178 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
179 if (!pn_new) {
180 rv = ENOMEM;
181 goto out;
182 }
183
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);
187 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
188
189 puffs_cc_yield(pcc);
190
191 rv = psbuf_expect_handle(pb, &fhand, &fhandlen);
192 if (rv)
193 goto out;
194
195 reqid = NEXTREQ(pctx);
196 psbuf_recycle(pb, PSB_OUT);
197 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, fhand, fhandlen);
198 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
199
200 puffs_cc_yield(pcc);
201 rv = psbuf_expect_status(pb);
202
203 if (rv == 0)
204 *newnode = pn_new;
205 else
206 nukenode(pn_new, pcn->pcn_name, 1);
207
208 out:
209 free(fhand);
210 PSSHFSRETURN(rv);
211 }
212
213 int
214 psshfs_node_readdir(struct puffs_cc *pcc, void *opc, struct dirent *dent,
215 off_t *readoff, size_t *reslen, const struct puffs_cred *pcr,
216 int *eofflag, off_t *cookies, size_t *ncookies)
217 {
218 struct psshfs_ctx *pctx = puffs_cc_getspecific(pcc);
219 struct puffs_node *pn = opc;
220 struct psshfs_node *psn = pn->pn_data;
221 struct psshfs_dir *pd;
222 int i, rv;
223
224 *ncookies = 0;
225 rv = sftp_readdir(pcc, pctx, pn);
226 if (rv)
227 return rv;
228
229 for (i = *readoff; i < psn->dentnext; i++) {
230 pd = &psn->dir[i];
231 if (pd->valid == 0)
232 continue;
233 if (!puffs_nextdent(&dent, pd->entryname,
234 pd->va.va_fileid, puffs_vtype2dt(pd->va.va_type), reslen))
235 break;
236 PUFFS_STORE_DCOOKIE(cookies, ncookies, (off_t)i);
237 }
238 if (i == psn->dentnext)
239 *eofflag = 1;
240
241 *readoff = i;
242 return 0;
243 }
244
245 int
246 psshfs_node_read(struct puffs_cc *pcc, void *opc, uint8_t *buf,
247 off_t offset, size_t *resid, const struct puffs_cred *pcr,
248 int ioflag)
249 {
250 PSSHFSAUTOVAR(pcc);
251 struct puffs_node *pn = opc;
252 char *fhand = NULL;
253 struct vattr va;
254 uint32_t readlen, fhandlen;
255
256 if (pn->pn_va.va_type == VDIR) {
257 rv = EISDIR;
258 goto err;
259 }
260
261 puffs_vattr_null(&va);
262 psbuf_req_str(pb, SSH_FXP_OPEN, reqid, PNPATH(pn));
263 psbuf_put_4(pb, SSH_FXF_READ);
264 psbuf_put_vattr(pb, &va);
265 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
266
267 puffs_cc_yield(pcc);
268
269 rv = psbuf_expect_handle(pb, &fhand, &fhandlen);
270 if (rv)
271 goto err;
272
273 readlen = *resid;
274 reqid = NEXTREQ(pctx);
275 psbuf_recycle(pb, PSB_OUT);
276 psbuf_req_data(pb, SSH_FXP_READ, reqid, fhand, fhandlen);
277 psbuf_put_8(pb, offset);
278 psbuf_put_4(pb, readlen);
279 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
280
281 puffs_cc_yield(pcc);
282
283 rv = psbuf_do_data(pb, buf, &readlen);
284 if (rv == 0)
285 *resid -= readlen;
286
287 reqid = NEXTREQ(pctx);
288 psbuf_recycle(pb, PSB_OUT);
289 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, fhand, fhandlen);
290
291 pssh_outbuf_enqueue_nocc(pctx, pb, NULL, NULL, reqid);
292 free(fhand);
293 return 0;
294
295 err:
296 free(fhand);
297 PSSHFSRETURN(rv);
298 }
299
300 /* XXX: we should getattr for size */
301 int
302 psshfs_node_write(struct puffs_cc *pcc, void *opc, uint8_t *buf,
303 off_t offset, size_t *resid, const struct puffs_cred *cred,
304 int ioflag)
305 {
306 PSSHFSAUTOVAR(pcc);
307 struct puffs_node *pn = opc;
308 char *fhand = NULL;
309 struct vattr va, kludgeva1, kludgeva2;
310 uint32_t writelen, oflags, fhandlen;
311
312 if (pn->pn_va.va_type == VDIR) {
313 rv = EISDIR;
314 goto err;
315 }
316
317 /*
318 * XXXX: this is wrong - we shouldn't muck the file permissions
319 * at this stage any more. However, we need this, since currently
320 * we can't tell the sftp server "hey, this data was already
321 * authenticated to UBC, it's ok to let us write this". Yes, it
322 * will fail e.g. if we don't own the file. Tough love.
323 *
324 * TODO-point: Investigate solving this with open filehandles
325 * or something like that.
326 */
327 kludgeva1 = pn->pn_va;
328
329 puffs_vattr_null(&kludgeva2);
330 kludgeva2.va_mode = 0700;
331 rv = psshfs_node_setattr(pcc, opc, &kludgeva2, cred, 0);
332 if (rv)
333 goto err;
334
335 /* XXXcontinuation: ok, file is mode 700 now, we can open it rw */
336
337 oflags = SSH_FXF_WRITE;
338 #if 0
339 /*
340 * At least OpenSSH doesn't appear to support this, so can't
341 * do it the right way.
342 */
343 if (ioflag & PUFFS_IO_APPEND)
344 oflags |= SSH_FXF_APPEND;
345 #endif
346 if (ioflag & PUFFS_IO_APPEND)
347 offset = pn->pn_va.va_size;
348
349 puffs_vattr_null(&va);
350 psbuf_req_str(pb, SSH_FXP_OPEN, reqid, PNPATH(pn));
351 psbuf_put_4(pb, oflags);
352 psbuf_put_vattr(pb, &va);
353 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
354
355 puffs_cc_yield(pcc);
356
357 rv = psbuf_expect_handle(pb, &fhand, &fhandlen);
358 if (rv)
359 goto err;
360
361 /* moreXXX: file is open, revert old creds for crying out loud! */
362 rv = psshfs_node_setattr(pcc, opc, &kludgeva1, cred, 0);
363
364 /* are we screwed a la royal jelly? */
365 if (rv)
366 goto closefile;
367
368 writelen = *resid;
369 reqid = NEXTREQ(pctx);
370 psbuf_recycle(pb, PSB_OUT);
371 psbuf_req_data(pb, SSH_FXP_WRITE, reqid, fhand, fhandlen);
372 psbuf_put_8(pb, offset);
373 psbuf_put_data(pb, buf, writelen);
374 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
375
376 puffs_cc_yield(pcc);
377
378 rv = psbuf_expect_status(pb);
379 if (rv == 0)
380 *resid = 0;
381
382 if (pn->pn_va.va_size < offset + writelen)
383 pn->pn_va.va_size = offset + writelen;
384
385 closefile:
386 reqid = NEXTREQ(pctx);
387 psbuf_recycle(pb, PSB_OUT);
388 psbuf_req_data(pb, SSH_FXP_CLOSE, reqid, fhand, fhandlen);
389
390 pssh_outbuf_enqueue_nocc(pctx, pb, NULL, NULL, reqid);
391 free(fhand);
392 return 0;
393
394 err:
395 free(fhand);
396 PSSHFSRETURN(rv);
397 }
398
399 int
400 psshfs_node_readlink(struct puffs_cc *pcc, void *opc,
401 const struct puffs_cred *cred, char *linkvalue, size_t *linklen)
402 {
403 PSSHFSAUTOVAR(pcc);
404 struct puffs_node *pn = opc;
405 char *linktmp = NULL;
406 uint32_t count;
407
408 if (pctx->protover < 3) {
409 rv = EOPNOTSUPP;
410 goto out;
411 }
412
413 psbuf_req_str(pb, SSH_FXP_READLINK, reqid, PNPATH(pn));
414 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
415
416 puffs_cc_yield(pcc);
417
418 rv = psbuf_expect_name(pb, &count);
419 if (count != 1) {
420 rv = EPROTO;
421 goto out;
422 }
423 rv = psbuf_get_str(pb, &linktmp, (uint32_t *)linklen);
424 if (rv)
425 rv = 0;
426 else {
427 rv = EPROTO;
428 goto out;
429 }
430 (void) memcpy(linkvalue, linktmp, *linklen);
431
432 out:
433 free(linktmp);
434 PSSHFSRETURN(rv);
435 }
436
437 int
438 psshfs_node_remove(struct puffs_cc *pcc, void *opc, void *targ,
439 const struct puffs_cn *pcn)
440 {
441 PSSHFSAUTOVAR(pcc);
442 struct puffs_node *pn_targ = targ;
443
444 if (pn_targ->pn_va.va_type == VDIR) {
445 rv = EPERM;
446 goto out;
447 }
448
449 psbuf_req_str(pb, SSH_FXP_REMOVE, reqid, PNPATH(pn_targ));
450 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
451
452 puffs_cc_yield(pcc);
453
454 rv = psbuf_expect_status(pb);
455
456 if (rv == 0)
457 nukenode(pn_targ, pcn->pcn_name, 0);
458
459 out:
460 PSSHFSRETURN(rv);
461 }
462
463 int
464 psshfs_node_mkdir(struct puffs_cc *pcc, void *opc, void **newnode,
465 const struct puffs_cn *pcn, const struct vattr *va)
466 {
467 PSSHFSAUTOVAR(pcc);
468 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
469 struct puffs_node *pn = opc;
470 struct puffs_node *pn_new;
471
472 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
473 if (!pn_new) {
474 rv = ENOMEM;
475 goto out;
476 }
477
478 psbuf_req_str(pb, SSH_FXP_MKDIR, reqid, PCNPATH(pcn));
479 psbuf_put_vattr(pb, va);
480 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
481
482 puffs_cc_yield(pcc);
483
484 rv = psbuf_expect_status(pb);
485
486 if (rv == 0)
487 *newnode = pn_new;
488 else
489 nukenode(pn_new, pcn->pcn_name, 1);
490
491 out:
492 PSSHFSRETURN(rv);
493 }
494
495 int
496 psshfs_node_rmdir(struct puffs_cc *pcc, void *opc, void *targ,
497 const struct puffs_cn *pcn)
498 {
499 PSSHFSAUTOVAR(pcc);
500 struct puffs_node *pn_targ = targ;
501
502 psbuf_req_str(pb, SSH_FXP_RMDIR, reqid, PNPATH(pn_targ));
503 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
504
505 puffs_cc_yield(pcc);
506
507 rv = psbuf_expect_status(pb);
508 if (rv == 0)
509 nukenode(pn_targ, pcn->pcn_name, 0);
510
511 PSSHFSRETURN(rv);
512 }
513
514 int
515 psshfs_node_symlink(struct puffs_cc *pcc, void *opc, void **newnode,
516 const struct puffs_cn *pcn, const struct vattr *va,
517 const char *link_target)
518 {
519 PSSHFSAUTOVAR(pcc);
520 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
521 struct puffs_node *pn = opc;
522 struct puffs_node *pn_new;
523
524 if (pctx->protover < 3) {
525 rv = EOPNOTSUPP;
526 goto out;
527 }
528
529 pn_new = allocnode(pu, pn, pcn->pcn_name, va);
530 if (!pn_new) {
531 rv = ENOMEM;
532 goto out;
533 }
534
535 /*
536 * XXX: ietf says: source, target. openssh says: ietf who?
537 * Let's go with openssh and build quirk tables later if we care
538 */
539 psbuf_req_str(pb, SSH_FXP_SYMLINK, reqid, link_target);
540 psbuf_put_str(pb, PCNPATH(pcn));
541 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
542
543 puffs_cc_yield(pcc);
544
545 rv = psbuf_expect_status(pb);
546 if (rv == 0)
547 *newnode = pn_new;
548 else
549 nukenode(pn_new, pcn->pcn_name, 1);
550
551 out:
552 PSSHFSRETURN(rv);
553 }
554
555 int
556 psshfs_node_rename(struct puffs_cc *pcc, void *opc, void *src,
557 const struct puffs_cn *pcn_src, void *targ_dir, void *targ,
558 const struct puffs_cn *pcn_targ)
559 {
560 PSSHFSAUTOVAR(pcc);
561 struct puffs_node *pn_sf = src;
562 struct puffs_node *pn_td = targ_dir, *pn_tf = targ;
563 struct psshfs_node *psn_targdir = pn_td->pn_data;
564
565 if (pctx->protover < 2) {
566 rv = EOPNOTSUPP;
567 goto out;
568 }
569
570 if (pn_tf) {
571 /* XXX: no backend implementation for now, so call directly */
572 rv = psshfs_node_remove(pcc, targ_dir, pn_tf, pcn_targ);
573 if (rv)
574 goto out;
575 }
576
577 psbuf_req_str(pb, SSH_FXP_RENAME, reqid, PCNPATH(pcn_src));
578 psbuf_put_str(pb, PCNPATH(pcn_targ));
579 pssh_outbuf_enqueue(pctx, pb, pcc, reqid);
580
581 puffs_cc_yield(pcc);
582
583 rv = psbuf_expect_status(pb);
584 if (rv == 0) {
585 struct psshfs_dir *pd;
586
587 /*
588 * XXX: interfaces didn't quite work with rename..
589 * the song remains the same. go figure .. ;)
590 */
591 nukenode(pn_sf, pcn_src->pcn_name, 0);
592 pd = direnter(pn_td, pcn_targ->pcn_name);
593 pd->entry = pn_sf;
594 puffs_setvattr(&pd->va, &pn_sf->pn_va);
595
596 if (opc != targ_dir) {
597 psn_targdir->childcount++;
598 if (pn_sf->pn_va.va_type == VDIR)
599 pn_td->pn_va.va_nlink++;
600 }
601 }
602
603 out:
604 PSSHFSRETURN(rv);
605 }
606
607 /*
608 * So this file system happened to be written in such a way that
609 * lookup for ".." is hard if we lose the in-memory node. We'd
610 * need to recreate the entire directory structure from the root
611 * node up to the ".." node we're looking up.
612 *
613 * And since our entire fs structure is purely fictional (i.e. it's
614 * only in-memory, not fetchable from the server), the easiest way
615 * to deal with it is to not allow nodes with children to be
616 * reclaimed.
617 *
618 * If a node with children is being attempted to be reclaimed, we
619 * just mark it "reclaimed" but leave it as is until all its children
620 * have been reclaimed. If a lookup for that node is done meanwhile,
621 * it will be found by lookup() and we just remove the "reclaimed"
622 * bit.
623 */
624 int
625 psshfs_node_reclaim(struct puffs_cc *pcc, void *opc, pid_t pid)
626 {
627 struct puffs_usermount *pu = puffs_cc_getusermount(pcc);
628 struct puffs_node *pn = opc, *pn_next, *pn_root;
629 struct psshfs_node *psn = pn->pn_data;
630
631 /*
632 * don't reclaim if we have file handle issued, otherwise
633 * we can't do fhtonode
634 */
635 if (psn->hasfh)
636 return 0;
637
638 psn->reclaimed = 1;
639 pn_root = puffs_getroot(pu);
640 for (; pn != pn_root; pn = pn_next) {
641 psn = pn->pn_data;
642 if (psn->reclaimed == 0 || psn->childcount != 0)
643 break;
644
645 pn_next = psn->parent;
646 doreclaim(pn);
647 }
648
649 return 0;
650 }
651