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