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