/* $NetBSD: ops.c,v 1.84 2015/06/03 14:07:05 manu Exp $ */ /*- * Copyright (c) 2010-2011 Emmanuel Dreyfus. All rights reserved. * * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "perfuse_priv.h" #include "fuse.h" extern int perfuse_diagflags; #if 0 static void print_node(const char *, puffs_cookie_t); #endif #ifdef PUFFS_KFLAG_CACHE_FS_TTL static void perfuse_newinfo_setttl(struct puffs_newinfo *, struct puffs_node *, struct fuse_entry_out *, struct fuse_attr_out *); #endif /* PUFFS_KFLAG_CACHE_FS_TTL */ static int xchg_msg(struct puffs_usermount *, puffs_cookie_t, perfuse_msg_t *, size_t, enum perfuse_xchg_pb_reply); static int mode_access(puffs_cookie_t, const struct puffs_cred *, mode_t); static int sticky_access(puffs_cookie_t, struct puffs_node *, const struct puffs_cred *); static void fuse_attr_to_vap(struct perfuse_state *, struct vattr *, struct fuse_attr *); static int node_lookup_common(struct puffs_usermount *, puffs_cookie_t, struct puffs_newinfo *, const char *, const struct puffs_cred *, struct puffs_node **); static int node_mk_common(struct puffs_usermount *, puffs_cookie_t, struct puffs_newinfo *, const struct puffs_cn *pcn, perfuse_msg_t *); static uint64_t readdir_last_cookie(struct fuse_dirent *, size_t); static ssize_t fuse_to_dirent(struct puffs_usermount *, puffs_cookie_t, struct fuse_dirent *, size_t); static void readdir_buffered(puffs_cookie_t, struct dirent *, off_t *, size_t *); static void node_ref(puffs_cookie_t); static void node_rele(puffs_cookie_t); static void requeue_request(struct puffs_usermount *, puffs_cookie_t opc, enum perfuse_qtype); static int dequeue_requests(puffs_cookie_t opc, enum perfuse_qtype, int); #define DEQUEUE_ALL 0 /* * From , inside #ifdef _KERNEL section */ #define IO_SYNC (0x40|IO_DSYNC) #define IO_DSYNC 0x00200 #define IO_DIRECT 0x02000 /* * From , inside #ifdef _KERNEL section */ #define F_WAIT 0x010 #define F_FLOCK 0x020 #define OFLAGS(fflags) ((fflags) - 1) /* * Borrowed from src/sys/kern/vfs_subr.c and src/sys/sys/vnode.h */ const enum vtype iftovt_tab[16] = { VNON, VFIFO, VCHR, VNON, VDIR, VNON, VBLK, VNON, VREG, VNON, VLNK, VNON, VSOCK, VNON, VNON, VBAD, }; const int vttoif_tab[9] = { 0, S_IFREG, S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFSOCK, S_IFIFO, S_IFMT, }; #define IFTOVT(mode) (iftovt_tab[((mode) & S_IFMT) >> 12]) #define VTTOIF(indx) (vttoif_tab[(int)(indx)]) #if 0 static void print_node(const char *func, puffs_cookie_t opc) { struct puffs_node *pn; struct perfuse_node_data *pnd; struct vattr *vap; pn = (struct puffs_node *)opc; pnd = PERFUSE_NODE_DATA(opc); vap = &pn->pn_va; printf("%s: \"%s\", opc = %p, nodeid = 0x%"PRIx64" ino = %"PRIu64"\n", func, pnd->pnd_name, opc, pnd->pnd_nodeid, vap->va_fileid); return; } #endif /* PERFUSE_DEBUG */ int perfuse_node_close_common(struct puffs_usermount *pu, puffs_cookie_t opc, int mode) { struct perfuse_state *ps; perfuse_msg_t *pm; int op; uint64_t fh; struct fuse_release_in *fri; struct perfuse_node_data *pnd; struct puffs_node *pn; int error; ps = puffs_getspecific(pu); pn = (struct puffs_node *)opc; pnd = PERFUSE_NODE_DATA(pn); if (puffs_pn_getvap(pn)->va_type == VDIR) { op = FUSE_RELEASEDIR; mode = FREAD; } else { op = FUSE_RELEASE; } /* * Destroy the filehandle before sending the * request to the FUSE filesystem, otherwise * we may get a second close() while we wait * for the reply, and we would end up closing * the same fh twice instead of closng both. */ fh = perfuse_get_fh(opc, mode); perfuse_destroy_fh(pn, fh); /* * release_flags may be set to FUSE_RELEASE_FLUSH * to flush locks. lock_owner must be set in that case * * ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. We come here from the * inactive method, which provides no creds, but obviously * runs with kernel privilege. */ pm = ps->ps_new_msg(pu, opc, op, sizeof(*fri), NULL); fri = GET_INPAYLOAD(ps, pm, fuse_release_in); fri->fh = fh; fri->flags = 0; fri->release_flags = 0; fri->lock_owner = pnd->pnd_lock_owner; fri->flags = (fri->lock_owner != 0) ? FUSE_RELEASE_FLUSH : 0; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n", __func__, (void *)opc, pnd->pnd_nodeid, fri->fh); #endif if ((error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply)) != 0) DERRX(EX_SOFTWARE, "%s: freed fh = 0x%"PRIx64" but filesystem " "returned error = %d", __func__, fh, error); ps->ps_destroy_msg(pm); return 0; } static int xchg_msg(struct puffs_usermount *pu, puffs_cookie_t opc, perfuse_msg_t *pm, size_t len, enum perfuse_xchg_pb_reply wait) { struct perfuse_state *ps; struct perfuse_node_data *pnd; struct perfuse_trace *pt = NULL; int error; ps = puffs_getspecific(pu); pnd = NULL; if ((struct puffs_node *)opc != NULL) pnd = PERFUSE_NODE_DATA(opc); #ifdef PERFUSE_DEBUG if ((perfuse_diagflags & PDF_FILENAME) && (opc != 0)) DPRINTF("file = \"%s\", ino = %"PRIu64" flags = 0x%x\n", perfuse_node_path(ps, opc), ((struct puffs_node *)opc)->pn_va.va_fileid, PERFUSE_NODE_DATA(opc)->pnd_flags); #endif ps->ps_xchgcount++; if (pnd) pnd->pnd_inxchg++; /* * Record FUSE call start if requested */ if (perfuse_diagflags & PDF_TRACE) pt = perfuse_trace_begin(ps, opc, pm); /* * Do actual FUSE exchange */ if ((error = ps->ps_xchg_msg(pu, pm, len, wait)) != 0) ps->ps_destroy_msg(pm); /* * Record FUSE call end if requested */ if (pt != NULL) perfuse_trace_end(ps, pt, error); ps->ps_xchgcount--; if (pnd) { pnd->pnd_inxchg--; (void)dequeue_requests(opc, PCQ_AFTERXCHG, DEQUEUE_ALL); } return error; } static int mode_access(puffs_cookie_t opc, const struct puffs_cred *pcr, mode_t mode) { struct puffs_node *pn; struct vattr *va; /* * pcr is NULL for self open through fsync or readdir. * In both case, access control is useless, as it was * done before, at open time. */ if (pcr == NULL) return 0; pn = (struct puffs_node *)opc; va = puffs_pn_getvap(pn); return puffs_access(va->va_type, va->va_mode, va->va_uid, va->va_gid, mode, pcr); } static int sticky_access(puffs_cookie_t opc, struct puffs_node *targ, const struct puffs_cred *pcr) { uid_t uid; int sticky, owner, parent_owner; /* * This covers the case where the kernel requests a DELETE * or RENAME on its own, and where puffs_cred_getuid would * return -1. While such a situation should not happen, * we allow it here. * * This also allows root to tamper with other users' files * that have the sticky bit. */ if (puffs_cred_isjuggernaut(pcr)) return 0; if (puffs_cred_getuid(pcr, &uid) != 0) DERRX(EX_SOFTWARE, "puffs_cred_getuid fails in %s", __func__); sticky = puffs_pn_getvap(opc)->va_mode & S_ISTXT; owner = puffs_pn_getvap(targ)->va_uid == uid; parent_owner = puffs_pn_getvap(opc)->va_uid == uid; if (sticky && !owner && !parent_owner) return EPERM; return 0; } static void fuse_attr_to_vap(struct perfuse_state *ps, struct vattr *vap, struct fuse_attr *fa) { vap->va_type = IFTOVT(fa->mode); vap->va_mode = fa->mode & ALLPERMS; vap->va_nlink = fa->nlink; vap->va_uid = fa->uid; vap->va_gid = fa->gid; vap->va_fsid = (long)ps->ps_fsid; vap->va_fileid = fa->ino; vap->va_size = fa->size; vap->va_blocksize = fa->blksize; vap->va_atime.tv_sec = (time_t)fa->atime; vap->va_atime.tv_nsec = (long) fa->atimensec; vap->va_mtime.tv_sec = (time_t)fa->mtime; vap->va_mtime.tv_nsec = (long)fa->mtimensec; vap->va_ctime.tv_sec = (time_t)fa->ctime; vap->va_ctime.tv_nsec = (long)fa->ctimensec; vap->va_birthtime.tv_sec = 0; vap->va_birthtime.tv_nsec = 0; vap->va_gen = 0; vap->va_flags = 0; vap->va_rdev = fa->rdev; vap->va_bytes = fa->blocks * S_BLKSIZE; vap->va_filerev = (u_quad_t)PUFFS_VNOVAL; vap->va_vaflags = 0; if (vap->va_blocksize == 0) vap->va_blocksize = DEV_BSIZE; if (vap->va_size == (size_t)PUFFS_VNOVAL) /* XXX */ vap->va_size = 0; return; } #ifdef PUFFS_KFLAG_CACHE_FS_TTL static void perfuse_newinfo_setttl(struct puffs_newinfo *pni, struct puffs_node *pn, struct fuse_entry_out *feo, struct fuse_attr_out *fao) { #ifdef PERFUSE_DEBUG if ((feo == NULL) && (fao == NULL)) DERRX(EX_SOFTWARE, "%s: feo and fao NULL", __func__); if ((feo != NULL) && (fao != NULL)) DERRX(EX_SOFTWARE, "%s: feo and fao != NULL", __func__); #endif /* PERFUSE_DEBUG */ if (fao != NULL) { struct timespec va_ttl; va_ttl.tv_sec = fao->attr_valid; va_ttl.tv_nsec = fao->attr_valid_nsec; puffs_newinfo_setvattl(pni, &va_ttl); } if (feo != NULL) { struct timespec va_ttl; struct timespec cn_ttl; struct timespec now; struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(pn); va_ttl.tv_sec = feo->attr_valid; va_ttl.tv_nsec = feo->attr_valid_nsec; cn_ttl.tv_sec = feo->entry_valid; cn_ttl.tv_nsec = feo->entry_valid_nsec; puffs_newinfo_setvattl(pni, &va_ttl); puffs_newinfo_setcnttl(pni, &cn_ttl); if (clock_gettime(CLOCK_REALTIME, &now) != 0) DERR(EX_OSERR, "clock_gettime failed"); timespecadd(&now, &cn_ttl, &pnd->pnd_cn_expire); } return; } #endif /* PUFFS_KFLAG_CACHE_FS_TTL */ static int node_lookup_common(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const char *path, const struct puffs_cred *pcr, struct puffs_node **pnp) { struct perfuse_state *ps; struct perfuse_node_data *oldpnd; perfuse_msg_t *pm; struct fuse_entry_out *feo; struct puffs_node *pn; size_t len; int error; /* * Prevent further lookups if the parent was removed */ if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ESTALE; if (pnp == NULL) DERRX(EX_SOFTWARE, "pnp must be != NULL"); ps = puffs_getspecific(pu); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FILENAME) DPRINTF("%s: opc = %p, file = \"%s\" looking up \"%s\"\n", __func__, (void *)opc, perfuse_node_path(ps, opc), path); if (strcmp(path, ".") == 0) DERRX(EX_SOFTWARE, "unexpected dot-lookup"); if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_RECLAIMED) DERRX(EX_SOFTWARE, "looking up reclaimed node opc = %p, name = \"%s\"", opc, path); if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_INVALID) DERRX(EX_SOFTWARE, "looking up freed node opc = %p, name = \"%s\"", opc, path); #endif /* PERFUSE_DEBUG */ len = strlen(path) + 1; pm = ps->ps_new_msg(pu, opc, FUSE_LOOKUP, len, pcr); (void)strlcpy(_GET_INPAYLOAD(ps, pm, char *), path, len); if ((error = xchg_msg(pu, opc, pm, sizeof(*feo), wait_reply)) != 0) return error; feo = GET_OUTPAYLOAD(ps, pm, fuse_entry_out); /* * Starting with ABI 7.4, inode number 0 means ENOENT, * with entry_valid / entry_valid_nsec giving negative * cache timeout (which we do not implement yet). */ if (feo->attr.ino == 0) { ps->ps_destroy_msg(pm); return ENOENT; } /* * Check for a known node, not reclaimed, with another name. * It may have been moved, or we can lookup ../ */ if (((oldpnd = perfuse_node_bynodeid(ps, feo->nodeid)) != NULL) && !(oldpnd->pnd_flags & PND_RECLAIMED)) { /* * Save the new node name if not .. */ if (strncmp(path, "..", len) != 0) (void)strlcpy(oldpnd->pnd_name, path, MAXPATHLEN); pn = oldpnd->pnd_pn; } else { pn = perfuse_new_pn(pu, path, opc); PERFUSE_NODE_DATA(pn)->pnd_nodeid = feo->nodeid; perfuse_node_cache(ps, pn); } #ifdef PERFUSE_DEBUG if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_RECLAIMED) DERRX(EX_SOFTWARE, "reclaimed in lookup opc = %p, name = \"%s\", ck = %p", opc, path, pn); if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_INVALID) DERRX(EX_SOFTWARE, "freed in lookup opc = %p, name = \"%s\", ck = %p", opc, path, pn); #endif /* PERFUSE_DEBUG */ fuse_attr_to_vap(ps, &pn->pn_va, &feo->attr); pn->pn_va.va_gen = (u_long)(feo->generation); PERFUSE_NODE_DATA(pn)->pnd_fuse_nlookup++; *pnp = pn; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FILENAME) DPRINTF("%s: opc = %p, looked up opc = %p, " "nodeid = 0x%"PRIx64" file = \"%s\"\n", __func__, (void *)opc, pn, feo->nodeid, path); #endif if (pni != NULL) { #ifdef PUFFS_KFLAG_CACHE_FS_TTL puffs_newinfo_setva(pni, &pn->pn_va); perfuse_newinfo_setttl(pni, pn, feo, NULL); #endif /* PUFFS_KFLAG_CACHE_FS_TTL */ puffs_newinfo_setcookie(pni, pn); puffs_newinfo_setvtype(pni, pn->pn_va.va_type); puffs_newinfo_setsize(pni, (voff_t)pn->pn_va.va_size); puffs_newinfo_setrdev(pni, pn->pn_va.va_rdev); } if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_NODELEAK) { PERFUSE_NODE_DATA(pn)->pnd_flags &= ~PND_NODELEAK; ps->ps_nodeleakcount--; } ps->ps_destroy_msg(pm); return 0; } /* * Common code for methods that create objects: * perfuse_node_mkdir * perfuse_node_mknod * perfuse_node_symlink */ static int node_mk_common(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn, perfuse_msg_t *pm) { struct perfuse_state *ps; struct puffs_node *pn; struct fuse_entry_out *feo; int error; ps = puffs_getspecific(pu); if ((error = xchg_msg(pu, opc, pm, sizeof(*feo), wait_reply)) != 0) return error; feo = GET_OUTPAYLOAD(ps, pm, fuse_entry_out); if (feo->nodeid == PERFUSE_UNKNOWN_NODEID) DERRX(EX_SOFTWARE, "%s: no nodeid", __func__); pn = perfuse_new_pn(pu, pcn->pcn_name, opc); PERFUSE_NODE_DATA(pn)->pnd_nodeid = feo->nodeid; PERFUSE_NODE_DATA(pn)->pnd_puffs_nlookup++; perfuse_node_cache(ps, pn); fuse_attr_to_vap(ps, &pn->pn_va, &feo->attr); pn->pn_va.va_gen = (u_long)(feo->generation); puffs_newinfo_setcookie(pni, pn); #ifdef PUFFS_KFLAG_CACHE_FS_TTL puffs_newinfo_setva(pni, &pn->pn_va); perfuse_newinfo_setttl(pni, pn, feo, NULL); #endif /* PUFFS_KFLAG_CACHE_FS_TTL */ #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FILENAME) DPRINTF("%s: opc = %p, file = \"%s\", flags = 0x%x " "nodeid = 0x%"PRIx64"\n", __func__, (void *)pn, pcn->pcn_name, PERFUSE_NODE_DATA(pn)->pnd_flags, feo->nodeid); #endif ps->ps_destroy_msg(pm); /* Parents is now dirty */ PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY; return 0; } static uint64_t readdir_last_cookie(struct fuse_dirent *fd, size_t fd_len) { size_t len; size_t seen = 0; char *ndp; do { len = FUSE_DIRENT_ALIGN(sizeof(*fd) + fd->namelen); seen += len; if (seen >= fd_len) break; ndp = (char *)(void *)fd + (size_t)len; fd = (struct fuse_dirent *)(void *)ndp; } while (1 /* CONSTCOND */); return fd->off; } static ssize_t fuse_to_dirent(struct puffs_usermount *pu, puffs_cookie_t opc, struct fuse_dirent *fd, size_t fd_len) { struct dirent *dents; size_t dents_len; ssize_t written; uint64_t fd_offset; struct fuse_dirent *fd_base; size_t len; fd_base = fd; fd_offset = 0; written = 0; dents = PERFUSE_NODE_DATA(opc)->pnd_dirent; dents_len = (size_t)PERFUSE_NODE_DATA(opc)->pnd_dirent_len; do { char *ndp; size_t reclen; char name[MAXPATHLEN]; reclen = _DIRENT_RECLEN(dents, fd->namelen); /* * Check we do not overflow the output buffer * struct fuse_dirent is bigger than struct dirent, * so we should always use fd_len and never reallocate * later. * If we have to reallocate,try to double the buffer * each time so that we do not have to do it too often. */ if (written + reclen > dents_len) { if (dents_len == 0) dents_len = fd_len; else dents_len = MAX(2 * dents_len, written + reclen); dents = PERFUSE_NODE_DATA(opc)->pnd_dirent; if ((dents = realloc(dents, dents_len)) == NULL) DERR(EX_OSERR, "%s: malloc failed", __func__); PERFUSE_NODE_DATA(opc)->pnd_dirent = dents; PERFUSE_NODE_DATA(opc)->pnd_dirent_len = dents_len; /* * (void *) for delint */ ndp = (char *)(void *)dents + written; dents = (struct dirent *)(void *)ndp; } strncpy(name, fd->name, fd->namelen); name[fd->namelen] = '\0'; /* * Filesystem was mounted without -o use_ino * Perform a lookup to find it. */ if (fd->ino == PERFUSE_UNKNOWN_INO) { struct puffs_node *pn; struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc); if (strcmp(name, "..") == 0) { /* * Avoid breaking out of fs * by lookup to .. on root */ if (pnd->pnd_nodeid == FUSE_ROOT_ID) fd->ino = FUSE_ROOT_ID; else fd->ino = pnd->pnd_parent_nodeid; } else if (strcmp(name, ".") == 0 ) { fd->ino = pnd->pnd_nodeid; } else { int error; error = node_lookup_common(pu, opc, NULL, name, NULL, &pn); if (error != 0) { DWARNX("node_lookup_common %s " "failed: %d", name, error); } else { fd->ino = pn->pn_va.va_fileid; (void)perfuse_node_reclaim(pu, pn); } } } dents->d_fileno = fd->ino; dents->d_reclen = (unsigned short)reclen; dents->d_namlen = fd->namelen; dents->d_type = fd->type; strlcpy(dents->d_name, name, fd->namelen + 1); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_READDIR) DPRINTF("%s: translated \"%s\" ino = %"PRIu64"\n", __func__, dents->d_name, dents->d_fileno); #endif dents = _DIRENT_NEXT(dents); written += reclen; /* * Move to the next record. * fd->off is not the offset, it is an opaque cookie * given by the filesystem to keep state across multiple * readdir() operation. * Use record alignement instead. */ len = FUSE_DIRENT_ALIGN(sizeof(*fd) + fd->namelen); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_READDIR) DPRINTF("%s: record at %"PRId64"/0x%"PRIx64" " "length = %zd/0x%zx. " "next record at %"PRId64"/0x%"PRIx64" " "max %zd/0x%zx\n", __func__, fd_offset, fd_offset, len, len, fd_offset + len, fd_offset + len, fd_len, fd_len); #endif fd_offset += len; /* * Check if next record is still within the packet * If it is not, we reached the end of the buffer. */ if (fd_offset >= fd_len) break; /* * (void *) for delint */ ndp = (char *)(void *)fd_base + (size_t)fd_offset; fd = (struct fuse_dirent *)(void *)ndp; } while (1 /* CONSTCOND */); /* * Adjust the dirent output length */ if (written != -1) PERFUSE_NODE_DATA(opc)->pnd_dirent_len = written; return written; } static void readdir_buffered(puffs_cookie_t opc, struct dirent *dent, off_t *readoff, size_t *reslen) { struct dirent *fromdent; struct perfuse_node_data *pnd; char *ndp; pnd = PERFUSE_NODE_DATA(opc); while (*readoff < pnd->pnd_dirent_len) { /* * (void *) for delint */ ndp = (char *)(void *)pnd->pnd_dirent + (size_t)*readoff; fromdent = (struct dirent *)(void *)ndp; if (*reslen < _DIRENT_SIZE(fromdent)) break; memcpy(dent, fromdent, _DIRENT_SIZE(fromdent)); *readoff += _DIRENT_SIZE(fromdent); *reslen -= _DIRENT_SIZE(fromdent); dent = _DIRENT_NEXT(dent); } #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_READDIR) DPRINTF("%s: readoff = %"PRId64", " "pnd->pnd_dirent_len = %"PRId64"\n", __func__, *readoff, pnd->pnd_dirent_len); #endif if (*readoff >= pnd->pnd_dirent_len) { free(pnd->pnd_dirent); pnd->pnd_dirent = NULL; pnd->pnd_dirent_len = 0; } return; } static void node_ref(puffs_cookie_t opc) { struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc); #ifdef PERFUSE_DEBUG if (pnd->pnd_flags & PND_INVALID) DERRX(EX_SOFTWARE, "Use of freed node opc = %p", opc); #endif /* PERFUSE_DEBUG */ pnd->pnd_ref++; return; } static void node_rele(puffs_cookie_t opc) { struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc); #ifdef PERFUSE_DEBUG if (pnd->pnd_flags & PND_INVALID) DERRX(EX_SOFTWARE, "Use of freed node opc = %p", opc); #endif /* PERFUSE_DEBUG */ pnd->pnd_ref--; if (pnd->pnd_ref == 0) (void)dequeue_requests(opc, PCQ_REF, DEQUEUE_ALL); return; } static void requeue_request(struct puffs_usermount *pu, puffs_cookie_t opc, enum perfuse_qtype type) { struct perfuse_cc_queue pcq; struct perfuse_node_data *pnd; pnd = PERFUSE_NODE_DATA(opc); pcq.pcq_type = type; pcq.pcq_cc = puffs_cc_getcc(pu); TAILQ_INSERT_TAIL(&pnd->pnd_pcq, &pcq, pcq_next); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_REQUEUE) DPRINTF("%s: REQUEUE opc = %p, pcc = %p (%s)\n", __func__, (void *)opc, pcq.pcq_cc, perfuse_qtypestr[type]); #endif puffs_cc_yield(pcq.pcq_cc); TAILQ_REMOVE(&pnd->pnd_pcq, &pcq, pcq_next); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_REQUEUE) DPRINTF("%s: RESUME opc = %p, pcc = %p (%s)\n", __func__, (void *)opc, pcq.pcq_cc, perfuse_qtypestr[type]); #endif return; } static int dequeue_requests(puffs_cookie_t opc, enum perfuse_qtype type, int max) { struct perfuse_cc_queue *pcq; struct perfuse_node_data *pnd; int dequeued; pnd = PERFUSE_NODE_DATA(opc); dequeued = 0; TAILQ_FOREACH(pcq, &pnd->pnd_pcq, pcq_next) { if (pcq->pcq_type != type) continue; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_REQUEUE) DPRINTF("%s: SCHEDULE opc = %p, pcc = %p (%s)\n", __func__, (void *)opc, pcq->pcq_cc, perfuse_qtypestr[type]); #endif puffs_cc_schedule(pcq->pcq_cc); if (++dequeued == max) break; } #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_REQUEUE) DPRINTF("%s: DONE opc = %p\n", __func__, (void *)opc); #endif return dequeued; } void perfuse_fs_init(struct puffs_usermount *pu) { struct perfuse_state *ps; perfuse_msg_t *pm; struct fuse_init_in *fii; struct fuse_init_out *fio; int error; ps = puffs_getspecific(pu); if (puffs_mount(pu, ps->ps_target, ps->ps_mountflags, ps->ps_root) != 0) DERR(EX_OSERR, "%s: puffs_mount failed", __func__); /* * Linux 2.6.34.1 sends theses flags: * FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_ATOMIC_O_TRUNC * FUSE_EXPORT_SUPPORT | FUSE_BIG_WRITES | FUSE_DONT_MASK * * Linux also sets max_readahead at 32 pages (128 kB) * * ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. */ pm = ps->ps_new_msg(pu, 0, FUSE_INIT, sizeof(*fii), NULL); fii = GET_INPAYLOAD(ps, pm, fuse_init_in); fii->major = FUSE_KERNEL_VERSION; fii->minor = FUSE_KERNEL_MINOR_VERSION; fii->max_readahead = (unsigned int)(32 * sysconf(_SC_PAGESIZE)); fii->flags = (FUSE_ASYNC_READ|FUSE_POSIX_LOCKS|FUSE_ATOMIC_O_TRUNC); if ((error = xchg_msg(pu, 0, pm, sizeof(*fio), wait_reply)) != 0) DERRX(EX_SOFTWARE, "init message exchange failed (%d)", error); fio = GET_OUTPAYLOAD(ps, pm, fuse_init_out); ps->ps_max_readahead = fio->max_readahead; ps->ps_max_write = fio->max_write; ps->ps_destroy_msg(pm); return; } int perfuse_fs_unmount(struct puffs_usermount *pu, int flags) { perfuse_msg_t *pm; struct perfuse_state *ps; puffs_cookie_t opc; int error; ps = puffs_getspecific(pu); opc = (puffs_cookie_t)puffs_getroot(pu); /* * ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. */ pm = ps->ps_new_msg(pu, opc, FUSE_DESTROY, 0, NULL); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0){ DWARN("unmount %s", ps->ps_target); if (!(flags & MNT_FORCE)) return error; else error = 0; } else { ps->ps_destroy_msg(pm); } ps->ps_umount(pu); if (perfuse_diagflags & PDF_MISC) DPRINTF("%s unmounted, exit\n", ps->ps_target); return 0; } int perfuse_fs_statvfs(struct puffs_usermount *pu, struct statvfs *svfsb) { struct perfuse_state *ps; perfuse_msg_t *pm; puffs_cookie_t opc; struct fuse_statfs_out *fso; int error; ps = puffs_getspecific(pu); opc = (puffs_cookie_t)puffs_getroot(pu); /* * ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. */ pm = ps->ps_new_msg(pu, opc, FUSE_STATFS, 0, NULL); if ((error = xchg_msg(pu, opc, pm, sizeof(*fso), wait_reply)) != 0) return error; fso = GET_OUTPAYLOAD(ps, pm, fuse_statfs_out); svfsb->f_flag = ps->ps_mountflags; svfsb->f_bsize = fso->st.bsize; svfsb->f_frsize = fso->st.frsize; svfsb->f_iosize = ((struct puffs_node *)opc)->pn_va.va_blocksize; svfsb->f_blocks = fso->st.blocks; svfsb->f_bfree = fso->st.bfree; svfsb->f_bavail = fso->st.bavail; svfsb->f_bresvd = fso->st.bfree - fso->st.bavail; svfsb->f_files = fso->st.files; svfsb->f_ffree = fso->st.ffree; svfsb->f_favail = fso->st.ffree;/* files not reserved for root */ svfsb->f_fresvd = 0; /* files reserved for root */ svfsb->f_syncreads = ps->ps_syncreads; svfsb->f_syncwrites = ps->ps_syncwrites; svfsb->f_asyncreads = ps->ps_asyncreads; svfsb->f_asyncwrites = ps->ps_asyncwrites; (void)memcpy(&svfsb->f_fsidx, &ps->ps_fsid, sizeof(ps->ps_fsid)); svfsb->f_fsid = (unsigned long)ps->ps_fsid; svfsb->f_namemax = MAXPATHLEN; /* XXX */ svfsb->f_owner = ps->ps_owner_uid; (void)strlcpy(svfsb->f_mntonname, ps->ps_target, _VFS_NAMELEN); if (ps->ps_filesystemtype != NULL) (void)strlcpy(svfsb->f_fstypename, ps->ps_filesystemtype, _VFS_NAMELEN); else (void)strlcpy(svfsb->f_fstypename, "fuse", _VFS_NAMELEN); if (ps->ps_source != NULL) strlcpy(svfsb->f_mntfromname, ps->ps_source, _VFS_NAMELEN); else strlcpy(svfsb->f_mntfromname, _PATH_FUSE, _VFS_NAMELEN); ps->ps_destroy_msg(pm); return 0; } int perfuse_fs_sync(struct puffs_usermount *pu, int waitfor, const struct puffs_cred *pcr) { /* * FUSE does not seem to have a FS sync callback. * Maybe do not even register this callback */ return puffs_fsnop_sync(pu, waitfor, pcr); } /* ARGSUSED0 */ int perfuse_fs_fhtonode(struct puffs_usermount *pu, void *fid, size_t fidsize, struct puffs_newinfo *pni) { DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__); return 0; } /* ARGSUSED0 */ int perfuse_fs_nodetofh(struct puffs_usermount *pu, puffs_cookie_t cookie, void *fid, size_t *fidsize) { DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__); return 0; } #if 0 /* ARGSUSED0 */ void perfuse_fs_extattrctl(struct puffs_usermount *pu, int cmd, puffs_cookie_t *cookie, int flags, int namespace, const char *attrname) { DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__); return 0; } #endif /* 0 */ /* ARGSUSED0 */ void perfuse_fs_suspend(struct puffs_usermount *pu, int status) { return; } int perfuse_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn) { struct perfuse_state *ps; struct puffs_node *pn; mode_t mode; int error; ps = puffs_getspecific(pu); node_ref(opc); /* * Check permissions */ switch(pcn->pcn_nameiop) { case NAMEI_DELETE: /* FALLTHROUGH */ case NAMEI_RENAME: /* FALLTHROUGH */ case NAMEI_CREATE: if (pcn->pcn_flags & NAMEI_ISLASTCN) mode = PUFFS_VEXEC|PUFFS_VWRITE; else mode = PUFFS_VEXEC; break; case NAMEI_LOOKUP: /* FALLTHROUGH */ default: mode = PUFFS_VEXEC; break; } if ((error = mode_access(opc, pcn->pcn_cred, mode)) != 0) goto out; error = node_lookup_common(pu, (puffs_cookie_t)opc, pni, pcn->pcn_name, pcn->pcn_cred, &pn); if (error != 0) goto out; /* * Kernel would kill us if the filesystem returned the parent * itself. If we want to live, hide that! */ if ((opc == (puffs_cookie_t)pn) && (strcmp(pcn->pcn_name, ".") != 0)) { DERRX(EX_SOFTWARE, "lookup \"%s\" in \"%s\" returned parent", pcn->pcn_name, perfuse_node_path(ps, opc)); /* NOTREACHED */ error = ESTALE; goto out; } /* * Removed node */ if (PERFUSE_NODE_DATA(pn)->pnd_flags & PND_REMOVED) { error = ENOENT; goto out; } /* * Check for sticky bit. Unfortunately there is no way to * do this before creating the puffs_node, since we require * this operation to get the node owner. */ switch (pcn->pcn_nameiop) { case NAMEI_DELETE: /* FALLTHROUGH */ case NAMEI_RENAME: error = sticky_access(opc, pn, pcn->pcn_cred); if (error != 0) { (void)perfuse_node_reclaim(pu, pn); goto out; } break; default: break; } PERFUSE_NODE_DATA(pn)->pnd_puffs_nlookup++; error = 0; out: node_rele(opc); return error; } int perfuse_node_create(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn, const struct vattr *vap) { perfuse_msg_t *pm; struct perfuse_state *ps; struct fuse_create_in *fci; struct fuse_entry_out *feo; struct fuse_open_out *foo; struct puffs_node *pn; const char *name; size_t namelen; size_t len; int error; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); /* * If create is unimplemented: Check that it does not * already exists, and if not, do mknod and open */ ps = puffs_getspecific(pu); if (ps->ps_flags & PS_NO_CREAT) { error = node_lookup_common(pu, opc, NULL, pcn->pcn_name, pcn->pcn_cred, &pn); if (error == 0) { (void)perfuse_node_reclaim(pu, pn); error = EEXIST; goto out; } error = perfuse_node_mknod(pu, opc, pni, pcn, vap); if (error != 0) goto out; error = node_lookup_common(pu, opc, NULL, pcn->pcn_name, pcn->pcn_cred, &pn); if (error != 0) goto out; /* * FUSE does the open at create time, while * NetBSD will open in a subsequent operation. * We need to open now, in order to retain FUSE * semantics. The calling process will not get * a file descriptor before the kernel sends * the open operation. */ error = perfuse_node_open(pu, (puffs_cookie_t)pn, FWRITE, pcn->pcn_cred); goto out; } name = pcn->pcn_name; namelen = pcn->pcn_namelen + 1; len = sizeof(*fci) + namelen; /* * flags should use O_WRONLY instead of O_RDWR, but it * breaks when the caller tries to read from file. * * mode must contain file type (ie: S_IFREG), use VTTOIF(vap->va_type) */ pm = ps->ps_new_msg(pu, opc, FUSE_CREATE, len, pcn->pcn_cred); fci = GET_INPAYLOAD(ps, pm, fuse_create_in); fci->flags = O_CREAT | O_TRUNC | O_RDWR; fci->mode = vap->va_mode | VTTOIF(vap->va_type); fci->umask = 0; /* Seems unused by libfuse */ (void)strlcpy((char*)(void *)(fci + 1), name, namelen); len = sizeof(*feo) + sizeof(*foo); if ((error = xchg_msg(pu, opc, pm, len, wait_reply)) != 0) { /* * create is unimplmented, remember it for later, * and start over using mknod and open instead. */ if (error == ENOSYS) { ps->ps_flags |= PS_NO_CREAT; error = perfuse_node_create(pu, opc, pni, pcn, vap); } goto out; } feo = GET_OUTPAYLOAD(ps, pm, fuse_entry_out); foo = (struct fuse_open_out *)(void *)(feo + 1); if (feo->nodeid == PERFUSE_UNKNOWN_NODEID) DERRX(EX_SOFTWARE, "%s: no nodeid", __func__); /* * Save the file handle and inode in node private data * so that we can reuse it later */ pn = perfuse_new_pn(pu, name, opc); perfuse_new_fh((puffs_cookie_t)pn, foo->fh, FWRITE); PERFUSE_NODE_DATA(pn)->pnd_nodeid = feo->nodeid; PERFUSE_NODE_DATA(pn)->pnd_puffs_nlookup++; perfuse_node_cache(ps, pn); fuse_attr_to_vap(ps, &pn->pn_va, &feo->attr); pn->pn_va.va_gen = (u_long)(feo->generation); puffs_newinfo_setcookie(pni, pn); #ifdef PUFFS_KFLAG_CACHE_FS_TTL puffs_newinfo_setva(pni, &pn->pn_va); perfuse_newinfo_setttl(pni, pn, feo, NULL); #endif /* PUFFS_KFLAG_CACHE_FS_TTL */ #ifdef PERFUSE_DEBUG if (perfuse_diagflags & (PDF_FH|PDF_FILENAME)) DPRINTF("%s: opc = %p, file = \"%s\", flags = 0x%x " "nodeid = 0x%"PRIx64", wfh = 0x%"PRIx64"\n", __func__, (void *)pn, pcn->pcn_name, PERFUSE_NODE_DATA(pn)->pnd_flags, feo->nodeid, foo->fh); #endif ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); return error; } int perfuse_node_mknod(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn, const struct vattr *vap) { struct perfuse_state *ps; perfuse_msg_t *pm; struct fuse_mknod_in *fmi; const char* path; size_t len; int error; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); /* * Only superuser can mknod objects other than * directories, files, socks, fifo and links. * * Create an object require -WX permission in the parent directory */ switch (vap->va_type) { case VDIR: /* FALLTHROUGH */ case VREG: /* FALLTHROUGH */ case VFIFO: /* FALLTHROUGH */ case VSOCK: break; default: /* VNON, VBLK, VCHR, VBAD */ if (!puffs_cred_isjuggernaut(pcn->pcn_cred)) { error = EPERM; goto out; } break; } ps = puffs_getspecific(pu); path = pcn->pcn_name; len = sizeof(*fmi) + pcn->pcn_namelen + 1; /* * mode must contain file type (ie: S_IFREG), use VTTOIF(vap->va_type) */ pm = ps->ps_new_msg(pu, opc, FUSE_MKNOD, len, pcn->pcn_cred); fmi = GET_INPAYLOAD(ps, pm, fuse_mknod_in); fmi->mode = vap->va_mode | VTTOIF(vap->va_type); fmi->rdev = (uint32_t)vap->va_rdev; fmi->umask = 0; /* Seems unused bu libfuse */ (void)strlcpy((char *)(void *)(fmi + 1), path, len - sizeof(*fmi)); error = node_mk_common(pu, opc, pni, pcn, pm); out: node_rele(opc); return error; } int perfuse_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode, const struct puffs_cred *pcr) { return perfuse_node_open2(pu, opc, mode, pcr, NULL); } int perfuse_node_open2(struct puffs_usermount *pu, puffs_cookie_t opc, int mode, const struct puffs_cred *pcr, int *oflags) { struct perfuse_state *ps; struct perfuse_node_data *pnd; perfuse_msg_t *pm; mode_t fmode; int op; struct fuse_open_in *foi; struct fuse_open_out *foo; struct puffs_node *pn; int error; ps = puffs_getspecific(pu); pn = (struct puffs_node *)opc; pnd = PERFUSE_NODE_DATA(opc); error = 0; if (pnd->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); if (puffs_pn_getvap(pn)->va_type == VDIR) op = FUSE_OPENDIR; else op = FUSE_OPEN; /* * libfuse docs says * - O_CREAT and O_EXCL should never be set. * - O_TRUNC may be used if mount option atomic_o_trunc is used XXX * * O_APPEND makes no sense since FUSE always sends * the file offset for write operations. If the * filesystem uses pwrite(), O_APPEND would cause * the offset to be ignored and cause file corruption. */ mode &= ~(O_CREAT|O_EXCL|O_APPEND); /* * Do not open twice, and do not reopen for reading * if we already have write handle. */ switch (mode & (FREAD|FWRITE)) { case FREAD: if (pnd->pnd_flags & (PND_RFH|PND_WFH)) goto out; break; case FWRITE: if (pnd->pnd_flags & PND_WFH) goto out; break; case FREAD|FWRITE: if (pnd->pnd_flags & PND_WFH) goto out; /* * Corner case: if already open for reading (PND_RFH) * and re-opening FREAD|FWRITE, we need to reopen, * but only for writing. Note the change on mode * will only affect perfuse_new_fh() */ if (pnd->pnd_flags & PND_RFH) mode &= ~FREAD; break; default: DWARNX("open without either FREAD nor FWRITE"); error = EPERM; goto out; } /* * Queue open on a node so that we do not open * twice. This would be better with read and * write distinguished. */ while (pnd->pnd_flags & PND_INOPEN) requeue_request(pu, opc, PCQ_OPEN); pnd->pnd_flags |= PND_INOPEN; /* * Convert PUFFS mode to FUSE mode: convert FREAD/FWRITE * to O_RDONLY/O_WRONLY while perserving the other options. */ fmode = mode & ~(FREAD|FWRITE); fmode |= (mode & FWRITE) ? O_RDWR : O_RDONLY; pm = ps->ps_new_msg(pu, opc, op, sizeof(*foi), pcr); foi = GET_INPAYLOAD(ps, pm, fuse_open_in); foi->flags = fmode; foi->unused = 0; if ((error = xchg_msg(pu, opc, pm, sizeof(*foo), wait_reply)) != 0) goto out; foo = GET_OUTPAYLOAD(ps, pm, fuse_open_out); /* * Save the file handle in node private data * so that we can reuse it later */ perfuse_new_fh(opc, foo->fh, mode); /* * Set direct I/O if the filesystems forces it */ if ((foo->open_flags & FUSE_FOPEN_DIRECT_IO) && (oflags != NULL)) *oflags |= PUFFS_OPEN_IO_DIRECT; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & (PDF_FH|PDF_FILENAME)) DPRINTF("%s: opc = %p, file = \"%s\", " "nodeid = 0x%"PRIx64", %s%sfh = 0x%"PRIx64"\n", __func__, (void *)opc, perfuse_node_path(ps, opc), pnd->pnd_nodeid, mode & FREAD ? "r" : "", mode & FWRITE ? "w" : "", foo->fh); #endif ps->ps_destroy_msg(pm); out: pnd->pnd_flags &= ~PND_INOPEN; (void)dequeue_requests(opc, PCQ_OPEN, DEQUEUE_ALL); node_rele(opc); return error; } /* ARGSUSED0 */ int perfuse_node_close(struct puffs_usermount *pu, puffs_cookie_t opc, int flags, const struct puffs_cred *pcr) { struct perfuse_node_data *pnd; pnd = PERFUSE_NODE_DATA(opc); if (!(pnd->pnd_flags & PND_OPEN)) return EBADF; /* * Actual close is postponed at inactive time. */ return 0; } int perfuse_node_access(struct puffs_usermount *pu, puffs_cookie_t opc, int mode, const struct puffs_cred *pcr) { perfuse_msg_t *pm; struct perfuse_state *ps; struct fuse_access_in *fai; int error; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); /* * If we previously detected the filesystem does not * implement access(), short-circuit the call and skip * to libpuffs access() emulation. */ ps = puffs_getspecific(pu); if (ps->ps_flags & PS_NO_ACCESS) { const struct vattr *vap; vap = puffs_pn_getvap((struct puffs_node *)opc); error = puffs_access(IFTOVT(vap->va_mode), vap->va_mode & ACCESSPERMS, vap->va_uid, vap->va_gid, (mode_t)mode, pcr); goto out; } /* * Plain access call */ pm = ps->ps_new_msg(pu, opc, FUSE_ACCESS, sizeof(*fai), pcr); fai = GET_INPAYLOAD(ps, pm, fuse_access_in); fai->mask = 0; fai->mask |= (mode & PUFFS_VREAD) ? R_OK : 0; fai->mask |= (mode & PUFFS_VWRITE) ? W_OK : 0; fai->mask |= (mode & PUFFS_VEXEC) ? X_OK : 0; error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply); ps->ps_destroy_msg(pm); /* * If unimplemented, start over with emulation */ if (error == ENOSYS) { ps->ps_flags |= PS_NO_ACCESS; error = perfuse_node_access(pu, opc, mode, pcr); } out: node_rele(opc); return error; } int perfuse_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc, struct vattr *vap, const struct puffs_cred *pcr) { return perfuse_node_getattr_ttl(pu, opc, vap, pcr, NULL); } int perfuse_node_getattr_ttl(struct puffs_usermount *pu, puffs_cookie_t opc, struct vattr *vap, const struct puffs_cred *pcr, struct timespec *va_ttl) { perfuse_msg_t *pm = NULL; struct perfuse_state *ps; struct perfuse_node_data *pnd = PERFUSE_NODE_DATA(opc); struct fuse_getattr_in *fgi; struct fuse_attr_out *fao; int error = 0; if ((pnd->pnd_flags & PND_REMOVED) && !(pnd->pnd_flags & PND_OPEN)) return ENOENT; node_ref(opc); /* * Serialize size access, see comment in perfuse_node_setattr(). */ while (pnd->pnd_flags & PND_INRESIZE) requeue_request(pu, opc, PCQ_RESIZE); pnd->pnd_flags |= PND_INRESIZE; ps = puffs_getspecific(pu); /* * FUSE_GETATTR_FH must be set in fgi->flags * if we use for fgi->fh */ pm = ps->ps_new_msg(pu, opc, FUSE_GETATTR, sizeof(*fgi), pcr); fgi = GET_INPAYLOAD(ps, pm, fuse_getattr_in); fgi->getattr_flags = 0; fgi->dummy = 0; fgi->fh = 0; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_OPEN) { fgi->fh = perfuse_get_fh(opc, FREAD); fgi->getattr_flags |= FUSE_GETATTR_FH; } #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_RESIZE) DPRINTF(">> %s %p %" PRIu64 "\n", __func__, (void *)opc, vap->va_size); #endif if ((error = xchg_msg(pu, opc, pm, sizeof(*fao), wait_reply)) != 0) goto out; fao = GET_OUTPAYLOAD(ps, pm, fuse_attr_out); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_RESIZE) DPRINTF("<< %s %p %" PRIu64 " -> %" PRIu64 "\n", __func__, (void *)opc, vap->va_size, fao->attr.size); #endif /* * We set birthtime, flags, filerev,vaflags to 0. * This seems the best bet, since the information is * not available from filesystem. */ fuse_attr_to_vap(ps, vap, &fao->attr); if (va_ttl != NULL) { va_ttl->tv_sec = fao->attr_valid; va_ttl->tv_nsec = fao->attr_valid_nsec; } ps->ps_destroy_msg(pm); error = 0; out: pnd->pnd_flags &= ~PND_INRESIZE; (void)dequeue_requests(opc, PCQ_RESIZE, DEQUEUE_ALL); node_rele(opc); return error; } int perfuse_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc, const struct vattr *vap, const struct puffs_cred *pcr) { return perfuse_node_setattr_ttl(pu, opc, __UNCONST(vap), pcr, NULL, 0); } int perfuse_node_setattr_ttl(struct puffs_usermount *pu, puffs_cookie_t opc, struct vattr *vap, const struct puffs_cred *pcr, struct timespec *va_ttl, int xflag) { perfuse_msg_t *pm; uint64_t fh; struct perfuse_state *ps; struct perfuse_node_data *pnd; struct fuse_setattr_in *fsi; struct fuse_attr_out *fao; struct vattr *old_va; enum perfuse_xchg_pb_reply reply; int error; #ifdef PERFUSE_DEBUG struct vattr *old_vap; int resize_debug = 0; #endif ps = puffs_getspecific(pu); pnd = PERFUSE_NODE_DATA(opc); /* * The only operation we can do once the file is removed * is to resize it, and we can do it only if it is open. * Do not even send the operation to the filesystem: the * file is not there anymore. */ if (pnd->pnd_flags & PND_REMOVED) { if (!(pnd->pnd_flags & PND_OPEN)) return ENOENT; return 0; } old_va = puffs_pn_getvap((struct puffs_node *)opc); /* * Check for permission to change size * It is always allowed if we already have a write file handle */ if ((vap->va_size != (u_quad_t)PUFFS_VNOVAL) && !(pnd->pnd_flags & PND_WFH) && (error = mode_access(opc, pcr, PUFFS_VWRITE)) != 0) return error; /* * Check for permission to change dates */ if (((vap->va_atime.tv_sec != (time_t)PUFFS_VNOVAL) || (vap->va_mtime.tv_sec != (time_t)PUFFS_VNOVAL)) && (puffs_access_times(old_va->va_uid, old_va->va_gid, old_va->va_mode, 0, pcr) != 0)) return EPERM; /* * Check for permission to change owner and group */ if (((vap->va_uid != (uid_t)PUFFS_VNOVAL) || (vap->va_gid != (gid_t)PUFFS_VNOVAL)) && (puffs_access_chown(old_va->va_uid, old_va->va_gid, vap->va_uid, vap->va_gid, pcr)) != 0) return EPERM; /* * Check for sticky bit on non-directory by non root user */ if ((vap->va_mode != (mode_t)PUFFS_VNOVAL) && (vap->va_mode & S_ISTXT) && (old_va->va_type != VDIR) && !puffs_cred_isjuggernaut(pcr)) return EFTYPE; /* * Check for permission to change permissions */ if ((vap->va_mode != (mode_t)PUFFS_VNOVAL) && (puffs_access_chmod(old_va->va_uid, old_va->va_gid, old_va->va_type, vap->va_mode, pcr)) != 0) return EPERM; node_ref(opc); if (pnd->pnd_flags & PND_WFH) fh = perfuse_get_fh(opc, FWRITE); else fh = FUSE_UNKNOWN_FH; /* * fchmod() sets mode and fh, and it may carry * a resize as well. That may break if the * filesystem does chmod then resize, and fails * because it does not have permission anymore. * We work this around by splitting into two setattr. */ if ((vap->va_size != (u_quad_t)PUFFS_VNOVAL) && (vap->va_mode != (mode_t)PUFFS_VNOVAL) && (fh != FUSE_UNKNOWN_FH)) { struct vattr resize_va; (void)memcpy(&resize_va, vap, sizeof(resize_va)); resize_va.va_mode = (mode_t)PUFFS_VNOVAL; if ((error = perfuse_node_setattr_ttl(pu, opc, &resize_va, pcr, va_ttl, xflag)) != 0) goto out2; vap->va_size = (u_quad_t)PUFFS_VNOVAL; } pm = ps->ps_new_msg(pu, opc, FUSE_SETATTR, sizeof(*fsi), pcr); fsi = GET_INPAYLOAD(ps, pm, fuse_setattr_in); fsi->valid = 0; /* * Get a fh if the node is open for writing */ if (fh != FUSE_UNKNOWN_FH) { fsi->fh = fh; fsi->valid |= FUSE_FATTR_FH; } if (vap->va_size != (u_quad_t)PUFFS_VNOVAL) { fsi->size = vap->va_size; fsi->valid |= FUSE_FATTR_SIZE; /* * Serialize anything that can touch file size * to avoid reordered GETATTR and SETATTR. * Out of order SETATTR can report stale size, * which will cause the kernel to truncate the file. * XXX Probably useless now we have a lock on GETATTR */ while (pnd->pnd_flags & PND_INRESIZE) requeue_request(pu, opc, PCQ_RESIZE); pnd->pnd_flags |= PND_INRESIZE; } /* * When not sending a time field, still fill with * current value, as the filesystem may just reset * the field to Epoch even if fsi->valid bit is * not set (GlusterFS does that). */ if (vap->va_atime.tv_sec != (time_t)PUFFS_VNOVAL) { fsi->atime = vap->va_atime.tv_sec; fsi->atimensec = (uint32_t)vap->va_atime.tv_nsec; fsi->valid |= FUSE_FATTR_ATIME; } else { fsi->atime = old_va->va_atime.tv_sec; fsi->atimensec = (uint32_t)old_va->va_atime.tv_nsec; } if (vap->va_mtime.tv_sec != (time_t)PUFFS_VNOVAL) { fsi->mtime = vap->va_mtime.tv_sec; fsi->mtimensec = (uint32_t)vap->va_mtime.tv_nsec; fsi->valid |= FUSE_FATTR_MTIME; } else { fsi->mtime = old_va->va_mtime.tv_sec; fsi->mtimensec = (uint32_t)old_va->va_mtime.tv_nsec; } if (vap->va_mode != (mode_t)PUFFS_VNOVAL) { fsi->mode = vap->va_mode; fsi->valid |= FUSE_FATTR_MODE; } if (vap->va_uid != (uid_t)PUFFS_VNOVAL) { fsi->uid = vap->va_uid; fsi->valid |= FUSE_FATTR_UID; } if (vap->va_gid != (gid_t)PUFFS_VNOVAL) { fsi->gid = vap->va_gid; fsi->valid |= FUSE_FATTR_GID; } if (pnd->pnd_lock_owner != 0) { fsi->lock_owner = pnd->pnd_lock_owner; fsi->valid |= FUSE_FATTR_LOCKOWNER; } #ifndef PUFFS_KFLAG_NOFLUSH_META /* * ftruncate() sends only va_size, and metadata cache * flush adds va_atime and va_mtime. Some FUSE * filesystems will attempt to detect ftruncate by * checking for FATTR_SIZE being set without * FATTR_UID|FATTR_GID|FATTR_ATIME|FATTR_MTIME|FATTR_MODE * * Try to adapt and remove FATTR_ATIME|FATTR_MTIME * if we suspect a ftruncate(). */ if ((vap->va_size != (u_quad_t)PUFFS_VNOVAL) && ((vap->va_mode == (mode_t)PUFFS_VNOVAL) && (vap->va_uid == (uid_t)PUFFS_VNOVAL) && (vap->va_gid == (gid_t)PUFFS_VNOVAL))) { fsi->atime = 0; fsi->atimensec = 0; fsi->mtime = 0; fsi->mtimensec = 0; fsi->valid &= ~(FUSE_FATTR_ATIME|FUSE_FATTR_MTIME); } /* * If only atime is changed, discard the operation: it * happens after read, and in that case the filesystem * already updaed atime. NB: utimes() also change mtime. */ if (fsi->valid == FUSE_FATTR_ATIME) fsi->valid &= ~FUSE_FATTR_ATIME; #endif /* PUFFS_KFLAG_NOFLUSH_META */ /* * If nothing remain, discard the operation. */ if (!(fsi->valid & (FUSE_FATTR_SIZE|FUSE_FATTR_ATIME|FUSE_FATTR_MTIME| FUSE_FATTR_MODE|FUSE_FATTR_UID|FUSE_FATTR_GID))) { error = 0; ps->ps_destroy_msg(pm); goto out; } #ifdef PERFUSE_DEBUG old_vap = puffs_pn_getvap((struct puffs_node *)opc); if ((perfuse_diagflags & PDF_RESIZE) && (old_vap->va_size != (u_quad_t)PUFFS_VNOVAL)) { resize_debug = 1; DPRINTF(">> %s %p %" PRIu64 " -> %" PRIu64 "\n", __func__, (void *)opc, puffs_pn_getvap((struct puffs_node *)opc)->va_size, fsi->size); } #endif /* * Do not honour FAF when changing size. How do * you want such a thing to work? */ reply = wait_reply; #ifdef PUFFS_SETATTR_FAF if ((xflag & PUFFS_SETATTR_FAF) && !(fsi->valid & FUSE_FATTR_SIZE)) reply = no_reply; #endif if ((error = xchg_msg(pu, opc, pm, sizeof(*fao), reply)) != 0) goto out; if (reply == no_reply) goto out; /* * Copy back the new values */ fao = GET_OUTPAYLOAD(ps, pm, fuse_attr_out); #ifdef PERFUSE_DEBUG if (resize_debug) DPRINTF("<< %s %p %" PRIu64 " -> %" PRIu64 "\n", __func__, (void *)opc, old_vap->va_size, fao->attr.size); #endif fuse_attr_to_vap(ps, old_va, &fao->attr); if (va_ttl != NULL) { va_ttl->tv_sec = fao->attr_valid; va_ttl->tv_nsec = fao->attr_valid_nsec; (void)memcpy(vap, old_va, sizeof(*vap)); } ps->ps_destroy_msg(pm); error = 0; out: if (pnd->pnd_flags & PND_INRESIZE) { pnd->pnd_flags &= ~PND_INRESIZE; (void)dequeue_requests(opc, PCQ_RESIZE, DEQUEUE_ALL); } out2: node_rele(opc); return error; } int perfuse_node_poll(struct puffs_usermount *pu, puffs_cookie_t opc, int *events) { struct perfuse_state *ps; perfuse_msg_t *pm; struct fuse_poll_in *fpi; struct fuse_poll_out *fpo; int error; node_ref(opc); ps = puffs_getspecific(pu); /* * kh is set if FUSE_POLL_SCHEDULE_NOTIFY is set. * * XXX ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. We have no way to * know the requesting process' credential, but since poll * is supposed to operate on a file that has been open, * permission should have already been checked at open time. * That still may breaks on filesystems that provides odd * semantics. */ pm = ps->ps_new_msg(pu, opc, FUSE_POLL, sizeof(*fpi), NULL); fpi = GET_INPAYLOAD(ps, pm, fuse_poll_in); fpi->fh = perfuse_get_fh(opc, FREAD); fpi->kh = 0; fpi->flags = 0; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", " "fh = 0x%"PRIx64"\n", __func__, (void *)opc, PERFUSE_NODE_DATA(opc)->pnd_nodeid, fpi->fh); #endif if ((error = xchg_msg(pu, opc, pm, sizeof(*fpo), wait_reply)) != 0) goto out; fpo = GET_OUTPAYLOAD(ps, pm, fuse_poll_out); *events = fpo->revents; ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); return error; } /* ARGSUSED2 */ int perfuse_node_fsync(struct puffs_usermount *pu, puffs_cookie_t opc, const struct puffs_cred *pcr, int flags, off_t offlo, off_t offhi) { int op; perfuse_msg_t *pm; struct perfuse_state *ps; struct perfuse_node_data *pnd; struct fuse_fsync_in *ffi; uint64_t fh; int error = 0; pm = NULL; ps = puffs_getspecific(pu); pnd = PERFUSE_NODE_DATA(opc); /* * No need to sync a removed node */ if (pnd->pnd_flags & PND_REMOVED) return 0; /* * We do not sync closed files. They have been * sync at inactive time already. */ if (!(pnd->pnd_flags & PND_OPEN)) return 0; node_ref(opc); if (puffs_pn_getvap((struct puffs_node *)opc)->va_type == VDIR) op = FUSE_FSYNCDIR; else /* VREG but also other types such as VLNK */ op = FUSE_FSYNC; /* * Do not sync if there are no change to sync * XXX remove that test on files if we implement mmap */ #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_SYNC) DPRINTF("%s: TEST opc = %p, file = \"%s\" is %sdirty\n", __func__, (void*)opc, perfuse_node_path(ps, opc), pnd->pnd_flags & PND_DIRTY ? "" : "not "); #endif if (!(pnd->pnd_flags & PND_DIRTY)) goto out; /* * It seems NetBSD can call fsync without open first * glusterfs complain in such a situation: * "FSYNC() ERR => -1 (Invalid argument)" * The file will be closed at inactive time. * * We open the directory for reading in order to sync. * This sounds rather counterintuitive, but it works. */ if (!(pnd->pnd_flags & PND_WFH)) { if ((error = perfuse_node_open(pu, opc, FREAD, pcr)) != 0) goto out; } if (op == FUSE_FSYNCDIR) fh = perfuse_get_fh(opc, FREAD); else fh = perfuse_get_fh(opc, FWRITE); /* * If fsync_flags is set, meta data should not be flushed. */ pm = ps->ps_new_msg(pu, opc, op, sizeof(*ffi), pcr); ffi = GET_INPAYLOAD(ps, pm, fuse_fsync_in); ffi->fh = fh; ffi->fsync_flags = (flags & FFILESYNC) ? 0 : 1; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n", __func__, (void *)opc, PERFUSE_NODE_DATA(opc)->pnd_nodeid, ffi->fh); #endif if ((error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply)) != 0) goto out; /* * No reply beyond fuse_out_header: nothing to do on success * just clear the dirty flag */ pnd->pnd_flags &= ~PND_DIRTY; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_SYNC) DPRINTF("%s: CLEAR opc = %p, file = \"%s\"\n", __func__, (void*)opc, perfuse_node_path(ps, opc)); #endif ps->ps_destroy_msg(pm); error = 0; out: /* * ENOSYS is not returned to kernel, */ if (error == ENOSYS) error = 0; node_rele(opc); return error; } int perfuse_node_remove(struct puffs_usermount *pu, puffs_cookie_t opc, puffs_cookie_t targ, const struct puffs_cn *pcn) { struct perfuse_state *ps; struct perfuse_node_data *pnd; perfuse_msg_t *pm; char *path; const char *name; size_t len; int error; pnd = PERFUSE_NODE_DATA(opc); if ((pnd->pnd_flags & PND_REMOVED) || (PERFUSE_NODE_DATA(targ)->pnd_flags & PND_REMOVED)) return ENOENT; #ifdef PERFUSE_DEBUG if (targ == NULL) DERRX(EX_SOFTWARE, "%s: targ is NULL", __func__); if (perfuse_diagflags & (PDF_FH|PDF_FILENAME)) DPRINTF("%s: opc = %p, remove opc = %p, file = \"%s\"\n", __func__, (void *)opc, (void *)targ, pcn->pcn_name); #endif node_ref(opc); node_ref(targ); /* * Await for all operations on the deleted node to drain, * as the filesystem may be confused to have it deleted * during a getattr */ while (PERFUSE_NODE_DATA(targ)->pnd_inxchg) requeue_request(pu, targ, PCQ_AFTERXCHG); ps = puffs_getspecific(pu); pnd = PERFUSE_NODE_DATA(opc); name = pcn->pcn_name; len = pcn->pcn_namelen + 1; pm = ps->ps_new_msg(pu, opc, FUSE_UNLINK, len, pcn->pcn_cred); path = _GET_INPAYLOAD(ps, pm, char *); (void)strlcpy(path, name, len); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; perfuse_cache_flush(targ); PERFUSE_NODE_DATA(targ)->pnd_flags |= PND_REMOVED; if (!(PERFUSE_NODE_DATA(targ)->pnd_flags & PND_OPEN)) puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2); /* * The parent directory needs a sync */ PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FILENAME) DPRINTF("%s: remove nodeid = 0x%"PRIx64" file = \"%s\"\n", __func__, PERFUSE_NODE_DATA(targ)->pnd_nodeid, pcn->pcn_name); #endif ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); node_rele(targ); return error; } int perfuse_node_link(struct puffs_usermount *pu, puffs_cookie_t opc, puffs_cookie_t targ, const struct puffs_cn *pcn) { struct perfuse_state *ps; perfuse_msg_t *pm; const char *name; size_t len; struct puffs_node *pn; struct fuse_link_in *fli; int error; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); node_ref(targ); ps = puffs_getspecific(pu); pn = (struct puffs_node *)targ; name = pcn->pcn_name; len = sizeof(*fli) + pcn->pcn_namelen + 1; pm = ps->ps_new_msg(pu, opc, FUSE_LINK, len, pcn->pcn_cred); fli = GET_INPAYLOAD(ps, pm, fuse_link_in); fli->oldnodeid = PERFUSE_NODE_DATA(pn)->pnd_nodeid; (void)strlcpy((char *)(void *)(fli + 1), name, len - sizeof(*fli)); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); node_rele(targ); return error; } int perfuse_node_rename(struct puffs_usermount *pu, puffs_cookie_t opc, puffs_cookie_t src, const struct puffs_cn *pcn_src, puffs_cookie_t targ_dir, puffs_cookie_t targ, const struct puffs_cn *pcn_targ) { struct perfuse_state *ps; struct perfuse_node_data *dstdir_pnd; perfuse_msg_t *pm; struct fuse_rename_in *fri; const char *newname; const char *oldname; char *np; int error; size_t len; size_t newname_len; size_t oldname_len; if ((PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) || (PERFUSE_NODE_DATA(src)->pnd_flags & PND_REMOVED) || (PERFUSE_NODE_DATA(targ_dir)->pnd_flags & PND_REMOVED)) return ENOENT; node_ref(opc); node_ref(src); /* * Await for all operations on the deleted node to drain, * as the filesystem may be confused to have it deleted * during a getattr */ if ((struct puffs_node *)targ != NULL) { node_ref(targ); while (PERFUSE_NODE_DATA(targ)->pnd_inxchg) requeue_request(pu, targ, PCQ_AFTERXCHG); } else { while (PERFUSE_NODE_DATA(src)->pnd_inxchg) requeue_request(pu, src, PCQ_AFTERXCHG); } ps = puffs_getspecific(pu); newname = pcn_targ->pcn_name; newname_len = pcn_targ->pcn_namelen + 1; oldname = pcn_src->pcn_name; oldname_len = pcn_src->pcn_namelen + 1; len = sizeof(*fri) + oldname_len + newname_len; pm = ps->ps_new_msg(pu, opc, FUSE_RENAME, len, pcn_targ->pcn_cred); fri = GET_INPAYLOAD(ps, pm, fuse_rename_in); fri->newdir = PERFUSE_NODE_DATA(targ_dir)->pnd_nodeid; np = (char *)(void *)(fri + 1); (void)strlcpy(np, oldname, oldname_len); np += oldname_len; (void)strlcpy(np, newname, newname_len); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; /* * Record new parent nodeid */ dstdir_pnd = PERFUSE_NODE_DATA(targ_dir); PERFUSE_NODE_DATA(src)->pnd_parent_nodeid = dstdir_pnd->pnd_nodeid; if (opc != targ_dir) dstdir_pnd->pnd_flags |= PND_DIRTY; if (strcmp(newname, "..") != 0) (void)strlcpy(PERFUSE_NODE_DATA(src)->pnd_name, newname, MAXPATHLEN); else PERFUSE_NODE_DATA(src)->pnd_name[0] = 0; /* forget name */ PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY; if ((struct puffs_node *)targ != NULL) { perfuse_cache_flush(targ); PERFUSE_NODE_DATA(targ)->pnd_flags |= PND_REMOVED; } #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FILENAME) DPRINTF("%s: nodeid = 0x%"PRIx64" file = \"%s\" renamed \"%s\" " "nodeid = 0x%"PRIx64" -> nodeid = 0x%"PRIx64" \"%s\"\n", __func__, PERFUSE_NODE_DATA(src)->pnd_nodeid, pcn_src->pcn_name, pcn_targ->pcn_name, PERFUSE_NODE_DATA(opc)->pnd_nodeid, PERFUSE_NODE_DATA(targ_dir)->pnd_nodeid, perfuse_node_path(ps, targ_dir)); #endif ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); node_rele(src); if ((struct puffs_node *)targ != NULL) node_rele(targ); return error; } int perfuse_node_mkdir(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn, const struct vattr *vap) { struct perfuse_state *ps; perfuse_msg_t *pm; struct fuse_mkdir_in *fmi; const char *path; size_t len; int error; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); ps = puffs_getspecific(pu); path = pcn->pcn_name; len = sizeof(*fmi) + pcn->pcn_namelen + 1; pm = ps->ps_new_msg(pu, opc, FUSE_MKDIR, len, pcn->pcn_cred); fmi = GET_INPAYLOAD(ps, pm, fuse_mkdir_in); fmi->mode = vap->va_mode; fmi->umask = 0; /* Seems unused by libfuse? */ (void)strlcpy((char *)(void *)(fmi + 1), path, len - sizeof(*fmi)); error = node_mk_common(pu, opc, pni, pcn, pm); node_rele(opc); return error; } int perfuse_node_rmdir(struct puffs_usermount *pu, puffs_cookie_t opc, puffs_cookie_t targ, const struct puffs_cn *pcn) { struct perfuse_state *ps; struct perfuse_node_data *pnd; perfuse_msg_t *pm; char *path; const char *name; size_t len; int error; pnd = PERFUSE_NODE_DATA(opc); if ((pnd->pnd_flags & PND_REMOVED) || (PERFUSE_NODE_DATA(targ)->pnd_flags & PND_REMOVED)) return ENOENT; /* * Attempt to rmdir dir/.. shoud raise ENOTEMPTY */ if (PERFUSE_NODE_DATA(targ)->pnd_nodeid == pnd->pnd_parent_nodeid) return ENOTEMPTY; node_ref(opc); node_ref(targ); /* * Await for all operations on the deleted node to drain, * as the filesystem may be confused to have it deleted * during a getattr */ while (PERFUSE_NODE_DATA(targ)->pnd_inxchg) requeue_request(pu, targ, PCQ_AFTERXCHG); ps = puffs_getspecific(pu); name = pcn->pcn_name; len = pcn->pcn_namelen + 1; pm = ps->ps_new_msg(pu, opc, FUSE_RMDIR, len, pcn->pcn_cred); path = _GET_INPAYLOAD(ps, pm, char *); (void)strlcpy(path, name, len); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; perfuse_cache_flush(targ); PERFUSE_NODE_DATA(targ)->pnd_flags |= PND_REMOVED; if (!(PERFUSE_NODE_DATA(targ)->pnd_flags & PND_OPEN)) puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N2); /* * The parent directory needs a sync */ PERFUSE_NODE_DATA(opc)->pnd_flags |= PND_DIRTY; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FILENAME) DPRINTF("%s: remove nodeid = 0x%"PRIx64" file = \"%s\"\n", __func__, PERFUSE_NODE_DATA(targ)->pnd_nodeid, perfuse_node_path(ps, targ)); #endif ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); node_rele(targ); return error; } /* vap is unused */ /* ARGSUSED4 */ int perfuse_node_symlink(struct puffs_usermount *pu, puffs_cookie_t opc, struct puffs_newinfo *pni, const struct puffs_cn *pcn_src, const struct vattr *vap, const char *link_target) { struct perfuse_state *ps; perfuse_msg_t *pm; char *np; const char *path; size_t path_len; size_t linkname_len; size_t len; int error; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); ps = puffs_getspecific(pu); path = pcn_src->pcn_name; path_len = pcn_src->pcn_namelen + 1; linkname_len = strlen(link_target) + 1; len = path_len + linkname_len; pm = ps->ps_new_msg(pu, opc, FUSE_SYMLINK, len, pcn_src->pcn_cred); np = _GET_INPAYLOAD(ps, pm, char *); (void)strlcpy(np, path, path_len); np += path_len; (void)strlcpy(np, link_target, linkname_len); error = node_mk_common(pu, opc, pni, pcn_src, pm); node_rele(opc); return error; } /* ARGSUSED4 */ int perfuse_node_readdir(struct puffs_usermount *pu, puffs_cookie_t opc, struct dirent *dent, off_t *readoff, size_t *reslen, const struct puffs_cred *pcr, int *eofflag, off_t *cookies, size_t *ncookies) { perfuse_msg_t *pm; uint64_t fh; struct perfuse_state *ps; struct perfuse_node_data *pnd; struct fuse_read_in *fri; struct fuse_out_header *foh; struct fuse_dirent *fd; size_t foh_len; int error; size_t fd_maxlen; error = 0; node_ref(opc); ps = puffs_getspecific(pu); /* * readdir state is kept at node level, and several readdir * requests can be issued at the same time on the same node. * We need to queue requests so that only one is in readdir * code at the same time. */ pnd = PERFUSE_NODE_DATA(opc); while (pnd->pnd_flags & PND_INREADDIR) requeue_request(pu, opc, PCQ_READDIR); pnd->pnd_flags |= PND_INREADDIR; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_READDIR) DPRINTF("%s: READDIR opc = %p enter critical section\n", __func__, (void *)opc); #endif /* * Re-initialize pnd->pnd_fd_cookie on the first readdir for a node */ if (*readoff == 0) pnd->pnd_fd_cookie = 0; /* * Do we already have the data bufered? */ if (pnd->pnd_dirent != NULL) goto out; pnd->pnd_dirent_len = 0; /* * It seems NetBSD can call readdir without open first * libfuse will crash if it is done that way, hence open first. */ if (!(pnd->pnd_flags & PND_OPEN)) { if ((error = perfuse_node_open(pu, opc, FREAD, pcr)) != 0) goto out; } fh = perfuse_get_fh(opc, FREAD); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", " "rfh = 0x%"PRIx64"\n", __func__, (void *)opc, PERFUSE_NODE_DATA(opc)->pnd_nodeid, fh); #endif pnd->pnd_all_fd = NULL; pnd->pnd_all_fd_len = 0; fd_maxlen = ps->ps_max_readahead - sizeof(*foh); do { size_t fd_len; char *afdp; pm = ps->ps_new_msg(pu, opc, FUSE_READDIR, sizeof(*fri), pcr); /* * read_flags, lock_owner and flags are unused in libfuse */ fri = GET_INPAYLOAD(ps, pm, fuse_read_in); fri->fh = fh; fri->offset = pnd->pnd_fd_cookie; fri->size = (uint32_t)fd_maxlen; fri->read_flags = 0; fri->lock_owner = 0; fri->flags = 0; if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; /* * There are many puffs_framebufs calls later, * therefore foh will not be valid for a long time. * Just get the length and forget it. */ foh = GET_OUTHDR(ps, pm); foh_len = foh->len; /* * Empty read: we reached the end of the buffer. */ if (foh_len == sizeof(*foh)) { ps->ps_destroy_msg(pm); *eofflag = 1; break; } /* * Check for corrupted message. */ if (foh_len < sizeof(*foh) + sizeof(*fd)) { ps->ps_destroy_msg(pm); DWARNX("readdir reply too short"); error = EIO; goto out; } fd = GET_OUTPAYLOAD(ps, pm, fuse_dirent); fd_len = foh_len - sizeof(*foh); pnd->pnd_all_fd = realloc(pnd->pnd_all_fd, pnd->pnd_all_fd_len + fd_len); if (pnd->pnd_all_fd == NULL) DERR(EX_OSERR, "%s: malloc failed", __func__); afdp = (char *)(void *)pnd->pnd_all_fd + pnd->pnd_all_fd_len; (void)memcpy(afdp, fd, fd_len); pnd->pnd_all_fd_len += fd_len; /* * The fd->off field is used as a cookie for * resuming the next readdir() where this one was left. */ pnd->pnd_fd_cookie = readdir_last_cookie(fd, fd_len); ps->ps_destroy_msg(pm); } while (1 /* CONSTCOND */); if (pnd->pnd_all_fd != NULL) { if (fuse_to_dirent(pu, opc, pnd->pnd_all_fd, pnd->pnd_all_fd_len) == -1) error = EIO; } out: if (pnd->pnd_all_fd != NULL) { free(pnd->pnd_all_fd); pnd->pnd_all_fd = NULL; pnd->pnd_all_fd_len = 0; } if (error == 0) readdir_buffered(opc, dent, readoff, reslen); /* * Schedule queued readdir requests */ pnd->pnd_flags &= ~PND_INREADDIR; (void)dequeue_requests(opc, PCQ_READDIR, DEQUEUE_ALL); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_READDIR) DPRINTF("%s: READDIR opc = %p exit critical section\n", __func__, (void *)opc); #endif node_rele(opc); return error; } int perfuse_node_readlink(struct puffs_usermount *pu, puffs_cookie_t opc, const struct puffs_cred *pcr, char *linkname, size_t *linklen) { struct perfuse_state *ps; perfuse_msg_t *pm; int error; size_t len; struct fuse_out_header *foh; if (PERFUSE_NODE_DATA(opc)->pnd_flags & PND_REMOVED) return ENOENT; node_ref(opc); ps = puffs_getspecific(pu); pm = ps->ps_new_msg(pu, opc, FUSE_READLINK, 0, pcr); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; foh = GET_OUTHDR(ps, pm); len = foh->len - sizeof(*foh); if (len > *linklen) DERRX(EX_PROTOCOL, "path len = %zd too long", len); if (len == 0) DERRX(EX_PROTOCOL, "path len = %zd too short", len); (void)memcpy(linkname, _GET_OUTPAYLOAD(ps, pm, char *), len); /* * FUSE filesystems return a NUL terminated string, we * do not want the trailing \0 */ while (len > 0 && linkname[len - 1] == '\0') len--; *linklen = len; ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); return error; } int perfuse_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc) { struct perfuse_state *ps; perfuse_msg_t *pm; struct perfuse_node_data *pnd; struct fuse_forget_in *ffi; int nlookup; struct timespec now; if (opc == 0) return 0; ps = puffs_getspecific(pu); pnd = PERFUSE_NODE_DATA(opc); /* * Never forget the root. */ if (pnd->pnd_nodeid == FUSE_ROOT_ID) return 0; /* * There is a race condition between reclaim and lookup. * When looking up an already known node, the kernel cannot * hold a reference on the result until it gets the PUFFS * reply. It mayy therefore reclaim the node after the * userland looked it up, and before it gets the reply. * On rely, the kernel re-creates the node, but at that * time the node has been reclaimed in userland. * * In order to avoid this, we refuse reclaiming nodes that * are too young since the last lookup - and that we do * not have removed on our own, of course. */ if (clock_gettime(CLOCK_REALTIME, &now) != 0) DERR(EX_OSERR, "clock_gettime failed"); if (timespeccmp(&pnd->pnd_cn_expire, &now, >) && !(pnd->pnd_flags & PND_REMOVED)) { if (!(pnd->pnd_flags & PND_NODELEAK)) { ps->ps_nodeleakcount++; pnd->pnd_flags |= PND_NODELEAK; } DWARNX("possible leaked node:: opc = %p \"%s\"", opc, pnd->pnd_name); return 0; } node_ref(opc); pnd->pnd_flags |= PND_RECLAIMED; pnd->pnd_puffs_nlookup--; nlookup = pnd->pnd_puffs_nlookup; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_RECLAIM) DPRINTF("%s (nodeid %"PRId64") reclaimed\n", perfuse_node_path(ps, opc), pnd->pnd_nodeid); #endif #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_RECLAIM) DPRINTF("%s (nodeid %"PRId64") is %sreclaimed, nlookup = %d " "%s%s%s%s, pending ops:%s%s%s\n", perfuse_node_path(ps, opc), pnd->pnd_nodeid, pnd->pnd_flags & PND_RECLAIMED ? "" : "not ", pnd->pnd_puffs_nlookup, pnd->pnd_flags & PND_OPEN ? "open " : "not open", pnd->pnd_flags & PND_RFH ? "r" : "", pnd->pnd_flags & PND_WFH ? "w" : "", pnd->pnd_flags & PND_BUSY ? "" : " none", pnd->pnd_flags & PND_INREADDIR ? " readdir" : "", pnd->pnd_flags & PND_INWRITE ? " write" : "", pnd->pnd_flags & PND_INOPEN ? " open" : ""); #endif /* * Make sure it is not looked up again */ if (!(pnd->pnd_flags & PND_REMOVED)) perfuse_cache_flush(opc); /* * Purge any activity on the node, while checking * that it remains eligible for a reclaim. */ while (pnd->pnd_ref > 1) requeue_request(pu, opc, PCQ_REF); /* * reclaim cancel? */ if (pnd->pnd_puffs_nlookup > nlookup) { pnd->pnd_flags &= ~PND_RECLAIMED; perfuse_node_cache(ps, opc); node_rele(opc); return 0; } #ifdef PERFUSE_DEBUG if ((pnd->pnd_flags & PND_OPEN) || !TAILQ_EMPTY(&pnd->pnd_pcq)) DERRX(EX_SOFTWARE, "%s: opc = %p \"%s\": still open", __func__, opc, pnd->pnd_name); if ((pnd->pnd_flags & PND_BUSY) || !TAILQ_EMPTY(&pnd->pnd_pcq)) DERRX(EX_SOFTWARE, "%s: opc = %p: queued operations", __func__, opc); if (pnd->pnd_inxchg != 0) DERRX(EX_SOFTWARE, "%s: opc = %p: ongoing operations", __func__, opc); #endif /* * Send the FORGET message * * ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. This is obviously * fine since we operate with kernel creds here. */ pm = ps->ps_new_msg(pu, opc, FUSE_FORGET, sizeof(*ffi), NULL); ffi = GET_INPAYLOAD(ps, pm, fuse_forget_in); ffi->nlookup = pnd->pnd_fuse_nlookup; /* * No reply is expected, pm is freed in xchg_msg */ (void)xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, no_reply); perfuse_destroy_pn(pu, opc); return 0; } int perfuse_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc) { struct perfuse_node_data *pnd; int error; if (opc == 0) return 0; pnd = PERFUSE_NODE_DATA(opc); if (!(pnd->pnd_flags & (PND_OPEN|PND_REMOVED))) return 0; node_ref(opc); /* * Make sure all operation are finished * There can be an ongoing write. Other * operation wait for all data before * the close/inactive. */ while (pnd->pnd_flags & PND_INWRITE) requeue_request(pu, opc, PCQ_AFTERWRITE); /* * The inactive operation may be cancelled, * If no open is in progress, set PND_INOPEN * so that a new open will be queued. */ if (pnd->pnd_flags & PND_INOPEN) goto out; pnd->pnd_flags |= PND_INOPEN; /* * Sync data */ if (pnd->pnd_flags & PND_DIRTY) { if ((error = perfuse_node_fsync(pu, opc, NULL, 0, 0, 0)) != 0) DWARN("%s: perfuse_node_fsync failed error = %d", __func__, error); } /* * Close handles */ if (pnd->pnd_flags & PND_WFH) { if ((error = perfuse_node_close_common(pu, opc, FWRITE)) != 0) DWARN("%s: close write FH failed error = %d", __func__, error); } if (pnd->pnd_flags & PND_RFH) { if ((error = perfuse_node_close_common(pu, opc, FREAD)) != 0) DWARN("%s: close read FH failed error = %d", __func__, error); } /* * This will cause a reclaim to be sent */ if (pnd->pnd_flags & PND_REMOVED) puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1); /* * Schedule awaiting operations */ pnd->pnd_flags &= ~PND_INOPEN; (void)dequeue_requests(opc, PCQ_OPEN, DEQUEUE_ALL); /* * errors are ignored, since the kernel ignores the return code. */ out: node_rele(opc); return 0; } /* ARGSUSED0 */ int perfuse_node_print(struct puffs_usermount *pu, puffs_cookie_t opc) { DERRX(EX_SOFTWARE, "%s: UNIMPLEMENTED (FATAL)", __func__); return 0; } int perfuse_node_pathconf(struct puffs_usermount *pu, puffs_cookie_t opc, int name, register_t *retval) { perfuse_msg_t *pm; struct perfuse_state *ps; struct fuse_statfs_out *fso; int error = 0; /* * Static values copied from UFS * in src/sys/ufs/ufs/ufs_vnops.c */ switch (name) { case _PC_LINK_MAX: *retval = LINK_MAX; break; case _PC_PATH_MAX: *retval = PATH_MAX; break; case _PC_PIPE_BUF: *retval = PIPE_BUF; break; case _PC_CHOWN_RESTRICTED: *retval = 1; break; case _PC_NO_TRUNC: *retval = 1; break; case _PC_SYNC_IO: *retval = 1; break; case _PC_FILESIZEBITS: *retval = 42; break; case _PC_SYMLINK_MAX: *retval = MAXPATHLEN; break; case _PC_2_SYMLINKS: *retval = 1; break; case _PC_NAME_MAX: ps = puffs_getspecific(pu); pm = ps->ps_new_msg(pu, opc, FUSE_STATFS, 0, NULL); error = xchg_msg(pu, opc, pm, sizeof(*fso), wait_reply); if (error != 0) return error; fso = GET_OUTPAYLOAD(ps, pm, fuse_statfs_out); *retval = fso->st.namelen; ps->ps_destroy_msg(pm); break; default: DWARN("Unimplemented pathconf for name = %d", name); error = ENOSYS; break; } return error; } int perfuse_node_advlock(struct puffs_usermount *pu, puffs_cookie_t opc, void *id, int op, struct flock *fl, int flags) { struct perfuse_state *ps; int fop; perfuse_msg_t *pm; uint64_t fh; struct fuse_lk_in *fli; struct fuse_out_header *foh; struct fuse_lk_out *flo; uint32_t owner; size_t len; int error; node_ref(opc); /* * Make sure we do have a filehandle, as the FUSE filesystem * expect one. E.g.: if we provide none, GlusterFS logs an error * "0-glusterfs-fuse: xl is NULL" * * We need the read file handle if the file is open read only, * in order to support shared locks on read-only files. * NB: The kernel always sends advlock for read-only * files at exit time when the process used lock, see * sys_exit -> exit1 -> fd_free -> fd_close -> VOP_ADVLOCK */ if ((fh = perfuse_get_fh(opc, FREAD)) == FUSE_UNKNOWN_FH) { error = EBADF; goto out; } ps = puffs_getspecific(pu); if (op == F_GETLK) fop = FUSE_GETLK; else fop = (flags & F_WAIT) ? FUSE_SETLKW : FUSE_SETLK; /* * XXX ps_new_msg() is called with NULL creds, which will * be interpreted as FUSE superuser. We have no way to * know the requesting process' credential, but since advlock() * is supposed to operate on a file that has been open(), * permission should have already been checked at open() time. */ pm = ps->ps_new_msg(pu, opc, fop, sizeof(*fli), NULL); fli = GET_INPAYLOAD(ps, pm, fuse_lk_in); fli->fh = fh; fli->owner = (uint64_t)(vaddr_t)id; fli->lk.start = fl->l_start; fli->lk.end = fl->l_start + fl->l_len; fli->lk.type = fl->l_type; fli->lk.pid = fl->l_pid; fli->lk_flags = (flags & F_FLOCK) ? FUSE_LK_FLOCK : 0; owner = (uint32_t)(vaddr_t)id; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n", __func__, (void *)opc, PERFUSE_NODE_DATA(opc)->pnd_nodeid, fli->fh); #endif if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; foh = GET_OUTHDR(ps, pm); len = foh->len - sizeof(*foh); /* * Save or clear the lock */ switch (op) { case F_GETLK: if (len != sizeof(*flo)) DERRX(EX_SOFTWARE, "%s: Unexpected lock reply len %zd", __func__, len); flo = GET_OUTPAYLOAD(ps, pm, fuse_lk_out); fl->l_start = flo->lk.start; fl->l_len = flo->lk.end - flo->lk.start; fl->l_pid = flo->lk.pid; fl->l_type = flo->lk.type; fl->l_whence = SEEK_SET; /* libfuse hardcodes it */ PERFUSE_NODE_DATA(opc)->pnd_lock_owner = flo->lk.pid; break; case F_UNLCK: owner = 0; /* FALLTHROUGH */ case F_SETLK: /* FALLTHROUGH */ case F_SETLKW: if (error != 0) PERFUSE_NODE_DATA(opc)->pnd_lock_owner = owner; if (len != 0) DERRX(EX_SOFTWARE, "%s: Unexpected unlock reply len %zd", __func__, len); break; default: DERRX(EX_SOFTWARE, "%s: Unexpected op %d", __func__, op); break; } ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); return error; } int perfuse_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 perfuse_state *ps; struct perfuse_node_data *pnd; const struct vattr *vap; perfuse_msg_t *pm; struct fuse_read_in *fri; struct fuse_out_header *foh; size_t readen; int error; ps = puffs_getspecific(pu); pnd = PERFUSE_NODE_DATA(opc); vap = puffs_pn_getvap((struct puffs_node *)opc); /* * NetBSD turns that into a getdents(2) output * We just do a EISDIR as this feature is of little use. */ if (vap->va_type == VDIR) return EISDIR; do { size_t max_read; max_read = ps->ps_max_readahead - sizeof(*foh); /* * flags may be set to FUSE_READ_LOCKOWNER * if lock_owner is provided. */ pm = ps->ps_new_msg(pu, opc, FUSE_READ, sizeof(*fri), pcr); fri = GET_INPAYLOAD(ps, pm, fuse_read_in); fri->fh = perfuse_get_fh(opc, FREAD); fri->offset = offset; fri->size = (uint32_t)MIN(*resid, max_read); fri->read_flags = 0; /* XXX Unused by libfuse? */ fri->lock_owner = pnd->pnd_lock_owner; fri->flags = 0; fri->flags |= (fri->lock_owner != 0) ? FUSE_READ_LOCKOWNER : 0; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", fh = 0x%"PRIx64"\n", __func__, (void *)opc, pnd->pnd_nodeid, fri->fh); #endif error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply); if (error != 0) return error; foh = GET_OUTHDR(ps, pm); readen = foh->len - sizeof(*foh); #ifdef PERFUSE_DEBUG if (readen > *resid) DERRX(EX_SOFTWARE, "%s: Unexpected big read %zd", __func__, readen); #endif (void)memcpy(buf, _GET_OUTPAYLOAD(ps, pm, char *), readen); buf += readen; offset += readen; *resid -= readen; ps->ps_destroy_msg(pm); } while ((*resid != 0) && (readen != 0)); if (ioflag & (IO_SYNC|IO_DSYNC)) ps->ps_syncreads++; else ps->ps_asyncreads++; return 0; } int perfuse_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) { return perfuse_node_write2(pu, opc, buf, offset, resid, pcr, ioflag, 0); } /* ARGSUSED7 */ int perfuse_node_write2(struct puffs_usermount *pu, puffs_cookie_t opc, uint8_t *buf, off_t offset, size_t *resid, const struct puffs_cred *pcr, int ioflag, int xflag) { struct perfuse_state *ps; struct perfuse_node_data *pnd; struct vattr *vap; perfuse_msg_t *pm; struct fuse_write_in *fwi; struct fuse_write_out *fwo; size_t data_len; size_t payload_len; size_t written; int inresize; int error; ps = puffs_getspecific(pu); pnd = PERFUSE_NODE_DATA(opc); vap = puffs_pn_getvap((struct puffs_node *)opc); written = 0; inresize = 0; error = 0; if (vap->va_type == VDIR) return EISDIR; node_ref(opc); /* * We need to queue write requests in order to avoid * dequeueing PCQ_AFTERWRITE when there are pending writes. */ while (pnd->pnd_flags & PND_INWRITE) requeue_request(pu, opc, PCQ_WRITE); pnd->pnd_flags |= PND_INWRITE; /* * append flag: re-read the file size so that * we get the latest value. */ if (ioflag & PUFFS_IO_APPEND) { if ((error = perfuse_node_getattr(pu, opc, vap, pcr)) != 0) goto out; offset = vap->va_size; } /* * Serialize size access, see comment in perfuse_node_setattr(). */ if ((u_quad_t)offset + *resid > vap->va_size) { while (pnd->pnd_flags & PND_INRESIZE) requeue_request(pu, opc, PCQ_RESIZE); pnd->pnd_flags |= PND_INRESIZE; inresize = 1; } #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_RESIZE) DPRINTF(">> %s %p %" PRIu64 "\n", __func__, (void *)opc, vap->va_size); #endif do { size_t max_write; /* * There is a writepage flag when data * is aligned to page size. Use it for * everything but the data after the last * page boundary. */ max_write = ps->ps_max_write - sizeof(*fwi); data_len = MIN(*resid, max_write); if (data_len > (size_t)sysconf(_SC_PAGESIZE)) data_len = data_len & ~(sysconf(_SC_PAGESIZE) - 1); payload_len = data_len + sizeof(*fwi); /* * flags may be set to FUSE_WRITE_CACHE (XXX usage?) * or FUSE_WRITE_LOCKOWNER, if lock_owner is provided. * write_flags is set to 1 for writepage. */ pm = ps->ps_new_msg(pu, opc, FUSE_WRITE, payload_len, pcr); fwi = GET_INPAYLOAD(ps, pm, fuse_write_in); fwi->fh = perfuse_get_fh(opc, FWRITE); fwi->offset = offset; fwi->size = (uint32_t)data_len; fwi->write_flags = (fwi->size % sysconf(_SC_PAGESIZE)) ? 0 : 1; fwi->lock_owner = pnd->pnd_lock_owner; fwi->flags = 0; fwi->flags |= (fwi->lock_owner != 0) ? FUSE_WRITE_LOCKOWNER : 0; fwi->flags |= (ioflag & IO_DIRECT) ? 0 : FUSE_WRITE_CACHE; (void)memcpy((fwi + 1), buf, data_len); #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_FH) DPRINTF("%s: opc = %p, nodeid = 0x%"PRIx64", " "fh = 0x%"PRIx64"\n", __func__, (void *)opc, pnd->pnd_nodeid, fwi->fh); #endif if ((error = xchg_msg(pu, opc, pm, sizeof(*fwo), wait_reply)) != 0) goto out; fwo = GET_OUTPAYLOAD(ps, pm, fuse_write_out); written = fwo->size; ps->ps_destroy_msg(pm); #ifdef PERFUSE_DEBUG if (written > *resid) DERRX(EX_SOFTWARE, "%s: Unexpected big write %zd", __func__, written); #endif *resid -= written; offset += written; buf += written; } while (*resid != 0); /* * puffs_ops(3) says * "everything must be written or an error will be generated" */ if (*resid != 0) error = EFBIG; out: #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_RESIZE) { if (offset > (off_t)vap->va_size) DPRINTF("<< %s %p %" PRIu64 " -> %lld\n", __func__, (void *)opc, vap->va_size, (long long)offset); else DPRINTF("<< %s %p \n", __func__, (void *)opc); } #endif /* * Update file size if we wrote beyond the end */ if (offset > (off_t)vap->va_size) vap->va_size = offset; /* * Statistics */ if (ioflag & (IO_SYNC|IO_DSYNC)) ps->ps_syncwrites++; else ps->ps_asyncwrites++; /* * Remember to sync the file */ pnd->pnd_flags |= PND_DIRTY; #ifdef PERFUSE_DEBUG if (perfuse_diagflags & PDF_SYNC) DPRINTF("%s: DIRTY opc = %p, file = \"%s\"\n", __func__, (void*)opc, perfuse_node_path(ps, opc)); #endif if (inresize) { #ifdef PERFUSE_DEBUG if (!(pnd->pnd_flags & PND_INRESIZE)) DERRX(EX_SOFTWARE, "file write grow without resize"); #endif pnd->pnd_flags &= ~PND_INRESIZE; (void)dequeue_requests(opc, PCQ_RESIZE, DEQUEUE_ALL); } /* * VOP_PUTPAGE causes FAF write where kernel does not * check operation result. At least warn if it failed. */ #ifdef PUFFS_WRITE_FAF if (error && (xflag & PUFFS_WRITE_FAF)) DWARN("Data loss caused by FAF write failed on \"%s\"", pnd->pnd_name); #endif /* PUFFS_WRITE_FAF */ /* * If there are no more queued write, we can resume * an operation awaiting write completion. */ pnd->pnd_flags &= ~PND_INWRITE; if (dequeue_requests(opc, PCQ_WRITE, 1) == 0) (void)dequeue_requests(opc, PCQ_AFTERWRITE, DEQUEUE_ALL); node_rele(opc); return error; } /* ARGSUSED0 */ void perfuse_cache_write(struct puffs_usermount *pu, puffs_cookie_t opc, size_t size, struct puffs_cacherun *runs) { return; } /* ARGSUSED4 */ int perfuse_node_getextattr(struct puffs_usermount *pu, puffs_cookie_t opc, int attrns, const char *attrname, size_t *attrsize, uint8_t *attr, size_t *resid, const struct puffs_cred *pcr) { struct perfuse_state *ps; char fuse_attrname[LINUX_XATTR_NAME_MAX + 1]; perfuse_msg_t *pm; struct fuse_getxattr_in *fgi; struct fuse_getxattr_out *fgo; struct fuse_out_header *foh; size_t attrnamelen; size_t len; char *np; int error; /* system namespace attrs are not accessible to non root users */ if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr)) return EPERM; node_ref(opc); ps = puffs_getspecific(pu); attrname = perfuse_native_ns(attrns, attrname, fuse_attrname); attrnamelen = strlen(attrname) + 1; len = sizeof(*fgi) + attrnamelen; pm = ps->ps_new_msg(pu, opc, FUSE_GETXATTR, len, pcr); fgi = GET_INPAYLOAD(ps, pm, fuse_getxattr_in); fgi->size = (unsigned int)((resid != NULL) ? *resid : 0); np = (char *)(void *)(fgi + 1); (void)strlcpy(np, attrname, attrnamelen); if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; /* * We just get fuse_getattr_out with list size if we requested * a null size. */ if (resid == NULL) { fgo = GET_OUTPAYLOAD(ps, pm, fuse_getxattr_out); if (attrsize != NULL) *attrsize = fgo->size; ps->ps_destroy_msg(pm); error = 0; goto out; } /* * And with a non null requested size, we get the list just * after the header */ foh = GET_OUTHDR(ps, pm); np = (char *)(void *)(foh + 1); len = foh->len - sizeof(*foh); if (attrsize != NULL) *attrsize = len; if (resid != NULL) { if (*resid < len) { error = ERANGE; ps->ps_destroy_msg(pm); goto out; } (void)memcpy(attr, np, len); *resid -= len; } ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); return error; } int perfuse_node_setextattr(struct puffs_usermount *pu, puffs_cookie_t opc, int attrns, const char *attrname, uint8_t *attr, size_t *resid, const struct puffs_cred *pcr) { struct perfuse_state *ps; char fuse_attrname[LINUX_XATTR_NAME_MAX + 1]; perfuse_msg_t *pm; struct fuse_setxattr_in *fsi; size_t attrnamelen; size_t datalen; size_t len; char *np; int error; /* system namespace attrs are not accessible to non root users */ if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr)) return EPERM; node_ref(opc); ps = puffs_getspecific(pu); attrname = perfuse_native_ns(attrns, attrname, fuse_attrname); attrnamelen = strlen(attrname) + 1; datalen = (resid != NULL) ? *resid : 0; len = sizeof(*fsi) + attrnamelen + datalen; pm = ps->ps_new_msg(pu, opc, FUSE_SETXATTR, len, pcr); fsi = GET_INPAYLOAD(ps, pm, fuse_setxattr_in); fsi->size = (unsigned int)datalen; fsi->flags = 0; np = (char *)(void *)(fsi + 1); (void)strlcpy(np, attrname, attrnamelen); np += attrnamelen; if (datalen) (void)memcpy(np, (char *)attr, datalen); if ((error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply)) != 0) goto out; ps->ps_destroy_msg(pm); if (resid) *resid = 0; error = 0; out: node_rele(opc); return error; } /* ARGSUSED2 */ int perfuse_node_listextattr(struct puffs_usermount *pu, puffs_cookie_t opc, int attrns, size_t *attrsize, uint8_t *attrs, size_t *resid, int flag, const struct puffs_cred *pcr) { struct perfuse_state *ps; perfuse_msg_t *pm; struct fuse_getxattr_in *fgi; struct fuse_getxattr_out *fgo; struct fuse_out_header *foh; char *np; size_t len, puffs_len, i, attrlen, outlen; int error; /* system namespace attrs are not accessible to non root users */ if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr)) return EPERM; node_ref(opc); ps = puffs_getspecific(pu); len = sizeof(*fgi); pm = ps->ps_new_msg(pu, opc, FUSE_LISTXATTR, len, pcr); fgi = GET_INPAYLOAD(ps, pm, fuse_getxattr_in); if (resid != NULL) fgi->size = (unsigned int)*resid; else fgi->size = 0; if ((error = xchg_msg(pu, opc, pm, UNSPEC_REPLY_LEN, wait_reply)) != 0) goto out; /* * We just get fuse_getattr_out with list size if we requested * a null size. */ if (resid == NULL) { fgo = GET_OUTPAYLOAD(ps, pm, fuse_getxattr_out); if (attrsize != NULL) *attrsize = fgo->size; ps->ps_destroy_msg(pm); error = 0; goto out; } /* * And with a non null requested size, we get the list just * after the header */ foh = GET_OUTHDR(ps, pm); np = (char *)(void *)(foh + 1); puffs_len = foh->len - sizeof(*foh); if (attrsize != NULL) *attrsize = puffs_len; if (attrs != NULL) { if (*resid < puffs_len) { error = ERANGE; ps->ps_destroy_msg(pm); goto out; } outlen = 0; for (i = 0; i < puffs_len; i += attrlen + 1) { attrlen = strlen(np + i); /* * Filter attributes per namespace */ if (!perfuse_ns_match(attrns, np + i)) continue; #ifdef PUFFS_EXTATTR_LIST_LENPREFIX /* * Convert the FUSE reply to length prefixed strings * if this is what the kernel wants. */ if (flag & PUFFS_EXTATTR_LIST_LENPREFIX) { (void)memcpy(attrs + outlen + 1, np + i, attrlen); *(attrs + outlen) = (uint8_t)attrlen; } else #endif /* PUFFS_EXTATTR_LIST_LENPREFIX */ (void)memcpy(attrs + outlen, np + i, attrlen + 1); outlen += attrlen + 1; } *resid -= outlen; } ps->ps_destroy_msg(pm); error = 0; out: node_rele(opc); return error; } int perfuse_node_deleteextattr(struct puffs_usermount *pu, puffs_cookie_t opc, int attrns, const char *attrname, const struct puffs_cred *pcr) { struct perfuse_state *ps; char fuse_attrname[LINUX_XATTR_NAME_MAX + 1]; perfuse_msg_t *pm; size_t attrnamelen; char *np; int error; /* system namespace attrs are not accessible to non root users */ if (attrns == EXTATTR_NAMESPACE_SYSTEM && !puffs_cred_isjuggernaut(pcr)) return EPERM; node_ref(opc); ps = puffs_getspecific(pu); attrname = perfuse_native_ns(attrns, attrname, fuse_attrname); attrnamelen = strlen(attrname) + 1; pm = ps->ps_new_msg(pu, opc, FUSE_REMOVEXATTR, attrnamelen, pcr); np = _GET_INPAYLOAD(ps, pm, char *); (void)strlcpy(np, attrname, attrnamelen); error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply); if (error != 0) goto out; ps->ps_destroy_msg(pm); out: node_rele(opc); return error; } int perfuse_node_fallocate(struct puffs_usermount *pu, puffs_cookie_t opc, off_t off, off_t len) { struct perfuse_state *ps; perfuse_msg_t *pm; struct fuse_fallocate_in *fai; int error; ps = puffs_getspecific(pu); if (ps->ps_flags & PS_NO_FALLOCATE) return EOPNOTSUPP; node_ref(opc); pm = ps->ps_new_msg(pu, opc, FUSE_FALLOCATE, sizeof(*fai), NULL); fai = GET_INPAYLOAD(ps, pm, fuse_fallocate_in); fai->fh = perfuse_get_fh(opc, FWRITE); fai->offset = off; fai->length = len; fai->mode = 0; error = xchg_msg(pu, opc, pm, NO_PAYLOAD_REPLY_LEN, wait_reply); if (error == EOPNOTSUPP || error == ENOSYS) { ps->ps_flags |= PS_NO_FALLOCATE; error = EOPNOTSUPP; } if (error != 0) goto out; ps->ps_destroy_msg(pm); out: node_rele(opc); return error; }