// SPDX-License-Identifier: GPL-2.0-only #include "fuse_i.h" #include #include #include #include #include #include struct fuse_iext { u64 start; u64 end; /* exclusive */ struct fuse_backing *backing; u64 backing_offset; }; void fuse_ext_map_destroy(struct fuse_backing *fb) { unsigned int i; for (i = 0; i < fb->nr_extents; i++) fuse_backing_put(fb->extents[i].backing); kvfree(fb->extents); fb->extents = NULL; fb->nr_extents = 0; } /* * Resolve @offset to an extent. * * A uniform map (all extents the same length, contiguous from offset zero) is * resolved with a single array index. A cyclic map is uniform by construction; * the index wraps and *ncycle is the number of complete cycles before @offset. * Only an irregular map needs a search: a binary search of the sorted array. */ static struct fuse_iext *fuse_find_extent(struct fuse_backing *fb, u64 offset, u64 *ncycle) { struct fuse_iext *ext = fb->extents; unsigned int lo, hi; u64 idx; *ncycle = 0; /* If the extents are uniform... */ if (fb->ext_len) { idx = div64_u64(offset, fb->ext_len); if (fb->cycle_length) { u32 rem; *ncycle = div_u64_rem(idx, fb->nr_extents, &rem); idx = rem; } else if (idx >= fb->nr_extents) { return NULL; } return &ext[idx]; } /* Extents are not uniform: binary search the extents array */ lo = 0; hi = fb->nr_extents; while (lo < hi) { unsigned int mid = lo + (hi - lo) / 2; if (offset < ext[mid].start) hi = mid; else if (offset >= ext[mid].end) lo = mid + 1; else return &ext[mid]; } return NULL; } static int fuse_add_extent(struct fuse_conn *fc, struct fuse_iext *new_fie, struct fuse_extent *ext) { loff_t end; u64 tmp; if (ext->reserved[0] || ext->reserved[1]) return fuse_EIO("reserved fields set"); if (!PAGE_ALIGNED(ext->offset) || !PAGE_ALIGNED(ext->length) || !PAGE_ALIGNED(ext->addr)) return fuse_EIO("not page aligned"); if (!ext->length) return fuse_EIO("zero sized extent"); if (overflows_type(ext->offset, loff_t) || check_add_overflow(ext->offset, ext->length, &end) || check_add_overflow(ext->addr, ext->length, &tmp)) return fuse_EIO("offset overflow"); new_fie->backing = fuse_backing_lookup(fc, ext->backing_id); if (!new_fie->backing) return fuse_EIO("backing not found"); if (new_fie->backing->type != FUSE_BACKING_DAXDEV) { fuse_backing_put(new_fie->backing); new_fie->backing = NULL; return fuse_EIO("backing is not dax device"); } new_fie->start = ext->offset; new_fie->end = end; new_fie->backing_offset = ext->addr; return 0; } static int fuse_iext_cmp(const void *a, const void *b) { const struct fuse_iext *x = a, *y = b; if (x->start < y->start) return -1; return x->start > y->start; } /* * fb has an array of extents; * - Sort it to guarantee they are in file offset order * - Check whether it is uniform (starts at ofs=0, all same size, no gaps) * - Validate if cyclic * * Array-based extent lists have better cache locality than a tree * (packed into cache lines). Uniform simple extent lists and all currently * valid cyclic extent lists can be indexed in O(1) at fault time. And * when search is needed, arrays can be bisected for balanced O(log2 n). */ static int fuse_ext_map_finish(struct fuse_backing *fb, bool cyclic) { struct fuse_iext *ext = fb->extents; unsigned int n = fb->nr_extents; unsigned int i; bool uniform; u64 len; sort(ext, n, sizeof(*ext), fuse_iext_cmp, NULL); len = ext[0].end - ext[0].start; uniform = ext[0].start == 0; /* Check for uniformity */ for (i = 1; i < n; i++) { if (ext[i].start < ext[i - 1].end) return fuse_EIO("overlap"); if (ext[i].start != ext[i - 1].end || ext[i].end - ext[i].start != len) uniform = false; } if (cyclic) { if (!uniform || check_mul_overflow(len, (u64)n, &fb->cycle_length)) return -EINVAL; } if (uniform) fb->ext_len = len; return 0; } static int fuse_ext_map_iomap_begin(struct inode *inode, loff_t offset, loff_t length, unsigned int flags, struct iomap *iomap, struct iomap *srcmap) { struct fuse_backing *fb = fuse_inode_backing(get_fuse_inode(inode)); struct fuse_iext *fie; u64 ncycle, seq_off, addr, tmp; loff_t ext_len; if (!fb || fb->type != FUSE_BACKING_EXTMAP) return fuse_EIO("missing or wrong type backing"); fie = fuse_find_extent(fb, offset, &ncycle); if (!fie) return fuse_EIO("missing mapping"); if (WARN_ON(fie->backing->type != FUSE_BACKING_DAXDEV)) return fuse_EIO("wrong type backing for extent"); if (fie->backing->dax_error) { fuse_make_bad(inode); return fuse_EIO("dax error"); } ext_len = fie->end - fie->start; seq_off = ncycle * fb->cycle_length; addr = fie->backing_offset; if (check_mul_overflow(ncycle, ext_len, &tmp) || check_add_overflow(addr, tmp, &addr)) return fuse_EIO("extent address overflow"); if (check_add_overflow(addr, ext_len, &tmp)) return fuse_EIO("extent end overflow"); iomap->offset = fie->start + seq_off; iomap->addr = addr; iomap->length = ext_len; iomap->dax_dev = fie->backing->dax_dev; iomap->type = IOMAP_MAPPED; iomap->flags = 0; return 0; } static const struct iomap_ops fuse_ext_map_iomap_ops = { .iomap_begin = fuse_ext_map_iomap_begin, }; static vm_fault_t fuse_ext_map_huge_fault(struct vm_fault *vmf, unsigned int order) { struct inode *inode = file_inode(vmf->vma->vm_file); bool write_fault = (vmf->flags & FAULT_FLAG_WRITE) && (vmf->vma->vm_flags & VM_SHARED); vm_fault_t ret; unsigned long pfn; if (!IS_ENABLED(CONFIG_FS_DAX)) return VM_FAULT_SIGBUS; if (WARN_ON_ONCE(!IS_DAX(inode))) return VM_FAULT_SIGBUS; if (write_fault) { sb_start_pagefault(inode->i_sb); file_update_time(vmf->vma->vm_file); } filemap_invalidate_lock_shared(inode->i_mapping); ret = dax_iomap_fault(vmf, order, &pfn, NULL, &fuse_ext_map_iomap_ops); if (ret & VM_FAULT_NEEDDSYNC) ret = dax_finish_sync_fault(vmf, order, pfn); filemap_invalidate_unlock_shared(inode->i_mapping); if (write_fault) sb_end_pagefault(inode->i_sb); return ret; } static vm_fault_t fuse_ext_map_fault(struct vm_fault *vmf) { return fuse_ext_map_huge_fault(vmf, 0); } static const struct vm_operations_struct fuse_ext_map_vm_ops = { .fault = fuse_ext_map_fault, .huge_fault = fuse_ext_map_huge_fault, .page_mkwrite = fuse_ext_map_fault, .pfn_mkwrite = fuse_ext_map_fault, }; static void fuse_rw_clamp(struct kiocb *iocb, struct iov_iter *ubuf) { struct inode *inode = iocb->ki_filp->f_mapping->host; loff_t i_size = i_size_read(inode); loff_t max_count = iocb->ki_pos >= i_size ? 0 : i_size - iocb->ki_pos; if (iov_iter_count(ubuf) > max_count) iov_iter_truncate(ubuf, max_count); } ssize_t fuse_ext_map_read_iter(struct kiocb *iocb, struct iov_iter *to) { struct inode *inode = iocb->ki_filp->f_mapping->host; ssize_t res; guard(rwsem_read)(&inode->i_rwsem); fuse_rw_clamp(iocb, to); if (!iov_iter_count(to)) return 0; if (!IS_ENABLED(CONFIG_FS_DAX)) return -EIO; res = dax_iomap_rw(iocb, to, &fuse_ext_map_iomap_ops); file_accessed(iocb->ki_filp); return res; } ssize_t fuse_ext_map_write_iter(struct kiocb *iocb, struct iov_iter *from) { ssize_t res; res = generic_write_checks(iocb, from); if (res <= 0) return res; fuse_rw_clamp(iocb, from); if (!iov_iter_count(from)) return -ENOSPC; res = kiocb_modified(iocb); if (res) return res; if (!IS_ENABLED(CONFIG_FS_DAX)) return -EIO; return dax_iomap_rw(iocb, from, &fuse_ext_map_iomap_ops); } int fuse_ext_map_mmap(struct file *file, struct vm_area_struct *vma) { file_accessed(file); vma->vm_ops = &fuse_ext_map_vm_ops; vm_flags_set(vma, VM_HUGEPAGE); return 0; } bool fuse_ext_map_is_dax(struct fuse_backing *fb) { if (WARN_ON(fb->type != FUSE_BACKING_EXTMAP || !fb->nr_extents)) return false; return fb->extents[0].backing->type == FUSE_BACKING_DAXDEV; } int fuse_ext_map_populate(struct fuse_conn *fc, struct fuse_notify_backing_map_out *arg, struct fuse_extent *ext) { struct fuse_backing *fb; unsigned int i; int err; if (!arg->num_extents) return fuse_EIO("no extents"); /* Adding extents to an existing extmap backing is not yet supported */ if (!(arg->flags & FUSE_BACKING_MAP_CREATE)) return -EINVAL; fb = kzalloc_obj(*fb); if (!fb) return -ENOMEM; refcount_set(&fb->count, 1); fb->type = FUSE_BACKING_EXTMAP; fb->backing_id = arg->backing_id; err = -ENOMEM; fb->extents = kvcalloc(arg->num_extents, sizeof(*fb->extents), GFP_KERNEL); if (!fb->extents) goto err_put; for (i = 0; i < arg->num_extents; i++) { err = fuse_add_extent(fc, &fb->extents[i], &ext[i]); if (err) goto err_put; fb->nr_extents++; } err = fuse_ext_map_finish(fb, arg->flags & FUSE_BACKING_MAP_CYCLIC); if (err) goto err_put; err = fuse_backing_add_64(fc, fb); if (!err) return 0; err_put: fuse_backing_put(fb); return err; }