summaryrefslogtreecommitdiffstats
path: root/fs/ntfs/aops.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /fs/ntfs/aops.c
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'fs/ntfs/aops.c')
-rw-r--r--fs/ntfs/aops.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
new file mode 100644
index 000000000..517d9a156
--- /dev/null
+++ b/fs/ntfs/aops.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * NTFS kernel address space operations and page cache handling.
+ *
+ * Copyright (c) 2001-2014 Anton Altaparmakov and Tuxera Inc.
+ * Copyright (c) 2002 Richard Russon
+ * Copyright (c) 2025 LG Electronics Co., Ltd.
+ */
+
+#include <linux/writeback.h>
+
+#include "attrib.h"
+#include "mft.h"
+#include "ntfs.h"
+#include "debug.h"
+#include "iomap.h"
+
+static void ntfs_iomap_read_end_io(struct bio *bio)
+{
+ int error = blk_status_to_errno(bio->bi_status);
+ struct folio_iter iter;
+
+ bio_for_each_folio_all(iter, bio) {
+ struct folio *folio = iter.folio;
+ struct ntfs_inode *ni = NTFS_I(folio->mapping->host);
+ s64 init_size;
+ loff_t pos = folio_pos(folio);
+
+ init_size = ni->initialized_size;
+ if (pos + iter.offset < init_size &&
+ pos + iter.offset + iter.length > init_size)
+ folio_zero_segment(folio, offset_in_folio(folio, init_size),
+ iter.offset + iter.length);
+
+ iomap_finish_folio_read(folio, iter.offset, iter.length, error);
+ }
+ bio_put(bio);
+}
+
+static void ntfs_iomap_bio_submit_read(const struct iomap_iter *iter,
+ struct iomap_read_folio_ctx *ctx)
+{
+ iomap_bio_submit_read_endio(iter, ctx, ntfs_iomap_read_end_io);
+}
+
+static const struct iomap_read_ops ntfs_iomap_bio_read_ops = {
+ .read_folio_range = iomap_bio_read_folio_range,
+ .submit_read = ntfs_iomap_bio_submit_read,
+};
+
+/*
+ * ntfs_read_folio - Read data for a folio from the device
+ * @file: open file to which the folio @folio belongs or NULL
+ * @folio: page cache folio to fill with data
+ *
+ * This function handles reading data into the page cache. It first checks
+ * for specific ntfs attribute type like encryption and compression.
+ *
+ * - If the attribute is encrypted, access is denied (-EACCES) because
+ * decryption is not supported in this path.
+ * - If the attribute is non-resident and compressed, the read operation is
+ * delegated to ntfs_read_compressed_block().
+ * - For normal resident or non-resident attribute, it utilizes the generic
+ * iomap infrastructure via iomap_bio_read_folio() to perform the I/O.
+ *
+ * Return: 0 on success, or -errno on error.
+ */
+static int ntfs_read_folio(struct file *file, struct folio *folio)
+{
+ struct ntfs_inode *ni = NTFS_I(folio->mapping->host);
+ struct iomap_read_folio_ctx ctx = {
+ .cur_folio = folio,
+ .ops = &ntfs_iomap_bio_read_ops,
+ };
+
+ /*
+ * Only $DATA attributes can be encrypted and only unnamed $DATA
+ * attributes can be compressed. Index root can have the flags set but
+ * this means to create compressed/encrypted files, not that the
+ * attribute is compressed/encrypted. Note we need to check for
+ * AT_INDEX_ALLOCATION since this is the type of both directory and
+ * index inodes.
+ */
+ if (ni->type != AT_INDEX_ALLOCATION) {
+ /*
+ * EFS-encrypted files are not supported.
+ * (decryption/encryption is not implemented yet)
+ */
+ if (NInoEncrypted(ni)) {
+ folio_unlock(folio);
+ return -EOPNOTSUPP;
+ }
+ if (NInoWofCompressed(ni)) {
+#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
+ return ntfs_read_wof_compressed_block(folio);
+#else
+ folio_unlock(folio);
+ return -EOPNOTSUPP;
+#endif
+ }
+ /* Compressed data streams are handled in compress.c. */
+ if (NInoNonResident(ni) && NInoCompressed(ni))
+ return ntfs_read_compressed_block(folio);
+ }
+
+ iomap_read_folio(&ntfs_read_iomap_ops, &ctx, NULL);
+ return 0;
+}
+
+/*
+ * ntfs_bmap - map logical file block to physical device block
+ * @mapping: address space mapping to which the block to be mapped belongs
+ * @block: logical block to map to its physical device block
+ *
+ * For regular, non-resident files (i.e. not compressed and not encrypted), map
+ * the logical @block belonging to the file described by the address space
+ * mapping @mapping to its physical device block.
+ *
+ * The size of the block is equal to the @s_blocksize field of the super block
+ * of the mounted file system which is guaranteed to be smaller than or equal
+ * to the cluster size thus the block is guaranteed to fit entirely inside the
+ * cluster which means we do not need to care how many contiguous bytes are
+ * available after the beginning of the block.
+ *
+ * Return the physical device block if the mapping succeeded or 0 if the block
+ * is sparse or there was an error.
+ *
+ * Note: This is a problem if someone tries to run bmap() on $Boot system file
+ * as that really is in block zero but there is nothing we can do. bmap() is
+ * just broken in that respect (just like it cannot distinguish sparse from
+ * not available or error).
+ */
+static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
+{
+ s64 ofs, size;
+ loff_t i_size;
+ s64 lcn;
+ unsigned long blocksize, flags;
+ struct ntfs_inode *ni = NTFS_I(mapping->host);
+ struct ntfs_volume *vol = ni->vol;
+ unsigned int delta;
+ unsigned char blocksize_bits;
+
+ ntfs_debug("Entering for mft_no 0x%llx, logical block 0x%llx.",
+ ni->mft_no, (unsigned long long)block);
+ if (ni->type != AT_DATA || !NInoNonResident(ni) || NInoEncrypted(ni) ||
+ NInoWofCompressed(ni) || NInoMstProtected(ni)) {
+ ntfs_error(vol->sb, "BMAP does not make sense for %s attributes, returning 0.",
+ (ni->type != AT_DATA) ? "non-data" :
+ (!NInoNonResident(ni) ? "resident" :
+ (NInoWofCompressed(ni) ? "WOF-compressed" :
+ "encrypted")));
+ return 0;
+ }
+ /* None of these can happen. */
+ blocksize = vol->sb->s_blocksize;
+ blocksize_bits = vol->sb->s_blocksize_bits;
+ ofs = (s64)block << blocksize_bits;
+ read_lock_irqsave(&ni->size_lock, flags);
+ size = ni->initialized_size;
+ i_size = i_size_read(VFS_I(ni));
+ read_unlock_irqrestore(&ni->size_lock, flags);
+ /*
+ * If the offset is outside the initialized size or the block straddles
+ * the initialized size then pretend it is a hole unless the
+ * initialized size equals the file size.
+ */
+ if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size)))
+ goto hole;
+ down_read(&ni->runlist.lock);
+ lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ntfs_bytes_to_cluster(vol, ofs),
+ false);
+ up_read(&ni->runlist.lock);
+ if (unlikely(lcn < LCN_HOLE)) {
+ /*
+ * Step down to an integer to avoid gcc doing a long long
+ * comparision in the switch when we know @lcn is between
+ * LCN_HOLE and LCN_EIO (i.e. -1 to -5).
+ *
+ * Otherwise older gcc (at least on some architectures) will
+ * try to use __cmpdi2() which is of course not available in
+ * the kernel.
+ */
+ switch ((int)lcn) {
+ case LCN_ENOENT:
+ /*
+ * If the offset is out of bounds then pretend it is a
+ * hole.
+ */
+ goto hole;
+ case LCN_ENOMEM:
+ ntfs_error(vol->sb,
+ "Not enough memory to complete mapping for inode 0x%llx. Returning 0.",
+ ni->mft_no);
+ break;
+ default:
+ ntfs_error(vol->sb,
+ "Failed to complete mapping for inode 0x%llx. Run chkdsk. Returning 0.",
+ ni->mft_no);
+ break;
+ }
+ return 0;
+ }
+ if (lcn < 0) {
+ /* It is a hole. */
+hole:
+ ntfs_debug("Done (returning hole).");
+ return 0;
+ }
+ /*
+ * The block is really allocated and fullfils all our criteria.
+ * Convert the cluster to units of block size and return the result.
+ */
+ delta = ofs & vol->cluster_size_mask;
+ if (unlikely(sizeof(block) < sizeof(lcn))) {
+ block = lcn = (ntfs_cluster_to_bytes(vol, lcn) + delta) >>
+ blocksize_bits;
+ /* If the block number was truncated return 0. */
+ if (unlikely(block != lcn)) {
+ ntfs_error(vol->sb,
+ "Physical block 0x%llx is too large to be returned, returning 0.",
+ (long long)lcn);
+ return 0;
+ }
+ } else
+ block = (ntfs_cluster_to_bytes(vol, lcn) + delta) >>
+ blocksize_bits;
+ ntfs_debug("Done (returning block 0x%llx).", (unsigned long long)lcn);
+ return block;
+}
+
+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);
+ struct iomap_read_folio_ctx ctx = {
+ .ops = &ntfs_iomap_bio_read_ops,
+ .rac = rac,
+ };
+
+ /*
+ * Resident files are not cached in the page cache,
+ * and readahead is not implemented for compressed files.
+ */
+ if (!NInoNonResident(ni) || NInoCompressed(ni) ||
+ NInoWofCompressed(ni))
+ return;
+ iomap_readahead(&ntfs_read_iomap_ops, &ctx, NULL);
+}
+
+static int ntfs_writepages(struct address_space *mapping,
+ struct writeback_control *wbc)
+{
+ 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,
+ };
+ bool need_iput = false;
+ int ret;
+
+ if (NVolShutdown(ni->vol))
+ return -EIO;
+
+ if (!NInoNonResident(ni))
+ return 0;
+
+ /*
+ * EFS-encrypted files are not supported.
+ * (decryption/encryption is not implemented yet)
+ */
+ if (NInoEncrypted(ni)) {
+ ntfs_debug("Encrypted I/O not supported");
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * Prevent eviction in writeback to avoid deadlock in
+ * ntfs_drop_big_inode().
+ */
+ if ((ni->type == AT_DATA || ni->type == AT_INDEX_ALLOCATION) &&
+ igrab(inode))
+ need_iput = true;
+
+ ret = iomap_writepages(&wpc);
+
+ if (need_iput)
+ iput(inode);
+
+ return ret;
+}
+
+static int ntfs_swap_activate(struct swap_info_struct *sis,
+ struct file *swap_file, sector_t *span)
+{
+ if (NInoWofCompressed(NTFS_I(file_inode(swap_file))))
+ return -EOPNOTSUPP;
+
+ return iomap_swapfile_activate(sis, swap_file, span,
+ &ntfs_read_iomap_ops);
+}
+
+const struct address_space_operations ntfs_aops = {
+ .read_folio = ntfs_read_folio,
+ .readahead = ntfs_readahead,
+ .writepages = ntfs_writepages,
+ .dirty_folio = iomap_dirty_folio,
+ .bmap = ntfs_bmap,
+ .migrate_folio = filemap_migrate_folio,
+ .is_partially_uptodate = iomap_is_partially_uptodate,
+ .error_remove_folio = generic_error_remove_folio,
+ .release_folio = iomap_release_folio,
+ .invalidate_folio = iomap_invalidate_folio,
+ .swap_activate = ntfs_swap_activate,
+};
+
+const struct address_space_operations ntfs_mft_aops = {
+ .read_folio = ntfs_read_folio,
+ .readahead = ntfs_readahead,
+ .writepages = ntfs_mft_writepages,
+ .dirty_folio = iomap_dirty_folio,
+ .bmap = ntfs_bmap,
+ .migrate_folio = filemap_migrate_folio,
+ .is_partially_uptodate = iomap_is_partially_uptodate,
+ .error_remove_folio = generic_error_remove_folio,
+ .release_folio = iomap_release_folio,
+ .invalidate_folio = iomap_invalidate_folio,
+};