static errno_t fuse_vfsop_root(mount_t mp, struct vnode **vpp, vfs_context_t context) { int err = 0; vnode_t vp = NULLVP; struct fuse_entry_out feo_root; struct fuse_data *data = fuse_get_mpdata(mp); fuse_trace_printf_vfsop(); if (data->rootvp != NULLVP) { *vpp = data->rootvp; return vnode_get(*vpp); } bzero(&feo_root, sizeof(feo_root)); feo_root.nodeid = FUSE_ROOT_ID; feo_root.generation = 0; feo_root.attr.ino = FUSE_ROOT_ID; feo_root.attr.size = FUSE_ROOT_SIZE; feo_root.attr.mode = VTTOIF(VDIR); err = FSNodeGetOrCreateFileVNodeByID(&vp, FN_IS_ROOT, &feo_root, mp, NULLVP /* dvp */, context, NULL /* oflags */); *vpp = vp; if (!err) { data->rootvp = *vpp; } return err; }
static errno_t fuse_vfsop_sync(mount_t mp, int waitfor, vfs_context_t context) { uint64_t mntflags; struct fuse_sync_cargs args; int allerror = 0; fuse_trace_printf_vfsop(); mntflags = vfs_flags(mp); if (fuse_isdeadfs_mp(mp)) { return 0; } if (vfs_isupdate(mp)) { return 0; } if (vfs_isrdonly(mp)) { return EROFS; // should panic!? } /* * Write back each (modified) fuse node. */ args.context = context; args.waitfor = waitfor; args.error = 0; #if M_FUSE4X_ENABLE_BIGLOCK struct fuse_data *data = fuse_get_mpdata(mp); fuse_biglock_unlock(data->biglock); #endif vnode_iterate(mp, 0, fuse_sync_callback, (void *)&args); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_lock(data->biglock); #endif if (args.error) { allerror = args.error; } /* * For other types of stale file system information, such as: * * - fs control info * - quota information * - modified superblock */ return allerror; }
static errno_t fuse_vfsop_getattr(mount_t mp, struct vfs_attr *attr, vfs_context_t context) { int err = 0; bool deading = false, faking = false; struct fuse_dispatcher fdi; struct fuse_statfs_out *fsfo; struct fuse_statfs_out faked; struct fuse_data *data; fuse_trace_printf_vfsop(); data = fuse_get_mpdata(mp); if (!data) { panic("fuse4x: no private data for mount point?"); } if (!(data->dataflags & FSESS_INITED)) { // coreservices process requests ATTR_VOL_CAPABILITIES on the mountpoint right before // returning from mount() syscall. We need to fake the output because daemon might // not be ready to response yet (and deadlock will happen). faking = true; goto dostatfs; } fdisp_init(&fdi, 0); fdisp_make(&fdi, FUSE_STATFS, mp, FUSE_ROOT_ID, context); if ((err = fdisp_wait_answ(&fdi))) { // If we cannot communicate with the daemon (most likely because // it's dead), we still want to portray that we are a bonafide // file system so that we can be gracefully unmounted. if (err == ENOTCONN) { deading = faking = true; goto dostatfs; } return err; } dostatfs: if (faking) { bzero(&faked, sizeof(faked)); fsfo = &faked; } else { fsfo = fdi.answ; } if (fsfo->st.bsize == 0) { fsfo->st.bsize = FUSE_DEFAULT_IOSIZE; } if (fsfo->st.frsize == 0) { fsfo->st.frsize = FUSE_DEFAULT_BLOCKSIZE; } /* optimal transfer block size; will go into f_iosize in the kernel */ fsfo->st.bsize = fuse_round_size(fsfo->st.bsize, FUSE_MIN_IOSIZE, FUSE_MAX_IOSIZE); /* file system fragment size; will go into f_bsize in the kernel */ fsfo->st.frsize = fuse_round_size(fsfo->st.frsize, FUSE_MIN_BLOCKSIZE, FUSE_MAX_BLOCKSIZE); /* We must have: f_iosize >= f_bsize (fsfo->st.bsize >= fsfo->st_frsize) */ if (fsfo->st.bsize < fsfo->st.frsize) { fsfo->st.bsize = fsfo->st.frsize; } /* * TBD: Possibility: * * For actual I/O to fuse4x's "virtual" storage device, we use * data->blocksize and data->iosize. These are really meant to be * constant across the lifetime of a single mount. If necessary, we * can experiment by updating the mount point's stat with the frsize * and bsize values we come across here. */ /* * FUSE user daemon will (might) give us this: * * __u64 blocks; // total data blocks in the file system * __u64 bfree; // free blocks in the file system * __u64 bavail; // free blocks available to non-superuser * __u64 files; // total file nodes in the file system * __u64 ffree; // free file nodes in the file system * __u32 bsize; // preferred/optimal file system block size * __u32 namelen; // maximum length of filenames * __u32 frsize; // fundamental file system block size * * On Mac OS X, we will map this data to struct vfs_attr as follows: * * Mac OS X FUSE * -------- ---- * uint64_t f_supported <- // handled here * uint64_t f_active <- // handled here * uint64_t f_objcount <- - * uint64_t f_filecount <- files * uint64_t f_dircount <- - * uint32_t f_bsize <- frsize * size_t f_iosize <- bsize * uint64_t f_blocks <- blocks * uint64_t f_bfree <- bfree * uint64_t f_bavail <- bavail * uint64_t f_bused <- blocks - bfree * uint64_t f_files <- files * uint64_t f_ffree <- ffree * fsid_t f_fsid <- // handled elsewhere * uid_t f_owner <- // handled elsewhere * ... capabilities <- // handled here * ... attributes <- // handled here * f_create_time <- - * f_modify_time <- - * f_access_time <- - * f_backup_time <- - * uint32_t f_fssubtype <- // daemon provides * char *f_vol_name <- // handled here * uint16_t f_signature <- // handled here * uint16_t f_carbon_fsid <- // handled here */ VFSATTR_RETURN(attr, f_filecount, fsfo->st.files); VFSATTR_RETURN(attr, f_bsize, fsfo->st.frsize); VFSATTR_RETURN(attr, f_iosize, fsfo->st.bsize); VFSATTR_RETURN(attr, f_blocks, fsfo->st.blocks); VFSATTR_RETURN(attr, f_bfree, fsfo->st.bfree); VFSATTR_RETURN(attr, f_bavail, fsfo->st.bavail); VFSATTR_RETURN(attr, f_bused, (fsfo->st.blocks - fsfo->st.bfree)); VFSATTR_RETURN(attr, f_files, fsfo->st.files); VFSATTR_RETURN(attr, f_ffree, fsfo->st.ffree); /* f_fsid and f_owner handled elsewhere. */ /* Handle capabilities and attributes. */ handle_capabilities_and_attributes(mp, attr); VFSATTR_RETURN(attr, f_create_time, kZeroTime); VFSATTR_RETURN(attr, f_modify_time, kZeroTime); VFSATTR_RETURN(attr, f_access_time, kZeroTime); VFSATTR_RETURN(attr, f_backup_time, kZeroTime); if (deading) { VFSATTR_RETURN(attr, f_fssubtype, (uint32_t)FUSE_FSSUBTYPE_INVALID); } else { VFSATTR_RETURN(attr, f_fssubtype, data->fssubtype); } /* Daemon needs to pass this. */ if (VFSATTR_IS_ACTIVE(attr, f_vol_name)) { if (data->volname[0] != 0) { strncpy(attr->f_vol_name, data->volname, MAXPATHLEN); attr->f_vol_name[MAXPATHLEN - 1] = 0; VFSATTR_SET_SUPPORTED(attr, f_vol_name); } } VFSATTR_RETURN(attr, f_signature, OSSwapBigToHostInt16(FUSEFS_SIGNATURE)); VFSATTR_RETURN(attr, f_carbon_fsid, 0); if (!faking) fuse_ticket_drop(fdi.tick); return 0; }
static errno_t fuse_vfsop_unmount(mount_t mp, int mntflags, vfs_context_t context) { int err = 0; int flags = 0; fuse_device_t fdev; struct fuse_data *data; struct fuse_dispatcher fdi; vnode_t fuse_rootvp = NULLVP; fuse_trace_printf_vfsop(); if (mntflags & MNT_FORCE) { flags |= FORCECLOSE; } data = fuse_get_mpdata(mp); if (!data) { panic("fuse4x: no mount private data in vfs_unmount"); } #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_lock(data->biglock); #endif fdev = data->fdev; if (fdata_dead_get(data)) { /* * If the file system daemon is dead, it's pointless to try to do * any unmount-time operations that go out to user space. Therefore, * we pretend that this is a force unmount. However, this isn't of much * use. That's because if any non-root vnode is in use, the vflush() * that the kernel does before calling our VFS_UNMOUNT will fail * if the original unmount wasn't forcible already. That earlier * vflush is called with SKIPROOT though, so it wouldn't bail out * on the root vnode being in use. * * If we want, we could set FORCECLOSE here so that a non-forced * unmount will be "upgraded" to a forced unmount if the root vnode * is busy (you are cd'd to the mount point, for example). It's not * quite pure to do that though. * * flags |= FORCECLOSE; * log("fuse4x: forcing unmount on a dead file system\n"); */ } else if (!(data->dataflags & FSESS_INITED)) { flags |= FORCECLOSE; log("fuse4x: forcing unmount on not-yet-alive file system\n"); fdata_set_dead(data); } fuse_rootvp = data->rootvp; fuse_trace_printf("%s: Calling vflush(mp, fuse_rootvp, flags=0x%X);\n", __FUNCTION__, flags); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(data->biglock); #endif err = vflush(mp, fuse_rootvp, flags); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_lock(data->biglock); #endif fuse_trace_printf("%s: Done.\n", __FUNCTION__); if (err) { #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(data->biglock); #endif return err; } if (vnode_isinuse(fuse_rootvp, 1) && !(flags & FORCECLOSE)) { #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(data->biglock); #endif return EBUSY; } if (fdata_dead_get(data)) { goto alreadydead; } fdisp_init(&fdi, 0 /* no data to send along */); fdisp_make(&fdi, FUSE_DESTROY, mp, FUSE_ROOT_ID, context); fuse_trace_printf("%s: Waiting for reply from FUSE_DESTROY.\n", __FUNCTION__); err = fdisp_wait_answ(&fdi); fuse_trace_printf("%s: Reply received.\n", __FUNCTION__); if (!err) { fuse_ticket_drop(fdi.tick); } /* * Note that dounmount() signals a VQ_UNMOUNT VFS event. */ fdata_set_dead(data); alreadydead: fuse_trace_printf("%s: Calling vnode_rele(fuse_rootp);\n", __FUNCTION__); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(data->biglock); #endif vnode_rele(fuse_rootvp); /* We got this reference in fuse_vfsop_mount(). */ #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_lock(data->biglock); #endif fuse_trace_printf("%s: Done.\n", __FUNCTION__); data->rootvp = NULLVP; fuse_trace_printf("%s: Calling vflush(mp, NULLVP, FORCECLOSE);\n", __FUNCTION__); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(data->biglock); #endif (void)vflush(mp, NULLVP, FORCECLOSE); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_lock(data->biglock); #endif fuse_trace_printf("%s: Done.\n", __FUNCTION__); fuse_lck_mtx_lock(fdev->mtx); vfs_setfsprivate(mp, NULL); data->dataflags &= ~FSESS_MOUNTED; OSAddAtomic(-1, (SInt32 *)&fuse_mount_count); #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(data->biglock); #endif if (!(data->dataflags & FSESS_OPENED)) { /* fdev->data was left for us to clean up */ fuse_device_close_final(fdev); /* fdev->data is gone now */ } fuse_lck_mtx_unlock(fdev->mtx); return 0; }
static errno_t fuse_vfsop_mount(mount_t mp, __unused vnode_t devvp, user_addr_t udata, vfs_context_t context) { int err = 0; int mntopts = 0; bool mounted = false; uint32_t max_read = ~0; size_t len; fuse_device_t fdev = NULL; struct fuse_data *data = NULL; fuse_mount_args fusefs_args; struct vfsstatfs *vfsstatfsp = vfs_statfs(mp); #if M_FUSE4X_ENABLE_BIGLOCK lck_mtx_t *biglock; #endif fuse_trace_printf_vfsop(); if (vfs_isupdate(mp)) { return ENOTSUP; } err = copyin(udata, &fusefs_args, sizeof(fusefs_args)); if (err) { return EINVAL; } /* * Interesting flags that we can receive from mount or may want to * otherwise forcibly set include: * * MNT_ASYNC * MNT_AUTOMOUNTED * MNT_DEFWRITE * MNT_DONTBROWSE * MNT_IGNORE_OWNERSHIP * MNT_JOURNALED * MNT_NODEV * MNT_NOEXEC * MNT_NOSUID * MNT_NOUSERXATTR * MNT_RDONLY * MNT_SYNCHRONOUS * MNT_UNION */ err = ENOTSUP; #if M_FUSE4X_ENABLE_UNSUPPORTED vfs_setlocklocal(mp); #endif /* M_FUSE4X_ENABLE_UNSUPPORTED */ /** Option Processing. **/ if (*fusefs_args.fstypename) { size_t typenamelen = strlen(fusefs_args.fstypename); if (typenamelen > FUSE_FSTYPENAME_MAXLEN) { return EINVAL; } snprintf(vfsstatfsp->f_fstypename, MFSTYPENAMELEN, "%s%s", FUSE_FSTYPENAME_PREFIX, fusefs_args.fstypename); } if (!*fusefs_args.fsname) return EINVAL; if ((fusefs_args.daemon_timeout > FUSE_MAX_DAEMON_TIMEOUT) || (fusefs_args.daemon_timeout < FUSE_MIN_DAEMON_TIMEOUT)) { return EINVAL; } if ((fusefs_args.init_timeout > FUSE_MAX_INIT_TIMEOUT) || (fusefs_args.init_timeout < FUSE_MIN_INIT_TIMEOUT)) { return EINVAL; } if (fusefs_args.altflags & FUSE_MOPT_SPARSE) { mntopts |= FSESS_SPARSE; } if (fusefs_args.altflags & FUSE_MOPT_AUTO_CACHE) { mntopts |= FSESS_AUTO_CACHE; } if (fusefs_args.altflags & FUSE_MOPT_AUTO_XATTR) { if (fusefs_args.altflags & FUSE_MOPT_NATIVE_XATTR) { return EINVAL; } mntopts |= FSESS_AUTO_XATTR; } else if (fusefs_args.altflags & FUSE_MOPT_NATIVE_XATTR) { mntopts |= FSESS_NATIVE_XATTR; } if (fusefs_args.altflags & FUSE_MOPT_JAIL_SYMLINKS) { mntopts |= FSESS_JAIL_SYMLINKS; } /* * Note that unlike Linux, which keeps allow_root in user-space and * passes allow_other in that case to the kernel, we let allow_root * reach the kernel. The 'if' ordering is important here. */ if (fusefs_args.altflags & FUSE_MOPT_ALLOW_ROOT) { int is_member = 0; if ((kauth_cred_ismember_gid(kauth_cred_get(), fuse_admin_group, &is_member) != 0) || !is_member) { log("fuse4x: caller is not a member of fuse4x admin group. " "Either add user (id=%d) to group (id=%d), " "or set correct '" SYSCTL_FUSE4X_TUNABLES_ADMIN "' sysctl value.\n", kauth_cred_getuid(kauth_cred_get()), fuse_admin_group); return EPERM; } mntopts |= FSESS_ALLOW_ROOT; } else if (fusefs_args.altflags & FUSE_MOPT_ALLOW_OTHER) { if (!fuse_allow_other && !fuse_vfs_context_issuser(context)) { int is_member = 0; if ((kauth_cred_ismember_gid(kauth_cred_get(), fuse_admin_group, &is_member) != 0) || !is_member) { log("fuse4x: caller is not a member of fuse4x admin group. " "Either add user (id=%d) to group (id=%d), " "or set correct '" SYSCTL_FUSE4X_TUNABLES_ADMIN "' sysctl value.\n", kauth_cred_getuid(kauth_cred_get()), fuse_admin_group); return EPERM; } } mntopts |= FSESS_ALLOW_OTHER; } if (fusefs_args.altflags & FUSE_MOPT_NO_APPLEDOUBLE) { mntopts |= FSESS_NO_APPLEDOUBLE; } if (fusefs_args.altflags & FUSE_MOPT_NO_APPLEXATTR) { mntopts |= FSESS_NO_APPLEXATTR; } if ((fusefs_args.altflags & FUSE_MOPT_FSID) && (fusefs_args.fsid != 0)) { fsid_t fsid; mount_t other_mp; uint32_t target_dev; target_dev = FUSE_MAKEDEV(FUSE_CUSTOM_FSID_DEVICE_MAJOR, fusefs_args.fsid); fsid.val[0] = target_dev; fsid.val[1] = FUSE_CUSTOM_FSID_VAL1; other_mp = vfs_getvfs(&fsid); if (other_mp != NULL) { return EPERM; } vfsstatfsp->f_fsid.val[0] = target_dev; vfsstatfsp->f_fsid.val[1] = FUSE_CUSTOM_FSID_VAL1; } else { vfs_getnewfsid(mp); } if (fusefs_args.altflags & FUSE_MOPT_NO_ATTRCACHE) { mntopts |= FSESS_NO_ATTRCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_READAHEAD) { mntopts |= FSESS_NO_READAHEAD; } if (fusefs_args.altflags & (FUSE_MOPT_NO_UBC | FUSE_MOPT_DIRECT_IO)) { mntopts |= FSESS_NO_UBC; } if (fusefs_args.altflags & FUSE_MOPT_NO_VNCACHE) { mntopts |= FSESS_NO_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NEGATIVE_VNCACHE) { if (mntopts & FSESS_NO_VNCACHE) { return EINVAL; } mntopts |= FSESS_NEGATIVE_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_SYNCWRITES) { /* Cannot mix 'nosyncwrites' with 'noubc' or 'noreadahead'. */ if (mntopts & (FSESS_NO_READAHEAD | FSESS_NO_UBC)) { log("fuse4x: cannot mix 'nosyncwrites' with 'noubc' or 'noreadahead'\n"); return EINVAL; } mntopts |= FSESS_NO_SYNCWRITES; vfs_clearflags(mp, MNT_SYNCHRONOUS); vfs_setflags(mp, MNT_ASYNC); /* We check for this only if we have nosyncwrites in the first place. */ if (fusefs_args.altflags & FUSE_MOPT_NO_SYNCONCLOSE) { mntopts |= FSESS_NO_SYNCONCLOSE; } } else { vfs_clearflags(mp, MNT_ASYNC); vfs_setflags(mp, MNT_SYNCHRONOUS); } if (mntopts & FSESS_NO_UBC) { /* If no buffer cache, disallow exec from file system. */ vfs_setflags(mp, MNT_NOEXEC); } vfs_setauthopaque(mp); vfs_setauthopaqueaccess(mp); if ((fusefs_args.altflags & FUSE_MOPT_DEFAULT_PERMISSIONS) && (fusefs_args.altflags & FUSE_MOPT_DEFER_PERMISSIONS)) { return EINVAL; } if (fusefs_args.altflags & FUSE_MOPT_DEFAULT_PERMISSIONS) { mntopts |= FSESS_DEFAULT_PERMISSIONS; vfs_clearauthopaque(mp); } if (fusefs_args.altflags & FUSE_MOPT_DEFER_PERMISSIONS) { mntopts |= FSESS_DEFER_PERMISSIONS; } if (fusefs_args.altflags & FUSE_MOPT_EXTENDED_SECURITY) { mntopts |= FSESS_EXTENDED_SECURITY; vfs_setextendedsecurity(mp); } if (fusefs_args.altflags & FUSE_MOPT_LOCALVOL) { vfs_setflags(mp, MNT_LOCAL); } /* done checking incoming option bits */ err = 0; vfs_setfsprivate(mp, NULL); fdev = fuse_device_get(fusefs_args.rdev); if (!fdev) { log("fuse4x: invalid device file (number=%d)\n", fusefs_args.rdev); return EINVAL; } fuse_lck_mtx_lock(fdev->mtx); data = fdev->data; if (!data) { fuse_lck_mtx_unlock(fdev->mtx); return ENXIO; } #if M_FUSE4X_ENABLE_BIGLOCK biglock = data->biglock; fuse_biglock_lock(biglock); #endif if (data->dataflags & FSESS_MOUNTED) { #if M_FUSE4X_ENABLE_BIGLOCK fuse_biglock_unlock(biglock); #endif fuse_lck_mtx_unlock(fdev->mtx); return EALREADY; } if (!(data->dataflags & FSESS_OPENED)) { fuse_lck_mtx_unlock(fdev->mtx); err = ENXIO; goto out; } data->dataflags |= FSESS_MOUNTED; OSAddAtomic(1, (SInt32 *)&fuse_mount_count); mounted = true; if (fdata_dead_get(data)) { fuse_lck_mtx_unlock(fdev->mtx); err = ENOTCONN; goto out; } if (!data->daemoncred) { panic("fuse4x: daemon found but identity unknown"); } if (fuse_vfs_context_issuser(context) && kauth_cred_getuid(vfs_context_ucred(context)) != kauth_cred_getuid(data->daemoncred)) { fuse_lck_mtx_unlock(fdev->mtx); err = EPERM; log("fuse4x: fuse daemon running by user_id=%d does not have privileges to mount on directory %s owned by user_id=%d\n", kauth_cred_getuid(data->daemoncred), vfsstatfsp->f_mntonname, kauth_cred_getuid(vfs_context_ucred(context))); goto out; } data->mp = mp; data->fdev = fdev; data->dataflags |= mntopts; data->daemon_timeout.tv_sec = fusefs_args.daemon_timeout; data->daemon_timeout.tv_nsec = 0; if (data->daemon_timeout.tv_sec) { data->daemon_timeout_p = &(data->daemon_timeout); } else { data->daemon_timeout_p = NULL; } data->init_timeout.tv_sec = fusefs_args.init_timeout; data->init_timeout.tv_nsec = 0; data->max_read = max_read; data->fssubtype = fusefs_args.fssubtype; data->mountaltflags = fusefs_args.altflags; data->noimplflags = (uint64_t)0; data->blocksize = fuse_round_size(fusefs_args.blocksize, FUSE_MIN_BLOCKSIZE, FUSE_MAX_BLOCKSIZE); data->iosize = fuse_round_size(fusefs_args.iosize, FUSE_MIN_IOSIZE, FUSE_MAX_IOSIZE); if (data->iosize < data->blocksize) { data->iosize = data->blocksize; } data->userkernel_bufsize = FUSE_DEFAULT_USERKERNEL_BUFSIZE; copystr(fusefs_args.fsname, vfsstatfsp->f_mntfromname, MNAMELEN - 1, &len); bzero(vfsstatfsp->f_mntfromname + len, MNAMELEN - len); copystr(fusefs_args.volname, data->volname, MAXPATHLEN - 1, &len); bzero(data->volname + len, MAXPATHLEN - len); /* previous location of vfs_setioattr() */ vfs_setfsprivate(mp, data); fuse_lck_mtx_unlock(fdev->mtx); /* Send a handshake message to the daemon. */ fuse_send_init(data, context); struct vfs_attr vfs_attr; VFSATTR_INIT(&vfs_attr); // Our vfs_getattr() doesn't look at most *_IS_ACTIVE()'s err = fuse_vfsop_getattr(mp, &vfs_attr, context); if (!err) { vfsstatfsp->f_bsize = vfs_attr.f_bsize; vfsstatfsp->f_iosize = vfs_attr.f_iosize; vfsstatfsp->f_blocks = vfs_attr.f_blocks; vfsstatfsp->f_bfree = vfs_attr.f_bfree; vfsstatfsp->f_bavail = vfs_attr.f_bavail; vfsstatfsp->f_bused = vfs_attr.f_bused; vfsstatfsp->f_files = vfs_attr.f_files; vfsstatfsp->f_ffree = vfs_attr.f_ffree; // vfsstatfsp->f_fsid already handled above vfsstatfsp->f_owner = kauth_cred_getuid(data->daemoncred); vfsstatfsp->f_flags = vfs_flags(mp); // vfsstatfsp->f_fstypename already handled above // vfsstatfsp->f_mntonname handled elsewhere // vfsstatfsp->f_mnfromname already handled above vfsstatfsp->f_fssubtype = data->fssubtype; } if (fusefs_args.altflags & FUSE_MOPT_BLOCKSIZE) { vfsstatfsp->f_bsize = data->blocksize; } else { //data->blocksize = vfsstatfsp->f_bsize; } if (fusefs_args.altflags & FUSE_MOPT_IOSIZE) { vfsstatfsp->f_iosize = data->iosize; } else { //data->iosize = (uint32_t)vfsstatfsp->f_iosize; vfsstatfsp->f_iosize = data->iosize; } out: if (err) { vfs_setfsprivate(mp, NULL); fuse_lck_mtx_lock(fdev->mtx); data = fdev->data; /* again */ if (mounted) { OSAddAtomic(-1, (SInt32 *)&fuse_mount_count); } if (data) { data->dataflags &= ~FSESS_MOUNTED; if (!(data->dataflags & FSESS_OPENED)) { #if M_FUSE4X_ENABLE_BIGLOCK assert(biglock == data->biglock); fuse_biglock_unlock(biglock); #endif fuse_device_close_final(fdev); /* data is gone now */ } } fuse_lck_mtx_unlock(fdev->mtx); } else { vnode_t fuse_rootvp = NULLVP; err = fuse_vfsop_root(mp, &fuse_rootvp, context); if (err) { goto out; /* go back and follow error path */ } err = vnode_ref(fuse_rootvp); (void)vnode_put(fuse_rootvp); if (err) { goto out; /* go back and follow error path */ } else { struct vfsioattr ioattr; vfs_ioattr(mp, &ioattr); ioattr.io_devblocksize = data->blocksize; vfs_setioattr(mp, &ioattr); } } #if M_FUSE4X_ENABLE_BIGLOCK fuse_lck_mtx_lock(fdev->mtx); data = fdev->data; /* ...and again */ if(data) { assert(data->biglock == biglock); fuse_biglock_unlock(biglock); } fuse_lck_mtx_unlock(fdev->mtx); #endif return err; }
static errno_t fuse_vfsop_setattr(mount_t mp, struct vfs_attr *fsap, vfs_context_t context) { int error = 0; fuse_trace_printf_vfsop(); kauth_cred_t cred = vfs_context_ucred(context); if (!fuse_vfs_context_issuser(context) && (kauth_cred_getuid(cred) != vfs_statfs(mp)->f_owner)) { return EACCES; } struct fuse_data *data = fuse_get_mpdata(mp); if (VFSATTR_IS_ACTIVE(fsap, f_vol_name)) { if (!fuse_implemented(data, FSESS_NOIMPLBIT(SETVOLNAME))) { error = ENOTSUP; goto out; } if (fsap->f_vol_name[0] == 0) { error = EINVAL; goto out; } size_t namelen = strlen(fsap->f_vol_name); if (namelen >= MAXPATHLEN) { error = ENAMETOOLONG; goto out; } vnode_t root_vp; error = fuse_vfsop_root(mp, &root_vp, context); if (error) { goto out; } struct fuse_dispatcher fdi; fdisp_init(&fdi, namelen + 1); fdisp_make_vp(&fdi, FUSE_SETVOLNAME, root_vp, context); memcpy((char *)fdi.indata, fsap->f_vol_name, namelen); ((char *)fdi.indata)[namelen] = '\0'; if (!(error = fdisp_wait_answ(&fdi))) { fuse_ticket_drop(fdi.tick); } (void)vnode_put(root_vp); if (error) { if (error == ENOSYS) { error = ENOTSUP; fuse_clear_implemented(data, FSESS_NOIMPLBIT(SETVOLNAME)); } goto out; } copystr(fsap->f_vol_name, data->volname, MAXPATHLEN - 1, &namelen); bzero(data->volname + namelen, MAXPATHLEN - namelen); VFSATTR_SET_SUPPORTED(fsap, f_vol_name); } out: return error; }
static int fuse_vfsop_unmount(struct mount *mp, int mntflags) { int err = 0; int flags = 0; struct cdev *fdev; struct fuse_data *data; struct fuse_dispatcher fdi; struct thread *td = curthread; fuse_trace_printf_vfsop(); if (mntflags & MNT_FORCE) { flags |= FORCECLOSE; } data = fuse_get_mpdata(mp); if (!data) { panic("no private data for mount point?"); } /* There is 1 extra root vnode reference (mp->mnt_data). */ FUSE_LOCK(); if (data->vroot != NULL) { struct vnode *vroot = data->vroot; data->vroot = NULL; FUSE_UNLOCK(); vrele(vroot); } else FUSE_UNLOCK(); err = vflush(mp, 0, flags, td); if (err) { debug_printf("vflush failed"); return err; } if (fdata_get_dead(data)) { goto alreadydead; } fdisp_init(&fdi, 0); fdisp_make(&fdi, FUSE_DESTROY, mp, 0, td, NULL); err = fdisp_wait_answ(&fdi); fdisp_destroy(&fdi); fdata_set_dead(data); alreadydead: FUSE_LOCK(); data->mp = NULL; fdev = data->fdev; fdata_trydestroy(data); FUSE_UNLOCK(); MNT_ILOCK(mp); mp->mnt_data = NULL; mp->mnt_flag &= ~MNT_LOCAL; MNT_IUNLOCK(mp); dev_rel(fdev); return 0; }
static int fuse_vfsop_mount(struct mount *mp) { int err; uint64_t mntopts, __mntopts; int max_read_set; uint32_t max_read; int daemon_timeout; int fd; size_t len; struct cdev *fdev; struct fuse_data *data; struct thread *td; struct file *fp, *fptmp; char *fspec, *subtype; struct vfsoptlist *opts; subtype = NULL; max_read_set = 0; max_read = ~0; err = 0; mntopts = 0; __mntopts = 0; td = curthread; fuse_trace_printf_vfsop(); if (mp->mnt_flag & MNT_UPDATE) return EOPNOTSUPP; mp->mnt_flag |= MNT_SYNCHRONOUS; mp->mnt_data = NULL; /* Get the new options passed to mount */ opts = mp->mnt_optnew; if (!opts) return EINVAL; /* `fspath' contains the mount point (eg. /mnt/fuse/sshfs); REQUIRED */ if (!vfs_getopts(opts, "fspath", &err)) return err; /* `from' contains the device name (eg. /dev/fuse0); REQUIRED */ fspec = vfs_getopts(opts, "from", &err); if (!fspec) return err; /* `fd' contains the filedescriptor for this session; REQUIRED */ if (vfs_scanopt(opts, "fd", "%d", &fd) != 1) return EINVAL; err = fuse_getdevice(fspec, td, &fdev); if (err != 0) return err; /* * With the help of underscored options the mount program * can inform us from the flags it sets by default */ FUSE_FLAGOPT(allow_other, FSESS_DAEMON_CAN_SPY); FUSE_FLAGOPT(push_symlinks_in, FSESS_PUSH_SYMLINKS_IN); FUSE_FLAGOPT(default_permissions, FSESS_DEFAULT_PERMISSIONS); FUSE_FLAGOPT(no_attrcache, FSESS_NO_ATTRCACHE); FUSE_FLAGOPT(no_readahed, FSESS_NO_READAHEAD); FUSE_FLAGOPT(no_datacache, FSESS_NO_DATACACHE); FUSE_FLAGOPT(no_namecache, FSESS_NO_NAMECACHE); FUSE_FLAGOPT(no_mmap, FSESS_NO_MMAP); FUSE_FLAGOPT(brokenio, FSESS_BROKENIO); if (vfs_scanopt(opts, "max_read=", "%u", &max_read) == 1) max_read_set = 1; if (vfs_scanopt(opts, "timeout=", "%u", &daemon_timeout) == 1) { if (daemon_timeout < FUSE_MIN_DAEMON_TIMEOUT) daemon_timeout = FUSE_MIN_DAEMON_TIMEOUT; else if (daemon_timeout > FUSE_MAX_DAEMON_TIMEOUT) daemon_timeout = FUSE_MAX_DAEMON_TIMEOUT; } else { daemon_timeout = FUSE_DEFAULT_DAEMON_TIMEOUT; } subtype = vfs_getopts(opts, "subtype=", &err); FS_DEBUG2G("mntopts 0x%jx\n", (uintmax_t)mntopts); err = fget(td, fd, CAP_READ, &fp); if (err != 0) { FS_DEBUG("invalid or not opened device: data=%p\n", data); goto out; } fptmp = td->td_fpop; td->td_fpop = fp; err = devfs_get_cdevpriv((void **)&data); td->td_fpop = fptmp; fdrop(fp, td); FUSE_LOCK(); if (err != 0 || data == NULL || data->mp != NULL) { FS_DEBUG("invalid or not opened device: data=%p data.mp=%p\n", data, data != NULL ? data->mp : NULL); err = ENXIO; FUSE_UNLOCK(); goto out; } if (fdata_get_dead(data)) { FS_DEBUG("device is dead during mount: data=%p\n", data); err = ENOTCONN; FUSE_UNLOCK(); goto out; } /* Sanity + permission checks */ if (!data->daemoncred) panic("fuse daemon found, but identity unknown"); if (mntopts & FSESS_DAEMON_CAN_SPY) err = priv_check(td, PRIV_VFS_FUSE_ALLOWOTHER); if (err == 0 && td->td_ucred->cr_uid != data->daemoncred->cr_uid) /* are we allowed to do the first mount? */ err = priv_check(td, PRIV_VFS_FUSE_MOUNT_NONUSER); if (err) { FUSE_UNLOCK(); goto out; } /* We need this here as this slot is used by getnewvnode() */ mp->mnt_stat.f_iosize = PAGE_SIZE; mp->mnt_data = data; data->ref++; data->mp = mp; data->dataflags |= mntopts; data->max_read = max_read; data->daemon_timeout = daemon_timeout; #ifdef XXXIP if (!priv_check(td, PRIV_VFS_FUSE_SYNC_UNMOUNT)) data->dataflags |= FSESS_CAN_SYNC_UNMOUNT; #endif FUSE_UNLOCK(); vfs_getnewfsid(mp); mp->mnt_flag |= MNT_LOCAL; mp->mnt_kern_flag |= MNTK_MPSAFE; if (subtype) { strlcat(mp->mnt_stat.f_fstypename, ".", MFSNAMELEN); strlcat(mp->mnt_stat.f_fstypename, subtype, MFSNAMELEN); } copystr(fspec, mp->mnt_stat.f_mntfromname, MNAMELEN - 1, &len); bzero(mp->mnt_stat.f_mntfromname + len, MNAMELEN - len); FS_DEBUG2G("mp %p: %s\n", mp, mp->mnt_stat.f_mntfromname); /* Now handshaking with daemon */ fuse_internal_send_init(data, td); out: if (err) { FUSE_LOCK(); if (data->mp == mp) { /* * Destroy device only if we acquired reference to * it */ FS_DEBUG("mount failed, destroy device: data=%p mp=%p" " err=%d\n", data, mp, err); data->mp = NULL; fdata_trydestroy(data); } FUSE_UNLOCK(); dev_rel(fdev); } return err; }
static errno_t fuse_vfsop_mount(mount_t mp, __unused vnode_t devvp, user_addr_t udata, vfs_context_t context) { int err = 0; int mntopts = 0; bool mounted = false; uint32_t drandom = 0; uint32_t max_read = ~0; size_t len; fuse_device_t fdev = NULL; struct fuse_data *data = NULL; fuse_mount_args fusefs_args; struct vfsstatfs *vfsstatfsp = vfs_statfs(mp); kern_return_t kr; thread_t init_thread; #if M_OSXFUSE_ENABLE_BIG_LOCK fuse_biglock_t *biglock; #endif fuse_trace_printf_vfsop(); if (vfs_isupdate(mp)) { return ENOTSUP; } err = copyin(udata, &fusefs_args, sizeof(fusefs_args)); if (err) { return EINVAL; } /* * Interesting flags that we can receive from mount or may want to * otherwise forcibly set include: * * MNT_ASYNC * MNT_AUTOMOUNTED * MNT_DEFWRITE * MNT_DONTBROWSE * MNT_IGNORE_OWNERSHIP * MNT_JOURNALED * MNT_NODEV * MNT_NOEXEC * MNT_NOSUID * MNT_NOUSERXATTR * MNT_RDONLY * MNT_SYNCHRONOUS * MNT_UNION */ #if M_OSXFUSE_ENABLE_UNSUPPORTED vfs_setlocklocal(mp); #endif /* M_OSXFUSE_ENABLE_UNSUPPORTED */ /** Option Processing. **/ if (fusefs_args.altflags & FUSE_MOPT_FSTYPENAME) { size_t typenamelen = strlen(fusefs_args.fstypename); if ((typenamelen <= 0) || (typenamelen > FUSE_FSTYPENAME_MAXLEN)) { return EINVAL; } snprintf(vfsstatfsp->f_fstypename, MFSTYPENAMELEN, "%s%s", OSXFUSE_FSTYPENAME_PREFIX, fusefs_args.fstypename); } if ((fusefs_args.daemon_timeout > FUSE_MAX_DAEMON_TIMEOUT) || (fusefs_args.daemon_timeout < FUSE_MIN_DAEMON_TIMEOUT)) { return EINVAL; } if (fusefs_args.altflags & FUSE_MOPT_SPARSE) { mntopts |= FSESS_SPARSE; } if (fusefs_args.altflags & FUSE_MOPT_SLOW_STATFS) { mntopts |= FSESS_SLOW_STATFS; } if (fusefs_args.altflags & FUSE_MOPT_AUTO_CACHE) { mntopts |= FSESS_AUTO_CACHE; } if (fusefs_args.altflags & FUSE_MOPT_AUTO_XATTR) { if (fusefs_args.altflags & FUSE_MOPT_NATIVE_XATTR) { return EINVAL; } mntopts |= FSESS_AUTO_XATTR; } else if (fusefs_args.altflags & FUSE_MOPT_NATIVE_XATTR) { mntopts |= FSESS_NATIVE_XATTR; } if (fusefs_args.altflags & FUSE_MOPT_NO_BROWSE) { vfs_setflags(mp, MNT_DONTBROWSE); } if (fusefs_args.altflags & FUSE_MOPT_JAIL_SYMLINKS) { mntopts |= FSESS_JAIL_SYMLINKS; } /* * Note that unlike Linux, which keeps allow_root in user-space and * passes allow_other in that case to the kernel, we let allow_root * reach the kernel. The 'if' ordering is important here. */ if (fusefs_args.altflags & FUSE_MOPT_ALLOW_ROOT) { int is_member = 0; if ((kauth_cred_ismember_gid(kauth_cred_get(), fuse_admin_group, &is_member) == 0) && is_member) { mntopts |= FSESS_ALLOW_ROOT; } else { IOLog("OSXFUSE: caller not a member of OSXFUSE admin group (%d)\n", fuse_admin_group); return EPERM; } } else if (fusefs_args.altflags & FUSE_MOPT_ALLOW_OTHER) { if (!fuse_allow_other && !fuse_vfs_context_issuser(context)) { int is_member = 0; if ((kauth_cred_ismember_gid(kauth_cred_get(), fuse_admin_group, &is_member) != 0) || !is_member) { return EPERM; } } mntopts |= FSESS_ALLOW_OTHER; } if (fusefs_args.altflags & FUSE_MOPT_NO_APPLEDOUBLE) { mntopts |= FSESS_NO_APPLEDOUBLE; } if (fusefs_args.altflags & FUSE_MOPT_NO_APPLEXATTR) { mntopts |= FSESS_NO_APPLEXATTR; } if ((fusefs_args.altflags & FUSE_MOPT_FSID) && (fusefs_args.fsid != 0)) { fsid_t fsid; mount_t other_mp; uint32_t target_dev; target_dev = FUSE_MAKEDEV(FUSE_CUSTOM_FSID_DEVICE_MAJOR, fusefs_args.fsid); fsid.val[0] = target_dev; fsid.val[1] = FUSE_CUSTOM_FSID_VAL1; other_mp = vfs_getvfs(&fsid); if (other_mp != NULL) { return EPERM; } vfsstatfsp->f_fsid.val[0] = target_dev; vfsstatfsp->f_fsid.val[1] = FUSE_CUSTOM_FSID_VAL1; } else { vfs_getnewfsid(mp); } if (fusefs_args.altflags & FUSE_MOPT_NO_LOCALCACHES) { mntopts |= FSESS_NO_ATTRCACHE; mntopts |= FSESS_NO_READAHEAD; mntopts |= FSESS_NO_UBC; mntopts |= FSESS_NO_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_ATTRCACHE) { mntopts |= FSESS_NO_ATTRCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_READAHEAD) { mntopts |= FSESS_NO_READAHEAD; } if (fusefs_args.altflags & (FUSE_MOPT_NO_UBC | FUSE_MOPT_DIRECT_IO)) { mntopts |= FSESS_NO_UBC; } if (fusefs_args.altflags & FUSE_MOPT_NO_VNCACHE) { mntopts |= FSESS_NO_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NEGATIVE_VNCACHE) { if (mntopts & FSESS_NO_VNCACHE) { return EINVAL; } mntopts |= FSESS_NEGATIVE_VNCACHE; } if (fusefs_args.altflags & FUSE_MOPT_NO_SYNCWRITES) { /* Cannot mix 'nosyncwrites' with 'noubc' or 'noreadahead'. */ if (mntopts & (FSESS_NO_READAHEAD | FSESS_NO_UBC)) { return EINVAL; } mntopts |= FSESS_NO_SYNCWRITES; vfs_clearflags(mp, MNT_SYNCHRONOUS); vfs_setflags(mp, MNT_ASYNC); /* We check for this only if we have nosyncwrites in the first place. */ if (fusefs_args.altflags & FUSE_MOPT_NO_SYNCONCLOSE) { mntopts |= FSESS_NO_SYNCONCLOSE; } } else { vfs_clearflags(mp, MNT_ASYNC); vfs_setflags(mp, MNT_SYNCHRONOUS); } if (mntopts & FSESS_NO_UBC) { /* If no buffer cache, disallow exec from file system. */ vfs_setflags(mp, MNT_NOEXEC); } vfs_setauthopaque(mp); vfs_setauthopaqueaccess(mp); if ((fusefs_args.altflags & FUSE_MOPT_DEFAULT_PERMISSIONS) && (fusefs_args.altflags & FUSE_MOPT_DEFER_PERMISSIONS)) { return EINVAL; } if (fusefs_args.altflags & FUSE_MOPT_DEFAULT_PERMISSIONS) { mntopts |= FSESS_DEFAULT_PERMISSIONS; vfs_clearauthopaque(mp); } if (fusefs_args.altflags & FUSE_MOPT_DEFER_PERMISSIONS) { mntopts |= FSESS_DEFER_PERMISSIONS; } if (fusefs_args.altflags & FUSE_MOPT_EXTENDED_SECURITY) { mntopts |= FSESS_EXTENDED_SECURITY; vfs_setextendedsecurity(mp); } if (fusefs_args.altflags & FUSE_MOPT_LOCALVOL) { mntopts |= FSESS_LOCALVOL; vfs_setflags(mp, MNT_LOCAL); } /* done checking incoming option bits */ err = 0; vfs_setfsprivate(mp, NULL); fdev = fuse_device_get(fusefs_args.rdev); if (!fdev) { return EINVAL; } fuse_device_lock(fdev); drandom = fuse_device_get_random(fdev); if (fusefs_args.random != drandom) { fuse_device_unlock(fdev); IOLog("OSXFUSE: failing mount because of mismatched random\n"); return EINVAL; } data = fuse_device_get_mpdata(fdev); if (!data) { fuse_device_unlock(fdev); return ENXIO; } #if M_OSXFUSE_ENABLE_BIG_LOCK biglock = data->biglock; fuse_biglock_lock(biglock); #endif if (data->mount_state != FM_NOTMOUNTED) { #if M_OSXFUSE_ENABLE_BIG_LOCK fuse_biglock_unlock(biglock); #endif fuse_device_unlock(fdev); return EALREADY; } if (!(data->dataflags & FSESS_OPENED)) { fuse_device_unlock(fdev); err = ENXIO; goto out; } data->mount_state = FM_MOUNTED; OSAddAtomic(1, (SInt32 *)&fuse_mount_count); mounted = true; if (fdata_dead_get(data)) { fuse_device_unlock(fdev); err = ENOTCONN; goto out; } if (!data->daemoncred) { panic("OSXFUSE: daemon found but identity unknown"); } if (fuse_vfs_context_issuser(context) && kauth_cred_getuid(vfs_context_ucred(context)) != kauth_cred_getuid(data->daemoncred)) { fuse_device_unlock(fdev); err = EPERM; goto out; } data->mp = mp; data->fdev = fdev; data->dataflags |= mntopts; data->daemon_timeout.tv_sec = fusefs_args.daemon_timeout; data->daemon_timeout.tv_nsec = 0; if (data->daemon_timeout.tv_sec) { data->daemon_timeout_p = &(data->daemon_timeout); } else { data->daemon_timeout_p = (struct timespec *)0; } data->max_read = max_read; data->fssubtype = fusefs_args.fssubtype; data->mountaltflags = fusefs_args.altflags; data->noimplflags = (uint64_t)0; data->blocksize = fuse_round_size(fusefs_args.blocksize, FUSE_MIN_BLOCKSIZE, FUSE_MAX_BLOCKSIZE); data->iosize = fuse_round_size(fusefs_args.iosize, FUSE_MIN_IOSIZE, FUSE_MAX_IOSIZE); if (data->iosize < data->blocksize) { data->iosize = data->blocksize; } data->userkernel_bufsize = FUSE_DEFAULT_USERKERNEL_BUFSIZE; copystr(fusefs_args.fsname, vfsstatfsp->f_mntfromname, MNAMELEN - 1, &len); bzero(vfsstatfsp->f_mntfromname + len, MNAMELEN - len); copystr(fusefs_args.volname, data->volname, MAXPATHLEN - 1, &len); bzero(data->volname + len, MAXPATHLEN - len); /* previous location of vfs_setioattr() */ vfs_setfsprivate(mp, data); fuse_device_unlock(fdev); /* Send a handshake message to the daemon. */ kr = kernel_thread_start(fuse_internal_init, data, &init_thread); if (kr != KERN_SUCCESS) { IOLog("OSXFUSE: could not start init thread\n"); err = ENOTCONN; } else { thread_deallocate(init_thread); } out: if (err) { vfs_setfsprivate(mp, NULL); fuse_device_lock(fdev); data = fuse_device_get_mpdata(fdev); /* again */ if (mounted) { OSAddAtomic(-1, (SInt32 *)&fuse_mount_count); } if (data) { data->mount_state = FM_NOTMOUNTED; if (!(data->dataflags & FSESS_OPENED)) { #if M_OSXFUSE_ENABLE_BIG_LOCK assert(biglock == data->biglock); fuse_biglock_unlock(biglock); #endif fuse_device_close_final(fdev); /* data is gone now */ } } fuse_device_unlock(fdev); } else { vnode_t fuse_rootvp = NULLVP; err = fuse_vfsop_root(mp, &fuse_rootvp, context); if (err) { goto out; /* go back and follow error path */ } err = vnode_ref(fuse_rootvp); #if M_OSXFUSE_ENABLE_BIG_LOCK /* * Even though fuse_rootvp will not be reclaimed when calling vnode_put * because we incremented its usecount by calling vnode_ref release * biglock just to be safe. */ fuse_biglock_unlock(biglock); #endif /* M_OSXFUSE_ENABLE_BIG_LOCK */ (void)vnode_put(fuse_rootvp); #if M_OSXFUSE_ENABLE_BIG_LOCK fuse_biglock_lock(biglock); #endif if (err) { goto out; /* go back and follow error path */ } else { struct vfsioattr ioattr; vfs_ioattr(mp, &ioattr); ioattr.io_maxreadcnt = ioattr.io_maxwritecnt = data->iosize; ioattr.io_segreadcnt = ioattr.io_segwritecnt = data->iosize / PAGE_SIZE; ioattr.io_maxsegreadsize = ioattr.io_maxsegwritesize = data->iosize; ioattr.io_devblocksize = data->blocksize; vfs_setioattr(mp, &ioattr); } } #if M_OSXFUSE_ENABLE_BIG_LOCK fuse_device_lock(fdev); data = fuse_device_get_mpdata(fdev); /* ...and again */ if(data) { assert(data->biglock == biglock); fuse_biglock_unlock(biglock); } fuse_device_unlock(fdev); #endif return err; }