mirror of
https://github.com/torvalds/linux.git
synced 2026-11-10 17:11:02 +01:00
ntfs: repack $MFT/$ATTRIBUTE LIST
Repack the non-resident $MFT/$ATTRIBUTE_LIST into a contiguous run
when its mapping pairs no longer fit in the base MFT record. Propagate
allocation and writeback errors, and check synchronous replacement writes.
Fixes: 495e90fa33 ("ntfs: update attrib operations")
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
parent
6d8c197c99
commit
b1d732e62a
|
|
@ -3844,13 +3844,13 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
|
||||||
*/
|
*/
|
||||||
if (ni->type == AT_ATTRIBUTE_LIST) {
|
if (ni->type == AT_ATTRIBUTE_LIST) {
|
||||||
ntfs_attr_put_search_ctx(ctx);
|
ntfs_attr_put_search_ctx(ctx);
|
||||||
if (ntfs_inode_free_space(base_ni, mp_size -
|
err = ntfs_inode_free_space(base_ni, mp_size -
|
||||||
cur_max_mp_size)) {
|
cur_max_mp_size);
|
||||||
ntfs_debug("Attribute list is too big. Defragment the volume\n");
|
if (err)
|
||||||
return -ENOSPC;
|
return err;
|
||||||
}
|
err = ntfs_attrlist_update(base_ni);
|
||||||
if (ntfs_attrlist_update(base_ni))
|
if (err)
|
||||||
return -EIO;
|
return err;
|
||||||
goto retry;
|
goto retry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4522,13 +4522,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
|
||||||
ntfs_bytes_to_cluster(vol, ni->allocated_size),
|
ntfs_bytes_to_cluster(vol, ni->allocated_size),
|
||||||
first_free_vcn -
|
first_free_vcn -
|
||||||
ntfs_bytes_to_cluster(vol, ni->allocated_size),
|
ntfs_bytes_to_cluster(vol, ni->allocated_size),
|
||||||
lcn_seek_from, DATA_ZONE, false, false, false);
|
lcn_seek_from, DATA_ZONE, false,
|
||||||
|
ni->type == AT_ATTRIBUTE_LIST, false);
|
||||||
if (IS_ERR(rl)) {
|
if (IS_ERR(rl)) {
|
||||||
ntfs_debug("Cluster allocation failed (%lld)",
|
ntfs_debug("Cluster allocation failed (%lld)",
|
||||||
(long long)first_free_vcn -
|
(long long)first_free_vcn -
|
||||||
ntfs_bytes_to_cluster(vol, ni->allocated_size));
|
ntfs_bytes_to_cluster(vol, ni->allocated_size));
|
||||||
return PTR_ERR(rl);
|
return PTR_ERR(rl);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* A contiguous ATTRIBUTE_LIST allocation keeps its mapping
|
||||||
|
* pairs small enough to fit in the base MFT record. The
|
||||||
|
* allocator can return a short run when contiguity was
|
||||||
|
* requested, so discard it and retry normally if necessary.
|
||||||
|
*/
|
||||||
|
if (ni->type == AT_ATTRIBUTE_LIST &&
|
||||||
|
(rl->vcn != ntfs_bytes_to_cluster(vol,
|
||||||
|
ni->allocated_size) ||
|
||||||
|
rl->length != first_free_vcn -
|
||||||
|
ntfs_bytes_to_cluster(vol, ni->allocated_size) ||
|
||||||
|
rl[1].length)) {
|
||||||
|
ntfs_cluster_free_from_rl(vol, rl);
|
||||||
|
kvfree(rl);
|
||||||
|
rl = ntfs_cluster_alloc(vol,
|
||||||
|
ntfs_bytes_to_cluster(vol,
|
||||||
|
ni->allocated_size),
|
||||||
|
first_free_vcn -
|
||||||
|
ntfs_bytes_to_cluster(vol,
|
||||||
|
ni->allocated_size),
|
||||||
|
lcn_seek_from, DATA_ZONE, false,
|
||||||
|
false, false);
|
||||||
|
if (IS_ERR(rl))
|
||||||
|
return PTR_ERR(rl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NInoCompressed(ni)) {
|
if (!NInoCompressed(ni)) {
|
||||||
|
|
@ -4921,7 +4947,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
|
||||||
ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n",
|
ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n",
|
||||||
(unsigned long long)ni->mft_no, ni->type, newsize);
|
(unsigned long long)ni->mft_no, ni->type, newsize);
|
||||||
|
|
||||||
if (ni->data_size == newsize) {
|
if (ni->data_size == newsize &&
|
||||||
|
(!prealloc_size || prealloc_size <= ni->allocated_size)) {
|
||||||
ntfs_debug("Size is already ok\n");
|
ntfs_debug("Size is already ok\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -4936,7 +4963,7 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NInoNonResident(ni)) {
|
if (NInoNonResident(ni)) {
|
||||||
if (newsize > ni->data_size)
|
if (newsize > ni->data_size || prealloc_size > ni->allocated_size)
|
||||||
err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size,
|
err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size,
|
||||||
NVolDisableSparse(ni->vol) ?
|
NVolDisableSparse(ni->vol) ?
|
||||||
HOLES_NO : HOLES_OK, true);
|
HOLES_NO : HOLES_OK, true);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@
|
||||||
#include "mft.h"
|
#include "mft.h"
|
||||||
#include "attrib.h"
|
#include "attrib.h"
|
||||||
#include "attrlist.h"
|
#include "attrlist.h"
|
||||||
|
#include "lcnalloc.h"
|
||||||
|
|
||||||
|
#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ntfs_attrlist_need - check whether inode need attribute list
|
* ntfs_attrlist_need - check whether inode need attribute list
|
||||||
|
|
@ -51,11 +54,151 @@ int ntfs_attrlist_need(struct ntfs_inode *ni)
|
||||||
return 0;
|
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_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;
|
||||||
|
|
||||||
|
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(attr_ni, 0);
|
||||||
|
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(attr_ni, 0);
|
||||||
|
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(struct ntfs_inode *base_ni)
|
int ntfs_attrlist_update(struct ntfs_inode *base_ni)
|
||||||
{
|
{
|
||||||
struct inode *attr_vi;
|
struct inode *attr_vi;
|
||||||
struct ntfs_inode *attr_ni;
|
struct ntfs_inode *attr_ni;
|
||||||
int err;
|
s64 written;
|
||||||
|
int err, retry_err;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
|
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
|
||||||
|
|
@ -74,21 +217,55 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
|
||||||
attr_ni = NTFS_I(attr_vi);
|
attr_ni = NTFS_I(attr_vi);
|
||||||
|
|
||||||
err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO);
|
err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO);
|
||||||
if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) {
|
if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT &&
|
||||||
err = ntfs_attr_truncate(attr_ni, 0);
|
NInoNonResident(attr_ni)) {
|
||||||
if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) {
|
retry_err = ntfs_attrlist_repack(attr_vi, attr_ni,
|
||||||
|
base_ni->attr_list_size);
|
||||||
|
if (retry_err) {
|
||||||
|
ntfs_error(base_ni->vol->sb, "Failed to repack attribute list");
|
||||||
iput(attr_vi);
|
iput(attr_vi);
|
||||||
|
return retry_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size,
|
||||||
|
HOLES_NO);
|
||||||
|
if (retry_err) {
|
||||||
ntfs_error(base_ni->vol->sb,
|
ntfs_error(base_ni->vol->sb,
|
||||||
"Failed to truncate attribute list of inode %#llx",
|
"Failed to resize attribute list after repack");
|
||||||
(long long)base_ni->mft_no);
|
iput(attr_vi);
|
||||||
return -EIO;
|
return retry_err;
|
||||||
}
|
}
|
||||||
} else if (err) {
|
} else if (err) {
|
||||||
iput(attr_vi);
|
iput(attr_vi);
|
||||||
ntfs_error(base_ni->vol->sb,
|
ntfs_error(base_ni->vol->sb,
|
||||||
"Failed to truncate attribute list of inode %#llx",
|
"Failed to truncate attribute list of inode %#llx",
|
||||||
(long long)base_ni->mft_no);
|
(long long)base_ni->mft_no);
|
||||||
return -EIO;
|
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(attr_ni, base_ni->attr_list_size,
|
||||||
|
NTFS_MAX_ATTR_LIST_SIZE);
|
||||||
|
if (retry_err == -ENOSPC) {
|
||||||
|
retry_err = ntfs_attrlist_repack(attr_vi, attr_ni,
|
||||||
|
NTFS_MAX_ATTR_LIST_SIZE);
|
||||||
|
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);
|
i_size_write(attr_vi, base_ni->attr_list_size);
|
||||||
|
|
@ -96,14 +273,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
|
||||||
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
|
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
|
||||||
NInoSetAttrListNonResident(base_ni);
|
NInoSetAttrListNonResident(base_ni);
|
||||||
|
|
||||||
if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
|
written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
|
||||||
base_ni->attr_list, false) !=
|
base_ni->attr_list, false);
|
||||||
base_ni->attr_list_size) {
|
if (written != base_ni->attr_list_size) {
|
||||||
|
err = written < 0 ? (int)written : -EIO;
|
||||||
iput(attr_vi);
|
iput(attr_vi);
|
||||||
ntfs_error(base_ni->vol->sb,
|
ntfs_error(base_ni->vol->sb,
|
||||||
"Failed to write attribute list of inode %#llx",
|
"Failed to write attribute list of inode %#llx",
|
||||||
(long long)base_ni->mft_no);
|
(long long)base_ni->mft_no);
|
||||||
return -EIO;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
NInoSetAttrListDirty(base_ni);
|
NInoSetAttrListDirty(base_ni);
|
||||||
|
|
|
||||||
|
|
@ -3745,6 +3745,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
|
||||||
u64 rl_length = 0;
|
u64 rl_length = 0;
|
||||||
s64 vcn;
|
s64 vcn;
|
||||||
struct runlist_element *rl;
|
struct runlist_element *rl;
|
||||||
|
int bio_err;
|
||||||
|
|
||||||
lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
|
lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
|
||||||
vcn = ntfs_pidx_to_cluster(vol, folio->index);
|
vcn = ntfs_pidx_to_cluster(vol, folio->index);
|
||||||
|
|
@ -3787,8 +3788,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
|
||||||
goto err_unlock_folio;
|
goto err_unlock_folio;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit_bio_wait(bio);
|
bio_err = submit_bio_wait(bio);
|
||||||
bio_put(bio);
|
bio_put(bio);
|
||||||
|
if (bio_err) {
|
||||||
|
ntfs_error(vi->i_sb,
|
||||||
|
"Synchronous attribute write failed (%d)",
|
||||||
|
bio_err);
|
||||||
|
ret = bio_err;
|
||||||
|
goto err_unlock_folio;
|
||||||
|
}
|
||||||
vcn += rl_length;
|
vcn += rl_length;
|
||||||
offset += length;
|
offset += length;
|
||||||
} while (lcn_count != 0);
|
} while (lcn_count != 0);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user