| // 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_locked( | |
| ni, 0, ni); | |
| 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 = {}; |