summaryrefslogtreecommitdiffstats
path: root/fs/ntfs/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs/file.c')
-rw-r--r--fs/ntfs/file.c1257
1 files changed, 1257 insertions, 0 deletions
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
new file mode 100644
index 000000000..8164326b7
--- /dev/null
+++ b/fs/ntfs/file.c
@@ -0,0 +1,1257 @@
+// 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)) &&
+ !NInoWofCompressed(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) || NInoWofCompressed(ni)) {
+ ntfs_warning(
+ vi->i_sb,
+ "Changes in inode size are not supported yet for %s files.",
+ NInoEncrypted(ni) ? "encrypted" : "compressed");
+ return -EOPNOTSUPP;
+ }
+
+ err = inode_newsize_ok(vi, attr->ia_size);
+ if (err)
+ return err;
+
+ inode_dio_wait(vi);
+
+ /*
+ * Serialize with page faults and pagecache instantiation so that
+ * readers cannot observe the size change until the attribute
+ * updates below have completed.
+ */
+ filemap_invalidate_lock(vi->i_mapping);
+ if (attr->ia_size > old_size) {
+ truncate_pagecache(vi, old_size);
+ i_size_write(vi, attr->ia_size);
+ pagecache_isize_extended(vi, old_size, attr->ia_size);
+ } else {
+ truncate_setsize(vi, attr->ia_size);
+ }
+
+ err = ntfs_truncate_vfs(vi, attr->ia_size, old_size);
+ if (err)
+ i_size_write(vi, old_size);
+ filemap_invalidate_unlock(vi->i_mapping);
+
+ 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 ((ia_valid & ATTR_SIZE) &&
+ (NInoCompressed(ni) || NInoEncrypted(ni) ||
+ NInoWofCompressed(ni))) {
+ err = -EOPNOTSUPP;
+ 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;
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_ea_set_wsl_inode(vi, 0, NULL, flags);
+ mutex_unlock(&ni->mrec_lock);
+ if (err)
+ goto out;
+
+ }
+
+ 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) || NInoWofCompressed(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) &&
+ !NInoWofCompressed(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;
+
+ if (NInoWofCompressed(NTFS_I(inode)) &&
+ (whence == SEEK_HOLE || whence == SEEK_DATA))
+ return -EOPNOTSUPP;
+
+ 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)) || NInoWofCompressed(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 int ntfs_expand_for_write(struct ntfs_inode *ni, loff_t end)
+{
+ struct ntfs_volume *vol = ni->vol;
+ loff_t prealloc_size = 0;
+ int err;
+
+ if (end <= ni->data_size)
+ return 0;
+
+ if (NInoCompressed(ni)) {
+ if (end > ni->allocated_size)
+ prealloc_size = round_up(end,
+ ni->itype.compressed.block_size);
+ } else if (end > ni->allocated_size &&
+ end < ni->allocated_size + vol->preallocated_size) {
+ prealloc_size = ni->allocated_size + vol->preallocated_size;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_attr_expand(ni, end, prealloc_size);
+ mutex_unlock(&ni->mrec_lock);
+
+ return err;
+}
+
+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, end;
+ int err;
+ loff_t old_data_size, old_init_size;
+
+ if (NVolShutdown(vol))
+ return -EIO;
+
+ if (NInoWofCompressed(ni))
+ return -EOPNOTSUPP;
+
+ 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;
+ end = pos + count;
+
+ old_data_size = ni->data_size;
+ old_init_size = ni->initialized_size;
+
+ if (end > old_data_size) {
+ ret = ntfs_expand_for_write(ni, end);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (NInoNonResident(ni) && !NInoCompressed(ni) &&
+ end > old_init_size) {
+ ret = ntfs_extend_initialized_size(vi, pos, end);
+ if (ret < 0)
+ goto out;
+ }
+
+ 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);
+ struct address_space *mapping = inode->i_mapping;
+ vm_fault_t ret;
+
+ if (NInoWofCompressed(NTFS_I(inode)))
+ return VM_FAULT_SIGBUS;
+
+ sb_start_pagefault(inode->i_sb);
+ file_update_time(vmf->vma->vm_file);
+
+ /*
+ * Serialize against truncate/fallocate which hold the lock
+ * exclusively while invalidating pagecache and changing extents.
+ */
+ filemap_invalidate_lock_shared(mapping);
+ ret = iomap_page_mkwrite(vmf, &ntfs_page_mkwrite_iomap_ops, NULL);
+ filemap_invalidate_unlock_shared(mapping);
+
+ 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)) || NInoWofCompressed(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);
+ 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)
+{
+ if (NInoWofCompressed(NTFS_I(inode)))
+ return -EOPNOTSUPP;
+
+ 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;
+
+ if (mode & ~(NTFS_FALLOC_FL_SUPPORTED))
+ return -EOPNOTSUPP;
+
+ if (NInoCompressed(ni) || NInoEncrypted(ni) || NInoWofCompressed(ni))
+ 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) || NInoWofCompressed(ni)) {
+ inode_unlock(vi);
+ return -EOPNOTSUPP;
+ }
+
+ inode_dio_wait(vi);
+ /* Take invalidate_lock for all fallocate operations to prevent races */
+ filemap_invalidate_lock(vi->i_mapping);
+
+ 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 (!err && mode == 0 && NInoNonResident(ni) &&
+ offset > old_size) {
+ truncate_pagecache(vi, old_size);
+ pagecache_isize_extended(vi, old_size, offset);
+ }
+
+ filemap_invalidate_unlock(vi->i_mapping);
+
+ if (!err) {
+ 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 = {};