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 52struct portal_node { 53 char *path; 54 int fd; 55}; 56 57__dead static void usage(void); 58 59PUFFSOP_PROTOS(portal); 60 61#define PORTAL_ROOT NULL 62#define METADATASIZE (sizeof(int) + sizeof(size_t)) 63 64qelem q; 65int readcfg, sigchild; 66const char *cfg; 67 68static void 69usage() 70{ 71 72 errx(1, "usage: %s [-o options] /path/portal.conf mount_point", 73 getprogname()); 74} 75 76static void 77sighup(int sig) 78{ 79 80 readcfg = 1; 81} 82 83static void 84sigcry(int sig) 85{ 86 87 sigchild = 1; 88} 89 90static void 91portal_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 */ 111static int 112readfd(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 */ 163static int 164readdata(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 207static int 208portal_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 224static int 225portal_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 */ 268static int 269sendfd(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 */ 316static int 317provide(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 386int 387main(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 478static struct portal_node * 479makenode(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 490static void 491credtr(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 */ 508int 509portal_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 530int fakeid = 3; 531 532/* XXX: libpuffs'ize */ 533int 534portal_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 */ 565int 566portal_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 573int 574portal_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 595int 596portal_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 669int 670portal_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 712int 713portal_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 726int 727portal_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 733 what = 0; 734 if (*events & POLLIN) 735 what |= PUFFS_FBIO_READ; 736 if (*events & POLLOUT) 737 what |= PUFFS_FBIO_WRITE; 738 if (*events & POLLERR) 739 what |= PUFFS_FBIO_ERROR; 740 741 if (puffs_framev_enqueue_waitevent(pcc, portn->fd, &what) == -1) { 742 *events = POLLERR; 743 return errno; 744 } 745 746 *events = 0; 747 if (what & PUFFS_FBIO_READ) 748 *events |= POLLIN; 749 if (what & PUFFS_FBIO_WRITE) 750 *events |= POLLOUT; 751 if (what & PUFFS_FBIO_ERROR) 752 *events |= POLLERR; 753 754 return 0; 755} 756 757int 758portal_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc) 759{ 760 761 if (opc == PORTAL_ROOT) 762 return 0; 763 764 puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1); 765 return 0; 766} 767 768int 769portal_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc) 770{ 771 struct portal_node *portn = opc; 772 773 if (portn->fd != -1) { 774 puffs_framev_removefd(pu, portn->fd, 0); 775 close(portn->fd); 776 } 777 free(portn->path); 778 free(portn); 779 780 return 0; 781} 782