]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fs/ntfs3: implement iomap-based file operations
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Fri, 26 Dec 2025 13:45:19 +0000 (16:45 +0300)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Mon, 29 Dec 2025 13:33:32 +0000 (13:33 +0000)
This patch modifies the ntfs3 driver by replacing the buffer_head-based
operations with the iomap ones.

Implementation details:
- Implements core iomap operations (ntfs_iomap_begin/end) for block mapping:
    Proper handling of resident attributes via IOMAP_INLINE.
    Support for sparse files through IOMAP_HOLE semantics.
    Correct unwritten extent handling for zeroing operations.
- Replaces custom implementations with standardized iomap helpers:
    Converts buffered reads to use iomap_read_folio and iomap_readahead.
    Implements iomap_file_buffered_write for write operations.
    Uses iomap_dio_rw for direct I/O paths.
    Migrates zero range operations to iomap_zero_range.
- Preserves special handling paths for compressed files
- Implements proper EOF/valid data size management during writes

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/attrib.c
fs/ntfs3/file.c
fs/ntfs3/frecord.c
fs/ntfs3/fslog.c
fs/ntfs3/fsntfs.c
fs/ntfs3/inode.c
fs/ntfs3/ntfs_fs.h
fs/ntfs3/super.c

index 3e188d6c229f6332ffba4ef8c0079fc566135313..aa745fb226f513229f6dbd4562b78554aabef5d3 100644 (file)
@@ -166,6 +166,12 @@ int attr_allocate_clusters(struct ntfs_sb_info *sbi, struct runs_tree *run,
                        continue;
                }
 
+               if (err == -ENOSPC && new_len && vcn - vcn0) {
+                       /* Keep already allocated clusters. */
+                       *alen = vcn - vcn0;
+                       return 0;
+               }
+
                if (err)
                        goto out;
 
@@ -886,7 +892,7 @@ bad_inode:
  *  - new allocated clusters are zeroed via blkdev_issue_zeroout.
  */
 int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
-                       CLST *len, bool *new, bool zero)
+                       CLST *len, bool *new, bool zero, void **res)
 {
        int err = 0;
        struct runs_tree *run = &ni->file.run;
@@ -903,6 +909,8 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
 
        if (new)
                *new = false;
+       if (res)
+               *res = NULL;
 
        /* Try to find in cache. */
        down_read(&ni->file.run_lock);
@@ -939,8 +947,15 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
        }
 
        if (!attr_b->non_res) {
+               u32 data_size = le32_to_cpu(attr_b->res.data_size);
                *lcn = RESIDENT_LCN;
-               *len = le32_to_cpu(attr_b->res.data_size);
+               *len = data_size;
+               if (res && data_size) {
+                       *res = kmemdup(resident_data(attr_b), data_size,
+                                      GFP_KERNEL);
+                       if (!*res)
+                               err = -ENOMEM;
+               }
                goto out;
        }
 
@@ -1028,7 +1043,8 @@ int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
                to_alloc = ((vcn0 + clen + clst_per_frame - 1) & cmask) - vcn;
                if (fr < clst_per_frame)
                        fr = clst_per_frame;
-               zero = true;
+               if (vcn != vcn0)
+                       zero = true;
 
                /* Check if 'vcn' and 'vcn0' in different attribute segments. */
                if (vcn < svcn || evcn1 <= vcn) {
@@ -1244,33 +1260,6 @@ undo1:
        goto out;
 }
 
-int attr_data_read_resident(struct ntfs_inode *ni, struct folio *folio)
-{
-       u64 vbo;
-       struct ATTRIB *attr;
-       u32 data_size;
-       size_t len;
-
-       attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL, NULL);
-       if (!attr)
-               return -EINVAL;
-
-       if (attr->non_res)
-               return E_NTFS_NONRESIDENT;
-
-       vbo = folio->index << PAGE_SHIFT;
-       data_size = le32_to_cpu(attr->res.data_size);
-       if (vbo > data_size)
-               len = 0;
-       else
-               len = min(data_size - vbo, folio_size(folio));
-
-       folio_fill_tail(folio, 0, resident_data(attr) + vbo, len);
-       folio_mark_uptodate(folio);
-
-       return 0;
-}
-
 int attr_data_write_resident(struct ntfs_inode *ni, struct folio *folio)
 {
        u64 vbo;
@@ -1287,7 +1276,7 @@ int attr_data_write_resident(struct ntfs_inode *ni, struct folio *folio)
                return E_NTFS_NONRESIDENT;
        }
 
-       vbo = folio->index << PAGE_SHIFT;
+       vbo = folio_pos(folio);
        data_size = le32_to_cpu(attr->res.data_size);
        if (vbo < data_size) {
                char *data = resident_data(attr);
@@ -1360,21 +1349,20 @@ int attr_load_runs_range(struct ntfs_inode *ni, enum ATTR_TYPE type,
        int retry = 0;
 
        for (vcn = from >> cluster_bits; vcn <= vcn_last; vcn += clen) {
-               if (!run_lookup_entry(run, vcn, &lcn, &clen, NULL)) {
-                       if (retry != 0) { /* Next run_lookup_entry(vcn) also failed. */
-                               err = -EINVAL;
-                               break;
-                       }
-                       err = attr_load_runs_vcn(ni, type, name, name_len, run,
-                                                vcn);
-                       if (err)
-                               break;
-
-                       clen = 0; /* Next run_lookup_entry(vcn) must be success. */
-                       retry++;
-               }
-               else
+               if (run_lookup_entry(run, vcn, &lcn, &clen, NULL)) {
                        retry = 0;
+                       continue;
+               }
+               if (retry) {
+                       err = -EINVAL;
+                       break;
+               }
+               err = attr_load_runs_vcn(ni, type, name, name_len, run, vcn);
+               if (err)
+                       break;
+
+               clen = 0; /* Next run_lookup_entry(vcn) must be success. */
+               retry++;
        }
 
        return err;
index c89b1e7e734c1efb8cbbf3f5cc3c514c08981f3c..58fa4da114bbe97811e93ce73ec130fed114989e 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/falloc.h>
 #include <linux/fiemap.h>
 #include <linux/fileattr.h>
+#include <linux/iomap.h>
 
 #include "debug.h"
 #include "ntfs.h"
@@ -189,9 +190,6 @@ static int ntfs_extend_initialized_size(struct file *file,
                                        const loff_t new_valid)
 {
        struct inode *inode = &ni->vfs_inode;
-       struct address_space *mapping = inode->i_mapping;
-       struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
-       loff_t pos = valid;
        int err;
 
        if (valid >= new_valid)
@@ -204,140 +202,41 @@ static int ntfs_extend_initialized_size(struct file *file,
 
        WARN_ON(is_compressed(ni));
 
-       for (;;) {
-               u32 zerofrom, len;
-               struct folio *folio;
-               u8 bits;
-               CLST vcn, lcn, clen;
-
-               if (is_sparsed(ni)) {
-                       bits = sbi->cluster_bits;
-                       vcn = pos >> bits;
-
-                       err = attr_data_get_block(ni, vcn, 1, &lcn, &clen, NULL,
-                                                 false);
-                       if (err)
-                               goto out;
-
-                       if (lcn == SPARSE_LCN) {
-                               pos = ((loff_t)clen + vcn) << bits;
-                               ni->i_valid = pos;
-                               goto next;
-                       }
-               }
-
-               zerofrom = pos & (PAGE_SIZE - 1);
-               len = PAGE_SIZE - zerofrom;
-
-               if (pos + len > new_valid)
-                       len = new_valid - pos;
-
-               err = ntfs_write_begin(NULL, mapping, pos, len, &folio, NULL);
-               if (err)
-                       goto out;
-
-               folio_zero_range(folio, zerofrom, folio_size(folio) - zerofrom);
-
-               err = ntfs_write_end(NULL, mapping, pos, len, len, folio, NULL);
-               if (err < 0)
-                       goto out;
-               pos += len;
-
-next:
-               if (pos >= new_valid)
-                       break;
-
-               balance_dirty_pages_ratelimited(mapping);
-               cond_resched();
+       err = iomap_zero_range(inode, valid, new_valid - valid, NULL,
+                              &ntfs_iomap_ops, &ntfs_iomap_folio_ops, NULL);
+       if (err) {
+               ni->i_valid = valid;
+               ntfs_inode_warn(inode,
+                               "failed to extend initialized size to %llx.",
+                               new_valid);
+               return err;
        }
 
        return 0;
-
-out:
-       ni->i_valid = valid;
-       ntfs_inode_warn(inode, "failed to extend initialized size to %llx.",
-                       new_valid);
-       return err;
 }
 
-/*
- * ntfs_zero_range - Helper function for punch_hole.
- *
- * It zeroes a range [vbo, vbo_to).
- */
-static int ntfs_zero_range(struct inode *inode, u64 vbo, u64 vbo_to)
+static void ntfs_filemap_close(struct vm_area_struct *vma)
 {
-       int err = 0;
-       struct address_space *mapping = inode->i_mapping;
-       u32 blocksize = i_blocksize(inode);
-       pgoff_t idx = vbo >> PAGE_SHIFT;
-       u32 from = vbo & (PAGE_SIZE - 1);
-       pgoff_t idx_end = (vbo_to + PAGE_SIZE - 1) >> PAGE_SHIFT;
-       loff_t page_off;
-       struct buffer_head *head, *bh;
-       u32 bh_next, bh_off, to;
-       sector_t iblock;
-       struct folio *folio;
-       bool dirty = false;
-
-       for (; idx < idx_end; idx += 1, from = 0) {
-               page_off = (loff_t)idx << PAGE_SHIFT;
-               to = (page_off + PAGE_SIZE) > vbo_to ? (vbo_to - page_off) :
-                                                      PAGE_SIZE;
-               iblock = page_off >> inode->i_blkbits;
-
-               folio = __filemap_get_folio(
-                       mapping, idx, FGP_LOCK | FGP_ACCESSED | FGP_CREAT,
-                       mapping_gfp_constraint(mapping, ~__GFP_FS));
-               if (IS_ERR(folio))
-                       return PTR_ERR(folio);
-
-               head = folio_buffers(folio);
-               if (!head)
-                       head = create_empty_buffers(folio, blocksize, 0);
-
-               bh = head;
-               bh_off = 0;
-               do {
-                       bh_next = bh_off + blocksize;
-
-                       if (bh_next <= from || bh_off >= to)
-                               continue;
-
-                       if (!buffer_mapped(bh)) {
-                               ntfs_get_block(inode, iblock, bh, 0);
-                               /* Unmapped? It's a hole - nothing to do. */
-                               if (!buffer_mapped(bh))
-                                       continue;
-                       }
-
-                       /* Ok, it's mapped. Make sure it's up-to-date. */
-                       if (folio_test_uptodate(folio))
-                               set_buffer_uptodate(bh);
-                       else if (bh_read(bh, 0) < 0) {
-                               err = -EIO;
-                               folio_unlock(folio);
-                               folio_put(folio);
-                               goto out;
-                       }
-
-                       mark_buffer_dirty(bh);
-               } while (bh_off = bh_next, iblock += 1,
-                        head != (bh = bh->b_this_page));
-
-               folio_zero_segment(folio, from, to);
-               dirty = true;
+       struct inode *inode = file_inode(vma->vm_file);
+       struct ntfs_inode *ni = ntfs_i(inode);
+       u64 from = (u64)vma->vm_pgoff << PAGE_SHIFT;
+       u64 to = min_t(u64, i_size_read(inode),
+                      from + vma->vm_end - vma->vm_start);
 
-               folio_unlock(folio);
-               folio_put(folio);
-               cond_resched();
-       }
-out:
-       if (dirty)
+       if (ni->i_valid < to) {
+               ni->i_valid = to;
                mark_inode_dirty(inode);
-       return err;
+       }
 }
 
+/* Copy of generic_file_vm_ops. */
+static const struct vm_operations_struct ntfs_file_vm_ops = {
+       .close = ntfs_filemap_close,
+       .fault = filemap_fault,
+       .map_pages = filemap_map_pages,
+       .page_mkwrite = filemap_page_mkwrite,
+};
+
 /*
  * ntfs_file_mmap_prepare - file_operations::mmap_prepare
  */
@@ -346,7 +245,6 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
        struct file *file = desc->file;
        struct inode *inode = file_inode(file);
        struct ntfs_inode *ni = ntfs_i(inode);
-       u64 from = ((u64)desc->pgoff << PAGE_SHIFT);
        bool rw = desc->vm_flags & VM_WRITE;
        int err;
 
@@ -378,7 +276,8 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
        }
 
        if (rw) {
-               u64 to = min_t(loff_t, i_size_read(inode),
+               u64 from = (u64)desc->pgoff << PAGE_SHIFT;
+               u64 to = min_t(u64, i_size_read(inode),
                               from + vma_desc_size(desc));
 
                if (is_sparsed(ni)) {
@@ -391,7 +290,8 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
 
                        for (; vcn < end; vcn += len) {
                                err = attr_data_get_block(ni, vcn, 1, &lcn,
-                                                         &len, &new, true);
+                                                         &len, &new, true,
+                                                         NULL);
                                if (err)
                                        goto out;
                        }
@@ -411,6 +311,8 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
        }
 
        err = generic_file_mmap_prepare(desc);
+       if (!err && rw)
+               desc->vm_ops = &ntfs_file_vm_ops;
 out:
        return err;
 }
@@ -465,7 +367,7 @@ static int ntfs_extend(struct inode *inode, loff_t pos, size_t count,
                 */
                for (; vcn < cend_v; vcn += clen) {
                        err = attr_data_get_block(ni, vcn, cend_v - vcn, &lcn,
-                                                 &clen, &new, true);
+                                                 &clen, &new, true, NULL);
                        if (err)
                                goto out;
                }
@@ -474,7 +376,7 @@ static int ntfs_extend(struct inode *inode, loff_t pos, size_t count,
                 */
                for (; vcn < cend; vcn += clen) {
                        err = attr_data_get_block(ni, vcn, cend - vcn, &lcn,
-                                                 &clen, &new, false);
+                                                 &clen, &new, false, NULL);
                        if (err)
                                goto out;
                }
@@ -503,25 +405,10 @@ out:
 
 static int ntfs_truncate(struct inode *inode, loff_t new_size)
 {
-       struct super_block *sb = inode->i_sb;
-       struct ntfs_inode *ni = ntfs_i(inode);
-       u64 new_valid;
        int err;
+       struct ntfs_inode *ni = ntfs_i(inode);
+       u64 new_valid = min_t(u64, ni->i_valid, new_size);
 
-       if (!S_ISREG(inode->i_mode))
-               return 0;
-
-       if (is_compressed(ni)) {
-               if (ni->i_valid > new_size)
-                       ni->i_valid = new_size;
-       } else {
-               err = block_truncate_page(inode->i_mapping, new_size,
-                                         ntfs_get_block);
-               if (err)
-                       return err;
-       }
-
-       new_valid = ntfs_up_block(sb, min_t(u64, ni->i_valid, new_size));
        truncate_setsize(inode, new_size);
 
        ni_lock(ni);
@@ -531,11 +418,11 @@ static int ntfs_truncate(struct inode *inode, loff_t new_size)
                            &new_valid, ni->mi.sbi->options->prealloc, NULL);
        up_write(&ni->file.run_lock);
 
-       if (new_valid < ni->i_valid)
-               ni->i_valid = new_valid;
+       ni->i_valid = new_valid;
 
        ni_unlock(ni);
-       if (unlikely(err))
+
+       if (err)
                return err;
 
        ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
@@ -646,13 +533,17 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
 
                tmp = min(vbo_a, end);
                if (tmp > vbo) {
-                       err = ntfs_zero_range(inode, vbo, tmp);
+                       err = iomap_zero_range(inode, vbo, tmp - vbo, NULL,
+                                              &ntfs_iomap_ops,
+                                              &ntfs_iomap_folio_ops, NULL);
                        if (err)
                                goto out;
                }
 
                if (vbo < end_a && end_a < end) {
-                       err = ntfs_zero_range(inode, end_a, end);
+                       err = iomap_zero_range(inode, end_a, end - end_a, NULL,
+                                              &ntfs_iomap_ops,
+                                              &ntfs_iomap_folio_ops, NULL);
                        if (err)
                                goto out;
                }
@@ -762,7 +653,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
                        for (; vcn < cend_v; vcn += clen) {
                                err = attr_data_get_block(ni, vcn, cend_v - vcn,
                                                          &lcn, &clen, &new,
-                                                         true);
+                                                         true, NULL);
                                if (err)
                                        goto out;
                        }
@@ -772,7 +663,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
                        for (; vcn < cend; vcn += clen) {
                                err = attr_data_get_block(ni, vcn, cend - vcn,
                                                          &lcn, &clen, &new,
-                                                         false);
+                                                         false, NULL);
                                if (err)
                                        goto out;
                        }
@@ -787,6 +678,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len)
                        ni_unlock(ni);
                        if (err)
                                goto out;
+                       i_size_write(inode, i_size);
                } else if (new_size > i_size) {
                        i_size_write(inode, new_size);
                }
@@ -923,12 +815,16 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
        struct file *file = iocb->ki_filp;
        struct inode *inode = file_inode(file);
        struct ntfs_inode *ni = ntfs_i(inode);
+       size_t bytes = iov_iter_count(iter);
        ssize_t err;
 
        err = check_read_restriction(inode);
        if (err)
                return err;
 
+       if (!bytes)
+               return 0; /* skip atime */
+
        if (is_compressed(ni)) {
                if (iocb->ki_flags & IOCB_DIRECT) {
                        ntfs_inode_warn(
@@ -940,13 +836,58 @@ static ssize_t ntfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
        }
 
        /* Check minimum alignment for dio. */
+       if ((iocb->ki_flags & IOCB_DIRECT) &&
+           (is_resident(ni) || ((iocb->ki_pos | iov_iter_alignment(iter)) &
+                                ni->mi.sbi->bdev_blocksize_mask))) {
+               /* Fallback to buffered I/O */
+               iocb->ki_flags &= ~IOCB_DIRECT;
+       }
+
        if (iocb->ki_flags & IOCB_DIRECT) {
-               struct super_block *sb = inode->i_sb;
-               struct ntfs_sb_info *sbi = sb->s_fs_info;
-               if ((iocb->ki_pos | iov_iter_alignment(iter)) &
-                   sbi->bdev_blocksize_mask) {
-                       iocb->ki_flags &= ~IOCB_DIRECT;
+               loff_t valid, i_size;
+               loff_t vbo = iocb->ki_pos;
+               loff_t end = vbo + bytes;
+               unsigned int dio_flags = IOMAP_DIO_PARTIAL;
+
+               if (iocb->ki_flags & IOCB_NOWAIT) {
+                       if (!inode_trylock_shared(inode))
+                               return -EAGAIN;
+               } else {
+                       inode_lock_shared(inode);
+               }
+
+               valid = ni->i_valid;
+               i_size = inode->i_size;
+
+               if (vbo < valid) {
+                       if (valid < end) {
+                               /* read cross 'valid' size. */
+                               dio_flags |= IOMAP_DIO_FORCE_WAIT;
+                       }
+
+                       err = iomap_dio_rw(iocb, iter, &ntfs_iomap_ops, NULL,
+                                          dio_flags, NULL, 0);
+
+                       if (err > 0) {
+                               end = vbo + err;
+                               if (valid < end) {
+                                       size_t to_zero = end - valid;
+                                       /* Fix iter. */
+                                       iov_iter_revert(iter, to_zero);
+                                       iov_iter_zero(to_zero, iter);
+                               }
+                       }
+               } else if (vbo < i_size) {
+                       if (end > i_size)
+                               bytes = i_size - vbo;
+                       iov_iter_zero(bytes, iter);
+                       iocb->ki_pos += bytes;
+                       err = bytes;
                }
+
+               inode_unlock_shared(inode);
+               file_accessed(iocb->ki_filp);
+               return err;
        }
 
        return generic_file_read_iter(iocb, iter);
@@ -1070,7 +1011,7 @@ static ssize_t ntfs_compress_write(struct kiocb *iocb, struct iov_iter *from)
                off = valid & (frame_size - 1);
 
                err = attr_data_get_block(ni, frame << NTFS_LZNT_CUNIT, 1, &lcn,
-                                         &clen, NULL, false);
+                                         &clen, NULL, false, NULL);
                if (err)
                        goto out;
 
@@ -1273,8 +1214,9 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
        struct file *file = iocb->ki_filp;
        struct inode *inode = file_inode(file);
        struct ntfs_inode *ni = ntfs_i(inode);
-       ssize_t ret;
-       int err;
+       struct super_block *sb = inode->i_sb;
+       struct ntfs_sb_info *sbi = sb->s_fs_info;
+       ssize_t ret, err;
 
        if (!inode_trylock(inode)) {
                if (iocb->ki_flags & IOCB_NOWAIT)
@@ -1312,15 +1254,73 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
        if (ret)
                goto out;
 
-       ret = is_compressed(ni) ? ntfs_compress_write(iocb, from) :
-                                 __generic_file_write_iter(iocb, from);
+       if (is_compressed(ni)) {
+               ret = ntfs_compress_write(iocb, from);
+               goto out;
+       }
+
+       /* Check minimum alignment for dio. */
+       if ((iocb->ki_flags & IOCB_DIRECT) &&
+           (is_resident(ni) || ((iocb->ki_pos | iov_iter_alignment(from)) &
+                                sbi->bdev_blocksize_mask))) {
+               /* Fallback to buffered I/O */
+               iocb->ki_flags &= ~IOCB_DIRECT;
+       }
+
+       if (!(iocb->ki_flags & IOCB_DIRECT)) {
+               ret = iomap_file_buffered_write(iocb, from, &ntfs_iomap_ops,
+                                               &ntfs_iomap_folio_ops, NULL);
+               inode_unlock(inode);
+
+               if (likely(ret > 0))
+                       ret = generic_write_sync(iocb, ret);
+
+               return ret;
+       }
+
+       ret = iomap_dio_rw(iocb, from, &ntfs_iomap_ops, NULL, IOMAP_DIO_PARTIAL,
+                          NULL, 0);
+
+       if (ret == -ENOTBLK) {
+               /* Returns -ENOTBLK in case of a page invalidation failure for writes.*/
+               /* The callers needs to fall back to buffered I/O in this case. */
+               ret = 0;
+       }
+
+       if (ret >= 0 && iov_iter_count(from)) {
+               loff_t offset = iocb->ki_pos, endbyte;
+
+               iocb->ki_flags &= ~IOCB_DIRECT;
+               err = iomap_file_buffered_write(iocb, from, &ntfs_iomap_ops,
+                                               &ntfs_iomap_folio_ops, NULL);
+               if (err < 0) {
+                       ret = err;
+                       goto out;
+               }
+
+               /*
+               * We need to ensure that the pages within the page cache for
+               * the range covered by this I/O are written to disk and
+               * invalidated. This is in attempt to preserve the expected
+               * direct I/O semantics in the case we fallback to buffered I/O
+               * to complete off the I/O request.
+               */
+               ret += err;
+               endbyte = offset + err - 1;
+               err = filemap_write_and_wait_range(inode->i_mapping, offset,
+                                                  endbyte);
+               if (err) {
+                       ret = err;
+                       goto out;
+               }
+
+               invalidate_mapping_pages(inode->i_mapping, offset >> PAGE_SHIFT,
+                                        endbyte >> PAGE_SHIFT);
+       }
 
 out:
        inode_unlock(inode);
 
-       if (ret > 0)
-               ret = generic_write_sync(iocb, ret);
-
        return ret;
 }
 
@@ -1359,6 +1359,8 @@ int ntfs_file_open(struct inode *inode, struct file *file)
 #endif
        }
 
+       file->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;
+
        return generic_file_open(inode, file);
 }
 
@@ -1408,16 +1410,30 @@ int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
        if (unlikely(is_bad_ni(ni)))
                return -EINVAL;
 
-       err = fiemap_prep(inode, fieinfo, start, &len, ~FIEMAP_FLAG_XATTR);
-       if (err)
-               return err;
+       if (is_compressed(ni)) {
+               /* Unfortunately cp -r incorrectly treats compressed clusters. */
+               ntfs_inode_warn(inode,
+                               "fiemap is not supported for compressed file");
+               return -EOPNOTSUPP;
+       }
 
-       ni_lock(ni);
+       if (S_ISDIR(inode->i_mode)) {
+               /* TODO: add support for dirs (ATTR_ALLOC). */
+               ntfs_inode_warn(inode,
+                               "fiemap is not supported for directories");
+               return -EOPNOTSUPP;
+       }
 
-       err = ni_fiemap(ni, fieinfo, start, len);
+       if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) {
+               ntfs_inode_warn(inode, "fiemap(xattr) is not supported");
+               return -EOPNOTSUPP;
+       }
 
-       ni_unlock(ni);
+       inode_lock_shared(inode);
+
+       err = iomap_fiemap(inode, fieinfo, start, len, &ntfs_iomap_ops);
 
+       inode_unlock_shared(inode);
        return err;
 }
 
@@ -1463,7 +1479,7 @@ int ntfs_file_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 
        if (!ret) {
                ntfs_set_state(sbi, NTFS_DIRTY_CLEAR);
-               ntfs_update_mftmirr(sbi, false);
+               ntfs_update_mftmirr(sbi);
        }
 
        err = sync_blockdev(sb->s_bdev);
index 3025a404e695868412e1a15cd4803303da8013e0..0dc28815331eec049f50f6b798031fa016133c97 100644 (file)
@@ -1850,183 +1850,11 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
        return REPARSE_LINK;
 }
 
-/*
- * ni_fiemap - Helper for file_fiemap().
- *
- * Assumed ni_lock.
- * TODO: Less aggressive locks.
- */
-int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
-             __u64 vbo, __u64 len)
-{
-       int err = 0;
-       struct ntfs_sb_info *sbi = ni->mi.sbi;
-       u8 cluster_bits = sbi->cluster_bits;
-       struct runs_tree run;
-       struct ATTRIB *attr;
-       CLST vcn = vbo >> cluster_bits;
-       CLST lcn, clen;
-       u64 valid = ni->i_valid;
-       u64 lbo, bytes;
-       u64 end, alloc_size;
-       size_t idx = -1;
-       u32 flags;
-       bool ok;
-
-       run_init(&run);
-       if (S_ISDIR(ni->vfs_inode.i_mode)) {
-               attr = ni_find_attr(ni, NULL, NULL, ATTR_ALLOC, I30_NAME,
-                                   ARRAY_SIZE(I30_NAME), NULL, NULL);
-       } else {
-               attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0, NULL,
-                                   NULL);
-               if (!attr) {
-                       err = -EINVAL;
-                       goto out;
-               }
-               if (is_attr_compressed(attr)) {
-                       /* Unfortunately cp -r incorrectly treats compressed clusters. */
-                       err = -EOPNOTSUPP;
-                       ntfs_inode_warn(
-                               &ni->vfs_inode,
-                               "fiemap is not supported for compressed file (cp -r)");
-                       goto out;
-               }
-       }
-
-       if (!attr || !attr->non_res) {
-               err = fiemap_fill_next_extent(
-                       fieinfo, 0, 0,
-                       attr ? le32_to_cpu(attr->res.data_size) : 0,
-                       FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_LAST |
-                               FIEMAP_EXTENT_MERGED);
-               goto out;
-       }
-
-       end = vbo + len;
-       alloc_size = le64_to_cpu(attr->nres.alloc_size);
-       if (end > alloc_size)
-               end = alloc_size;
-
-       while (vbo < end) {
-               if (idx == -1) {
-                       ok = run_lookup_entry(&run, vcn, &lcn, &clen, &idx);
-               } else {
-                       CLST vcn_next = vcn;
-
-                       ok = run_get_entry(&run, ++idx, &vcn, &lcn, &clen) &&
-                            vcn == vcn_next;
-                       if (!ok)
-                               vcn = vcn_next;
-               }
-
-               if (!ok) {
-                       err = attr_load_runs_vcn(ni, attr->type,
-                                                attr_name(attr),
-                                                attr->name_len, &run, vcn);
-
-                       if (err)
-                               break;
-
-                       ok = run_lookup_entry(&run, vcn, &lcn, &clen, &idx);
-
-                       if (!ok) {
-                               err = -EINVAL;
-                               break;
-                       }
-               }
-
-               if (!clen) {
-                       err = -EINVAL; // ?
-                       break;
-               }
-
-               if (lcn == SPARSE_LCN) {
-                       vcn += clen;
-                       vbo = (u64)vcn << cluster_bits;
-                       continue;
-               }
-
-               flags = FIEMAP_EXTENT_MERGED;
-               if (S_ISDIR(ni->vfs_inode.i_mode)) {
-                       ;
-               } else if (is_attr_compressed(attr)) {
-                       CLST clst_data;
-
-                       err = attr_is_frame_compressed(ni, attr,
-                                                      vcn >> attr->nres.c_unit,
-                                                      &clst_data, &run);
-                       if (err)
-                               break;
-                       if (clst_data < NTFS_LZNT_CLUSTERS)
-                               flags |= FIEMAP_EXTENT_ENCODED;
-               } else if (is_attr_encrypted(attr)) {
-                       flags |= FIEMAP_EXTENT_DATA_ENCRYPTED;
-               }
-
-               vbo = (u64)vcn << cluster_bits;
-               bytes = (u64)clen << cluster_bits;
-               lbo = (u64)lcn << cluster_bits;
-
-               vcn += clen;
-
-               if (vbo + bytes >= end)
-                       bytes = end - vbo;
-
-               if (vbo + bytes <= valid) {
-                       ;
-               } else if (vbo >= valid) {
-                       flags |= FIEMAP_EXTENT_UNWRITTEN;
-               } else {
-                       /* vbo < valid && valid < vbo + bytes */
-                       u64 dlen = valid - vbo;
-
-                       if (vbo + dlen >= end)
-                               flags |= FIEMAP_EXTENT_LAST;
-
-                       err = fiemap_fill_next_extent(fieinfo, vbo, lbo, dlen,
-                                                     flags);
-
-                       if (err < 0)
-                               break;
-                       if (err == 1) {
-                               err = 0;
-                               break;
-                       }
-
-                       vbo = valid;
-                       bytes -= dlen;
-                       if (!bytes)
-                               continue;
-
-                       lbo += dlen;
-                       flags |= FIEMAP_EXTENT_UNWRITTEN;
-               }
-
-               if (vbo + bytes >= end)
-                       flags |= FIEMAP_EXTENT_LAST;
-
-               err = fiemap_fill_next_extent(fieinfo, vbo, lbo, bytes, flags);
-               if (err < 0)
-                       break;
-               if (err == 1) {
-                       err = 0;
-                       break;
-               }
-
-               vbo += bytes;
-       }
-
-out:
-       run_close(&run);
-       return err;
-}
-
 static struct page *ntfs_lock_new_page(struct address_space *mapping,
-               pgoff_t index, gfp_t gfp)
+                                      pgoff_t index, gfp_t gfp)
 {
-       struct folio *folio = __filemap_get_folio(mapping, index,
-                       FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
+       struct folio *folio = __filemap_get_folio(
+               mapping, index, FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
        struct page *page;
 
        if (IS_ERR(folio))
@@ -2186,7 +2014,7 @@ int ni_decompress_file(struct ntfs_inode *ni)
 
                for (vcn = vbo >> sbi->cluster_bits; vcn < end; vcn += clen) {
                        err = attr_data_get_block(ni, vcn, cend - vcn, &lcn,
-                                                 &clen, &new, false);
+                                                 &clen, &new, false, NULL);
                        if (err)
                                goto out;
                }
@@ -3017,7 +2845,8 @@ loff_t ni_seek_data_or_hole(struct ntfs_inode *ni, loff_t offset, bool data)
 
        /* Enumerate all fragments. */
        for (vcn = offset >> cluster_bits;; vcn += clen) {
-               err = attr_data_get_block(ni, vcn, 1, &lcn, &clen, NULL, false);
+               err = attr_data_get_block(ni, vcn, 1, &lcn, &clen, NULL, false,
+                                         NULL);
                if (err) {
                        return err;
                }
index ee24ef0dd7256f934652450fc476d4c349f6fd69..464d661d9694a22adb0b0f7ad9581bf1f16dfabe 100644 (file)
@@ -5130,7 +5130,7 @@ commit_undo:
 
 undo_action_done:
 
-       ntfs_update_mftmirr(sbi, 0);
+       ntfs_update_mftmirr(sbi);
 
        sbi->flags &= ~NTFS_FLAGS_NEED_REPLAY;
 
index ff0b2595f32a21c91a2b7bfc2e8853b3695e5d4e..e9c39c62aea4cb3dcbaabc9e772664031ca2c7b2 100644 (file)
@@ -843,9 +843,8 @@ int ntfs_refresh_zone(struct ntfs_sb_info *sbi)
 /*
  * ntfs_update_mftmirr - Update $MFTMirr data.
  */
-void ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
+void ntfs_update_mftmirr(struct ntfs_sb_info *sbi)
 {
-       int err;
        struct super_block *sb = sbi->sb;
        u32 blocksize, bytes;
        sector_t block1, block2;
@@ -884,12 +883,7 @@ void ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait)
 
                put_bh(bh1);
                bh1 = NULL;
-
-               err = wait ? sync_dirty_buffer(bh2) : 0;
-
                put_bh(bh2);
-               if (err)
-                       return;
        }
 
        sbi->flags &= ~NTFS_FLAGS_MFTMIRR;
@@ -1357,9 +1351,7 @@ int ntfs_get_bh(struct ntfs_sb_info *sbi, const struct runs_tree *run, u64 vbo,
                                        err = -ENOMEM;
                                        goto out;
                                }
-
                                wait_on_buffer(bh);
-
                                lock_buffer(bh);
                                if (!buffer_uptodate(bh)) {
                                        memset(bh->b_data, 0, blocksize);
index 4b50fdb4ff470e12b87ea13399122dce52965627..b969ad7c32584466efa81791521e35f57a7b4ab3 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/nls.h>
 #include <linux/uio.h>
 #include <linux/writeback.h>
+#include <linux/iomap.h>
 
 #include "debug.h"
 #include "ntfs.h"
@@ -166,9 +167,7 @@ next_attr:
 
                std5 = Add2Ptr(attr, roff);
 
-#ifdef STATX_BTIME
                nt2kernel(std5->cr_time, &ni->i_crtime);
-#endif
                nt2kernel(std5->a_time, &ts);
                inode_set_atime_to_ts(inode, ts);
                nt2kernel(std5->c_time, &ts);
@@ -555,167 +554,96 @@ struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
        return inode;
 }
 
-enum get_block_ctx {
-       GET_BLOCK_GENERAL = 0,
-       GET_BLOCK_WRITE_BEGIN = 1,
-       GET_BLOCK_DIRECT_IO_R = 2,
-       GET_BLOCK_DIRECT_IO_W = 3,
-       GET_BLOCK_BMAP = 4,
-};
-
-static noinline int ntfs_get_block_vbo(struct inode *inode, u64 vbo,
-                                      struct buffer_head *bh, int create,
-                                      enum get_block_ctx ctx)
+static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
 {
-       struct super_block *sb = inode->i_sb;
-       struct ntfs_sb_info *sbi = sb->s_fs_info;
-       struct ntfs_inode *ni = ntfs_i(inode);
-       struct folio *folio = bh->b_folio;
-       u8 cluster_bits = sbi->cluster_bits;
-       u32 block_size = sb->s_blocksize;
-       u64 bytes, lbo, valid;
-       u32 off;
-       int err;
-       CLST vcn, lcn, len;
-       bool new;
-
-       /* Clear previous state. */
-       clear_buffer_new(bh);
-       clear_buffer_uptodate(bh);
-
-       if (is_resident(ni)) {
-               bh->b_blocknr = RESIDENT_LCN;
-               bh->b_size = block_size;
-               if (!folio) {
-                       /* direct io (read) or bmap call */
-                       err = 0;
-               } else {
-                       ni_lock(ni);
-                       err = attr_data_read_resident(ni, folio);
-                       ni_unlock(ni);
+       return iomap_bmap(mapping, block, &ntfs_iomap_ops);
+}
 
-                       if (!err)
-                               set_buffer_uptodate(bh);
+static void ntfs_iomap_read_end_io(struct bio *bio)
+{
+       int error = blk_status_to_errno(bio->bi_status);
+       struct folio_iter fi;
+
+       bio_for_each_folio_all(fi, bio) {
+               struct folio *folio = fi.folio;
+               struct inode *inode = folio->mapping->host;
+               struct ntfs_inode *ni = ntfs_i(inode);
+               u64 valid = ni->i_valid;
+               u32 f_size = folio_size(folio);
+               loff_t f_pos = folio_pos(folio);
+
+
+               if (valid < f_pos + f_size) {
+                       u32 z_from = valid <= f_pos ?
+                                            0 :
+                                            offset_in_folio(folio, valid);
+                       /* The only thing ntfs_iomap_read_end_io used for. */
+                       folio_zero_segment(folio, z_from, f_size);
                }
-               return err;
-       }
-
-       vcn = vbo >> cluster_bits;
-       off = vbo & sbi->cluster_mask;
-       new = false;
-
-       err = attr_data_get_block(ni, vcn, 1, &lcn, &len, create ? &new : NULL,
-                                 create && sbi->cluster_size > PAGE_SIZE);
-       if (err)
-               goto out;
-
-       if (!len)
-               return 0;
-
-       bytes = ((u64)len << cluster_bits) - off;
 
-       if (lcn >= sbi->used.bitmap.nbits) {
-               /* This case includes resident/compressed/sparse. */
-               if (!create) {
-                       if (bh->b_size > bytes)
-                               bh->b_size = bytes;
-                       return 0;
-               }
-               WARN_ON(1);
+               iomap_finish_folio_read(folio, fi.offset, fi.length, error);
        }
+       bio_put(bio);
+}
 
-       if (new)
-               set_buffer_new(bh);
-
-       lbo = ((u64)lcn << cluster_bits) + off;
-
-       set_buffer_mapped(bh);
-       bh->b_bdev = sb->s_bdev;
-       bh->b_blocknr = lbo >> sb->s_blocksize_bits;
-
-       valid = ni->i_valid;
-
-       if (ctx == GET_BLOCK_DIRECT_IO_W) {
-               /* ntfs_direct_IO will update ni->i_valid. */
-               if (vbo >= valid)
-                       set_buffer_new(bh);
-       } else if (create) {
-               /* Normal write. */
-               if (bytes > bh->b_size)
-                       bytes = bh->b_size;
-
-               if (vbo >= valid)
-                       set_buffer_new(bh);
-
-               if (vbo + bytes > valid) {
-                       ni->i_valid = vbo + bytes;
-                       mark_inode_dirty(inode);
-               }
-       } else if (vbo >= valid) {
-               /* Read out of valid data. */
-               clear_buffer_mapped(bh);
-       } else if (vbo + bytes <= valid) {
-               /* Normal read. */
-       } else if (vbo + block_size <= valid) {
-               /* Normal short read. */
-               bytes = block_size;
-       } else {
+/*
+ * Copied from iomap/bio.c.
+ */
+static int ntfs_iomap_bio_read_folio_range(const struct iomap_iter *iter,
+                                          struct iomap_read_folio_ctx *ctx,
+                                          size_t plen)
+{
+       struct folio *folio = ctx->cur_folio;
+       const struct iomap *iomap = &iter->iomap;
+       loff_t pos = iter->pos;
+       size_t poff = offset_in_folio(folio, pos);
+       loff_t length = iomap_length(iter);
+       sector_t sector;
+       struct bio *bio = ctx->read_ctx;
+
+       sector = iomap_sector(iomap, pos);
+       if (!bio || bio_end_sector(bio) != sector ||
+           !bio_add_folio(bio, folio, plen, poff)) {
+               gfp_t gfp = mapping_gfp_constraint(folio->mapping, GFP_KERNEL);
+               gfp_t orig_gfp = gfp;
+               unsigned int nr_vecs = DIV_ROUND_UP(length, PAGE_SIZE);
+
+               if (bio)
+                       submit_bio(bio);
+
+               if (ctx->rac) /* same as readahead_gfp_mask */
+                       gfp |= __GFP_NORETRY | __GFP_NOWARN;
+               bio = bio_alloc(iomap->bdev, bio_max_segs(nr_vecs), REQ_OP_READ,
+                               gfp);
                /*
-                * Read across valid size: vbo < valid && valid < vbo + block_size
+                * If the bio_alloc fails, try it again for a single page to
+                * avoid having to deal with partial page reads.  This emulates
+                * what do_mpage_read_folio does.
                 */
-               bytes = block_size;
-
-               if (folio) {
-                       u32 voff = valid - vbo;
-
-                       bh->b_size = block_size;
-                       off = vbo & (PAGE_SIZE - 1);
-                       folio_set_bh(bh, folio, off);
-
-                       if (bh_read(bh, 0) < 0) {
-                               err = -EIO;
-                               goto out;
-                       }
-                       folio_zero_segment(folio, off + voff, off + block_size);
-               }
-       }
-
-       if (bh->b_size > bytes)
-               bh->b_size = bytes;
-
-#ifndef __LP64__
-       if (ctx == GET_BLOCK_DIRECT_IO_W || ctx == GET_BLOCK_DIRECT_IO_R) {
-               static_assert(sizeof(size_t) < sizeof(loff_t));
-               if (bytes > 0x40000000u)
-                       bh->b_size = 0x40000000u;
+               if (!bio)
+                       bio = bio_alloc(iomap->bdev, 1, REQ_OP_READ, orig_gfp);
+               if (ctx->rac)
+                       bio->bi_opf |= REQ_RAHEAD;
+               bio->bi_iter.bi_sector = sector;
+               bio->bi_end_io = ntfs_iomap_read_end_io;
+               bio_add_folio_nofail(bio, folio, plen, poff);
+               ctx->read_ctx = bio;
        }
-#endif
-
        return 0;
-
-out:
-       return err;
 }
 
-int ntfs_get_block(struct inode *inode, sector_t vbn,
-                  struct buffer_head *bh_result, int create)
+static void ntfs_iomap_bio_submit_read(struct iomap_read_folio_ctx *ctx)
 {
-       return ntfs_get_block_vbo(inode, (u64)vbn << inode->i_blkbits,
-                                 bh_result, create, GET_BLOCK_GENERAL);
-}
+       struct bio *bio = ctx->read_ctx;
 
-static int ntfs_get_block_bmap(struct inode *inode, sector_t vsn,
-                              struct buffer_head *bh_result, int create)
-{
-       return ntfs_get_block_vbo(inode,
-                                 (u64)vsn << inode->i_sb->s_blocksize_bits,
-                                 bh_result, create, GET_BLOCK_BMAP);
+       if (bio)
+               submit_bio(bio);
 }
 
-static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
-{
-       return generic_block_bmap(mapping, block, ntfs_get_block_bmap);
-}
+static const struct iomap_read_ops ntfs_iomap_bio_read_ops = {
+       .read_folio_range = ntfs_iomap_bio_read_folio_range,
+       .submit_read = ntfs_iomap_bio_submit_read,
+};
 
 static int ntfs_read_folio(struct file *file, struct folio *folio)
 {
@@ -724,6 +652,10 @@ static int ntfs_read_folio(struct file *file, struct folio *folio)
        struct inode *inode = mapping->host;
        struct ntfs_inode *ni = ntfs_i(inode);
        loff_t vbo = folio_pos(folio);
+       struct iomap_read_folio_ctx ctx = {
+               .cur_folio = folio,
+               .ops = &ntfs_iomap_bio_read_ops,
+       };
 
        if (unlikely(is_bad_ni(ni))) {
                folio_unlock(folio);
@@ -737,24 +669,14 @@ static int ntfs_read_folio(struct file *file, struct folio *folio)
                return 0;
        }
 
-       if (is_resident(ni)) {
-               ni_lock(ni);
-               err = attr_data_read_resident(ni, folio);
-               ni_unlock(ni);
-               if (err != E_NTFS_NONRESIDENT) {
-                       folio_unlock(folio);
-                       return err;
-               }
-       }
-
        if (is_compressed(ni)) {
                /* ni_lock is taken inside ni_read_folio_cmpr after page locks */
                err = ni_read_folio_cmpr(ni, folio);
                return err;
        }
 
-       /* Normal + sparse files. */
-       return mpage_read_folio(folio, ntfs_get_block);
+       iomap_read_folio(&ntfs_iomap_ops, &ctx);
+       return 0;
 }
 
 static void ntfs_readahead(struct readahead_control *rac)
@@ -762,8 +684,10 @@ static void ntfs_readahead(struct readahead_control *rac)
        struct address_space *mapping = rac->mapping;
        struct inode *inode = mapping->host;
        struct ntfs_inode *ni = ntfs_i(inode);
-       u64 valid;
-       loff_t pos;
+       struct iomap_read_folio_ctx ctx = {
+               .ops = &ntfs_iomap_bio_read_ops,
+               .rac = rac,
+       };
 
        if (is_resident(ni)) {
                /* No readahead for resident. */
@@ -775,115 +699,281 @@ static void ntfs_readahead(struct readahead_control *rac)
                return;
        }
 
-       valid = ni->i_valid;
-       pos = readahead_pos(rac);
+       iomap_readahead(&ntfs_iomap_ops, &ctx);
+}
 
-       if (valid < i_size_read(inode) && pos <= valid &&
-           valid < pos + readahead_length(rac)) {
-               /* Range cross 'valid'. Read it page by page. */
-               return;
+int ntfs_set_size(struct inode *inode, u64 new_size)
+{
+       struct super_block *sb = inode->i_sb;
+       struct ntfs_sb_info *sbi = sb->s_fs_info;
+       struct ntfs_inode *ni = ntfs_i(inode);
+       int err;
+
+       /* Check for maximum file size. */
+       if (is_sparsed(ni) || is_compressed(ni)) {
+               if (new_size > sbi->maxbytes_sparse) {
+                       return -EFBIG;
+               }
+       } else if (new_size > sbi->maxbytes) {
+               return -EFBIG;
        }
 
-       mpage_readahead(rac, ntfs_get_block);
-}
+       ni_lock(ni);
+       down_write(&ni->file.run_lock);
 
-static int ntfs_get_block_direct_IO_R(struct inode *inode, sector_t iblock,
-                                     struct buffer_head *bh_result, int create)
-{
-       return ntfs_get_block_vbo(inode, (u64)iblock << inode->i_blkbits,
-                                 bh_result, create, GET_BLOCK_DIRECT_IO_R);
-}
+       err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
+                           &ni->i_valid, true, NULL);
 
-static int ntfs_get_block_direct_IO_W(struct inode *inode, sector_t iblock,
-                                     struct buffer_head *bh_result, int create)
-{
-       return ntfs_get_block_vbo(inode, (u64)iblock << inode->i_blkbits,
-                                 bh_result, create, GET_BLOCK_DIRECT_IO_W);
+       if (!err) {
+               i_size_write(inode, new_size);
+               mark_inode_dirty(inode);
+       }
+
+       up_write(&ni->file.run_lock);
+       ni_unlock(ni);
+
+       return err;
 }
 
-static ssize_t ntfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+/*
+ * Function to get mapping vbo -> lbo.
+ * used with:
+ * - iomap_zero_range
+ * - iomap_truncate_page
+ * - iomap_dio_rw
+ * - iomap_file_buffered_write
+ * - iomap_bmap
+ * - iomap_fiemap
+ * - iomap_bio_read_folio
+ * - iomap_bio_readahead
+ */
+static int ntfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
+                           unsigned int flags, struct iomap *iomap,
+                           struct iomap *srcmap)
 {
-       struct file *file = iocb->ki_filp;
-       struct address_space *mapping = file->f_mapping;
-       struct inode *inode = mapping->host;
        struct ntfs_inode *ni = ntfs_i(inode);
-       loff_t vbo = iocb->ki_pos;
-       loff_t end;
-       int wr = iov_iter_rw(iter) & WRITE;
-       size_t iter_count = iov_iter_count(iter);
-       loff_t valid;
-       ssize_t ret;
+       struct ntfs_sb_info *sbi = ni->mi.sbi;
+       u8 cluster_bits = sbi->cluster_bits;
+       CLST vcn = offset >> cluster_bits;
+       u32 off = offset & sbi->cluster_mask;
+       bool rw = flags & IOMAP_WRITE;
+       loff_t endbyte = offset + length;
+       void *res = NULL;
+       int err;
+       CLST lcn, clen, clen_max;
+       bool new_clst = false;
+       if (unlikely(ntfs3_forced_shutdown(sbi->sb)))
+               return -EIO;
 
-       if (is_resident(ni)) {
-               /* Switch to buffered write. */
-               ret = 0;
-               goto out;
+       if ((flags & IOMAP_REPORT) && offset > ntfs_get_maxbytes(ni)) {
+               /* called from fiemap/bmap. */
+               return -EINVAL;
        }
-       if (is_compressed(ni)) {
-               ret = 0;
-               goto out;
+
+       clen_max = rw ? (bytes_to_cluster(sbi, endbyte) - vcn) : 1;
+
+       err = attr_data_get_block(
+               ni, vcn, clen_max, &lcn, &clen, rw ? &new_clst : NULL,
+               flags == IOMAP_WRITE && (off || (endbyte & sbi->cluster_mask)),
+               &res);
+
+       if (err) {
+               return err;
        }
 
-       ret = blockdev_direct_IO(iocb, inode, iter,
-                                wr ? ntfs_get_block_direct_IO_W :
-                                     ntfs_get_block_direct_IO_R);
+       if (lcn == EOF_LCN) {
+               /* request out of file. */
+               if (flags & IOMAP_REPORT) {
+                       /* special code for report. */
+                       return -ENOENT;
+               }
 
-       if (ret > 0)
-               end = vbo + ret;
-       else if (wr && ret == -EIOCBQUEUED)
-               end = vbo + iter_count;
-       else
-               goto out;
+               if (rw) {
+                       /* should never be here. */
+                       return -EINVAL;
+               }
+               lcn = SPARSE_LCN;
+       }
 
-       valid = ni->i_valid;
-       if (wr) {
-               if (end > valid && !S_ISBLK(inode->i_mode)) {
-                       ni->i_valid = end;
-                       mark_inode_dirty(inode);
+       if (lcn == RESIDENT_LCN) {
+               if (offset >= clen) {
+                       kfree(res);
+                       if (flags & IOMAP_REPORT) {
+                               /* special code for report. */
+                               return -ENOENT;
+                       }
+                       return -EFAULT;
                }
-       } else if (vbo < valid && valid < end) {
-               /* Fix page. */
-               iov_iter_revert(iter, end - valid);
-               iov_iter_zero(end - valid, iter);
+
+               iomap->private = iomap->inline_data = res;
+               iomap->type = IOMAP_INLINE;
+               iomap->offset = 0;
+               iomap->length = clen; /* resident size in bytes. */
+               iomap->flags = 0;
+               return 0;
        }
 
-out:
-       return ret;
+       if (!clen) {
+               /* broken file? */
+               return -EINVAL;
+       }
+
+       if (lcn == COMPRESSED_LCN) {
+               /* should never be here. */
+               return -EOPNOTSUPP;
+       }
+
+       iomap->flags = new_clst ? IOMAP_F_NEW : 0;
+       iomap->bdev = inode->i_sb->s_bdev;
+
+       /* Translate clusters into bytes. */
+       iomap->offset = offset;
+       iomap->addr = ((loff_t)lcn << cluster_bits) + off;
+       iomap->length = ((loff_t)clen << cluster_bits) - off;
+       if (length && iomap->length > length)
+               iomap->length = length;
+       else
+               endbyte = offset + iomap->length;
+
+       if (lcn == SPARSE_LCN) {
+               iomap->addr = IOMAP_NULL_ADDR;
+               iomap->type = IOMAP_HOLE;
+       } else if (endbyte <= ni->i_valid) {
+               iomap->type = IOMAP_MAPPED;
+       } else if (offset < ni->i_valid) {
+               iomap->type = IOMAP_MAPPED;
+               if (flags & IOMAP_REPORT)
+                       iomap->length = ni->i_valid - offset;
+       } else if (rw || (flags & IOMAP_ZERO)) {
+               iomap->type = IOMAP_MAPPED;
+       } else {
+               iomap->type = IOMAP_UNWRITTEN;
+       }
+
+       if ((flags & IOMAP_ZERO) && iomap->type == IOMAP_MAPPED) {
+               /* Avoid too large requests. */
+               u32 tail;
+               u32 off_a = iomap->addr & (PAGE_SIZE - 1);
+               if (off_a)
+                       tail = PAGE_SIZE - off_a;
+               else
+                       tail = PAGE_SIZE;
+
+               if (iomap->length > tail)
+                       iomap->length = tail;
+       }
+
+       return 0;
 }
 
-int ntfs_set_size(struct inode *inode, u64 new_size)
+static int ntfs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
+                         ssize_t written, unsigned int flags,
+                         struct iomap *iomap)
 {
-       struct super_block *sb = inode->i_sb;
-       struct ntfs_sb_info *sbi = sb->s_fs_info;
+       int err = 0;
        struct ntfs_inode *ni = ntfs_i(inode);
-       int err;
+       loff_t endbyte = pos + written;
 
-       /* Check for maximum file size. */
-       if (is_sparsed(ni) || is_compressed(ni)) {
-               if (new_size > sbi->maxbytes_sparse) {
-                       err = -EFBIG;
-                       goto out;
-               }
-       } else if (new_size > sbi->maxbytes) {
-               err = -EFBIG;
-               goto out;
-       }
+       if ((flags & IOMAP_WRITE) || (flags & IOMAP_ZERO)) {
+               if (iomap->type == IOMAP_INLINE) {
+                       u32 data_size;
+                       struct ATTRIB *attr;
+                       struct mft_inode *mi;
 
-       ni_lock(ni);
-       down_write(&ni->file.run_lock);
+                       attr = ni_find_attr(ni, NULL, NULL, ATTR_DATA, NULL, 0,
+                                           NULL, &mi);
+                       if (!attr || attr->non_res) {
+                               err = -EINVAL;
+                               goto out;
+                       }
 
-       err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
-                           &ni->i_valid, true, NULL);
+                       data_size = le32_to_cpu(attr->res.data_size);
+                       if (!(pos < data_size && endbyte <= data_size)) {
+                               err = -EINVAL;
+                               goto out;
+                       }
 
-       up_write(&ni->file.run_lock);
-       ni_unlock(ni);
+                       /* Update resident data. */
+                       memcpy(resident_data(attr) + pos,
+                              iomap_inline_data(iomap, pos), written);
+                       mi->dirty = true;
+                       ni->i_valid = data_size;
+               } else if (ni->i_valid < endbyte) {
+                       ni->i_valid = endbyte;
+                       mark_inode_dirty(inode);
+               }
+       }
 
-       mark_inode_dirty(inode);
+       if ((flags & IOMAP_ZERO) && iomap->type == IOMAP_MAPPED) {
+               balance_dirty_pages_ratelimited(inode->i_mapping);
+               cond_resched();
+       }
 
 out:
+       if (iomap->type == IOMAP_INLINE) {
+               kfree(iomap->private);
+               iomap->private = NULL;
+       }
+
        return err;
 }
 
+/*
+ * write_begin + put_folio + write_end.
+ * iomap_zero_range
+ * iomap_truncate_page
+ * iomap_file_buffered_write
+ */
+static void ntfs_iomap_put_folio(struct inode *inode, loff_t pos,
+                                unsigned int len, struct folio *folio)
+{
+       struct ntfs_inode *ni = ntfs_i(inode);
+       loff_t end = pos + len;
+       u32 f_size = folio_size(folio);
+       loff_t f_pos = folio_pos(folio);
+       loff_t f_end = f_pos + f_size;
+
+       if (ni->i_valid < end && end < f_end) {
+               /* zero range [end - f_end). */
+               /* The only thing ntfs_iomap_put_folio used for. */
+               folio_zero_segment(folio, offset_in_folio(folio, end), f_size);
+       }
+       folio_unlock(folio);
+       folio_put(folio);
+}
+
+static ssize_t ntfs_writeback_range(struct iomap_writepage_ctx *wpc,
+                                   struct folio *folio, u64 offset,
+                                   unsigned int len, u64 end_pos)
+{
+       struct iomap *iomap = &wpc->iomap;
+       struct inode *inode = wpc->inode;
+
+       /* Check iomap position. */
+       if (!(iomap->offset <= offset &&
+             offset < iomap->offset + iomap->length)) {
+               int err;
+               struct ntfs_sb_info *sbi = ntfs_sb(inode->i_sb);
+               loff_t i_size_up = ntfs_up_cluster(sbi, inode->i_size);
+               loff_t len_max = i_size_up - offset;
+
+               err = ntfs_iomap_begin(inode, offset, len_max, IOMAP_WRITE,
+                                      iomap, NULL);
+               if (err) {
+                       ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
+                       return err;
+               }
+       }
+
+       return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
+}
+
+
+const struct iomap_writeback_ops ntfs_writeback_ops = {
+       .writeback_range = ntfs_writeback_range,
+       .writeback_submit = iomap_ioend_writeback_submit,
+};
+
 static int ntfs_resident_writepage(struct folio *folio,
                                   struct writeback_control *wbc)
 {
@@ -911,40 +1001,15 @@ static int ntfs_resident_writepage(struct folio *folio,
 
 static int ntfs_writepages(struct address_space *mapping,
                           struct writeback_control *wbc)
-{
-       struct inode *inode = mapping->host;
-
-       /* Avoid any operation if inode is bad. */
-       if (unlikely(is_bad_ni(ntfs_i(inode))))
-               return -EINVAL;
-
-       if (unlikely(ntfs3_forced_shutdown(inode->i_sb)))
-               return -EIO;
-
-       if (is_resident(ntfs_i(inode))) {
-               struct folio *folio = NULL;
-               int error;
-
-               while ((folio = writeback_iter(mapping, wbc, folio, &error)))
-                       error = ntfs_resident_writepage(folio, wbc);
-               return error;
-       }
-       return mpage_writepages(mapping, wbc, ntfs_get_block);
-}
-
-static int ntfs_get_block_write_begin(struct inode *inode, sector_t vbn,
-                                     struct buffer_head *bh_result, int create)
-{
-       return ntfs_get_block_vbo(inode, (u64)vbn << inode->i_blkbits,
-                                 bh_result, create, GET_BLOCK_WRITE_BEGIN);
-}
-
-int ntfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
-                    loff_t pos, u32 len, struct folio **foliop, void **fsdata)
 {
        int err;
        struct inode *inode = mapping->host;
        struct ntfs_inode *ni = ntfs_i(inode);
+       struct iomap_writepage_ctx wpc = {
+               .inode = mapping->host,
+               .wbc = wbc,
+               .ops = &ntfs_writeback_ops,
+       };
 
        /* Avoid any operation if inode is bad. */
        if (unlikely(is_bad_ni(ni)))
@@ -954,100 +1019,15 @@ int ntfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
                return -EIO;
 
        if (is_resident(ni)) {
-               struct folio *folio = __filemap_get_folio(
-                       mapping, pos >> PAGE_SHIFT, FGP_WRITEBEGIN,
-                       mapping_gfp_mask(mapping));
-
-               if (IS_ERR(folio)) {
-                       err = PTR_ERR(folio);
-                       goto out;
-               }
-
-               ni_lock(ni);
-               err = attr_data_read_resident(ni, folio);
-               ni_unlock(ni);
-
-               if (!err) {
-                       *foliop = folio;
-                       goto out;
-               }
-               folio_unlock(folio);
-               folio_put(folio);
-
-               if (err != E_NTFS_NONRESIDENT)
-                       goto out;
-       }
-
-       err = block_write_begin(mapping, pos, len, foliop,
-                               ntfs_get_block_write_begin);
-
-out:
-       return err;
-}
-
-/*
- * ntfs_write_end - Address_space_operations::write_end.
- */
-int ntfs_write_end(const struct kiocb *iocb, struct address_space *mapping,
-                  loff_t pos, u32 len, u32 copied, struct folio *folio,
-                  void *fsdata)
-{
-       struct inode *inode = mapping->host;
-       struct ntfs_inode *ni = ntfs_i(inode);
-       u64 valid = ni->i_valid;
-       bool dirty = false;
-       int err;
-
-       if (is_resident(ni)) {
-               ni_lock(ni);
-               err = attr_data_write_resident(ni, folio);
-               ni_unlock(ni);
-               if (!err) {
-                       struct buffer_head *head = folio_buffers(folio);
-                       dirty = true;
-                       /* Clear any buffers in folio. */
-                       if (head) {
-                               struct buffer_head *bh = head;
-
-                               do {
-                                       clear_buffer_dirty(bh);
-                                       clear_buffer_mapped(bh);
-                                       set_buffer_uptodate(bh);
-                               } while (head != (bh = bh->b_this_page));
-                       }
-                       folio_mark_uptodate(folio);
-                       err = copied;
-               }
-               folio_unlock(folio);
-               folio_put(folio);
-       } else {
-               err = generic_write_end(iocb, mapping, pos, len, copied, folio,
-                                       fsdata);
-       }
-
-       if (err >= 0) {
-               if (!(ni->std_fa & FILE_ATTRIBUTE_ARCHIVE)) {
-                       inode_set_mtime_to_ts(inode,
-                                             inode_set_ctime_current(inode));
-                       ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
-                       dirty = true;
-               }
-
-               if (valid != ni->i_valid) {
-                       /* ni->i_valid is changed in ntfs_get_block_vbo. */
-                       dirty = true;
-               }
+               struct folio *folio;
 
-               if (pos + err > inode->i_size) {
-                       i_size_write(inode, pos + err);
-                       dirty = true;
-               }
+               while ((folio = writeback_iter(mapping, wbc, folio, &err)))
+                       err = ntfs_resident_writepage(folio, wbc);
 
-               if (dirty)
-                       mark_inode_dirty(inode);
+               return err;
        }
 
-       return err;
+       return iomap_writepages(&wpc);
 }
 
 int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc)
@@ -1062,6 +1042,7 @@ int ntfs_sync_inode(struct inode *inode)
 
 /*
  * Helper function to read file.
+ * Used to read $AttrDef and $UpCase
  */
 int inode_read_data(struct inode *inode, void *data, size_t bytes)
 {
@@ -2107,18 +2088,26 @@ const struct address_space_operations ntfs_aops = {
        .read_folio     = ntfs_read_folio,
        .readahead      = ntfs_readahead,
        .writepages     = ntfs_writepages,
-       .write_begin    = ntfs_write_begin,
-       .write_end      = ntfs_write_end,
-       .direct_IO      = ntfs_direct_IO,
        .bmap           = ntfs_bmap,
-       .dirty_folio    = block_dirty_folio,
-       .migrate_folio  = buffer_migrate_folio,
-       .invalidate_folio = block_invalidate_folio,
+       .dirty_folio    = iomap_dirty_folio,
+       .migrate_folio  = filemap_migrate_folio,
+       .release_folio  = iomap_release_folio,
+       .invalidate_folio = iomap_invalidate_folio,
 };
 
 const struct address_space_operations ntfs_aops_cmpr = {
        .read_folio     = ntfs_read_folio,
-       .dirty_folio    = block_dirty_folio,
-       .direct_IO      = ntfs_direct_IO,
+       .dirty_folio    = iomap_dirty_folio,
+       .release_folio  = iomap_release_folio,
+       .invalidate_folio = iomap_invalidate_folio,
+};
+
+const struct iomap_ops ntfs_iomap_ops = {
+       .iomap_begin    = ntfs_iomap_begin,
+       .iomap_end      = ntfs_iomap_end,
+};
+
+const struct iomap_write_ops ntfs_iomap_folio_ops = {
+       .put_folio = ntfs_iomap_put_folio,
 };
 // clang-format on
index 32823e1428a7640fde2a4baf38693e432aa001ec..b7017dd4d7cd7e4f4b763a55bb805dacd924e313 100644 (file)
@@ -442,8 +442,7 @@ int attr_set_size(struct ntfs_inode *ni, enum ATTR_TYPE type,
                  u64 new_size, const u64 *new_valid, bool keep_prealloc,
                  struct ATTRIB **ret);
 int attr_data_get_block(struct ntfs_inode *ni, CLST vcn, CLST clen, CLST *lcn,
-                       CLST *len, bool *new, bool zero);
-int attr_data_read_resident(struct ntfs_inode *ni, struct folio *folio);
+                       CLST *len, bool *new, bool zero, void **res);
 int attr_data_write_resident(struct ntfs_inode *ni, struct folio *folio);
 int attr_load_runs_vcn(struct ntfs_inode *ni, enum ATTR_TYPE type,
                       const __le16 *name, u8 name_len, struct runs_tree *run,
@@ -568,8 +567,6 @@ enum REPARSE_SIGN ni_parse_reparse(struct ntfs_inode *ni, struct ATTRIB *attr,
                                   struct REPARSE_DATA_BUFFER *buffer);
 int ni_write_inode(struct inode *inode, int sync, const char *hint);
 #define _ni_write_inode(i, w) ni_write_inode(i, w, __func__)
-int ni_fiemap(struct ntfs_inode *ni, struct fiemap_extent_info *fieinfo,
-             __u64 vbo, __u64 len);
 int ni_read_folio_cmpr(struct ntfs_inode *ni, struct folio *folio);
 int ni_decompress_file(struct ntfs_inode *ni);
 int ni_read_frame(struct ntfs_inode *ni, u64 frame_vbo, struct page **pages,
@@ -614,7 +611,7 @@ int ntfs_look_free_mft(struct ntfs_sb_info *sbi, CLST *rno, bool mft,
 void ntfs_mark_rec_free(struct ntfs_sb_info *sbi, CLST rno, bool is_mft);
 int ntfs_clear_mft_tail(struct ntfs_sb_info *sbi, size_t from, size_t to);
 int ntfs_refresh_zone(struct ntfs_sb_info *sbi);
-void ntfs_update_mftmirr(struct ntfs_sb_info *sbi, int wait);
+void ntfs_update_mftmirr(struct ntfs_sb_info *sbi);
 void ntfs_bad_inode(struct inode *inode, const char *hint);
 #define _ntfs_bad_inode(i) ntfs_bad_inode(i, __func__)
 enum NTFS_DIRTY_FLAGS {
@@ -745,13 +742,6 @@ int indx_update_dup(struct ntfs_inode *ni, struct ntfs_sb_info *sbi,
 struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
                         const struct cpu_str *name);
 int ntfs_set_size(struct inode *inode, u64 new_size);
-int ntfs_get_block(struct inode *inode, sector_t vbn,
-                  struct buffer_head *bh_result, int create);
-int ntfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
-                    loff_t pos, u32 len, struct folio **foliop, void **fsdata);
-int ntfs_write_end(const struct kiocb *iocb, struct address_space *mapping,
-                  loff_t pos, u32 len, u32 copied, struct folio *folio,
-                  void *fsdata);
 int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc);
 int ntfs_sync_inode(struct inode *inode);
 int inode_read_data(struct inode *inode, void *data, size_t bytes);
@@ -762,6 +752,8 @@ int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir,
 int ntfs_link_inode(struct inode *inode, struct dentry *dentry);
 int ntfs_unlink_inode(struct inode *dir, const struct dentry *dentry);
 void ntfs_evict_inode(struct inode *inode);
+extern const struct iomap_ops ntfs_iomap_ops;
+extern const struct iomap_write_ops ntfs_iomap_folio_ops;
 extern const struct inode_operations ntfs_link_inode_operations;
 extern const struct address_space_operations ntfs_aops;
 extern const struct address_space_operations ntfs_aops_cmpr;
index df65877f172c122573a0faf454e67e67f37cbc10..5c104991b0675ee8d3187cdd0a77367ef90856d0 100644 (file)
@@ -58,9 +58,9 @@
 #include <linux/buffer_head.h>
 #include <linux/exportfs.h>
 #include <linux/fs.h>
-#include <linux/fs_struct.h>
 #include <linux/fs_context.h>
 #include <linux/fs_parser.h>
+#include <linux/fs_struct.h>
 #include <linux/log2.h>
 #include <linux/minmax.h>
 #include <linux/module.h>
@@ -674,7 +674,7 @@ static noinline void ntfs3_put_sbi(struct ntfs_sb_info *sbi)
                sbi->volume.ni = NULL;
        }
 
-       ntfs_update_mftmirr(sbi, 0);
+       ntfs_update_mftmirr(sbi);
 
        indx_clear(&sbi->security.index_sii);
        indx_clear(&sbi->security.index_sdh);
@@ -821,7 +821,12 @@ static int ntfs_sync_fs(struct super_block *sb, int wait)
        if (!err)
                ntfs_set_state(sbi, NTFS_DIRTY_CLEAR);
 
-       ntfs_update_mftmirr(sbi, wait);
+       ntfs_update_mftmirr(sbi);
+
+       if (wait) {
+               sync_blockdev(sb->s_bdev);
+               blkdev_issue_flush(sb->s_bdev);
+       }
 
        return err;
 }