- Make MFT extension work on existing Windows-created volumes by

dynamically reserving MFT tail records, accounting for records added
    during allocation, and avoiding false -ENOSPC failures.
 
  - Repack non-resident $MFT/$ATTRIBUTE_LIST when its mapping pairs no longer
    fit in the base MFT record, while propagating allocation and writeback
    errors.
 
  - Serialize runlist updates with the runlist lock and restore both the
    in-memory runlist and on-disk mapping pairs when allocation rollback is
    required.
 
  - Propagate folio errors and harden inode failure handling by treating
    interrupted reads as transient failures and discarding and unhashing
    inodes whose initialization fails.
 
  - Fix the $MFTMirr write offset when mirror records span multiple folios,
    preventing mirror records from overwriting the first record with large
    MFT record sizes.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmqtRIEWHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCBK8EAChFdTxig3sYog3dL27SX+1yNC4
 S1QtSQMvPJzcvLG2/mESC57snx1u6AXZ6enaQ/m8zQQvkWHFdwD+odgjOQ3474Yc
 MS7xQn5pCsAo3LmSWiDsQfHmvxgDMYlIRU1vmqr7fG2pj+W6BR2LB1PD3RI9exI7
 0WWAXBPZH5w5C9GE1Zo7TF9Xwby5Or31RS8+R57PXA/PJ1ivpWnlkpfLi4M/YkZK
 GIpunZafSpcKbEsuWcjhdz11bR4G9Qlwuuq0MDguLC/qsqsobHCeSbdx+4IsEAq5
 02yBl5hYm2E4u2KBedpe7oRwFvlPN0uakEGYS8SA1ad9XamjGIw6T0tkzkDL48Pq
 dhVAeX2oa8O9u+VK+qF/HIUylh/UbmHQJW8iSiZWO8WdULGBG8oCHI1hcSnMguwJ
 njyK75UXz4fMsKW6ZpRu0sRGqtKKcbg8IrCvLslPIOS2A9OAwSzytDKI+x1Kbgu0
 SVPYjf6XeOz83tvE+2OhfTT1hWkeKezMiUe4E/y9rgEDxv5vE4C5pvpDfRlj3oyn
 2JTXXjjUQGSQw/9cKbLsbElDH/FLEojLAsIFgM+2FbcG+x7PUUgSGdC4R4qZ9MUF
 cuMzfhi8vKyCYmJc8nNE4J6b6UkBNOFJMYBcArtByFW3LqfIzmqwW81Xx0Hz4cxi
 dmOhD6gXXYoi9m3lBQ==
 =rT1L
 -----END PGP SIGNATURE-----

Merge tag 'ntfs-for-7.3-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs

Pull ntfs fixes from Namjae Jeon:

 - Make MFT extension work on existing Windows-created volumes by
   dynamically reserving MFT tail records, accounting for records added
   during allocation, and avoiding false -ENOSPC failures

 - Repack non-resident $MFT/$ATTRIBUTE_LIST when its mapping pairs no
   longer fit in the base MFT record, while propagating allocation and
   writeback errors

 - Serialize runlist updates with the runlist lock and restore both the
   in-memory runlist and on-disk mapping pairs when allocation rollback
   is required

 - Propagate folio errors and harden inode failure handling by treating
   interrupted reads as transient failures and discarding and unhashing
   inodes whose initialization fails

 - Fix the $MFTMirr write offset when mirror records span multiple
   folios, preventing mirror records from overwriting the first record
   with large MFT record sizes

* tag 'ntfs-for-7.3-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs:
  ntfs: fix $MFTMirr write offset when it spans multiple folios
  ntfs: unhash failed inode reads
  ntfs: discard inodes that fail initialization
  ntfs: ignore interrupted inode reads as corruption
  ntfs: propagate folio errors
  ntfs: protect runlist updates with the runlist lock
  ntfs: account for MFT records added during allocation
  ntfs: repack $MFT/$ATTRIBUTE LIST
  ntfs: use dynamic MFT tail reservation
This commit is contained in:
Linus Torvalds 2026-09-18 11:02:08 -07:00
commit bfda5a01aa
12 changed files with 840 additions and 207 deletions

View File

@ -2917,7 +2917,7 @@ int ntfs_attr_add(struct ntfs_inode *ni, __le32 type,
attr_ni = NULL;
/* Allocate new extent. */
err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL);
err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL, -1);
if (err) {
ntfs_error(sb, "Failed to allocate extent record");
goto err_out;
@ -3550,7 +3550,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
* new extent and move attribute to it.
*/
ni = NULL;
err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL);
err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL, -1);
if (err) {
ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err);
return err;
@ -3574,7 +3574,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
* update allocated and compressed size.
*/
static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
struct mft_record *m, struct ntfs_attr_search_ctx *ctx)
struct mft_record *m, struct ntfs_attr_search_ctx *ctx,
struct ntfs_inode *locked_ni, bool defer_attrlist)
{
int sparse, err = 0;
struct ntfs_inode *base_ni;
@ -3610,6 +3611,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) &&
!(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) {
if (defer_attrlist)
return -ENOSPC;
if (!NInoAttrList(base_ni)) {
err = ntfs_inode_add_attrlist(base_ni);
if (err)
@ -3623,7 +3626,7 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
goto out;
}
err = ntfs_attrlist_update(base_ni);
err = ntfs_attrlist_update_locked(base_ni, locked_ni);
if (err)
goto out;
err = -EAGAIN;
@ -3703,6 +3706,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
* ntfs_attr_update_mapping_pairs - update mapping pairs for ntfs attribute
* @ni: non-resident ntfs inode for which we need update
* @from_vcn: update runlist starting this VCN
* @locked_ni: inode whose runlist write lock is already held
* @defer_attrlist: return -ENOSPC instead of updating an attribute list
*
* Build mapping pairs from @na->rl and write them to the disk. Also, this
* function updates sparse bit, allocated and compressed size (allocates/frees
@ -3712,7 +3717,10 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
* call to this function. Vice-versa @na->compressed_size will be calculated and
* set to correct value during this function.
*/
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni,
s64 from_vcn,
struct ntfs_inode *locked_ni,
bool defer_attrlist)
{
struct ntfs_attr_search_ctx *ctx;
struct ntfs_inode *base_ni;
@ -3804,7 +3812,8 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
continue;
}
err = ntfs_attr_update_meta(a, ni, m, ctx);
err = ntfs_attr_update_meta(a, ni, m, ctx, locked_ni,
defer_attrlist);
if (err < 0) {
if (err == -EAGAIN) {
ntfs_attr_put_search_ctx(ctx);
@ -3844,18 +3853,28 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
*/
if (ni->type == AT_ATTRIBUTE_LIST) {
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_free_space(base_ni, mp_size -
cur_max_mp_size)) {
ntfs_debug("Attribute list is too big. Defragment the volume\n");
return -ENOSPC;
ctx = NULL;
if (locked_ni == ni || defer_attrlist) {
err = -ENOSPC;
goto put_err_out;
}
if (ntfs_attrlist_update(base_ni))
return -EIO;
err = ntfs_inode_free_space(base_ni, mp_size -
cur_max_mp_size);
if (err)
return err;
err = ntfs_attrlist_update_locked(
base_ni, locked_ni);
if (err)
return err;
goto retry;
}
/* Add attribute list if it isn't present, and retry. */
if (!NInoAttrList(base_ni)) {
if (defer_attrlist) {
err = -ENOSPC;
goto put_err_out;
}
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_add_attrlist(base_ni)) {
ntfs_error(sb, "Can not add attrlist");
@ -3883,13 +3902,21 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
}
}
if (defer_attrlist &&
(ctx->ntfs_ino->nr_extents == -1 ||
NInoAttrList(ctx->ntfs_ino)) &&
ctx->attr->type != AT_ATTRIBUTE_LIST) {
err = -ENOSPC;
goto put_err_out;
}
/* Update lowest vcn. */
a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn);
mark_mft_record_dirty(ctx->ntfs_ino);
if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) &&
ctx->attr->type != AT_ATTRIBUTE_LIST) {
ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn);
err = ntfs_attrlist_update(base_ni);
err = ntfs_attrlist_update_locked(base_ni, locked_ni);
if (err)
goto put_err_out;
}
@ -3976,7 +4003,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
unsigned int de_cnt = 0;
/* Allocate new mft record. */
err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL);
err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL,
base_ni->mft_no == FILE_MFT &&
ni->type == AT_DATA &&
ni->name == AT_UNNAMED ? stop_vcn : -1);
if (err) {
ntfs_error(sb, "Failed to allocate extent record");
goto put_err_out;
@ -4061,6 +4091,19 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
return err;
}
int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
s64 from_vcn,
struct ntfs_inode *locked_ni)
{
return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni,
false);
}
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
{
return ntfs_attr_update_mapping_pairs_locked(ni, from_vcn, NULL);
}
/*
* ntfs_attr_make_resident - convert a non-resident to a resident attribute
* @ni: open ntfs attribute to make resident
@ -4194,7 +4237,9 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
*
* Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes.
*/
static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize)
static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni,
const s64 newsize,
struct ntfs_inode *locked_ni)
{
struct ntfs_volume *vol;
struct ntfs_attr_search_ctx *ctx;
@ -4202,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
s64 nr_freed_clusters;
int err;
struct ntfs_inode *base_ni;
bool runlist_locked = locked_ni == ni;
ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n",
(unsigned long long)ni->mft_no, ni->type, (long long)newsize);
@ -4247,18 +4293,24 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
* clusters if there is a change.
*/
if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) {
struct ntfs_attr_search_ctx *ctx;
/*
* ntfs_cluster_free() and ntfs_rl_truncate_nolock()
* both require this lock.
*/
if (!runlist_locked)
down_write(&ni->runlist.lock);
err = ntfs_attr_map_whole_runlist(ni);
if (err) {
ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n");
return err;
goto unlock_runlist;
}
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx) {
ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
return -ENOMEM;
err = -ENOMEM;
goto unlock_runlist;
}
/* Deallocate all clusters starting with the first free one. */
@ -4266,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
if (nr_freed_clusters < 0) {
ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n");
ntfs_attr_put_search_ctx(ctx);
return (int)nr_freed_clusters;
err = (int)nr_freed_clusters;
goto unlock_runlist;
}
ntfs_attr_put_search_ctx(ctx);
@ -4279,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
kvfree(ni->runlist.rl);
ni->runlist.rl = NULL;
ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n");
return -EIO;
err = -EIO;
goto unlock_runlist;
}
/* Prepare to mapping pairs update. */
@ -4295,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9;
/* Write mapping pairs for new runlist. */
err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n");
return err;
goto unlock_runlist;
}
if (!runlist_locked)
up_write(&ni->runlist.lock);
}
/* Get the first attribute record. */
@ -4341,7 +4397,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
/* If the attribute now has zero size, make it resident. */
if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) {
if (!runlist_locked)
down_write(&ni->runlist.lock);
err = ntfs_attr_make_resident(ni, ctx);
if (!runlist_locked)
up_write(&ni->runlist.lock);
if (err) {
/* If couldn't make resident, just continue. */
if (err != -EPERM)
@ -4358,6 +4418,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
put_err_out:
ntfs_attr_put_search_ctx(ctx);
return err;
unlock_runlist:
if (!runlist_locked)
up_write(&ni->runlist.lock);
return err;
}
/*
@ -4366,13 +4431,14 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
* @prealloc_size: preallocation size (in bytes) to which to expand the attribute
* @newsize: new size (in bytes) to which to expand the attribute
* @holes: how to create a hole if expanding
* @need_lock: whether mrec lock is needed or not
* @locked_ni: inode whose runlist lock is already held
*
* Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes,
* by allocating new clusters.
*/
static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize,
const s64 prealloc_size, unsigned int holes, bool need_lock)
const s64 prealloc_size, unsigned int holes,
struct ntfs_inode *locked_ni)
{
s64 lcn_seek_from;
s64 first_free_vcn;
@ -4519,13 +4585,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
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);
lcn_seek_from, DATA_ZONE, false,
ni->type == AT_ATTRIBUTE_LIST, false);
if (IS_ERR(rl)) {
ntfs_debug("Cluster allocation failed (%lld)",
(long long)first_free_vcn -
ntfs_bytes_to_cluster(vol, ni->allocated_size));
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)) {
@ -4544,7 +4636,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
/* Prepare to mapping pairs update. */
ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn);
err = ntfs_attr_update_mapping_pairs(ni, 0);
err = ntfs_attr_update_mapping_pairs_locked(
ni, 0, locked_ni);
if (err) {
ntfs_debug("Mapping pairs update failed");
goto rollback;
@ -4588,11 +4681,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
ntfs_debug("Leaking clusters");
/* Now, truncate the runlist itself. */
if (need_lock)
if (ni != locked_ni)
down_write(&ni->runlist.lock);
err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist,
ntfs_bytes_to_cluster(vol, org_alloc_size));
if (need_lock)
if (ni != locked_ni)
up_write(&ni->runlist.lock);
if (err2) {
/*
@ -4606,11 +4699,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
/* Prepare to mapping pairs update. */
ni->allocated_size = org_alloc_size;
/* Restore mapping pairs. */
if (need_lock)
if (ni != locked_ni)
down_read(&ni->runlist.lock);
if (ntfs_attr_update_mapping_pairs(ni, 0))
if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true))
ntfs_error(sb, "Failed to restore old mapping pairs");
if (need_lock)
if (ni != locked_ni)
up_read(&ni->runlist.lock);
if (NInoSparse(ni) || NInoCompressed(ni)) {
@ -4715,7 +4808,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
/* Resize non-resident attribute */
return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true);
return ntfs_non_resident_attr_expand(
attr_ni, newsize, prealloc_size, holes, NULL);
} else if (err != -ENOSPC && err != -EPERM) {
ntfs_error(sb, "Failed to make attribute non-resident");
goto put_err_out;
@ -4836,7 +4930,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
}
/* Allocate new mft record. */
err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL);
err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL, -1);
if (err) {
ntfs_error(sb, "Couldn't allocate MFT record");
goto put_err_out;
@ -4890,13 +4984,14 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
if (NInoNonResident(ni)) {
if (newsize > i_size) {
down_write(&ni->runlist.lock);
err = ntfs_non_resident_attr_expand(ni, newsize, 0,
NVolDisableSparse(ni->vol) ?
HOLES_NO : HOLES_OK,
false);
err = ntfs_non_resident_attr_expand(
ni, newsize, 0,
NVolDisableSparse(ni->vol) ?
HOLES_NO : HOLES_OK, ni);
up_write(&ni->runlist.lock);
} else
err = ntfs_non_resident_attr_shrink(ni, newsize);
err = ntfs_non_resident_attr_shrink(
ni, newsize, NULL);
} else
err = ntfs_resident_attr_resize(ni, newsize, 0,
NVolDisableSparse(ni->vol) ?
@ -4905,7 +5000,9 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
return err;
}
int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size)
int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize,
const s64 prealloc_size,
struct ntfs_inode *locked_ni)
{
int err = 0;
@ -4918,7 +5015,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",
(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");
return 0;
}
@ -4933,10 +5031,11 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
}
if (NInoNonResident(ni)) {
if (newsize > ni->data_size)
err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size,
NVolDisableSparse(ni->vol) ?
HOLES_NO : HOLES_OK, true);
if (newsize > ni->data_size || prealloc_size > ni->allocated_size)
err = ntfs_non_resident_attr_expand(
ni, newsize, prealloc_size,
NVolDisableSparse(ni->vol) ?
HOLES_NO : HOLES_OK, locked_ni);
} else
err = ntfs_resident_attr_resize(ni, newsize, prealloc_size,
NVolDisableSparse(ni->vol) ?
@ -4947,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
return err;
}
int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize,
const s64 prealloc_size)
{
return ntfs_attr_expand_locked(ni, newsize, prealloc_size, NULL);
}
/*
* ntfs_attr_truncate_i - resize an ntfs attribute
* @ni: open ntfs inode to resize
@ -4959,7 +5064,9 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
* newly allocated space is marked as not initialised and no real allocation
* on disk is performed.
*/
int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes)
int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize,
unsigned int holes,
struct ntfs_inode *locked_ni)
{
int err;
@ -4993,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int
if (NInoNonResident(ni)) {
if (newsize > ni->data_size)
err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true);
err = ntfs_non_resident_attr_expand(
ni, newsize, 0, holes, locked_ni);
else
err = ntfs_non_resident_attr_shrink(ni, newsize);
err = ntfs_non_resident_attr_shrink(
ni, newsize, locked_ni);
} else
err = ntfs_resident_attr_resize(ni, newsize, 0, holes);
ntfs_debug("Return status %d\n", err);
return err;
}
int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize,
unsigned int holes)
{
return ntfs_attr_truncate_i_locked(ni, newsize, holes, NULL);
}
/*
* Resize an attribute, creating a hole if relevant
*/
@ -5019,10 +5134,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
struct ntfs_volume *vol = ni->vol;
struct ntfs_attr_search_ctx *ctx;
struct runlist_element *rl, *rlc;
struct runlist_element *old_rl = NULL;
s64 vcn = vcn_start, lcn, clu_count;
s64 lcn_seek_from = -1;
int err = 0;
size_t new_rl_count;
size_t new_rl_count, old_rl_count;
err = ntfs_attr_map_whole_runlist(ni);
if (err)
@ -5115,6 +5231,19 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
WARN_ON(rlc->vcn != vcn);
lcn = rlc->lcn;
clu_count = rlc->length;
old_rl_count = ni->runlist.count;
old_rl = kmemdup(ni->runlist.rl,
old_rl_count * sizeof(*old_rl), GFP_NOFS);
if (!old_rl) {
err = -ENOMEM;
if (ntfs_cluster_free_from_rl(vol, rlc)) {
ntfs_error(vol->sb,
"Failed to free cluster allocation after runlist backup failure.");
NVolSetErrors(vol);
}
kvfree(rlc);
goto out;
}
rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count);
if (IS_ERR(rl)) {
@ -5138,15 +5267,32 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
if (update_mp) {
ntfs_attr_reinit_search_ctx(ctx);
err = ntfs_attr_update_mapping_pairs(ni, 0);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
int err2;
err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx);
if (err2 < 0)
if (err2 < 0 || err2 != clu_count) {
ntfs_error(vol->sb,
"Failed to free cluster allocation. Leaving inconstant metadata.\n");
goto out;
"Failed to free cluster allocation. Leaving inconsistent metadata.\n");
NVolSetErrors(vol);
goto out;
}
/*
* Restore the runlist before repairing the on-disk
* mapping pairs.
*/
kvfree(ni->runlist.rl);
ni->runlist.rl = old_rl;
ni->runlist.count = old_rl_count;
old_rl = NULL;
if (ntfs_attr_update_mapping_pairs_locked(
ni, 0, ni)) {
ntfs_error(vol->sb,
"Failed to restore mapping pairs after allocation rollback.\n");
NVolSetErrors(vol);
}
}
} else {
VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9);
@ -5158,6 +5304,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
*lcn_count = clu_count;
*balloc = true;
out:
kvfree(old_rl);
ntfs_attr_put_search_ctx(ctx);
return err;
}
@ -5401,7 +5548,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
ni->data_size += ntfs_cluster_to_bytes(vol, len);
if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size)
ni->initialized_size += ntfs_cluster_to_bytes(vol, len);
ret = ntfs_attr_update_mapping_pairs(ni, 0);
ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (ret)
return ret;
@ -5486,7 +5633,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
}
if (ni->allocated_size > 0) {
ret = ntfs_attr_update_mapping_pairs(ni, 0);
ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (ret) {
up_write(&ni->runlist.lock);
goto out_rl;
@ -5564,7 +5711,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
ni->runlist.rl = rl;
ni->runlist.count = new_rl_count;
ret = ntfs_attr_update_mapping_pairs(ni, 0);
ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (ret) {
kvfree(punch_rl);
@ -5740,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
if (NInoRunlistDirty(ni)) {
mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
down_write(&ni->runlist.lock);
err = ntfs_attr_update_mapping_pairs(ni, 0);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err)
ntfs_error(ni->vol->sb, "Updating mapping pairs failed");
else

View File

@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
const s64 i_size);
int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size);
int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize,
const s64 prealloc_size,
struct ntfs_inode *locked_ni);
int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes);
int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize,
unsigned int holes,
struct ntfs_inode *locked_ni);
int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize);
int ntfs_attr_rm(struct ntfs_inode *ni);
int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
@ -133,6 +139,9 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
__le16 *name, u8 name_len, u8 *val, u32 size,
__le16 flags);
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn);
int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
s64 from_vcn,
struct ntfs_inode *locked_ni);
struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
/*

View File

@ -12,6 +12,9 @@
#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
@ -51,11 +54,155 @@ int ntfs_attrlist_need(struct ntfs_inode *ni)
return 0;
}
int ntfs_attrlist_update(struct ntfs_inode *base_ni)
/*
* 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;
int err;
s64 written;
int err, retry_err;
/*
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
@ -72,23 +219,66 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
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(attr_ni, base_ni->attr_list_size, HOLES_NO);
if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) {
err = ntfs_attr_truncate(attr_ni, 0);
if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) {
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 truncate attribute list of inode %#llx",
(long long)base_ni->mft_no);
return -EIO;
"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 -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_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);
@ -96,14 +286,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
NInoSetAttrListNonResident(base_ni);
if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
base_ni->attr_list, false) !=
base_ni->attr_list_size) {
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 -EIO;
return err;
}
NInoSetAttrListDirty(base_ni);
@ -111,6 +302,11 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
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

View File

@ -16,5 +16,7 @@ int ntfs_attrlist_need(struct ntfs_inode *ni);
int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr);
int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx);
int ntfs_attrlist_update(struct ntfs_inode *base_ni);
int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
struct ntfs_inode *locked_ni);
#endif /* defined _NTFS_ATTRLIST_H */

View File

@ -1450,7 +1450,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
ni->runlist.rl = rl;
rlc = NULL;
err = ntfs_attr_update_mapping_pairs(ni, 0);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (err)
err = -EIO;

View File

@ -111,7 +111,8 @@ static int ntfs_trim_prealloc(struct inode *vi)
ntfs_error(vol->sb, "Preallocated block rollback failed");
} else {
ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr);
err = ntfs_attr_update_mapping_pairs(ni, 0);
err = ntfs_attr_update_mapping_pairs_locked(
ni, 0, ni);
if (err)
ntfs_error(vol->sb,
"Failed to rollback mapping pairs for prealloc");

View File

@ -170,17 +170,19 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no)
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_inode(vi);
unlock_new_inode(vi);
if (err) {
remove_inode_hash(vi);
discard_new_inode(vi);
} else
unlock_new_inode(vi);
}
/*
* There is no point in keeping bad inodes around. This also
* simplifies things in that we never need to check for bad inodes
* elsewhere.
*/
if (unlikely(err)) {
iput(vi);
if (unlikely(err))
vi = ERR_PTR(err);
}
return vi;
}
@ -231,17 +233,19 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type,
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_attr_inode(base_vi, vi);
unlock_new_inode(vi);
if (err) {
remove_inode_hash(vi);
discard_new_inode(vi);
} else
unlock_new_inode(vi);
}
/*
* There is no point in keeping bad attribute inodes around. This also
* simplifies things in that we never need to check for bad attribute
* inodes elsewhere.
*/
if (unlikely(err)) {
iput(vi);
if (unlikely(err))
vi = ERR_PTR(err);
}
return vi;
}
@ -286,17 +290,19 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name,
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_index_inode(base_vi, vi);
unlock_new_inode(vi);
if (err) {
remove_inode_hash(vi);
discard_new_inode(vi);
} else
unlock_new_inode(vi);
}
/*
* There is no point in keeping bad index inodes around. This also
* simplifies things in that we never need to check for bad index
* inodes elsewhere.
*/
if (unlikely(err)) {
iput(vi);
if (unlikely(err))
vi = ERR_PTR(err);
}
return vi;
}
@ -1241,7 +1247,8 @@ static int ntfs_read_locked_inode(struct inode *vi)
if (m)
unmap_mft_record(ni);
err_out:
if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) {
if (err != -EOPNOTSUPP && err != -ENOMEM &&
err != -EINTR && err != -ERESTARTSYS && vol_err == true) {
ntfs_error(vol->sb,
"Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no);
@ -1467,12 +1474,13 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(base_ni);
err_out:
if (err != -ENOENT)
if (err != -ENOENT && err != -EINTR && err != -ERESTARTSYS)
ntfs_error(vol->sb,
"Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no, ni->type, ni->name_len,
base_ni->mft_no);
if (err != -ENOENT && err != -ENOMEM)
if (err != -ENOENT && err != -ENOMEM &&
err != -EINTR && err != -ERESTARTSYS)
NVolSetErrors(vol);
return err;
}
@ -1676,8 +1684,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
/* Get the index bitmap attribute inode. */
bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len);
if (IS_ERR(bvi)) {
ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
err = PTR_ERR(bvi);
if (err != -EINTR && err != -ERESTARTSYS)
ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
goto unm_err_out;
}
bni = NTFS_I(bvi);
@ -1721,10 +1730,12 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
if (m)
unmap_mft_record(base_ni);
err_out:
ntfs_error(vi->i_sb,
"Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
err, ni->mft_no, ni->name_len);
if (err != -EOPNOTSUPP && err != -ENOMEM)
if (err != -EINTR && err != -ERESTARTSYS)
ntfs_error(vi->i_sb,
"Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
err, ni->mft_no, ni->name_len);
if (err != -EOPNOTSUPP && err != -ENOMEM &&
err != -EINTR && err != -ERESTARTSYS)
NVolSetErrors(vol);
return err;
}
@ -2772,7 +2783,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
if (NInoNonResident(ni) && NInoRunlistDirty(ni)) {
down_write(&ni->runlist.lock);
err = ntfs_attr_update_mapping_pairs(ni, 0);
err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (!err)
NInoClearRunlistDirty(ni);
up_write(&ni->runlist.lock);
@ -3713,7 +3724,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
FGP_CREAT | FGP_LOCK,
mapping_gfp_mask(mapping));
if (IS_ERR(folio)) {
ret = -ENOMEM;
ret = PTR_ERR(folio);
break;
}
} else {
@ -3745,6 +3756,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
u64 rl_length = 0;
s64 vcn;
struct runlist_element *rl;
int bio_err;
lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
vcn = ntfs_pidx_to_cluster(vol, folio->index);
@ -3787,8 +3799,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
goto err_unlock_folio;
}
submit_bio_wait(bio);
bio_err = submit_bio_wait(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;
offset += length;
} while (lcn_count != 0);

View File

@ -213,7 +213,8 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
return m;
atomic_dec(&ni->count);
ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m));
if (PTR_ERR(m) != -EINTR && PTR_ERR(m) != -ERESTARTSYS)
ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m));
return m;
}
@ -462,7 +463,7 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
{
u8 *kmirr;
struct folio *folio;
unsigned int folio_ofs, lcn_folio_off = 0;
unsigned int folio_ofs;
int err = 0;
struct bio *bio;
@ -492,15 +493,11 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
memcpy(kmirr, m, vol->mft_record_size);
kunmap_local(kmirr);
if (vol->cluster_size_bits > PAGE_SHIFT) {
lcn_folio_off = folio->index << PAGE_SHIFT;
lcn_folio_off &= vol->cluster_size_mask;
}
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector =
ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
lcn_folio_off + folio_ofs);
((u64)folio->index << PAGE_SHIFT) +
folio_ofs);
if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs))
err = submit_bio_wait(bio);
@ -841,12 +838,126 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk.";
#define RESERVED_MFT_RECORDS 64
#define FIRST_NORMAL_MFT_RECORD 24
#define MFT_RECORD_RESERVE 4
/*
* ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name
* Records 12-15 are marked in use by Windows but normally have no name
* and no links. Keep them as the last bootstrap option when a volume
* mounted without an in-memory tail reserve needs its first $MFT metadata
* extent.
*/
static bool mft_reserved_is_free(struct ntfs_volume *vol,
struct ntfs_inode *mft_ni, s64 mft_no)
{
struct attr_record *a;
struct mft_record *m;
struct folio *folio;
void *mapped;
pgoff_t index = NTFS_MFT_NR_TO_PIDX(vol, mft_no);
unsigned int ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no);
u32 attrs_offset, bytes_in_use;
bool available = false, have_std = false;
int i;
for (i = 0; i < mft_ni->nr_extents; i++) {
if (mft_ni->ext.extent_ntfs_inos[i] &&
mft_ni->ext.extent_ntfs_inos[i]->mft_no == mft_no)
return false;
}
m = kmalloc(vol->mft_record_size, GFP_NOFS);
if (!m)
return false;
folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL);
if (IS_ERR(folio))
goto free_m;
folio_lock(folio);
mapped = kmap_local_folio(folio, 0);
memcpy(m, (u8 *)mapped + ofs, vol->mft_record_size);
kunmap_local(mapped);
folio_unlock(folio);
folio_put(folio);
if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size))
goto free_m;
if (!ntfs_is_mft_record(m->magic) ||
!(m->flags & MFT_RECORD_IN_USE) || m->base_mft_record ||
m->link_count)
goto out;
attrs_offset = le16_to_cpu(m->attrs_offset);
bytes_in_use = le32_to_cpu(m->bytes_in_use);
if (attrs_offset > bytes_in_use || bytes_in_use > vol->mft_record_size ||
bytes_in_use - attrs_offset < sizeof(a->type))
goto out;
for (a = (struct attr_record *)((u8 *)m + attrs_offset);
(u8 *)a + sizeof(a->type) <= (u8 *)m + bytes_in_use;) {
u32 len;
if (a->type == AT_END) {
if ((u8 *)a + sizeof(a->type) + sizeof(a->length) >
(u8 *)m + bytes_in_use)
break;
/* Also accept a record emptied by an earlier bootstrap. */
available = have_std ||
(u8 *)a == (u8 *)m + attrs_offset;
break;
}
if (a->type == AT_FILE_NAME)
break;
len = le32_to_cpu(a->length);
if (len < offsetof(struct attr_record, data) ||
(u8 *)a + len > (u8 *)m + bytes_in_use)
break;
if (a->type == AT_STANDARD_INFORMATION) {
u32 value_len, value_ofs;
if (have_std || a->non_resident ||
len < offsetof(struct attr_record,
data.resident.reserved) + 1)
break;
value_len = le32_to_cpu(a->data.resident.value_length);
value_ofs = le16_to_cpu(a->data.resident.value_offset);
if (value_ofs > len || value_len > len - value_ofs)
break;
have_std = true;
}
a = (struct attr_record *)((u8 *)a + len);
}
out:
kfree(m);
return available;
free_m:
kfree(m);
return false;
}
static s64 mft_reserve_end(const u8 *buf, s64 buf_start, s64 buf_end,
s64 start, s64 pass_end, s64 initialized_mft_records)
{
s64 end = start + 1;
s64 limit = min_t(s64, start + MFT_RECORD_RESERVE, pass_end);
if (limit > initialized_mft_records)
limit = initialized_mft_records;
if (limit > buf_end)
limit = buf_end;
while (end < limit &&
!(buf[(end - buf_start) >> 3] &
(1 << ((end - buf_start) & 7))))
end++;
return end;
}
/*
* mft_bitmap_alloc_free_rec - find and allocate a free MFT record
* @vol: volume on which to search for a free mft record
* @base_ni: open base inode if allocating an extent mft record or NULL
* @max_mft_no: first record which must not be allocated, or -1
* @new_reserve_end: if not NULL, end of a free run starting after the result
*
* Search for a free mft record in the mft bitmap attribute on the ntfs volume
* @vol.
@ -862,10 +973,12 @@ static const char *es = " Leaving inconsistent metadata. Unmount and run chkds
*
* Locking: Caller must hold vol->mftbmp_lock for writing.
*/
static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol,
struct ntfs_inode *base_ni)
static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol,
struct ntfs_inode *base_ni,
s64 max_mft_no, s64 *new_reserve_end)
{
s64 pass_end, ll, data_pos, pass_start, ofs, bit;
s64 initialized_mft_records;
unsigned long flags;
struct address_space *mftbmp_mapping;
u8 *buf = NULL, *byte;
@ -882,30 +995,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags);
pass_end = NTFS_I(vol->mft_ino)->allocated_size >>
vol->mft_record_size_bits;
initialized_mft_records = NTFS_I(vol->mft_ino)->initialized_size >>
vol->mft_record_size_bits;
read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags);
read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags);
ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3;
read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags);
if (pass_end > ll)
pass_end = ll;
pass = 1;
if (!base_ni)
data_pos = vol->mft_data_pos;
else
data_pos = base_ni->mft_no + 1;
if (data_pos < RESERVED_MFT_RECORDS)
data_pos = RESERVED_MFT_RECORDS;
if (data_pos >= pass_end) {
data_pos = RESERVED_MFT_RECORDS;
if (max_mft_no >= 0 && pass_end > max_mft_no)
pass_end = max_mft_no;
if (base_ni && base_ni->mft_no == FILE_MFT) {
data_pos = FILE_first_user;
pass = 2;
/* This happens on a freshly formatted volume. */
if (data_pos >= pass_end)
return -ENOSPC;
}
if (base_ni && base_ni->mft_no == FILE_MFT) {
data_pos = 0;
pass = 2;
} else {
pass = 1;
if (!base_ni)
data_pos = vol->mft_data_pos;
else
data_pos = base_ni->mft_no + 1;
if (data_pos < FIRST_NORMAL_MFT_RECORD)
data_pos = FIRST_NORMAL_MFT_RECORD;
if (data_pos >= pass_end) {
data_pos = FIRST_NORMAL_MFT_RECORD;
pass = 2;
/* This happens on a freshly formatted volume. */
if (data_pos >= pass_end)
return -ENOSPC;
}
}
pass_start = data_pos;
@ -940,38 +1059,28 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
size, data_pos, bit);
for (; bit < size && data_pos + bit < pass_end;
bit &= ~7ull, bit += 8) {
/*
* If we're extending $MFT and running out of the first
* mft record (base record) then give up searching since
* no guarantee that the found record will be accessible.
*/
if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) {
folio_unlock(folio);
kunmap_local(buf);
folio_put(folio);
return -ENOSPC;
}
byte = buf + (bit >> 3);
if (*byte == 0xff)
continue;
b = ffz((unsigned long)*byte);
if (b < 8 && b >= (bit & 7)) {
b = bit & 7;
for (; b < 8; b++) {
if (*byte & (1 << b))
continue;
ll = data_pos + (bit & ~7ull) + b;
if (ll >= pass_end)
break;
/* Keep the dynamic tail reserve for $MFT metadata. */
if ((!base_ni || base_ni->mft_no != FILE_MFT) &&
ll >= vol->mft_record_reserve_pos &&
ll < vol->mft_record_reserve_end)
continue;
if (unlikely(ll >= (1ll << 32))) {
folio_unlock(folio);
kunmap_local(buf);
folio_put(folio);
return -ENOSPC;
}
*byte |= 1 << b;
folio_mark_dirty(folio);
folio_unlock(folio);
kunmap_local(buf);
folio_put(folio);
ntfs_debug("Done. (Found and allocated mft record 0x%llx.)",
ll);
return ll;
goto found;
}
}
ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx",
@ -994,7 +1103,8 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
* part of the zone which we omitted earlier.
*/
pass_end = pass_start;
data_pos = pass_start = RESERVED_MFT_RECORDS;
data_pos = FIRST_NORMAL_MFT_RECORD;
pass_start = FIRST_NORMAL_MFT_RECORD;
ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.",
pass, pass_start, pass_end);
if (data_pos >= pass_end)
@ -1004,9 +1114,22 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
/* No free mft records in currently initialized mft bitmap. */
ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)");
return -ENOSPC;
found:
if (new_reserve_end)
*new_reserve_end = mft_reserve_end(buf, data_pos,
data_pos + size, ll, pass_end,
initialized_mft_records);
*byte |= 1 << b;
folio_mark_dirty(folio);
folio_unlock(folio);
kunmap_local(buf);
folio_put(folio);
ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", ll);
return ll;
}
static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
static int ntfs_mft_attr_extend(struct ntfs_inode *ni,
struct ntfs_inode *locked_ni)
{
int ret = 0;
struct ntfs_inode *base_ni;
@ -1027,7 +1150,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
}
}
ret = ntfs_attr_update_mapping_pairs(ni, 0);
ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, locked_ni);
if (ret)
pr_err("MP update failed\n");
@ -1215,7 +1338,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size +
le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
if (unlikely(ret)) {
ret = ntfs_mft_attr_extend(mftbmp_ni);
ret = ntfs_mft_attr_extend(mftbmp_ni, mftbmp_ni);
if (!ret)
goto extended_ok;
if (ret != -EAGAIN)
@ -1326,7 +1449,9 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
NVolSetErrors(vol);
}
mark_mft_record_dirty(ctx->ntfs_ino);
} else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) {
} else if (status.mp_extended &&
ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0,
mftbmp_ni)) {
ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
NVolSetErrors(vol);
}
@ -1414,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0);
if (likely(!ret)) {
ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap.");
ntfs_inc_free_mft_records(vol, 8 * 8);
return 0;
}
ntfs_error(vol->sb, "Failed to write to mft bitmap.");
@ -1471,8 +1595,9 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
* @vol: volume on which to extend the mft data attribute
*
* Extend the mft data attribute on the ntfs volume @vol by 16 mft records
* worth of clusters or if not enough space for this by one mft record worth
* of clusters.
* worth of clusters or if not enough space for this by two mft records worth
* of clusters. Keeping at least two new records breaks the recursion between
* extending $MFT and allocating a record for a new $MFT attribute extent.
*
* Note: Only changes allocated_size, i.e. does not touch initialized_size or
* data_size.
@ -1526,10 +1651,8 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
}
lcn = rl->lcn + rl->length;
ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn);
/* Minimum allocation is one mft record worth of clusters. */
min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size);
if (!min_nr)
min_nr = 1;
/* Keep room for the allocating record and at least one MFT reserve. */
min_nr = DIV_ROUND_UP_ULL((u64)vol->mft_record_size * 2, vol->cluster_size);
/* Want to allocate 16 mft records worth of clusters. */
nr = vol->mft_record_size << 4 >> vol->cluster_size_bits;
if (!nr)
@ -1653,7 +1776,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size +
le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
if (unlikely(ret)) {
ret = ntfs_mft_attr_extend(mft_ni);
ret = ntfs_mft_attr_extend(mft_ni, NULL);
if (!ret)
goto extended_ok;
if (ret != -EAGAIN)
@ -1928,6 +2051,7 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
* @ni: [OUT] on success, set to the allocated ntfs inode
* @base_ni: [IN] open base inode if allocating an extent mft record or NULL
* @ni_mrec: [OUT] on successful return this is the mapped mft record
* @mft_data_vcn: [IN] lowest VCN of a new $MFT/$DATA extent, or -1
*
* Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol.
*
@ -1955,30 +2079,23 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
* optimize this we start scanning at the place specified by @base_ni or if
* @base_ni is NULL we start where we last stopped and we perform wrap around
* when we reach the end. Note, we do not try to allocate mft records below
* number 64 because numbers 0 to 15 are the defined system files anyway and 16
* to 64 are special in that they are used for storing extension mft records
* for the $DATA attribute of $MFT. This is required to avoid the possibility
* of creating a runlist with a circular dependency which once written to disk
* can never be read in again. Windows will only use records 16 to 24 for
* normal files if the volume is completely out of space. We never use them
* which means that when the volume is really out of space we cannot create any
* more files while Windows can still create up to 8 small files. We can start
* doing this at some later time, it does not matter much for now.
* number 24 because numbers 0 to 15 are the defined system files and records
* 16 to 23 are kept for metadata compatibility. Records reserved dynamically
* at the initialized MFT tail are skipped by normal allocation and consumed by
* $MFT metadata extent allocation.
*
* When scanning the mft bitmap, we only search up to the last allocated mft
* record. If there are no free records left in the range 64 to number of
* record. If there are no free records left in the range 24 to number of
* allocated mft records, then we extend the $MFT/$DATA attribute in order to
* create free mft records. We extend the allocated size of $MFT/$DATA by 16
* records at a time or one cluster, if cluster size is above 16kiB. If there
* is not sufficient space to do this, we try to extend by a single mft record
* or one cluster, if cluster size is above the mft record size.
* is not sufficient space to do this, we try to extend by two mft records or
* one cluster, if a cluster already contains at least two mft records.
*
* No matter how many mft records we allocate, we initialize only the first
* allocated mft record, incrementing mft data size and initialized size
* accordingly, open an struct ntfs_inode for it and return it to the caller, unless
* there are less than 64 mft records, in which case we allocate and initialize
* mft records until we reach record 64 which we consider as the first free mft
* record for use by normal files.
* When extending the initialized MFT tail, we also initialize up to four
* additional records and reserve them in memory for future $MFT metadata
* extents. If there are less than 24 mft records, records are initialized
* until record 24, which is the first record used for normal files.
*
* If during any stage we overflow the initialized data in the mft bitmap, we
* extend the initialized size (and data size) by 8 bytes, allocating another
@ -2014,9 +2131,13 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
*/
int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
struct ntfs_inode **ni, struct ntfs_inode *base_ni,
struct mft_record **ni_mrec)
struct mft_record **ni_mrec, const s64 mft_data_vcn)
{
s64 ll, bit, old_data_initialized, old_data_size;
s64 nr_new_mft_records = 0;
s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1;
s64 candidate_reserve_end = -1;
s64 *reserve_endp;
unsigned long flags;
struct folio *folio;
struct ntfs_inode *mft_ni, *mftbmp_ni;
@ -2027,7 +2148,9 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
unsigned int ofs;
int err;
__le16 seq_no, usn;
bool record_formatted = false;
bool record_formatted = false, from_reserve = false, tail_alloc = false;
bool reserve_created = false;
bool forced_reserved_record = false;
unsigned int memalloc_flags;
if (base_ni && *ni)
@ -2036,6 +2159,21 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
/* @mode and @base_ni are mutually exclusive. */
if (mode && base_ni)
return -EINVAL;
if (mft_data_vcn >= 0 &&
(!base_ni || base_ni->mft_no != FILE_MFT))
return -EINVAL;
if (mft_data_vcn >= 0) {
u64 vbo;
if ((u64)mft_data_vcn > (U64_MAX >> vol->cluster_size_bits))
return -EOVERFLOW;
vbo = (u64)mft_data_vcn << vol->cluster_size_bits;
/*
* The whole extent record must be reachable without this
* extent, including when an MFT record spans multiple clusters.
*/
max_mft_no = vbo >> vol->mft_record_size_bits;
}
if (base_ni)
ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).",
@ -2050,10 +2188,39 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
mutex_lock(&mft_ni->mrec_lock);
mftbmp_ni = NTFS_I(vol->mftbmp_ino);
search_free_rec:
from_reserve = false;
reserve_created = false;
candidate_reserve_end = -1;
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni);
if (base_ni && base_ni->mft_no == FILE_MFT &&
vol->mft_record_reserve_pos < vol->mft_record_reserve_end &&
(max_mft_no < 0 || vol->mft_record_reserve_pos < max_mft_no)) {
bit = vol->mft_record_reserve_pos;
err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit);
if (unlikely(err)) {
ntfs_error(vol->sb,
"Failed to allocate reserved MFT record 0x%llx.",
bit);
goto err_out;
}
vol->mft_record_reserve_pos++;
from_reserve = true;
ntfs_debug("Allocated MFT metadata record 0x%llx from tail reserve.",
bit);
goto have_alloc_rec;
}
reserve_endp = vol->mft_record_reserve_pos >=
vol->mft_record_reserve_end ? &candidate_reserve_end : NULL;
bit = mft_bitmap_alloc_free_rec(vol, base_ni, max_mft_no, reserve_endp);
if (bit >= 0) {
if (candidate_reserve_end > bit + 1) {
vol->mft_record_reserve_pos = bit + 1;
vol->mft_record_reserve_end = candidate_reserve_end;
reserve_created = true;
ntfs_debug("Reserved free MFT records [0x%llx, 0x%llx) for metadata.",
bit + 1, candidate_reserve_end);
}
ntfs_debug("Found and allocated free record (#1), bit 0x%llx.",
(long long)bit);
goto have_alloc_rec;
@ -2068,6 +2235,24 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
}
if (base_ni && base_ni->mft_no == FILE_MFT) {
static const u8 bootstrap_records[] = {
FILE_reserved15, FILE_reserved12, FILE_reserved13,
FILE_reserved14,
};
int i;
for (i = 0; i < ARRAY_SIZE(bootstrap_records); i++) {
if (max_mft_no >= 0 && bootstrap_records[i] >= max_mft_no)
continue;
if (!mft_reserved_is_free(vol, mft_ni,
bootstrap_records[i]))
continue;
bit = bootstrap_records[i];
forced_reserved_record = true;
ntfs_debug("Using reserved MFT record %lld to bootstrap metadata extension.",
bit);
goto have_alloc_rec;
}
memalloc_nofs_restore(memalloc_flags);
return bit;
}
@ -2087,10 +2272,10 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
old_data_initialized = mftbmp_ni->initialized_size;
read_unlock_irqrestore(&mftbmp_ni->size_lock, flags);
if (old_data_initialized << 3 > ll &&
old_data_initialized > RESERVED_MFT_RECORDS / 8) {
old_data_initialized << 3 > FIRST_NORMAL_MFT_RECORD) {
bit = ll;
if (bit < RESERVED_MFT_RECORDS)
bit = RESERVED_MFT_RECORDS;
if (bit < FIRST_NORMAL_MFT_RECORD)
bit = FIRST_NORMAL_MFT_RECORD;
if (unlikely(bit >= (1ll << 32)))
goto max_err_out;
ntfs_debug("Found free record (#2), bit 0x%llx.",
@ -2176,6 +2361,11 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
read_lock_irqsave(&mft_ni->size_lock, flags);
old_data_initialized = mft_ni->initialized_size;
read_unlock_irqrestore(&mft_ni->size_lock, flags);
tail_alloc = (!base_ni || base_ni->mft_no != FILE_MFT) &&
bit >= (old_data_initialized >> vol->mft_record_size_bits) &&
vol->mft_record_reserve_pos >= vol->mft_record_reserve_end;
if (tail_alloc)
ll = (bit + 2) << vol->mft_record_size_bits;
if (ll <= old_data_initialized) {
ntfs_debug("Allocated mft record already initialized.");
goto mft_rec_already_initialized;
@ -2208,6 +2398,29 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
mft_ni->initialized_size);
}
read_unlock_irqrestore(&mft_ni->size_lock, flags);
if (tail_alloc) {
s64 bitmap_records;
read_lock_irqsave(&mft_ni->size_lock, flags);
reserve_end = mft_ni->allocated_size >>
vol->mft_record_size_bits;
read_unlock_irqrestore(&mft_ni->size_lock, flags);
read_lock_irqsave(&mftbmp_ni->size_lock, flags);
bitmap_records = mftbmp_ni->initialized_size << 3;
read_unlock_irqrestore(&mftbmp_ni->size_lock, flags);
if (reserve_end > bitmap_records)
reserve_end = bitmap_records;
if (reserve_end > bit + 1 + MFT_RECORD_RESERVE)
reserve_end = bit + 1 + MFT_RECORD_RESERVE;
reserve_start = bit + 1;
if (reserve_end > reserve_start) {
ll = reserve_end << vol->mft_record_size_bits;
} else {
reserve_start = -1;
reserve_end = -1;
ll = (bit + 1) << vol->mft_record_size_bits;
}
}
} else if (ll > mft_ni->allocated_size) {
err = -ENOSPC;
goto undo_mftbmp_alloc_nolock;
@ -2276,14 +2489,25 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(mft_ni);
if (reserve_start >= 0 && reserve_end > reserve_start) {
vol->mft_record_reserve_pos = reserve_start;
vol->mft_record_reserve_end = reserve_end;
ntfs_debug("Reserved MFT records [0x%llx, 0x%llx) for metadata.",
reserve_start, reserve_end);
}
read_lock_irqsave(&mft_ni->size_lock, flags);
ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.",
mft_ni->allocated_size, i_size_read(vol->mft_ino),
mft_ni->initialized_size);
WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size);
WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino));
nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >>
vol->mft_record_size_bits;
read_unlock_irqrestore(&mft_ni->size_lock, flags);
mft_rec_already_initialized:
/* Account for newly visible MFT records before dropping the lock. */
if (nr_new_mft_records > 0)
ntfs_inc_free_mft_records(vol, nr_new_mft_records);
/*
* We can finally drop the mft bitmap lock as the mft data attribute
* has been fully updated. The only disparity left is that the
@ -2315,8 +2539,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
/* If we just formatted the mft record no need to do it again. */
if (!record_formatted) {
/* Sanity check that the mft record is really not in use. */
if (ntfs_is_file_record(m->magic) &&
(m->flags & MFT_RECORD_IN_USE)) {
if (!forced_reserved_record && ntfs_is_file_record(m->magic) &&
(m->flags & MFT_RECORD_IN_USE)) {
ntfs_warning(vol->sb,
"Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.",
bit);
@ -2389,9 +2613,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.",
bit);
err = PTR_ERR(m_tmp);
/* Set the mft record itself not in use. */
m->flags &= cpu_to_le16(
~le16_to_cpu(MFT_RECORD_IN_USE));
if (forced_reserved_record) {
m->base_mft_record = 0;
m->flags |= MFT_RECORD_IN_USE;
} else {
/* Set the mft record itself not in use. */
m->flags &= cpu_to_le16(~le16_to_cpu(MFT_RECORD_IN_USE));
}
/* Make sure the mft record is written out to disk. */
ntfs_mft_mark_dirty(folio);
folio_unlock(folio);
@ -2463,7 +2691,8 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
(*ni)->mft_no = bit;
if (ni_mrec)
*ni_mrec = (*ni)->mrec;
ntfs_dec_free_mft_records(vol, 1);
if (!forced_reserved_record)
ntfs_dec_free_mft_records(vol, 1);
return 0;
undo_data_init:
write_lock_irqsave(&mft_ni->size_lock, flags);
@ -2475,10 +2704,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
undo_mftbmp_alloc_nolock:
if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es);
NVolSetErrors(vol);
}
if ((from_reserve || reserve_created) &&
vol->mft_record_reserve_pos == bit + 1)
vol->mft_record_reserve_pos = bit;
if (!base_ni || base_ni->mft_no != FILE_MFT)
up_write(&vol->mftbmp_lock);
err_out:
@ -2514,9 +2746,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
int err;
u16 seq_no;
__le16 old_seq_no;
__le64 old_base_mft_record;
struct mft_record *ni_mrec;
unsigned int memalloc_flags;
struct ntfs_inode *base_ni;
bool keep_reserved;
if (!vol || !ni)
return -EINVAL;
@ -2529,9 +2763,23 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
/* Cache the mft reference for later. */
mft_no = ni->mft_no;
if (likely(ni->nr_extents >= 0))
base_ni = ni;
else
base_ni = ni->ext.base_ntfs_ino;
keep_reserved = mft_no >= FILE_reserved12 &&
mft_no <= FILE_reserved15 &&
base_ni->mft_no == FILE_MFT;
/* Mark the mft record as not in use. */
ni_mrec->flags &= ~MFT_RECORD_IN_USE;
old_base_mft_record = ni_mrec->base_mft_record;
if (keep_reserved) {
/* Restore the special, unnamed form used by reserved records. */
ni_mrec->base_mft_record = 0;
ni_mrec->flags |= MFT_RECORD_IN_USE;
} else {
/* Mark the mft record as not in use. */
ni_mrec->flags &= ~MFT_RECORD_IN_USE;
}
/* Increment the sequence number, skipping zero, if it is not zero. */
old_seq_no = ni_mrec->sequence_number;
@ -2560,24 +2808,28 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
if (err)
goto sync_rollback;
if (likely(ni->nr_extents >= 0))
base_ni = ni;
else
base_ni = ni->ext.base_ntfs_ino;
if (keep_reserved) {
unmap_mft_record(ni);
return 0;
}
/* Clear the bit in the $MFT/$BITMAP corresponding to this record. */
memalloc_flags = memalloc_nofs_save();
if (base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no);
if (!err)
ntfs_inc_free_mft_records(vol, 1);
if (!err && base_ni->mft_no == FILE_MFT &&
mft_no + 1 == vol->mft_record_reserve_pos &&
mft_no < vol->mft_record_reserve_end)
vol->mft_record_reserve_pos = mft_no;
if (base_ni->mft_no != FILE_MFT)
up_write(&vol->mftbmp_lock);
memalloc_nofs_restore(memalloc_flags);
if (err)
goto bitmap_rollback;
unmap_mft_record(ni);
ntfs_inc_free_mft_records(vol, 1);
return 0;
/* Rollback what we did... */
@ -2595,6 +2847,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
"Eeek! Rollback failed in %s. Leaving inconsistent metadata!\n", __func__);
ni_mrec->flags |= MFT_RECORD_IN_USE;
ni_mrec->sequence_number = old_seq_no;
ni_mrec->base_mft_record = old_base_mft_record;
NInoSetDirty(ni);
write_mft_record(ni, ni_mrec, 0);
unmap_mft_record(ni);

View File

@ -78,7 +78,7 @@ static inline int write_mft_record(struct ntfs_inode *ni, struct mft_record *m,
int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
struct ntfs_inode **ni, struct ntfs_inode *base_ni,
struct mft_record **ni_mrec);
struct mft_record **ni_mrec, const s64 mft_data_vcn);
int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni);
int ntfs_mft_records_write(const struct ntfs_volume *vol, const u64 mref,
const s64 count, struct mft_record *b);

View File

@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
mark_inode_dirty(dir);
err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL,
&ni_mrec);
&ni_mrec, -1);
if (err) {
iput(vi);
return ERR_PTR(err);

View File

@ -2064,8 +2064,7 @@ static unsigned long __get_nr_free_mft_records(struct ntfs_volume *vol,
/* If errors occurred we may well have gone below zero, fix this. */
if (nr_free < 0)
nr_free = 0;
else
atomic64_set(&vol->free_mft_records, nr_free);
atomic64_set(&vol->free_mft_records, nr_free);
ntfs_debug("Exiting.");
return nr_free;
@ -2131,7 +2130,14 @@ static int ntfs_statfs(struct dentry *dentry, struct kstatfs *sfs)
read_unlock_irqrestore(&mft_ni->size_lock, flags);
/* Free inodes in fs (based on current total count). */
sfs->f_ffree = atomic64_read(&vol->free_mft_records);
size = atomic64_read(&vol->free_mft_records);
if (unlikely(size < 0 || size > (s64)sfs->f_files))
ntfs_warning(vol->sb, "Invalid free MFT record count %lld.", size);
if (size < 0)
size = 0;
else if (size > (s64)sfs->f_files)
size = sfs->f_files;
sfs->f_ffree = size;
/*
* File system id. This is extremely *nix flavour dependent and even

View File

@ -55,6 +55,10 @@
* @attrdef_size: Size of the attribute definition table in bytes.
* @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef.
* @mft_data_pos: Mft record number at which to allocate the next mft record.
* @mft_record_reserve_pos: First record in the in-memory MFT metadata reserve
* (protected by mftbmp_lock).
* @mft_record_reserve_end: First record beyond the MFT metadata reserve
* (protected by mftbmp_lock).
* @mft_zone_start: First cluster of the mft zone.
* @mft_zone_end: First cluster beyond the mft zone.
* @mft_zone_pos: Current position in the mft zone.
@ -119,6 +123,8 @@ struct ntfs_volume {
s32 attrdef_size;
struct attr_def *attrdef;
s64 mft_data_pos;
s64 mft_record_reserve_pos;
s64 mft_record_reserve_end;
s64 mft_zone_start;
s64 mft_zone_end;
s64 mft_zone_pos;
@ -252,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr)
static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr)
{
if (!NVolFreeClusterKnown(vol))
return;
atomic64_add(nr, &vol->free_mft_records);
}
static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr)
{
if (!NVolFreeClusterKnown(vol))
return;
atomic64_sub(nr, &vol->free_mft_records);
}