diff options
| author | Kees Cook <kees+treewide@kernel.org> | 2026-09-02 15:31:14 -0700 |
|---|---|---|
| committer | Kees Cook <kees@kernel.org> | 2026-09-04 21:37:00 -0700 |
| commit | 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch) | |
| tree | c65086f9bdcd48c6360fb7cb4598bca084da1f32 /fs/ntfs/aops.c | |
| download | linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.tar.gz linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.zip | |
treewide: refresh kmalloc_obj() conversionsgrafted
This is another run of the Coccinelle script for converting kmalloc()
family of allocations to kmalloc_obj() via the existing rules in
scripts/coccinelle/api/kmalloc_objs.cocci
This catches both the set of kmalloc() uses added since the first
kmalloc_obj() conversions in v7.0 and adds a large group missed in the
first pass due to Coccinelle not interacting well with the cleanup.h
scoped_...() family of macros[1]. I worked around this with spatch's
"--macro-file" argument to a file with all the scoped_...() macros mapped
to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control
flow indicator I could find.
Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc,
riscv, and s390 with no new warnings.
Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1]
Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2]
Signed-off-by: Kees Cook <kees+treewide@kernel.org>
Diffstat (limited to 'fs/ntfs/aops.c')
| -rw-r--r-- | fs/ntfs/aops.c | 331 |
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, +}; |
