blob: bb191953dcb1b115663724abcae213eea4745518 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Attribute list attribute handling code.
* Part of this file is based on code from the NTFS-3G.
*
* Copyright (c) 2004-2005 Anton Altaparmakov
* Copyright (c) 2004-2005 Yura Pakhuchiy
* Copyright (c) 2006 Szabolcs Szakacsits
* Copyright (c) 2025 LG Electronics Co., Ltd.
*/
#include "mft.h"
#include "attrib.h"
#include "attrlist.h"
#include "lcnalloc.h"
#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024)
/*
* ntfs_attrlist_need - check whether inode need attribute list
* @ni: opened ntfs inode for which perform check
*
* Check whether all are attributes belong to one MFT record, in that case
* attribute list is not needed.
*
* Return 1 if inode need attribute list, 0 if not, or -errno on error.
*/
int ntfs_attrlist_need(struct ntfs_inode *ni)
{
struct attr_list_entry *ale;
if (!ni) {
ntfs_debug("Invalid arguments.\n");
return -EINVAL;
}
ntfs_debug("Entering for inode 0x%llx.\n", (long long) ni->mft_no);
if (!NInoAttrList(ni)) {
ntfs_debug("Inode haven't got attribute list.\n");
return -EINVAL;
}
if (!ni->attr_list) {
ntfs_debug("Corrupt in-memory struct.\n");
return -EINVAL;
}
ale = (struct attr_list_entry *)ni->attr_list;
while ((u8 *)ale < ni->attr_list + ni->attr_list_size) {
if (MREF_LE(ale->mft_reference) != ni->mft_no)
return 1;
ale = (struct attr_list_entry *)((u8 *)ale + le16_to_cpu(ale->length));
}
return 0;
}
/*
* Repack the $MFT/$ATTRIBUTE_LIST data into one run.
*
* The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT
* record. Once that record has no room left, extending a fragmented list
* can require one more mapping-pairs byte than the record can hold. There
* is no attribute that can legally be moved out in that state: $STANDARD_
* INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to
* stay in the base record. Move the list data to one contiguous run. The
* caller supplies the minimum allocation size so a recovery can use the
* smallest useful run while normal updates can still request the maximum
* legal list size as a reserve.
*/
static int ntfs_attrlist_repack(struct inode *attr_vi,
struct ntfs_inode *attr_ni, s64 min_alloc_size,
struct ntfs_inode *locked_ni)
{
struct ntfs_volume *vol = attr_ni->vol;
struct runlist_element *old_rl, *new_rl;
u8 *data = NULL;
s64 data_size, alloc_size, nr_clusters, written;
s64 old_alloc_size;
size_t old_rl_count, new_rl_count;
unsigned long flags;
int err, restore_err;
if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) ||
min_alloc_size < 0)
return -EINVAL;
/* The buffered I/O below can reacquire the attribute runlist lock. */
if (attr_ni == locked_ni)
return -ENOSPC;
err = ntfs_attr_map_whole_runlist(attr_ni);
if (err)
return err;
data_size = attr_ni->data_size;
if (data_size < 0)
return -EIO;
if (data_size) {
data = kvmalloc(data_size, GFP_NOFS);
if (!data)
return -ENOMEM;
written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data);
if (written != data_size) {
err = written < 0 ? (int)written : -EIO;
goto out_free_data;
}
}
old_alloc_size = attr_ni->allocated_size;
alloc_size = max_t(s64, old_alloc_size, min_alloc_size);
nr_clusters = ntfs_bytes_to_cluster(vol,
alloc_size + vol->cluster_size - 1);
if (nr_clusters <= 0) {
err = -EFBIG;
goto out_free_data;
}
/* A single run keeps the mapping pairs at the minimum size. */
new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE,
true, true, false);
if (IS_ERR(new_rl)) {
err = PTR_ERR(new_rl);
goto out_free_data;
}
new_rl_count = 0;
if (new_rl->vcn == 0 && new_rl->length == nr_clusters &&
!new_rl[1].length)
new_rl_count = 2;
if (new_rl_count != 2) {
ntfs_cluster_free_from_rl(vol, new_rl);
kvfree(new_rl);
err = -ENOSPC;
goto out_free_data;
}
old_rl = attr_ni->runlist.rl;
old_rl_count = attr_ni->runlist.count;
down_write(&attr_ni->runlist.lock);
attr_ni->runlist.rl = new_rl;
attr_ni->runlist.count = new_rl_count;
up_write(&attr_ni->runlist.lock);
write_lock_irqsave(&attr_ni->size_lock, flags);
attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters);
write_unlock_irqrestore(&attr_ni->size_lock, flags);
/* Populate the replacement extent before publishing its mapping pairs. */
if (data_size) {
written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true);
if (written != data_size) {
err = written < 0 ? (int)written : -EIO;
goto restore_old_runlist;
}
}
err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni);
if (err)
goto restore_old_runlist;
/* The new mapping is now authoritative; release the old data runs. */
if (ntfs_cluster_free_from_rl(vol, old_rl)) {
ntfs_error(vol->sb,
"Failed to free old ATTRIBUTE_LIST extent: inode %#llx",
(long long)attr_ni->mft_no);
NVolSetErrors(vol);
}
kvfree(old_rl);
kvfree(data);
return 0;
restore_old_runlist:
down_write(&attr_ni->runlist.lock);
attr_ni->runlist.rl = old_rl;
attr_ni->runlist.count = old_rl_count;
up_write(&attr_ni->runlist.lock);
write_lock_irqsave(&attr_ni->size_lock, flags);
attr_ni->allocated_size = old_alloc_size;
write_unlock_irqrestore(&attr_ni->size_lock, flags);
restore_err = ntfs_attr_update_mapping_pairs_locked(
attr_ni, 0, locked_ni);
if (restore_err) {
ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)",
restore_err);
NVolSetErrors(vol);
}
ntfs_cluster_free_from_rl(vol, new_rl);
kvfree(new_rl);
err = err ? err : restore_err;
out_free_data:
kvfree(data);
return err;
}
int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
struct ntfs_inode *locked_ni)
{
struct inode *attr_vi;
struct ntfs_inode *attr_ni;
s64 written;
int err, retry_err;
/*
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
* inodes. Do not look up the attribute-list inode after SB_ACTIVE has
* been cleared; it may already be I_FREEING, and waiting on it can
* self-deadlock.
*/
if (!(VFS_I(base_ni)->i_sb->s_flags & SB_ACTIVE))
return -EIO;
attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
if (IS_ERR(attr_vi)) {
err = PTR_ERR(attr_vi);
return err;
}
attr_ni = NTFS_I(attr_vi);
/* Truncation and page-cache writes can reacquire this runlist lock. */
if (attr_ni == locked_ni) {
iput(attr_vi);
return -ENOSPC;
}
err = ntfs_attr_truncate_i_locked(
attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni);
if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT &&
NInoNonResident(attr_ni)) {
retry_err = ntfs_attrlist_repack(attr_vi, attr_ni,
base_ni->attr_list_size, locked_ni);
if (retry_err) {
ntfs_error(base_ni->vol->sb, "Failed to repack attribute list");
iput(attr_vi);
return retry_err;
}
retry_err = ntfs_attr_truncate_i_locked(
attr_ni, base_ni->attr_list_size,
HOLES_NO, locked_ni);
if (retry_err) {
ntfs_error(base_ni->vol->sb,
"Failed to resize attribute list after repack");
iput(attr_vi);
return retry_err;
}
} else if (err) {
iput(attr_vi);
ntfs_error(base_ni->vol->sb,
"Failed to truncate attribute list of inode %#llx",
(long long)base_ni->mft_no);
return err;
}
/*
* Reserve the maximum legal list size while the MFT metadata area is
* still easy to allocate contiguously. This prevents a later list entry
* from needing another mapping-pairs byte in the full base MFT record.
* Failure to obtain the optional reserve must not reject the current
* metadata update; the repack retry above remains available if needed.
*/
if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) &&
attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) {
retry_err = ntfs_attr_expand_locked(
attr_ni, base_ni->attr_list_size,
NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
if (retry_err == -ENOSPC) {
retry_err = ntfs_attrlist_repack(
attr_vi, attr_ni,
NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
if (retry_err == -ENOSPC)
retry_err = 0;
}
if (retry_err) {
ntfs_error(base_ni->vol->sb,
"Failed to reserve attribute list space");
iput(attr_vi);
return retry_err;
}
}
i_size_write(attr_vi, base_ni->attr_list_size);
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
NInoSetAttrListNonResident(base_ni);
written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
base_ni->attr_list, false);
if (written != base_ni->attr_list_size) {
err = written < 0 ? (int)written : -EIO;
iput(attr_vi);
ntfs_error(base_ni->vol->sb,
"Failed to write attribute list of inode %#llx",
(long long)base_ni->mft_no);
return err;
}
NInoSetAttrListDirty(base_ni);
iput(attr_vi);
return 0;
}
int ntfs_attrlist_update(struct ntfs_inode *base_ni)
{
return ntfs_attrlist_update_locked(base_ni, NULL);
}
/*
* ntfs_attrlist_entry_add - add an attribute list attribute entry
* @ni: opened ntfs inode, which contains that attribute
* @attr: attribute record to add to attribute list
*
* Return 0 on success and -errno on error.
*/
int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
{
struct attr_list_entry *ale;
__le64 mref;
struct ntfs_attr_search_ctx *ctx;
u8 *new_al;
int entry_len, entry_offset, err;
struct mft_record *ni_mrec;
u8 *old_al;
__le64 lowest_vcn;
if (!ni || !attr) {
ntfs_debug("Invalid arguments.\n");
return -EINVAL;
}
ntfs_debug("Entering for inode 0x%llx, attr 0x%x.\n",
ni->mft_no, (unsigned int) le32_to_cpu(attr->type));
ni_mrec = map_mft_record(ni);
if (IS_ERR(ni_mrec)) {
ntfs_debug("Invalid arguments.\n");
return -EIO;
}
mref = MK_LE_MREF(ni->mft_no, le16_to_cpu(ni_mrec->sequence_number));
unmap_mft_record(ni);
if (ni->nr_extents == -1)
ni = ni->ext.base_ntfs_ino;
if (!NInoAttrList(ni)) {
ntfs_debug("Attribute list isn't present.\n");
return -ENOENT;
}
/* Determine size and allocate memory for new attribute list. */
entry_len = (sizeof(struct attr_list_entry) + sizeof(__le16) *
attr->name_length + 7) & ~7;
new_al = kvzalloc(ni->attr_list_size + entry_len, GFP_NOFS);
if (!new_al)
return -ENOMEM;
/* Find place for the new entry. */
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx) {
err = -ENOMEM;
ntfs_error(ni->vol->sb, "Failed to get search context");
goto err_out;
}
if (attr->non_resident)
lowest_vcn = attr->data.non_resident.lowest_vcn;
else
lowest_vcn = 0;
err = ntfs_attr_lookup(attr->type, (attr->name_length) ? (__le16 *)
((u8 *)attr + le16_to_cpu(attr->name_offset)) :
AT_UNNAMED, attr->name_length, CASE_SENSITIVE,
le64_to_cpu(lowest_vcn),
(attr->non_resident) ? NULL : ((u8 *)attr +
le16_to_cpu(attr->data.resident.value_offset)), (attr->non_resident) ?
0 : le32_to_cpu(attr->data.resident.value_length), ctx);
if (!err) {
/* Found some extent, check it to be before new extent. */
if (ctx->al_entry->lowest_vcn == lowest_vcn) {
err = -EEXIST;
ntfs_debug("Such attribute already present in the attribute list.\n");
ntfs_attr_put_search_ctx(ctx);
goto err_out;
}
/* Add new entry after this extent. */
ale = (struct attr_list_entry *)((u8 *)ctx->al_entry +
le16_to_cpu(ctx->al_entry->length));
} else {
/* Check for real errors. */
if (err != -ENOENT) {
ntfs_debug("Attribute lookup failed.\n");
ntfs_attr_put_search_ctx(ctx);
goto err_out;
}
/* No previous extents found. */
ale = ctx->al_entry;
}
/* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */
ntfs_attr_put_search_ctx(ctx);
/* Determine new entry offset. */
entry_offset = ((u8 *)ale - ni->attr_list);
/* Set pointer to new entry. */
ale = (struct attr_list_entry *)(new_al + entry_offset);
memset(ale, 0, entry_len);
/* Form new entry. */
ale->type = attr->type;
ale->length = cpu_to_le16(entry_len);
ale->name_length = attr->name_length;
ale->name_offset = offsetof(struct attr_list_entry, name);
if (attr->non_resident)
ale->lowest_vcn = attr->data.non_resident.lowest_vcn;
else
ale->lowest_vcn = 0;
ale->mft_reference = mref;
ale->instance = attr->instance;
memcpy(ale->name, (u8 *)attr + le16_to_cpu(attr->name_offset),
attr->name_length * sizeof(__le16));
/* Copy entries from old attribute list to new. */
memcpy(new_al, ni->attr_list, entry_offset);
memcpy(new_al + entry_offset + entry_len, ni->attr_list +
entry_offset, ni->attr_list_size - entry_offset);
/* Set new runlist. */
old_al = ni->attr_list;
ni->attr_list = new_al;
ni->attr_list_size = ni->attr_list_size + entry_len;
err = ntfs_attrlist_update(ni);
if (err) {
ni->attr_list = old_al;
ni->attr_list_size -= entry_len;
goto err_out;
}
kvfree(old_al);
return 0;
err_out:
kvfree(new_al);
return err;
}
/*
* ntfs_attrlist_entry_rm - remove an attribute list attribute entry
* @ctx: attribute search context describing the attribute list entry
*
* Remove the attribute list entry @ctx->al_entry from the attribute list.
*
* Return 0 on success and -errno on error.
*/
int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
{
u8 *new_al;
int new_al_len;
struct ntfs_inode *base_ni;
struct attr_list_entry *ale;
if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) {
ntfs_debug("Invalid arguments.\n");
return -EINVAL;
}
if (ctx->base_ntfs_ino)
base_ni = ctx->base_ntfs_ino;
else
base_ni = ctx->ntfs_ino;
ale = ctx->al_entry;
ntfs_debug("Entering for inode 0x%llx, attr 0x%x, lowest_vcn %lld.\n",
(long long)ctx->ntfs_ino->mft_no,
(unsigned int)le32_to_cpu(ctx->al_entry->type),
(long long)le64_to_cpu(ctx->al_entry->lowest_vcn));
if (!NInoAttrList(base_ni)) {
ntfs_debug("Attribute list isn't present.\n");
return -ENOENT;
}
/* Allocate memory for new attribute list. */
new_al_len = base_ni->attr_list_size - le16_to_cpu(ale->length);
new_al = kvzalloc(new_al_len, GFP_NOFS);
if (!new_al)
return -ENOMEM;
/* Copy entries from old attribute list to new. */
memcpy(new_al, base_ni->attr_list, (u8 *)ale - base_ni->attr_list);
memcpy(new_al + ((u8 *)ale - base_ni->attr_list), (u8 *)ale + le16_to_cpu(
ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list));
/* Set new runlist. */
kvfree(base_ni->attr_list);
base_ni->attr_list = new_al;
base_ni->attr_list_size = new_al_len;
return ntfs_attrlist_update(base_ni);
}