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