| // 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, | |
| }; |