| // 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" |
| |
| /* |
| * 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); |
| |
| /* |
| * 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; |
| } |
| /* Compressed data streams are handled in compress.c. */ |
| if (NInoNonResident(ni) && NInoCompressed(ni)) |
| return ntfs_read_compressed_block(folio); |
| } |
| |
| iomap_bio_read_folio(folio, &ntfs_read_iomap_ops); |
| 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) || |
| 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" : |
| "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); |
| |
| /* |
| * Resident files are not cached in the page cache, |
| * and readahead is not implemented for compressed files. |
| */ |
| if (!NInoNonResident(ni) || NInoCompressed(ni)) |
| return; |
| iomap_bio_readahead(rac, &ntfs_read_iomap_ops); |
| } |
| |
| 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, |
| }; |
| |
| 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; |
| } |
| |
| return iomap_writepages(&wpc); |
| } |
| |
| static int ntfs_swap_activate(struct swap_info_struct *sis, |
| struct file *swap_file, sector_t *span) |
| { |
| 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, |
| .direct_IO = noop_direct_IO, |
| .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, |
| }; |