root/fs/ntfs/file.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * NTFS kernel file operations.
 *
 * Copyright (c) 2001-2015 Anton Altaparmakov and Tuxera Inc.
 * Copyright (c) 2025 LG Electronics Co., Ltd.
 */

#include <linux/writeback.h>
#include <linux/blkdev.h>
#include <linux/fs.h>
#include <linux/iomap.h>
#include <linux/uio.h>
#include <linux/posix_acl.h>
#include <linux/posix_acl_xattr.h>
#include <linux/compat.h>
#include <linux/falloc.h>

#include "lcnalloc.h"
#include "ntfs.h"
#include "reparse.h"
#include "ea.h"
#include "iomap.h"
#include "bitmap.h"
#include "volume.h"

#include <linux/filelock.h>

/*
 * ntfs_file_open - called when an inode is about to be opened
 * @vi:         inode to be opened
 * @filp:       file structure describing the inode
 *
 * Limit file size to the page cache limit on architectures where unsigned long
 * is 32-bits. This is the most we can do for now without overflowing the page
 * cache page index. Doing it this way means we don't run into problems because
 * of existing too large files. It would be better to allow the user to read
 * the beginning of the file but I doubt very much anyone is going to hit this
 * check on a 32-bit architecture, so there is no point in adding the extra
 * complexity required to support this.
 *
 * On 64-bit architectures, the check is hopefully optimized away by the
 * compiler.
 *
 * After the check passes, just call generic_file_open() to do its work.
 */
static int ntfs_file_open(struct inode *vi, struct file *filp)
{
        struct ntfs_inode *ni = NTFS_I(vi);

        if (NVolShutdown(ni->vol))
                return -EIO;

        if (sizeof(unsigned long) < 8) {
                if (i_size_read(vi) > MAX_LFS_FILESIZE)
                        return -EOVERFLOW;
        }

        filp->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;

        return generic_file_open(vi, filp);
}

/*
 * Trim preallocated space on file release.
 *
 * When the preallo_size mount option is set (default 64KB), writes extend
 * allocated_size and runlist in units of preallocated size to reduce
 * runlist merge overhead for small writes. This can leave
 * allocated_size > data_size if not all preallocated space is used.
 *
 * We perform the trim here because ->release() is called only when
 * the file is no longer open. At this point, no further writes can occur,
 * so it is safe to reclaim the unused preallocated space.
 *
 * Returns 0 on success, or negative error on failure.
 */
static int ntfs_trim_prealloc(struct inode *vi)
{
        struct ntfs_inode *ni = NTFS_I(vi);
        struct ntfs_volume *vol = ni->vol;
        struct runlist_element *rl;
        s64 aligned_data_size;
        s64 vcn_ds, vcn_tr;
        ssize_t rc;
        int err = 0;

        inode_lock(vi);
        mutex_lock(&ni->mrec_lock);
        down_write(&ni->runlist.lock);

        aligned_data_size = round_up(ni->data_size, vol->cluster_size);
        if (aligned_data_size >= ni->allocated_size)
                goto out_unlock;

        vcn_ds = ntfs_bytes_to_cluster(vol, aligned_data_size);
        vcn_tr = -1;
        rc = ni->runlist.count - 2;
        rl = ni->runlist.rl;

        while (rc >= 0 && rl[rc].lcn == LCN_HOLE && vcn_ds <= rl[rc].vcn) {
                vcn_tr = rl[rc].vcn;
                rc--;
        }

        if (vcn_tr >= 0) {
                err = ntfs_rl_truncate_nolock(vol, &ni->runlist, vcn_tr);
                if (err) {
                        kvfree(ni->runlist.rl);
                        ni->runlist.rl = NULL;
                        ntfs_error(vol->sb, "Preallocated block rollback failed");
                } else {
                        ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr);
                        err = ntfs_attr_update_mapping_pairs(ni, 0);
                        if (err)
                                ntfs_error(vol->sb,
                                           "Failed to rollback mapping pairs for prealloc");
                }
        }

out_unlock:
        up_write(&ni->runlist.lock);
        mutex_unlock(&ni->mrec_lock);
        inode_unlock(vi);

        return err;
}

static int ntfs_file_release(struct inode *vi, struct file *filp)
{
        if (!NInoCompressed(NTFS_I(vi)))
                return ntfs_trim_prealloc(vi);

        return 0;
}

/*
 * ntfs_file_fsync - sync a file to disk
 * @filp:       file to be synced
 * @start:      start offset to be synced
 * @end:        end offset to be synced
 * @datasync:   if non-zero only flush user data and not metadata
 *
 * Data integrity sync of a file to disk.  Used for fsync, fdatasync, and msync
 * system calls.  This function is inspired by fs/buffer.c::file_fsync().
 *
 * If @datasync is false, write the mft record and all associated extent mft
 * records as well as the $DATA attribute and then sync the block device.
 *
 * If @datasync is true and the attribute is non-resident, we skip the writing
 * of the mft record and all associated extent mft records (this might still
 * happen due to the write_inode_now() call).
 *
 * Also, if @datasync is true, we do not wait on the inode to be written out
 * but we always wait on the page cache pages to be written out.
 */
static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end,
                           int datasync)
{
        struct inode *vi = filp->f_mapping->host;
        struct ntfs_inode *ni = NTFS_I(vi);
        struct ntfs_volume *vol = ni->vol;
        int err, ret = 0;
        struct inode *parent_vi, *ia_vi;
        struct ntfs_attr_search_ctx *ctx;

        ntfs_debug("Entering for inode 0x%llx.", ni->mft_no);

        if (NVolShutdown(vol))
                return -EIO;

        err = file_write_and_wait_range(filp, start, end);
        if (err)
                return err;

        if (!datasync || !NInoNonResident(NTFS_I(vi)))
                ret = __ntfs_write_inode(vi, 1);
        write_inode_now(vi, !datasync);

        ctx = ntfs_attr_get_search_ctx(ni, NULL);
        if (!ctx)
                return -ENOMEM;

        mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_CHILD);
        while (!(err = ntfs_attr_lookup(AT_UNUSED, NULL, 0, 0, 0, NULL, 0, ctx))) {
                if (ctx->attr->type == AT_FILE_NAME) {
                        struct file_name_attr *fn = (struct file_name_attr *)((u8 *)ctx->attr +
                                        le16_to_cpu(ctx->attr->data.resident.value_offset));

                        parent_vi = ntfs_iget(vi->i_sb, MREF_LE(fn->parent_directory));
                        if (IS_ERR(parent_vi))
                                continue;
                        mutex_lock_nested(&NTFS_I(parent_vi)->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
                        ia_vi = ntfs_index_iget(parent_vi, I30, 4);
                        mutex_unlock(&NTFS_I(parent_vi)->mrec_lock);
                        if (IS_ERR(ia_vi)) {
                                iput(parent_vi);
                                continue;
                        }
                        write_inode_now(ia_vi, 1);
                        iput(ia_vi);
                        write_inode_now(parent_vi, 1);
                        iput(parent_vi);
                } else if (ctx->attr->non_resident) {
                        struct inode *attr_vi;
                        __le16 *name;

                        name = (__le16 *)((u8 *)ctx->attr + le16_to_cpu(ctx->attr->name_offset));
                        if (ctx->attr->type == AT_DATA && ctx->attr->name_length == 0)
                                continue;

                        attr_vi = ntfs_attr_iget(vi, ctx->attr->type,
                                                 name, ctx->attr->name_length);
                        if (IS_ERR(attr_vi))
                                continue;
                        spin_lock(&attr_vi->i_lock);
                        if (inode_state_read_once(attr_vi) & I_DIRTY_PAGES) {
                                spin_unlock(&attr_vi->i_lock);
                                filemap_write_and_wait(attr_vi->i_mapping);
                        } else
                                spin_unlock(&attr_vi->i_lock);
                        iput(attr_vi);
                }
        }
        mutex_unlock(&ni->mrec_lock);
        ntfs_attr_put_search_ctx(ctx);

        write_inode_now(vol->mftbmp_ino, 1);
        down_write(&vol->lcnbmp_lock);
        write_inode_now(vol->lcnbmp_ino, 1);
        up_write(&vol->lcnbmp_lock);
        write_inode_now(vol->mft_ino, 1);

        /*
         * NOTE: If we were to use mapping->private_list (see ext2 and
         * fs/buffer.c) for dirty blocks then we could optimize the below to be
         * sync_mapping_buffers(vi->i_mapping).
         */
        err = sync_blockdev(vi->i_sb->s_bdev);
        if (unlikely(err && !ret))
                ret = err;
        if (likely(!ret))
                ntfs_debug("Done.");
        else
                ntfs_warning(vi->i_sb,
                                "Failed to f%ssync inode 0x%llx.  Error %u.",
                                datasync ? "data" : "", ni->mft_no, -ret);
        if (!ret)
                blkdev_issue_flush(vi->i_sb->s_bdev);
        return ret;
}

static int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
{
        struct ntfs_inode *ni = NTFS_I(vi);
        int err;
        loff_t old_size = vi->i_size;

        if (NInoCompressed(ni) || NInoEncrypted(ni)) {
                ntfs_warning(vi->i_sb,
                        "Changes in inode size are not supported yet for %s files, ignoring.",
                        NInoCompressed(ni) ? "compressed" : "encrypted");
                return -EOPNOTSUPP;
        }

        err = inode_newsize_ok(vi, attr->ia_size);
        if (err)
                return err;

        inode_dio_wait(vi);
        truncate_setsize(vi, attr->ia_size);
        err = ntfs_truncate_vfs(vi, attr->ia_size, old_size);
        if (err) {
                i_size_write(vi, old_size);
                return err;
        }

        if (NInoNonResident(ni) && attr->ia_size > old_size &&
            old_size % PAGE_SIZE != 0) {
                loff_t len = min_t(loff_t,
                                round_up(old_size, PAGE_SIZE) - old_size,
                                attr->ia_size - old_size);
                err = iomap_zero_range(vi, old_size, len,
                                NULL, &ntfs_seek_iomap_ops,
                                &ntfs_iomap_folio_ops, NULL);
        }

        return err;
}

/*
 * ntfs_setattr
 *
 * Called from notify_change() when an attribute is being changed.
 *
 * NOTE: Changes in inode size are not supported yet for compressed or
 * encrypted files.
 */
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                 struct iattr *attr)
{
        struct inode *vi = d_inode(dentry);
        int err;
        unsigned int ia_valid = attr->ia_valid;
        struct ntfs_inode *ni = NTFS_I(vi);
        struct ntfs_volume *vol = ni->vol;

        if (NVolShutdown(vol))
                return -EIO;

        err = setattr_prepare(idmap, dentry, attr);
        if (err)
                goto out;

        if (!(vol->vol_flags & VOLUME_IS_DIRTY))
                ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);

        if (ia_valid & ATTR_SIZE) {
                err = ntfs_setattr_size(vi, attr);
                if (err)
                        goto out;

                ia_valid |= ATTR_MTIME | ATTR_CTIME;
        }

        setattr_copy(idmap, vi, attr);

        if (vol->sb->s_flags & SB_POSIXACL && !S_ISLNK(vi->i_mode)) {
                err = posix_acl_chmod(idmap, dentry, vi->i_mode);
                if (err)
                        goto out;
        }

        if (0222 & vi->i_mode)
                ni->flags &= ~FILE_ATTR_READONLY;
        else
                ni->flags |= FILE_ATTR_READONLY;

        if (ia_valid & (ATTR_UID | ATTR_GID | ATTR_MODE)) {
                unsigned int flags = 0;

                if (ia_valid & ATTR_UID)
                        flags |= NTFS_EA_UID;
                if (ia_valid & ATTR_GID)
                        flags |= NTFS_EA_GID;
                if (ia_valid & ATTR_MODE)
                        flags |= NTFS_EA_MODE;

                if (S_ISDIR(vi->i_mode))
                        vi->i_mode &= ~vol->dmask;
                else
                        vi->i_mode &= ~vol->fmask;

                mutex_lock(&ni->mrec_lock);
                ntfs_ea_set_wsl_inode(vi, 0, NULL, flags);
                mutex_unlock(&ni->mrec_lock);
        }

        mark_inode_dirty(vi);
out:
        return err;
}

int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
                struct kstat *stat, unsigned int request_mask,
                unsigned int query_flags)
{
        struct inode *inode = d_backing_inode(path->dentry);
        struct ntfs_inode *ni = NTFS_I(inode);

        generic_fillattr(idmap, request_mask, inode, stat);

        stat->blksize = NTFS_SB(inode->i_sb)->cluster_size;
        stat->blocks = (((u64)NTFS_I(inode)->i_dealloc_clusters <<
                        NTFS_SB(inode->i_sb)->cluster_size_bits) >> 9) + inode->i_blocks;
        stat->result_mask |= STATX_BTIME;
        stat->btime = NTFS_I(inode)->i_crtime;

        if (NInoCompressed(ni))
                stat->attributes |= STATX_ATTR_COMPRESSED;

        if (NInoEncrypted(ni))
                stat->attributes |= STATX_ATTR_ENCRYPTED;

        if (inode->i_flags & S_IMMUTABLE)
                stat->attributes |= STATX_ATTR_IMMUTABLE;

        if (inode->i_flags & S_APPEND)
                stat->attributes |= STATX_ATTR_APPEND;

        stat->attributes_mask |= STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED |
                                 STATX_ATTR_IMMUTABLE | STATX_ATTR_APPEND;

        /*
         * If it's a compressed or encrypted file, NTFS currently
         * does not support DIO. For normal files, we report the bdev
         * logical block size.
         */
        if (request_mask & STATX_DIOALIGN && S_ISREG(inode->i_mode)) {
                unsigned int align =
                        bdev_logical_block_size(inode->i_sb->s_bdev);

                stat->result_mask |= STATX_DIOALIGN;
                if (!NInoCompressed(ni) && !NInoEncrypted(ni)) {
                        stat->dio_mem_align = align;
                        stat->dio_offset_align = align;
                }
        }

        return 0;
}

static loff_t ntfs_file_llseek(struct file *file, loff_t offset, int whence)
{
        struct inode *inode = file->f_mapping->host;

        switch (whence) {
        case SEEK_HOLE:
                inode_lock_shared(inode);
                offset = iomap_seek_hole(inode, offset, &ntfs_seek_iomap_ops);
                inode_unlock_shared(inode);
                break;
        case SEEK_DATA:
                inode_lock_shared(inode);
                offset = iomap_seek_data(inode, offset, &ntfs_seek_iomap_ops);
                inode_unlock_shared(inode);
                break;
        default:
                return generic_file_llseek_size(file, offset, whence,
                                                inode->i_sb->s_maxbytes,
                                                i_size_read(inode));
        }
        if (offset < 0)
                return offset;
        return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
}

static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
        struct inode *vi = file_inode(iocb->ki_filp);
        struct super_block *sb = vi->i_sb;
        ssize_t ret;

        if (NVolShutdown(NTFS_SB(sb)))
                return -EIO;

        if (NInoCompressed(NTFS_I(vi)) && iocb->ki_flags & IOCB_DIRECT)
                return -EOPNOTSUPP;

        inode_lock_shared(vi);

        if (iocb->ki_flags & IOCB_DIRECT) {
                size_t count = iov_iter_count(to);

                if ((iocb->ki_pos | count) & (sb->s_blocksize - 1)) {
                        ret = -EINVAL;
                        goto inode_unlock;
                }

                file_accessed(iocb->ki_filp);
                ret = iomap_dio_rw(iocb, to, &ntfs_read_iomap_ops, NULL, 0,
                                NULL, 0);
        } else {
                ret = generic_file_read_iter(iocb, to);
        }

inode_unlock:
        inode_unlock_shared(vi);

        return ret;
}

static int ntfs_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
                int error, unsigned int flags)
{
        struct inode *inode = file_inode(iocb->ki_filp);

        if (error)
                return error;

        if (size) {
                if (i_size_read(inode) < iocb->ki_pos + size) {
                        i_size_write(inode, iocb->ki_pos + size);
                        mark_inode_dirty(inode);
                }
        }

        return 0;
}

static const struct iomap_dio_ops ntfs_write_dio_ops = {
        .end_io                 = ntfs_file_write_dio_end_io,
};

static ssize_t ntfs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
        ssize_t ret;

        ret = iomap_dio_rw(iocb, from, &ntfs_dio_iomap_ops,
                        &ntfs_write_dio_ops, 0, NULL, 0);
        if (ret == -ENOTBLK)
                ret = 0;
        else if (ret < 0)
                goto out;

        if (iov_iter_count(from)) {
                loff_t offset, end;
                ssize_t written;
                int ret2;

                offset = iocb->ki_pos;
                iocb->ki_flags &= ~IOCB_DIRECT;
                written = iomap_file_buffered_write(iocb, from,
                                &ntfs_write_iomap_ops, &ntfs_iomap_folio_ops,
                                NULL);
                if (written < 0) {
                        ret = written;
                        goto out;
                }

                ret += written;
                end = iocb->ki_pos + written - 1;
                ret2 = filemap_write_and_wait_range(iocb->ki_filp->f_mapping,
                                offset, end);
                if (ret2) {
                        ret = -EIO;
                        goto out;
                }
                invalidate_mapping_pages(iocb->ki_filp->f_mapping,
                                         offset >> PAGE_SHIFT,
                                         end >> PAGE_SHIFT);
        }

out:
        return ret;
}

static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
        struct file *file = iocb->ki_filp;
        struct inode *vi = file->f_mapping->host;
        struct ntfs_inode *ni = NTFS_I(vi);
        struct ntfs_volume *vol = ni->vol;
        ssize_t ret;
        ssize_t count;
        loff_t pos;
        int err;
        loff_t old_data_size, old_init_size;

        if (NVolShutdown(vol))
                return -EIO;

        if (NInoEncrypted(ni)) {
                ntfs_error(vi->i_sb, "Writing for %s files is not supported yet",
                           NInoCompressed(ni) ? "Compressed" : "Encrypted");
                return -EOPNOTSUPP;
        }

        if (NInoCompressed(ni) && iocb->ki_flags & IOCB_DIRECT)
                return -EOPNOTSUPP;

        if (iocb->ki_flags & IOCB_NOWAIT) {
                if (!inode_trylock(vi))
                        return -EAGAIN;
        } else
                inode_lock(vi);

        ret = generic_write_checks(iocb, from);
        if (ret <= 0)
                goto out_lock;

        err = file_modified(iocb->ki_filp);
        if (err) {
                ret = err;
                goto out_lock;
        }

        if (!(vol->vol_flags & VOLUME_IS_DIRTY))
                ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);

        pos = iocb->ki_pos;
        count = ret;

        old_data_size = ni->data_size;
        old_init_size = ni->initialized_size;

        if (NInoNonResident(ni) && NInoCompressed(ni)) {
                ret = ntfs_compress_write(ni, pos, count, from);
                if (ret > 0)
                        iocb->ki_pos += ret;
                goto out;
        }

        if (NInoNonResident(ni) && iocb->ki_flags & IOCB_DIRECT)
                ret = ntfs_dio_write_iter(iocb, from);
        else
                ret = iomap_file_buffered_write(iocb, from, &ntfs_write_iomap_ops,
                                &ntfs_iomap_folio_ops, NULL);
out:
        if (ret < 0 && ret != -EIOCBQUEUED) {
                if (ni->initialized_size != old_init_size) {
                        mutex_lock(&ni->mrec_lock);
                        ntfs_attr_set_initialized_size(ni, old_init_size);
                        mutex_unlock(&ni->mrec_lock);
                }
                if (ni->data_size != old_data_size) {
                        truncate_setsize(vi, old_data_size);
                        ntfs_attr_truncate(ni, old_data_size);
                }
        }
out_lock:
        inode_unlock(vi);
        if (ret > 0)
                ret = generic_write_sync(iocb, ret);
        return ret;
}

static vm_fault_t ntfs_filemap_page_mkwrite(struct vm_fault *vmf)
{
        struct inode *inode = file_inode(vmf->vma->vm_file);
        vm_fault_t ret;

        sb_start_pagefault(inode->i_sb);
        file_update_time(vmf->vma->vm_file);

        ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL);
        sb_end_pagefault(inode->i_sb);
        return ret;
}

static const struct vm_operations_struct ntfs_file_vm_ops = {
        .fault          = filemap_fault,
        .map_pages      = filemap_map_pages,
        .page_mkwrite   = ntfs_filemap_page_mkwrite,
};

static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
{
        struct file *file = desc->file;
        struct inode *inode = file_inode(file);

        if (NVolShutdown(NTFS_SB(file->f_mapping->host->i_sb)))
                return -EIO;

        if (NInoCompressed(NTFS_I(inode)))
                return -EOPNOTSUPP;

        if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) {
                struct inode *inode = file_inode(file);
                loff_t from, to;
                int err;

                from = ((loff_t)desc->pgoff << PAGE_SHIFT);
                to = min_t(loff_t, i_size_read(inode),
                           from + desc->end - desc->start);

                if (NTFS_I(inode)->initialized_size < to) {
                        err = ntfs_extend_initialized_size(inode, to, to, false);
                        if (err)
                                return err;
                }
        }


        file_accessed(file);
        desc->vm_ops = &ntfs_file_vm_ops;
        return 0;
}

static int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
                u64 start, u64 len)
{
        return iomap_fiemap(inode, fieinfo, start, len, &ntfs_read_iomap_ops);
}

static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode,
                struct delayed_call *done)
{
        struct ntfs_inode *ni = NTFS_I(inode);
        char *target;
        int err;

        if (!dentry)
                return ERR_PTR(-ECHILD);

        if (!ni->target)
                return ERR_PTR(-EINVAL);

        if (ni->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT ||
            (ni->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
             !(ni->reparse_flags & cpu_to_le32(SYMLINK_FLAG_RELATIVE)))) {
                if (NVolNativeSymlinkRel(ni->vol)) {
                        err = ntfs_translate_symlink_path(dentry, ni->target, &target);
                        if (err < 0)
                                return ERR_PTR(err);
                        set_delayed_call(done, kfree_link, target);
                        return target;
                }
        }

        return ni->target;
}

static ssize_t ntfs_file_splice_read(struct file *in, loff_t *ppos,
                struct pipe_inode_info *pipe, size_t len, unsigned int flags)
{
        if (NVolShutdown(NTFS_SB(in->f_mapping->host->i_sb)))
                return -EIO;

        return filemap_splice_read(in, ppos, pipe, len, flags);
}

static int ntfs_ioctl_shutdown(struct super_block *sb, unsigned long arg)
{
        u32 flags;

        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;

        if (get_user(flags, (__u32 __user *)arg))
                return -EFAULT;

        return ntfs_force_shutdown(sb, flags);
}

static int ntfs_ioctl_get_volume_label(struct file *filp, unsigned long arg)
{
        struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb);
        char __user *buf = (char __user *)arg;
        char label[FSLABEL_MAX];
        ssize_t len;

        mutex_lock(&vol->volume_label_lock);
        if (!vol->volume_label) {
                label[0] = '\0';
                len = 0;
        } else {
                len = strscpy(label, vol->volume_label, sizeof(label));
                if (len == -E2BIG)
                        len = FSLABEL_MAX - 1;
        }
        mutex_unlock(&vol->volume_label_lock);

        if (copy_to_user(buf, label, len + 1))
                return -EFAULT;
        return 0;
}

static int ntfs_ioctl_set_volume_label(struct file *filp, unsigned long arg)
{
        struct ntfs_volume *vol = NTFS_SB(file_inode(filp)->i_sb);
        char *label;
        int ret;

        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;

        label = strndup_user((const char __user *)arg, FSLABEL_MAX);
        if (IS_ERR(label))
                return PTR_ERR(label);

        ret = mnt_want_write_file(filp);
        if (ret)
                goto out;

        ret = ntfs_write_volume_label(vol, label);
        mnt_drop_write_file(filp);
out:
        kfree(label);
        return ret;
}

static int ntfs_ioctl_fitrim(struct ntfs_volume *vol, unsigned long arg)
{
        struct fstrim_range __user *user_range;
        struct fstrim_range range;
        struct block_device *dev;
        int err;

        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;

        dev = vol->sb->s_bdev;
        if (!bdev_max_discard_sectors(dev))
                return -EOPNOTSUPP;

        user_range = (struct fstrim_range __user *)arg;
        if (copy_from_user(&range, user_range, sizeof(range)))
                return -EFAULT;

        if (range.len == 0)
                return -EINVAL;

        if (range.len < vol->cluster_size)
                return -EINVAL;

        range.minlen = max_t(u32, range.minlen, bdev_discard_granularity(dev));

        err = ntfs_trim_fs(vol, &range);
        if (err < 0)
                return err;

        if (copy_to_user(user_range, &range, sizeof(range)))
                return -EFAULT;

        return 0;
}

long ntfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
        switch (cmd) {
        case FS_IOC_SHUTDOWN:
                return ntfs_ioctl_shutdown(file_inode(filp)->i_sb, arg);
        case FS_IOC_GETFSLABEL:
                return ntfs_ioctl_get_volume_label(filp, arg);
        case FS_IOC_SETFSLABEL:
                return ntfs_ioctl_set_volume_label(filp, arg);
        case FITRIM:
                return ntfs_ioctl_fitrim(NTFS_SB(file_inode(filp)->i_sb), arg);
        default:
                return -ENOTTY;
        }
}

#ifdef CONFIG_COMPAT
long ntfs_compat_ioctl(struct file *filp, unsigned int cmd,
                unsigned long arg)
{
        return ntfs_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
#endif

static int ntfs_allocate_range(struct ntfs_inode *ni, int mode, loff_t offset,
                loff_t len)
{
        struct inode *vi = VFS_I(ni);
        struct ntfs_volume *vol = ni->vol;
        s64 need_space;
        loff_t old_size, new_size;
        s64 start_vcn, end_vcn;
        int err;

        old_size = i_size_read(vi);
        new_size = max_t(loff_t, old_size, offset + len);
        start_vcn = ntfs_bytes_to_cluster(vol, offset);
        end_vcn = ntfs_bytes_to_cluster(vol, offset + len - 1) + 1;

        err = inode_newsize_ok(vi, new_size);
        if (err)
                goto out;

        need_space = ntfs_bytes_to_cluster(vol, ni->allocated_size);
        if (need_space > start_vcn)
                need_space = end_vcn - need_space;
        else
                need_space = end_vcn - start_vcn;
        if (need_space > 0 &&
            need_space > (atomic64_read(&vol->free_clusters) -
                          atomic64_read(&vol->dirty_clusters))) {
                err = -ENOSPC;
                goto out;
        }

        err = ntfs_attr_fallocate(ni, offset, len,
                        mode & FALLOC_FL_KEEP_SIZE ? true : false);

        if (!(mode & FALLOC_FL_KEEP_SIZE) && new_size != old_size)
                i_size_write(vi, ni->data_size);
out:
        return err;
}

static int ntfs_punch_hole(struct ntfs_inode *ni, int mode, loff_t offset,
                loff_t len)
{
        struct ntfs_volume *vol = ni->vol;
        struct inode *vi = VFS_I(ni);
        loff_t end_offset;
        s64 start_vcn, end_vcn;
        int err = 0;

        loff_t offset_down = round_down(offset, max_t(unsigned int,
                                vol->cluster_size, PAGE_SIZE));

        if (NVolDisableSparse(vol)) {
                err = -EOPNOTSUPP;
                goto out;
        }

        if (offset >= ni->data_size)
                goto out;

        if (offset + len > ni->data_size)
                end_offset = ni->data_size;
        else
                end_offset = offset + len;

        err = filemap_write_and_wait_range(vi->i_mapping, offset_down, LLONG_MAX);
        if (err)
                goto out;
        truncate_pagecache(vi, offset_down);

        start_vcn = ntfs_bytes_to_cluster(vol, offset);
        end_vcn = ntfs_bytes_to_cluster(vol, end_offset - 1) + 1;

        if (offset & vol->cluster_size_mask) {
                if (offset < ni->initialized_size) {
                        loff_t to;

                        to = min_t(loff_t,
                                   ntfs_cluster_to_bytes(vol, start_vcn + 1),
                                   end_offset);
                        err = iomap_zero_range(vi, offset, to - offset,
                                               NULL, &ntfs_seek_iomap_ops,
                                               &ntfs_iomap_folio_ops, NULL);
                        if (err < 0)
                                goto out;
                }
                if (end_vcn - start_vcn == 1)
                        goto out;
                start_vcn++;
        }

        if (end_offset & vol->cluster_size_mask) {
                loff_t from;

                from = ntfs_cluster_to_bytes(vol, end_vcn - 1);
                if (from < ni->initialized_size) {
                        err = iomap_zero_range(vi, from, end_offset - from,
                                               NULL, &ntfs_seek_iomap_ops,
                                               &ntfs_iomap_folio_ops, NULL);
                        if (err < 0)
                                goto out;
                }
                if (end_vcn - start_vcn == 1)
                        goto out;
                end_vcn--;
        }

        mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
        err = ntfs_non_resident_attr_punch_hole(ni, start_vcn,
                        end_vcn - start_vcn);
        mutex_unlock(&ni->mrec_lock);
out:
        return err;
}

static int ntfs_collapse_range(struct ntfs_inode *ni, loff_t offset, loff_t len)
{
        struct ntfs_volume *vol = ni->vol;
        struct inode *vi = VFS_I(ni);
        loff_t old_size, new_size;
        s64 start_vcn, end_vcn;
        int err;

        loff_t offset_down = round_down(offset,
                        max_t(unsigned long, vol->cluster_size, PAGE_SIZE));

        if ((offset & vol->cluster_size_mask) ||
            (len & vol->cluster_size_mask) ||
            offset >= ni->allocated_size) {
                err = -EINVAL;
                goto out;
        }

        old_size = i_size_read(vi);
        start_vcn = ntfs_bytes_to_cluster(vol, offset);
        end_vcn = ntfs_bytes_to_cluster(vol, offset + len - 1) + 1;

        if (ntfs_cluster_to_bytes(vol, end_vcn) > ni->allocated_size)
                end_vcn = (round_up(ni->allocated_size - 1,
                           vol->cluster_size) >> vol->cluster_size_bits) + 1;
        new_size = old_size - ntfs_cluster_to_bytes(vol, end_vcn - start_vcn);
        if (new_size < 0)
                new_size = 0;
        err = filemap_write_and_wait_range(vi->i_mapping,
                        offset_down, LLONG_MAX);
        if (err)
                goto out;

        truncate_pagecache(vi, offset_down);

        mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
        err = ntfs_non_resident_attr_collapse_range(ni, start_vcn,
                        end_vcn - start_vcn);
        mutex_unlock(&ni->mrec_lock);

        if (new_size != old_size)
                i_size_write(vi, ni->data_size);
out:
        return err;
}

static int ntfs_insert_range(struct ntfs_inode *ni, loff_t offset, loff_t len)
{
        struct ntfs_volume *vol = ni->vol;
        struct inode *vi = VFS_I(ni);
        loff_t offset_down = round_down(offset,
                        max_t(unsigned long, vol->cluster_size, PAGE_SIZE));
        loff_t alloc_size, end_offset = offset + len;
        loff_t old_size, new_size;
        s64 start_vcn, end_vcn;
        int err;

        if (NVolDisableSparse(vol)) {
                err = -EOPNOTSUPP;
                goto out;
        }

        if ((offset & vol->cluster_size_mask) ||
            (len & vol->cluster_size_mask) ||
             offset >= ni->allocated_size) {
                err = -EINVAL;
                goto out;
        }

        old_size = i_size_read(vi);
        start_vcn = ntfs_bytes_to_cluster(vol, offset);
        end_vcn = ntfs_bytes_to_cluster(vol, end_offset - 1) + 1;

        new_size = old_size + ntfs_cluster_to_bytes(vol, end_vcn - start_vcn);
        alloc_size = ni->allocated_size +
                ntfs_cluster_to_bytes(vol, end_vcn - start_vcn);
        if (alloc_size < 0) {
                err = -EFBIG;
                goto out;
        }
        err = inode_newsize_ok(vi, alloc_size);
        if (err)
                goto out;

        err = filemap_write_and_wait_range(vi->i_mapping,
                        offset_down, LLONG_MAX);
        if (err)
                goto out;

        truncate_pagecache(vi, offset_down);

        mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
        err = ntfs_non_resident_attr_insert_range(ni, start_vcn,
                        end_vcn - start_vcn);
        mutex_unlock(&ni->mrec_lock);

        if (new_size != old_size)
                i_size_write(vi, ni->data_size);
out:
        return err;
}

#define NTFS_FALLOC_FL_SUPPORTED                                        \
                (FALLOC_FL_ALLOCATE_RANGE | FALLOC_FL_KEEP_SIZE |       \
                 FALLOC_FL_INSERT_RANGE | FALLOC_FL_PUNCH_HOLE |        \
                 FALLOC_FL_COLLAPSE_RANGE)

static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
{
        struct inode *vi = file_inode(file);
        struct ntfs_inode *ni = NTFS_I(vi);
        struct ntfs_volume *vol = ni->vol;
        int err = 0;
        loff_t old_size;
        bool map_locked = false;

        if (mode & ~(NTFS_FALLOC_FL_SUPPORTED))
                return -EOPNOTSUPP;

        if (!NVolFreeClusterKnown(vol))
                wait_event(vol->free_waitq, NVolFreeClusterKnown(vol));

        if ((ni->vol->mft_zone_end - ni->vol->mft_zone_start) == 0)
                return -ENOSPC;

        if (NInoNonResident(ni) && !NInoFullyMapped(ni)) {
                down_write(&ni->runlist.lock);
                err = ntfs_attr_map_whole_runlist(ni);
                up_write(&ni->runlist.lock);
                if (err)
                        return err;
        }

        if (!(vol->vol_flags & VOLUME_IS_DIRTY)) {
                err = ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
                if (err)
                        return err;
        }

        old_size = i_size_read(vi);

        inode_lock(vi);
        if (NInoCompressed(ni) || NInoEncrypted(ni)) {
                err = -EOPNOTSUPP;
                goto out;
        }

        inode_dio_wait(vi);
        if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_COLLAPSE_RANGE |
                    FALLOC_FL_INSERT_RANGE)) {
                filemap_invalidate_lock(vi->i_mapping);
                map_locked = true;
        }

        switch (mode & FALLOC_FL_MODE_MASK) {
        case FALLOC_FL_ALLOCATE_RANGE:
        case FALLOC_FL_KEEP_SIZE:
                err = ntfs_allocate_range(ni, mode, offset, len);
                break;
        case FALLOC_FL_PUNCH_HOLE:
                err = ntfs_punch_hole(ni, mode, offset, len);
                break;
        case FALLOC_FL_COLLAPSE_RANGE:
                err = ntfs_collapse_range(ni, offset, len);
                break;
        case FALLOC_FL_INSERT_RANGE:
                err = ntfs_insert_range(ni, offset, len);
                break;
        default:
                err = -EOPNOTSUPP;
        }

        if (err)
                goto out;

        err = file_modified(file);
out:
        if (map_locked)
                filemap_invalidate_unlock(vi->i_mapping);
        if (!err) {
                if (mode == 0 && NInoNonResident(ni) &&
                    offset > old_size && old_size % PAGE_SIZE != 0) {
                        loff_t len = min_t(loff_t,
                                           round_up(old_size, PAGE_SIZE) - old_size,
                                           offset - old_size);
                        err = iomap_zero_range(vi, old_size, len, NULL,
                                               &ntfs_seek_iomap_ops,
                                               &ntfs_iomap_folio_ops, NULL);
                }
                NInoSetFileNameDirty(ni);
                inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
                mark_inode_dirty(vi);
        }

        inode_unlock(vi);
        return err;
}

const struct file_operations ntfs_file_ops = {
        .llseek         = ntfs_file_llseek,
        .read_iter      = ntfs_file_read_iter,
        .write_iter     = ntfs_file_write_iter,
        .fsync          = ntfs_file_fsync,
        .mmap_prepare   = ntfs_file_mmap_prepare,
        .open           = ntfs_file_open,
        .release        = ntfs_file_release,
        .splice_read    = ntfs_file_splice_read,
        .splice_write   = iter_file_splice_write,
        .unlocked_ioctl = ntfs_ioctl,
#ifdef CONFIG_COMPAT
        .compat_ioctl   = ntfs_compat_ioctl,
#endif
        .fallocate      = ntfs_fallocate,
        .setlease       = generic_setlease,
};

const struct inode_operations ntfs_file_inode_ops = {
        .setattr        = ntfs_setattr,
        .getattr        = ntfs_getattr,
        .listxattr      = ntfs_listxattr,
        .get_acl        = ntfs_get_acl,
        .set_acl        = ntfs_set_acl,
        .fiemap         = ntfs_fiemap,
};

const struct inode_operations ntfs_symlink_inode_operations = {
        .get_link       = ntfs_get_link,
        .setattr        = ntfs_setattr,
        .listxattr      = ntfs_listxattr,
};

const struct inode_operations ntfs_special_inode_operations = {
        .setattr        = ntfs_setattr,
        .getattr        = ntfs_getattr,
        .listxattr      = ntfs_listxattr,
        .get_acl        = ntfs_get_acl,
        .set_acl        = ntfs_set_acl,
};

const struct file_operations ntfs_empty_file_ops = {};

const struct inode_operations ntfs_empty_inode_ops = {};