blob: 78d1ce41958e0bf807deff2b89cdb80d437d68c4 [file]
// 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,
};