mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
Update the address space operations to use the iomap framework, replacing legacy buffer-head based code. Acked-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
264 lines
8.2 KiB
C
264 lines
8.2 KiB
C
// 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%lx, 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%lx. Returning 0.",
|
|
ni->mft_no);
|
|
break;
|
|
default:
|
|
ntfs_error(vol->sb,
|
|
"Failed to complete mapping for inode 0x%lx. 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,
|
|
};
|