puffs_portal.c revision 1.4 1 /* $NetBSD: puffs_portal.c,v 1.4 2011/08/29 14:35:02 joerg Exp $ */
2
3 /*
4 * Copyright (c) 2007 Antti Kantee. All Rights Reserved.
5 * Development was supported by the Finnish Cultural Foundation.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
17 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 #ifndef lint
31 __RCSID("$NetBSD: puffs_portal.c,v 1.4 2011/08/29 14:35:02 joerg Exp $");
32 #endif /* !lint */
33
34 #include <sys/types.h>
35 #include <sys/wait.h>
36
37 #include <assert.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <mntopts.h>
41 #include <paths.h>
42 #include <poll.h>
43 #include <puffs.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <util.h>
49
50 #include "portald.h"
51
52 struct portal_node {
53 char *path;
54 int fd;
55 };
56
57 __dead static void usage(void);
58
59 PUFFSOP_PROTOS(portal);
60
61 #define PORTAL_ROOT NULL
62 #define METADATASIZE (sizeof(int) + sizeof(size_t))
63
64 qelem q;
65 int readcfg, sigchild;
66 const char *cfg;
67
68 static void
69 usage()
70 {
71
72 errx(1, "usage: %s [-o options] /path/portal.conf mount_point",
73 getprogname());
74 }
75
76 static void
77 sighup(int sig)
78 {
79
80 readcfg = 1;
81 }
82
83 static void
84 sigcry(int sig)
85 {
86
87 sigchild = 1;
88 }
89
90 static void
91 portal_loopfn(struct puffs_usermount *pu)
92 {
93
94 if (readcfg)
95 conf_read(&q, cfg);
96 readcfg = 0;
97
98 if (sigchild) {
99 sigchild = 0;
100 while (waitpid(-1, NULL, WNOHANG) != -1)
101 continue;
102 }
103 }
104
105 #define PUFBUF_FD 1
106 #define PUFBUF_DATA 2
107
108 #define CMSIZE (sizeof(struct cmsghdr) + sizeof(int))
109
110 /* receive file descriptor produced by our child process */
111 static int
112 readfd(struct puffs_framebuf *pufbuf, int fd, int *done)
113 {
114 struct cmsghdr *cmp;
115 struct msghdr msg;
116 struct iovec iov;
117 ssize_t n;
118 int error, rv;
119
120 rv = 0;
121 cmp = emalloc(CMSG_SPACE(sizeof(int)));
122
123 iov.iov_base = &error;
124 iov.iov_len = sizeof(int);
125 msg.msg_iov = &iov;
126 msg.msg_iovlen = 1;
127 msg.msg_name = NULL;
128 msg.msg_namelen = 0;
129 msg.msg_control = cmp;
130 msg.msg_controllen = CMSG_SPACE(sizeof(int));
131
132 n = recvmsg(fd, &msg, 0);
133 if (n == -1) {
134 rv = errno;
135 goto out;
136 }
137 if (n == 0) {
138 rv = ECONNRESET;
139 goto out;
140 }
141
142 /* the data for the server */
143 puffs_framebuf_putdata_atoff(pufbuf, 0, &error, sizeof(int));
144 if (error) {
145 rv = error;
146 goto out;
147 }
148 puffs_framebuf_putdata_atoff(pufbuf, sizeof(int),
149 CMSG_DATA(cmp), sizeof(int));
150 *done = 1;
151
152 out:
153 free(cmp);
154 return rv;
155 }
156
157 /*
158 * receive data from provider
159 *
160 * XXX: should read directly into the buffer and adjust offsets
161 * instead of doing memcpy
162 */
163 static int
164 readdata(struct puffs_framebuf *pufbuf, int fd, int *done)
165 {
166 char buf[1024];
167 size_t max;
168 ssize_t n;
169 size_t moved;
170
171 /* don't override metadata */
172 if (puffs_framebuf_telloff(pufbuf) == 0)
173 puffs_framebuf_seekset(pufbuf, METADATASIZE);
174 puffs_framebuf_getdata_atoff(pufbuf, sizeof(int), &max, sizeof(size_t));
175 moved = puffs_framebuf_tellsize(pufbuf) - METADATASIZE;
176 assert(max >= moved);
177 max -= moved;
178
179 do {
180 n = read(fd, buf, MIN(sizeof(buf), max));
181 if (n == 0) {
182 if (moved)
183 break;
184 else
185 return -1; /* caught by read */
186 }
187 if (n < 0) {
188 if (moved)
189 return 0;
190
191 if (errno != EAGAIN)
192 return errno;
193 else
194 return 0;
195 }
196
197 puffs_framebuf_putdata(pufbuf, buf, n);
198 moved += n;
199 max -= n;
200 } while (max > 0);
201
202 *done = 1;
203
204 return 0;
205 }
206
207 static int
208 portal_frame_rf(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf,
209 int fd, int *done)
210 {
211 int type;
212
213 if (puffs_framebuf_getdata_atoff(pufbuf, 0, &type, sizeof(int)) == -1)
214 return EINVAL;
215
216 if (type == PUFBUF_FD)
217 return readfd(pufbuf, fd, done);
218 else if (type == PUFBUF_DATA)
219 return readdata(pufbuf, fd, done);
220 else
221 abort();
222 }
223
224 static int
225 portal_frame_wf(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf,
226 int fd, int *done)
227 {
228 void *win;
229 size_t pbsize, pboff, winlen;
230 ssize_t n;
231 int error;
232
233 pboff = puffs_framebuf_telloff(pufbuf);
234 pbsize = puffs_framebuf_tellsize(pufbuf);
235 error = 0;
236
237 do {
238 assert(pbsize > pboff);
239 winlen = pbsize - pboff;
240 if (puffs_framebuf_getwindow(pufbuf, pboff, &win, &winlen)==-1)
241 return errno;
242 n = write(fd, win, winlen);
243 if (n == 0) {
244 if (pboff != 0)
245 break;
246 else
247 return -1; /* caught by node_write */
248 }
249 if (n < 0) {
250 if (pboff != 0)
251 break;
252
253 if (errno != EAGAIN)
254 return errno;
255 return 0;
256 }
257
258 pboff += n;
259 puffs_framebuf_seekset(pufbuf, pboff);
260 } while (pboff != pbsize);
261
262 *done = 1;
263 puffs_framebuf_putdata_atoff(pufbuf, 0, &pboff, sizeof(size_t));
264 return error;
265 }
266
267 /* transfer file descriptor to master file server */
268 static int
269 sendfd(int s, int fd, int error)
270 {
271 struct cmsghdr *cmp;
272 struct msghdr msg;
273 struct iovec iov;
274 ssize_t n;
275 int rv;
276
277 rv = 0;
278 cmp = emalloc(CMSG_LEN(sizeof(int)));
279
280 iov.iov_base = &error;
281 iov.iov_len = sizeof(int);
282
283 msg.msg_iov = &iov;
284 msg.msg_iovlen = 1;
285 msg.msg_name = NULL;
286 msg.msg_namelen = 0;
287 if (error == 0) {
288 cmp->cmsg_level = SOL_SOCKET;
289 cmp->cmsg_type = SCM_RIGHTS;
290 cmp->cmsg_len = CMSG_LEN(sizeof(int));
291
292 msg.msg_control = cmp;
293 msg.msg_controllen = CMSG_LEN(sizeof(int));
294 *(int *)CMSG_DATA(cmp) = fd;
295 } else {
296 msg.msg_control = NULL;
297 msg.msg_controllen = 0;
298 }
299
300 n = sendmsg(s, &msg, 0);
301 if (n == -1)
302 rv = errno;
303 else if (n < (ssize_t)sizeof(int))
304 rv = EPROTO;
305
306 free(cmp);
307 return rv;
308 }
309
310 /*
311 * Produce I/O file descriptor by forking (like original portald).
312 *
313 * child: run provider and transfer produced fd to parent
314 * parent: yield until child produces fd. receive it and store it.
315 */
316 static int
317 provide(struct puffs_usermount *pu, struct portal_node *portn,
318 struct portal_cred *portc, char **v)
319 {
320 struct puffs_cc *pcc = puffs_cc_getcc(pu);
321 struct puffs_framebuf *pufbuf;
322 int s[2];
323 int fd, error;
324 int data;
325
326 pufbuf = puffs_framebuf_make();
327 if (pufbuf == NULL)
328 return ENOMEM;
329
330 data = PUFBUF_FD;
331 if (puffs_framebuf_putdata(pufbuf, &data, sizeof(int)) == -1)
332 goto bad;
333
334 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, s) == -1)
335 goto bad;
336
337 switch (fork()) {
338 case -1:
339 goto bad;
340 case 0:
341 error = activate_argv(portc, portn->path, v, &fd);
342 sendfd(s[1], fd, error);
343 exit(0);
344 default:
345 puffs_framev_addfd(pu, s[0], PUFFS_FBIO_READ);
346 puffs_framev_enqueue_directreceive(pcc, s[0], pufbuf, 0);
347 puffs_framev_removefd(pu, s[0], 0);
348 close(s[0]);
349 close(s[1]);
350
351 if (puffs_framebuf_tellsize(pufbuf) < sizeof(int)) {
352 errno = EIO;
353 goto bad;
354 }
355 puffs_framebuf_getdata_atoff(pufbuf, 0, &error, sizeof(int));
356 if (error) {
357 errno = error;
358 goto bad;
359 }
360
361 if (puffs_framebuf_tellsize(pufbuf) != 2*sizeof(int)) {
362 errno = EIO;
363 goto bad;
364 }
365
366 puffs_framebuf_getdata_atoff(pufbuf, sizeof(int),
367 &fd, sizeof(int));
368 puffs_framebuf_destroy(pufbuf);
369
370 data = 1;
371 if (ioctl(fd, FIONBIO, &data) == -1)
372 return errno;
373
374 if (puffs_framev_addfd(pu, fd, PUFFS_FBIO_WRITE) == -1)
375 return errno;
376
377 portn->fd = fd;
378 return 0;
379 }
380
381 bad:
382 puffs_framebuf_destroy(pufbuf);
383 return errno;
384 }
385
386 int
387 main(int argc, char *argv[])
388 {
389 extern char *optarg;
390 extern int optind;
391 struct puffs_usermount *pu;
392 struct puffs_ops *pops;
393 mntoptparse_t mp;
394 int pflags, mntflags;
395 int detach;
396 int ch;
397
398 setprogname(argv[0]);
399
400 mntflags = pflags = 0;
401 detach = 1;
402 while ((ch = getopt(argc, argv, "o:s")) != -1) {
403 switch (ch) {
404 case 'o':
405 mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
406 if (mp == NULL)
407 err(1, "getmntopts");
408 freemntopts(mp);
409 break;
410 case 's': /* stay on top */
411 detach = 0;
412 break;
413 default:
414 usage();
415 /*NOTREACHED*/
416 }
417 }
418 pflags |= PUFFS_KFLAG_NOCACHE | PUFFS_KFLAG_LOOKUP_FULLPNBUF;
419 if (pflags & PUFFS_FLAG_OPDUMP)
420 detach = 0;
421 argc -= optind;
422 argv += optind;
423
424 if (argc != 2)
425 usage();
426
427 PUFFSOP_INIT(pops);
428
429 PUFFSOP_SETFSNOP(pops, unmount);
430 PUFFSOP_SETFSNOP(pops, sync);
431 PUFFSOP_SETFSNOP(pops, statvfs);
432
433 PUFFSOP_SET(pops, portal, node, lookup);
434 PUFFSOP_SET(pops, portal, node, getattr);
435 PUFFSOP_SET(pops, portal, node, setattr);
436 PUFFSOP_SET(pops, portal, node, open);
437 PUFFSOP_SET(pops, portal, node, read);
438 PUFFSOP_SET(pops, portal, node, write);
439 PUFFSOP_SET(pops, portal, node, seek);
440 PUFFSOP_SET(pops, portal, node, poll);
441 PUFFSOP_SET(pops, portal, node, inactive);
442 PUFFSOP_SET(pops, portal, node, reclaim);
443
444 pu = puffs_init(pops, _PATH_PUFFS, "portal", NULL, pflags);
445 if (pu == NULL)
446 err(1, "init");
447
448 if (signal(SIGHUP, sighup) == SIG_ERR)
449 warn("cannot set sighup handler");
450 if (signal(SIGCHLD, sigcry) == SIG_ERR)
451 err(1, "cannot set sigchild handler");
452 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
453 err(1, "cannot ignore sigpipe");
454
455 readcfg = 0;
456 cfg = argv[0];
457 if (*cfg != '/')
458 errx(1, "need absolute path for config");
459 q.q_forw = q.q_back = &q;
460 if (conf_read(&q, cfg) == -1)
461 err(1, "cannot read cfg \"%s\"", cfg);
462
463 puffs_ml_setloopfn(pu, portal_loopfn);
464 puffs_framev_init(pu, portal_frame_rf, portal_frame_wf, NULL,NULL,NULL);
465
466 if (detach)
467 if (puffs_daemon(pu, 1, 1) == -1)
468 err(1, "puffs_daemon");
469
470 if (puffs_mount(pu, argv[1], mntflags, PORTAL_ROOT) == -1)
471 err(1, "mount");
472 if (puffs_mainloop(pu) == -1)
473 err(1, "mainloop");
474
475 return 0;
476 }
477
478 static struct portal_node *
479 makenode(const char *path)
480 {
481 struct portal_node *portn;
482
483 portn = emalloc(sizeof(struct portal_node));
484 portn->path = estrdup(path);
485 portn->fd = -1;
486
487 return portn;
488 }
489
490 static void
491 credtr(struct portal_cred *portc, const struct puffs_cred *puffc, int mode)
492 {
493 memset(portc, 0, sizeof(struct portal_cred));
494
495 portc->pcr_flag = mode;
496 puffs_cred_getuid(puffc, &portc->pcr_uid);
497 puffs_cred_getgid(puffc, &portc->pcr_gid);
498 puffs_cred_getgroups(puffc, portc->pcr_groups,
499 (short *)&portc->pcr_ngroups);
500 }
501
502 /*
503 * XXX: we could also simply already resolve the name at this stage
504 * instead of deferring it to open. But doing it in open is how the
505 * original portald does it, and I don't want to introduce any funny
506 * incompatibilities.
507 */
508 int
509 portal_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc,
510 struct puffs_newinfo *pni, const struct puffs_cn *pcn)
511 {
512 struct portal_node *portn;
513
514 assert(opc == PORTAL_ROOT);
515
516 if (pcn->pcn_nameiop != NAMEI_LOOKUP
517 && pcn->pcn_nameiop != NAMEI_CREATE)
518 return EOPNOTSUPP;
519
520 portn = makenode(pcn->pcn_name);
521 puffs_newinfo_setcookie(pni, portn);
522 puffs_newinfo_setvtype(pni, VREG);
523
524 pcn->pcn_flags &= ~NAMEI_REQUIREDIR;
525 pcn->pcn_consume = strlen(pcn->pcn_name) - pcn->pcn_namelen;
526
527 return 0;
528 }
529
530 int fakeid = 3;
531
532 /* XXX: libpuffs'ize */
533 int
534 portal_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc,
535 struct vattr *va, const struct puffs_cred *pcr)
536 {
537 struct timeval tv;
538 struct timespec ts;
539
540 puffs_vattr_null(va);
541 if (opc == PORTAL_ROOT) {
542 va->va_type = VDIR;
543 va->va_mode = 0777;
544 va->va_nlink = 2;
545 } else {
546 va->va_type = VREG;
547 va->va_mode = 0666;
548 va->va_nlink = 1;
549 }
550 va->va_uid = va->va_gid = 0;
551 va->va_fileid = fakeid++;
552 va->va_size = va->va_bytes = 0;
553 va->va_gen = 0;
554 va->va_rdev = PUFFS_VNOVAL;
555 va->va_blocksize = DEV_BSIZE;
556
557 gettimeofday(&tv, NULL);
558 TIMEVAL_TO_TIMESPEC(&tv, &ts);
559 va->va_atime = va->va_ctime = va->va_mtime = va->va_birthtime = ts;
560
561 return 0;
562 }
563
564 /* for writing, just pretend we care */
565 int
566 portal_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc,
567 const struct vattr *va, const struct puffs_cred *pcr)
568 {
569
570 return 0;
571 }
572
573 int
574 portal_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode,
575 const struct puffs_cred *pcr)
576 {
577 struct portal_node *portn = opc;
578 struct portal_cred portc;
579 char **v;
580
581 if (opc == PORTAL_ROOT)
582 return 0;
583
584 if (mode & O_NONBLOCK)
585 return EOPNOTSUPP;
586
587 v = conf_match(&q, portn->path);
588 if (v == NULL)
589 return ENOENT;
590
591 credtr(&portc, pcr, mode);
592 return provide(pu, portn, &portc, v);
593 }
594
595 int
596 portal_node_read(struct puffs_usermount *pu, puffs_cookie_t opc,
597 uint8_t *buf, off_t offset, size_t *resid,
598 const struct puffs_cred *pcr, int ioflag)
599 {
600 struct puffs_cc *pcc = puffs_cc_getcc(pu);
601 struct portal_node *portn = opc;
602 struct puffs_framebuf *pufbuf;
603 size_t xfersize, winsize, boff;
604 void *win;
605 int rv, error;
606 int data, dummy;
607
608 assert(opc != PORTAL_ROOT);
609 error = 0;
610
611 /* if we can't (re-)enable it, treat it as EOF */
612 rv = puffs_framev_enablefd(pu, portn->fd, PUFFS_FBIO_READ);
613 if (rv == -1)
614 return 0;
615
616 pufbuf = puffs_framebuf_make();
617 data = PUFBUF_DATA;
618 puffs_framebuf_putdata(pufbuf, &data, sizeof(int));
619 puffs_framebuf_putdata(pufbuf, resid, sizeof(size_t));
620
621 /* if we are doing nodelay, do read directly */
622 if (ioflag & PUFFS_IO_NDELAY) {
623 rv = readdata(pufbuf, portn->fd, &dummy);
624 if (rv != 0) {
625 error = rv;
626 goto out;
627 }
628 } else {
629 rv = puffs_framev_enqueue_directreceive(pcc,
630 portn->fd, pufbuf, 0);
631
632 if (rv == -1) {
633 error = errno;
634 goto out;
635 }
636 }
637
638 xfersize = puffs_framebuf_tellsize(pufbuf) - METADATASIZE;
639 if (xfersize == 0) {
640 assert(ioflag & PUFFS_IO_NDELAY);
641 error = EAGAIN;
642 goto out;
643 }
644
645 *resid -= xfersize;
646 boff = 0;
647 while (xfersize > 0) {
648 winsize = xfersize;
649 rv = puffs_framebuf_getwindow(pufbuf, METADATASIZE,
650 &win, &winsize);
651 assert(rv == 0);
652 assert(winsize > 0);
653
654 memcpy(buf + boff, win, winsize);
655 xfersize -= winsize;
656 boff += winsize;
657 }
658
659 out:
660 puffs_framev_disablefd(pu, portn->fd, PUFFS_FBIO_READ);
661 puffs_framebuf_destroy(pufbuf);
662
663 /* a trickery, from readdata() */
664 if (error == -1)
665 return 0;
666 return error;
667 }
668
669 int
670 portal_node_write(struct puffs_usermount *pu, puffs_cookie_t opc,
671 uint8_t *buf, off_t offset, size_t *resid,
672 const struct puffs_cred *pcr, int ioflag)
673 {
674 struct puffs_cc *pcc = puffs_cc_getcc(pu);
675 struct portal_node *portn = opc;
676 struct puffs_framebuf *pufbuf;
677 size_t written;
678 int error, rv, dummy;
679
680 assert(opc != PORTAL_ROOT);
681
682 pufbuf = puffs_framebuf_make();
683 puffs_framebuf_putdata(pufbuf, buf, *resid);
684
685 error = 0;
686 if (ioflag & PUFFS_IO_NDELAY) {
687 rv = portal_frame_wf(pu, pufbuf, portn->fd, &dummy);
688 if (rv) {
689 error = rv;
690 goto out;
691 }
692 } else {
693 rv = puffs_framev_enqueue_directsend(pcc, portn->fd, pufbuf, 0);
694 if (rv == -1) {
695 error = errno;
696 goto out;
697 }
698 }
699
700 rv = puffs_framebuf_getdata_atoff(pufbuf, 0, &written, sizeof(size_t));
701 assert(rv == 0);
702 assert(written <= *resid);
703 *resid -= written;
704
705 out:
706 puffs_framebuf_destroy(pufbuf);
707 if (error == -1)
708 error = 0;
709 return 0;
710 }
711
712 int
713 portal_node_seek(struct puffs_usermount *pu, puffs_cookie_t opc,
714 off_t oldoff, off_t newoff, const struct puffs_cred *pcr)
715 {
716 struct portal_node *portn = opc;
717
718 if (opc == PORTAL_ROOT || portn->fd == -1)
719 return EOPNOTSUPP;
720
721 if (lseek(portn->fd, newoff, SEEK_SET) == -1)
722 return errno;
723 return 0;
724 }
725
726 int
727 portal_node_poll(struct puffs_usermount *pu, puffs_cookie_t opc, int *events)
728 {
729 struct puffs_cc *pcc = puffs_cc_getcc(pu);
730 struct portal_node *portn = opc;
731 int what;
732 int rv;
733
734 what = 0;
735 if (*events & POLLIN)
736 what |= PUFFS_FBIO_READ;
737 if (*events & POLLOUT)
738 what |= PUFFS_FBIO_WRITE;
739 if (*events & POLLERR)
740 what |= PUFFS_FBIO_ERROR;
741
742 rv = puffs_framev_enqueue_waitevent(pcc, portn->fd, &what);
743 if (rv) {
744 *events = POLLERR;
745 return rv;
746 }
747
748 *events = 0;
749 if (what & PUFFS_FBIO_READ)
750 *events |= POLLIN;
751 if (what & PUFFS_FBIO_WRITE)
752 *events |= POLLOUT;
753 if (what & PUFFS_FBIO_ERROR)
754 *events |= POLLERR;
755
756 return 0;
757 }
758
759 int
760 portal_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc)
761 {
762
763 if (opc == PORTAL_ROOT)
764 return 0;
765
766 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1);
767 return 0;
768 }
769
770 int
771 portal_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc)
772 {
773 struct portal_node *portn = opc;
774
775 if (portn->fd != -1) {
776 puffs_framev_removefd(pu, portn->fd, 0);
777 close(portn->fd);
778 }
779 free(portn->path);
780 free(portn);
781
782 return 0;
783 }
784