/* $NetBSD: puffs_portal.c,v 1.10 2019/05/23 11:13:17 kre Exp $ */ /* * Copyright (c) 2007 Antti Kantee. All Rights Reserved. * Development was supported by the Finnish Cultural Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: puffs_portal.c,v 1.10 2019/05/23 11:13:17 kre Exp $"); #endif /* !lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "portald.h" struct portal_node { char *path; int fd; }; __dead static void usage(void); PUFFSOP_PROTOS(portal); #define PORTAL_ROOT NULL #define METADATASIZE (sizeof(int) + sizeof(size_t)) qelem q; int readcfg, sigchild; const char *cfg; static void usage(void) { errx(1, "usage: %s [-o options] /path/portal.conf mount_point", getprogname()); } static void sighup(int sig) { readcfg = 1; } static void sigcry(int sig) { sigchild = 1; } static void portal_loopfn(struct puffs_usermount *pu) { if (readcfg) conf_read(&q, cfg); readcfg = 0; if (sigchild) { sigchild = 0; while (waitpid(-1, NULL, WNOHANG) != -1) continue; } } #define PUFBUF_FD 1 #define PUFBUF_DATA 2 #define CMSIZE (sizeof(struct cmsghdr) + sizeof(int)) /* receive file descriptor produced by our child process */ static int readfd(struct puffs_framebuf *pufbuf, int fd, int *done) { struct cmsghdr *cmp; struct msghdr msg; struct iovec iov; ssize_t n; int error, rv; rv = 0; cmp = emalloc(CMSG_SPACE(sizeof(int))); iov.iov_base = &error; iov.iov_len = sizeof(int); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = cmp; msg.msg_controllen = CMSG_SPACE(sizeof(int)); n = recvmsg(fd, &msg, 0); if (n == -1) { rv = errno; goto out; } if (n == 0) { rv = ECONNRESET; goto out; } /* the data for the server */ puffs_framebuf_putdata_atoff(pufbuf, 0, &error, sizeof(int)); if (error) { rv = error; goto out; } puffs_framebuf_putdata_atoff(pufbuf, sizeof(int), CMSG_DATA(cmp), sizeof(int)); *done = 1; out: free(cmp); return rv; } /* * receive data from provider * * XXX: should read directly into the buffer and adjust offsets * instead of doing memcpy */ static int readdata(struct puffs_framebuf *pufbuf, int fd, int *done) { char buf[1024]; size_t max; ssize_t n; size_t moved; /* don't override metadata */ if (puffs_framebuf_telloff(pufbuf) == 0) puffs_framebuf_seekset(pufbuf, METADATASIZE); puffs_framebuf_getdata_atoff(pufbuf, sizeof(int), &max, sizeof(size_t)); moved = puffs_framebuf_tellsize(pufbuf) - METADATASIZE; assert(max >= moved); max -= moved; do { n = read(fd, buf, MIN(sizeof(buf), max)); if (n == 0) { /* * Deal with EOF here by closing the file descriptor * and thus causing an error on subsequent accesses. * This is the last kevent notification we are going * to be getting for regular files. */ close(fd); if (moved) break; else return -1; /* caught by read */ } if (n < 0) { if (moved) return 0; if (errno != EAGAIN) return errno; else return 0; } puffs_framebuf_putdata(pufbuf, buf, n); moved += n; max -= n; } while (max > 0); *done = 1; return 0; } static int portal_frame_rf(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf, int fd, int *done) { int type; if (puffs_framebuf_getdata_atoff(pufbuf, 0, &type, sizeof(int)) == -1) return EINVAL; if (type == PUFBUF_FD) return readfd(pufbuf, fd, done); else if (type == PUFBUF_DATA) return readdata(pufbuf, fd, done); else abort(); } static int portal_frame_wf(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf, int fd, int *done) { void *win; size_t pbsize, pboff, winlen; ssize_t n; int error; pboff = puffs_framebuf_telloff(pufbuf); pbsize = puffs_framebuf_tellsize(pufbuf); error = 0; do { assert(pbsize > pboff); winlen = pbsize - pboff; if (puffs_framebuf_getwindow(pufbuf, pboff, &win, &winlen)==-1) return errno; n = write(fd, win, winlen); if (n == 0) { if (pboff != 0) break; else return -1; /* caught by node_write */ } if (n < 0) { if (pboff != 0) break; if (errno != EAGAIN) return errno; return 0; } pboff += n; puffs_framebuf_seekset(pufbuf, pboff); } while (pboff != pbsize); *done = 1; puffs_framebuf_putdata_atoff(pufbuf, 0, &pboff, sizeof(size_t)); return error; } /* transfer file descriptor to master file server */ static int sendfd(int s, int fd, int error) { struct cmsghdr *cmp; struct msghdr msg; struct iovec iov; ssize_t n; int rv; rv = 0; cmp = emalloc(CMSG_LEN(sizeof(int))); iov.iov_base = &error; iov.iov_len = sizeof(int); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; if (error == 0) { cmp->cmsg_level = SOL_SOCKET; cmp->cmsg_type = SCM_RIGHTS; cmp->cmsg_len = CMSG_LEN(sizeof(int)); msg.msg_control = cmp; msg.msg_controllen = CMSG_LEN(sizeof(int)); *(int *)CMSG_DATA(cmp) = fd; } else { msg.msg_control = NULL; msg.msg_controllen = 0; } n = sendmsg(s, &msg, 0); if (n == -1) rv = errno; else if (n < (ssize_t)sizeof(int)) rv = EPROTO; free(cmp); return rv; } /* * Produce I/O file descriptor by forking (like original portald). * * child: run provider and transfer produced fd to parent * parent: yield until child produces fd. receive it and store it. */ static int provide(struct puffs_usermount *pu, struct portal_node *portn, struct portal_cred *portc, char **v) { struct puffs_cc *pcc = puffs_cc_getcc(pu); struct puffs_framebuf *pufbuf; int s[2]; int fd, error; int data; pufbuf = puffs_framebuf_make(); if (pufbuf == NULL) return ENOMEM; data = PUFBUF_FD; if (puffs_framebuf_putdata(pufbuf, &data, sizeof(int)) == -1) goto bad; if (socketpair(AF_LOCAL, SOCK_STREAM, 0, s) == -1) goto bad; switch (fork()) { case -1: close(s[0]); close(s[1]); goto bad; case 0: close(s[0]); error = activate_argv(portc, portn->path, v, &fd); sendfd(s[1], fd, error); exit(0); default: close(s[1]); puffs_framev_addfd(pu, s[0], PUFFS_FBIO_READ); puffs_framev_enqueue_directreceive(pcc, s[0], pufbuf, 0); puffs_framev_removefd(pu, s[0], 0); close(s[0]); if (puffs_framebuf_tellsize(pufbuf) < sizeof(int)) { errno = EIO; goto bad; } puffs_framebuf_getdata_atoff(pufbuf, 0, &error, sizeof(int)); if (error) { errno = error; goto bad; } if (puffs_framebuf_tellsize(pufbuf) != 2*sizeof(int)) { errno = EIO; goto bad; } puffs_framebuf_getdata_atoff(pufbuf, sizeof(int), &fd, sizeof(int)); puffs_framebuf_destroy(pufbuf); data = 1; if (ioctl(fd, FIONBIO, &data) == -1) return errno; if (puffs_framev_addfd(pu, fd, PUFFS_FBIO_WRITE) == -1) return errno; portn->fd = fd; return 0; } bad: puffs_framebuf_destroy(pufbuf); return errno; } int main(int argc, char *argv[]) { extern char *optarg; extern int optind; struct puffs_usermount *pu; struct puffs_ops *pops; mntoptparse_t mp; int pflags, mntflags; int detach; int ch; setprogname(argv[0]); mntflags = pflags = 0; detach = 1; while ((ch = getopt(argc, argv, "o:s")) != -1) { switch (ch) { case 'o': mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags); if (mp == NULL) err(1, "getmntopts"); freemntopts(mp); break; case 's': /* stay on top */ detach = 0; break; default: usage(); /*NOTREACHED*/ } } pflags |= PUFFS_KFLAG_NOCACHE | PUFFS_KFLAG_LOOKUP_FULLPNBUF; if (pflags & PUFFS_FLAG_OPDUMP) detach = 0; argc -= optind; argv += optind; if (argc != 2) usage(); PUFFSOP_INIT(pops); PUFFSOP_SETFSNOP(pops, unmount); PUFFSOP_SETFSNOP(pops, sync); PUFFSOP_SETFSNOP(pops, statvfs); PUFFSOP_SET(pops, portal, node, lookup); PUFFSOP_SET(pops, portal, node, getattr); PUFFSOP_SET(pops, portal, node, setattr); PUFFSOP_SET(pops, portal, node, open); PUFFSOP_SET(pops, portal, node, read); PUFFSOP_SET(pops, portal, node, write); PUFFSOP_SET(pops, portal, node, seek); PUFFSOP_SET(pops, portal, node, poll); PUFFSOP_SET(pops, portal, node, inactive); PUFFSOP_SET(pops, portal, node, reclaim); pu = puffs_init(pops, _PATH_PUFFS, "portal", NULL, pflags); if (pu == NULL) err(1, "init"); if (signal(SIGHUP, sighup) == SIG_ERR) warn("cannot set sighup handler"); if (signal(SIGCHLD, sigcry) == SIG_ERR) err(1, "cannot set sigchild handler"); if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) err(1, "cannot ignore sigpipe"); readcfg = 0; cfg = argv[0]; if (*cfg != '/') errx(1, "need absolute path for config"); q.q_forw = q.q_back = &q; if (conf_read(&q, cfg) == -1) err(1, "cannot read cfg \"%s\"", cfg); puffs_ml_setloopfn(pu, portal_loopfn); puffs_framev_init(pu, portal_frame_rf, portal_frame_wf, NULL,NULL,NULL); if (detach) if (puffs_daemon(pu, 1, 1) == -1) err(1, "puffs_daemon"); if (puffs_mount(pu, argv[1], mntflags, PORTAL_ROOT) == -1) err(1, "mount"); if (puffs_mainloop(pu) == -1) err(1, "mainloop"); return 0; } static struct portal_node * makenode(const char *path) { struct portal_node *portn; portn = emalloc(sizeof(struct portal_node)); portn->path = estrdup(path); portn->fd = -1; return portn; } static void credtr(struct portal_cred *portc, const struct puffs_cred *puffc, int mode) { memset(portc, 0, sizeof(struct portal_cred)); portc->pcr_flag = mode; puffs_cred_getuid(puffc, &portc->pcr_uid); puffs_cred_getgid(puffc, &portc->pcr_gid); puffs_cred_getgroups(puffc, portc->pcr_groups, (short *)&portc->pcr_ngroups); } /* * XXX: we could also simply already resolve the name at this stage * instead of deferring it to open. But doing it in open is how the * original portald does it, and I don't want to introduce any funny * incompatibilities. */ int portal_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn) { struct portal_node *portn; assert(opc == PORTAL_ROOT); if (pcn->pcn_nameiop != NAMEI_LOOKUP && pcn->pcn_nameiop != NAMEI_CREATE) return EOPNOTSUPP; portn = makenode(pcn->pcn_name); puffs_newinfo_setcookie(pni, portn); puffs_newinfo_setvtype(pni, VREG); pcn->pcn_flags &= ~NAMEI_REQUIREDIR; pcn->pcn_consume = strlen(pcn->pcn_name) - pcn->pcn_namelen; return 0; } unsigned int fakeid = 3; /* XXX: libpuffs'ize */ int portal_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc, struct vattr *va, const struct puffs_cred *pcr) { struct timeval tv; struct timespec ts; int res = 0; puffs_vattr_null(va); if (opc == PORTAL_ROOT) { va->va_type = VDIR; va->va_mode = 0777; va->va_nlink = 2; va->va_uid = va->va_gid = 0; #if 0 /* XXX Huh? */ va->va_fileid = fakeid++; #else va->va_fileid = 2; /*XXX; ROOTINO*/ #endif va->va_size = va->va_bytes = 0; va->va_gen = 0; va->va_rdev = PUFFS_VNOVAL; va->va_blocksize = DEV_BSIZE; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, &ts); va->va_atime = va->va_ctime = va->va_mtime = va->va_birthtime = ts; } else { /* cheat for now */ int error; int newfd; struct stat st; struct portal_node *portn = opc; struct portal_cred portc; char **v = conf_match(&q, portn->path); if (v == NULL) return ENOENT; if (portn->fd == -1) { credtr(&portc, pcr, 0777); error = provide(pu, portn, &portc, v); if (error) return error; newfd = 1; } else newfd = 0; if (fstat(portn->fd, &st) == -1) res = errno; else { #if 0 va->va_type = S_ISDIR(st.st_mode) ? VDIR : VREG; /* XXX */ #else va->va_type = puffs_mode2vt(st.st_mode); #endif /* puffs supplies S_IFMT bits later */ va->va_mode = (st.st_mode & ~S_IFMT); va->va_nlink = st.st_nlink ? st.st_nlink : 1; va->va_uid = st.st_uid; va->va_gid = st.st_gid; va->va_fileid = st.st_ino ? st.st_ino : fakeid++; va->va_size = va->va_bytes = st.st_size; va->va_gen = 0; va->va_rdev = st.st_rdev; va->va_blocksize = st.st_blksize; va->va_atime = st.st_atim; va->va_ctime = st.st_ctim; va->va_mtime = st.st_mtim; va->va_birthtime = st.st_birthtim; } if (newfd) { puffs_framev_removefd(pu, portn->fd, 0); close(portn->fd); portn->fd = -1; } } return res; } /* for writing, just pretend we care */ int portal_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc, const struct vattr *va, const struct puffs_cred *pcr) { return 0; } int portal_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode, const struct puffs_cred *pcr) { struct portal_node *portn = opc; struct portal_cred portc; char **v; if (opc == PORTAL_ROOT) return 0; if (mode & O_NONBLOCK) return EOPNOTSUPP; v = conf_match(&q, portn->path); if (v == NULL) return ENOENT; credtr(&portc, pcr, mode); return provide(pu, portn, &portc, v); } int portal_node_read(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, off_t offset, size_t *resid, const struct puffs_cred *pcr, int ioflag) { struct puffs_cc *pcc = puffs_cc_getcc(pu); struct portal_node *portn = opc; struct puffs_framebuf *pufbuf; size_t xfersize, winsize, boff; void *win; int rv, error; int data, dummy; assert(opc != PORTAL_ROOT); error = 0; /* if we can't (re-)enable it, treat it as EOF */ rv = puffs_framev_enablefd(pu, portn->fd, PUFFS_FBIO_READ); if (rv == -1) return 0; pufbuf = puffs_framebuf_make(); data = PUFBUF_DATA; puffs_framebuf_putdata(pufbuf, &data, sizeof(int)); puffs_framebuf_putdata(pufbuf, resid, sizeof(size_t)); /* if we are doing nodelay, do read directly */ if (ioflag & PUFFS_IO_NDELAY) { rv = readdata(pufbuf, portn->fd, &dummy); if (rv != 0) { error = rv; goto out; } } else { rv = puffs_framev_enqueue_directreceive(pcc, portn->fd, pufbuf, 0); if (rv == -1) { error = errno; goto out; } } xfersize = puffs_framebuf_tellsize(pufbuf) - METADATASIZE; if (xfersize == 0) { assert(ioflag & PUFFS_IO_NDELAY); error = EAGAIN; goto out; } *resid -= xfersize; boff = 0; while (xfersize > 0) { winsize = xfersize; rv = puffs_framebuf_getwindow(pufbuf, METADATASIZE, &win, &winsize); assert(rv == 0); assert(winsize > 0); memcpy(buf + boff, win, winsize); xfersize -= winsize; boff += winsize; } out: puffs_framev_disablefd(pu, portn->fd, PUFFS_FBIO_READ); puffs_framebuf_destroy(pufbuf); /* a trickery, from readdata() */ if (error == -1) return 0; return error; } int portal_node_write(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, off_t offset, size_t *resid, const struct puffs_cred *pcr, int ioflag) { struct puffs_cc *pcc = puffs_cc_getcc(pu); struct portal_node *portn = opc; struct puffs_framebuf *pufbuf; size_t written; int error, rv, dummy; assert(opc != PORTAL_ROOT); pufbuf = puffs_framebuf_make(); puffs_framebuf_putdata(pufbuf, buf, *resid); error = 0; if (ioflag & PUFFS_IO_NDELAY) { rv = portal_frame_wf(pu, pufbuf, portn->fd, &dummy); if (rv) { error = rv; goto out; } } else { rv = puffs_framev_enqueue_directsend(pcc, portn->fd, pufbuf, 0); if (rv == -1) { error = errno; goto out; } } rv = puffs_framebuf_getdata_atoff(pufbuf, 0, &written, sizeof(size_t)); assert(rv == 0); assert(written <= *resid); *resid -= written; out: puffs_framebuf_destroy(pufbuf); if (error == -1) error = 0; return 0; } int portal_node_seek(struct puffs_usermount *pu, puffs_cookie_t opc, off_t oldoff, off_t newoff, const struct puffs_cred *pcr) { struct portal_node *portn = opc; if (opc == PORTAL_ROOT || portn->fd == -1) return EOPNOTSUPP; if (lseek(portn->fd, newoff, SEEK_SET) == -1) return errno; return 0; } int portal_node_poll(struct puffs_usermount *pu, puffs_cookie_t opc, int *events) { struct puffs_cc *pcc = puffs_cc_getcc(pu); struct portal_node *portn = opc; int what; what = 0; if (*events & POLLIN) what |= PUFFS_FBIO_READ; if (*events & POLLOUT) what |= PUFFS_FBIO_WRITE; if (*events & POLLERR) what |= PUFFS_FBIO_ERROR; if (puffs_framev_enqueue_waitevent(pcc, portn->fd, &what) == -1) { *events = POLLERR; return errno; } *events = 0; if (what & PUFFS_FBIO_READ) *events |= POLLIN; if (what & PUFFS_FBIO_WRITE) *events |= POLLOUT; if (what & PUFFS_FBIO_ERROR) *events |= POLLERR; return 0; } int portal_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc) { if (opc == PORTAL_ROOT) return 0; puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1); return 0; } int portal_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc) { struct portal_node *portn = opc; if (portn->fd != -1) { puffs_framev_removefd(pu, portn->fd, 0); close(portn->fd); } free(portn->path); free(portn); return 0; }