ntfs: use dynamic MFT tail reservation
Subsystems (2)
Namjae Jeon
maintainerPATCH 1/1 ntfs: use dynamic MFT tail reservation
The ntfs MFT allocator historically treated records below 64 as a permanent extension area and stopped searching for $MFT extent records after record 400. Windows and ntfs3 do not maintain that on-disk layout, so an NTFS volume can have free MFT records while ntfs returns -ENOSPC when $MFT:$DATA needs another mapping-pairs extent.
Use record 24 as the first normal record and maintain an in-memory tail reserve of up to four initialized records. Normal allocations skip the reserve, while $MFT metadata extent allocations consume it. When a new tail is initialized, allocate at least two records and reserve the following records to avoid recursive allocation during MFT extension. Existing free runs can seed the reserve on volumes mounted without one.
For $MFT/$DATA, constrain an extent record to a record whose byte offset is below the new extent lowest VCN byte offset. This preserves bootstrap reachability without an arbitrary record 400 limit. If no safe record is available, validate and use reserved records 15, 12, 13, and 14 as bootstrap candidates while keeping their MFT bitmap entries in use.
This allows existing Windows volumes to extend $MFT using their actual free records and prevents normal file allocation from consuming the metadata reserve.
--- fs/ntfs/attrib.c | 11 +- fs/ntfs/mft.c | 420 +++++++++++++++++++++++++++++++++++++---------- fs/ntfs/mft.h | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs/volume.h | 6 + 5 files changed, 349 insertions(+), 92 deletions(-)
fs/ntfs/attrib.c +7 -4
@@ -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;
@@ -3976,7 +3976,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;
@@ -4836,7 +4839,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;
fs/ntfs/mft.c +334 -86
631 lines not on this page · raw message
fs/ntfs/mft.h +1 -1
@@ -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);
fs/ntfs/namei.c +1 -1
@@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dmark_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);
fs/ntfs/volume.h +6 -0
@@ -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;
Signature · 1 line
2.25.1
Namjae Jeon
maintainerPATCH 1/1 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.
--- fs/ntfs/attrib.c | 47 ++++++++--- fs/ntfs/attrlist.c | 202 ++++++++++++++++++++++++++++++++++++++++++--- fs/ntfs/inode.c | 10 ++- 3 files changed, 236 insertions(+), 23 deletions(-)
fs/ntfs/attrib.c +37 -10
@@ -3844,13 +3844,13 @@ 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 ; - } -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 (base_ni); +if (err) +return err; goto retry; }
@@ -4522,13 +4522,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsizntfs_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)) {
@@ -4921,7 +4947,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallontfs_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 ; }
@@ -4936,7 +4963,7 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo }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,NVolDisableSparse (ni->vol) ?HOLES_NO :HOLES_OK , true);
fs/ntfs/attrlist.c +190 -12
242 lines not on this page · raw message
fs/ntfs/inode.c +9 -1
@@ -3746,6 +3746,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);
@@ -3788,8 +3789,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 );
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: repack $MFT/$ATTRIBUTE LIST
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 7 lines
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 495e90fa3348 ("ntfs: update attrib operations")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 372 lines
--- fs/ntfs/attrib.c | 47 ++++++++--- fs/ntfs/attrlist.c | 202 ++++++++++++++++++++++++++++++++++++++++++--- fs/ntfs/inode.c | 10 ++- 3 files changed, 236 insertions(+), 23 deletions(-)fs/ntfs/attrib.c +37
-10@@ -3844,13 +3844,13 @@ 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 ; - } -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 (base_ni); +if (err) +return err; goto retry; }@@ -4522,13 +4522,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsizntfs_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)) {@@ -4921,7 +4947,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallontfs_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 ; }@@ -4936,7 +4963,7 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo }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,NVolDisableSparse (ni->vol) ?HOLES_NO :HOLES_OK , true);fs/ntfs/attrlist.c +190
-12@@ -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,151 @@ int ntfs_attrlist_need(struct ntfs_inode *ni)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) {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 @@ -74,21 +217,55 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni) attr_ni =NTFS_I (attr_vi); 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 ) { +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); +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 (attr_ni, base_ni->attr_list_size, +HOLES_NO ); +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 (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);@@ -96,14 +273,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);fs/ntfs/inode.c +9
-2@@ -3746,6 +3746,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);@@ -3788,8 +3789,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 ); --2.25.1
Signature · 2 lines
Thanks, Hyunchul
Namjae Jeon
maintainerPATCH 1/1 ntfs: account for MFT records added during allocation
In reply to Namjae JeonWhen no free MFT record is available in the initialized $MFT/$BITMAP, ntfs_mft_record_alloc() extends $MFT/$DATA and formats the requested record together with a dynamically sized tail reserve. Those records become visible through the $MFT file size before charging the requested record to the free-record counter.
Account for all newly visible records before releasing the MFT allocation lock, then subtract the one record being allocated. Keep MFT counter updates independent of the asynchronous free-cluster scan and update the counter when a record is successfully cleared in the MFT bitmap.
Store the clamped result of the MFT bitmap scan and keep statfs from exposing an invalid cached count if an accounting error occurs.
--- fs/ntfs/mft.c | 11 ++++++++--- fs/ntfs/super.c | 12 +++++++++--- fs/ntfs/volume.h | 6 ------ 3 files changed, 17 insertions(+), 12 deletions(-)
fs/ntfs/mft.c +8 -3
@@ -1539,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." );
@@ -2135,6 +2134,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, 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;
@@ -2501,8 +2501,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, 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
@@ -2813,6 +2818,8 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)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)
@@ -2822,9 +2829,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)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... */
fs/ntfs/super.c +9 -3
@@ -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
fs/ntfs/volume.h +0 -6
@@ -258,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); }
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: account for MFT records added during allocation
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 12 lines
When no free MFT record is available in the initialized $MFT/$BITMAP,
ntfs_mft_record_alloc()extends $MFT/$DATA and formats the requested record together with a dynamically sized tail reserve. Those records become visible through the $MFT file size before charging the requested record to the free-record counter.Account for all newly visible records before releasing the MFT allocation lock, then subtract the one record being allocated. Keep MFT counter updates independent of the asynchronous free-cluster scan and update the counter when a record is successfully cleared in the MFT bitmap.
Quoted 6 lines
Store the clamped result of the MFT bitmap scan and keep statfs from exposing an invalid cached count if an accounting error occurs.
Fixes 115380f9a2f9 ("ntfs: update mft operations")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 114 lines
--- fs/ntfs/mft.c | 11 ++++++++--- fs/ntfs/super.c | 12 +++++++++--- fs/ntfs/volume.h | 6 ------ 3 files changed, 17 insertions(+), 12 deletions(-)fs/ntfs/mft.c +8
-3@@ -1539,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." );@@ -2135,6 +2134,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, 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;@@ -2501,8 +2501,13 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode, 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 @@ -2813,6 +2818,8 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)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)@@ -2822,9 +2829,7 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)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... */ fs/ntfs/super.c +9
-3@@ -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 fs/ntfs/volume.h +0
-7@@ -258,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); } --2.25.1
Signature · 2 lines
Thanks, Hyunchul
Namjae Jeon
maintainerPATCH 1/1 ntfs: protect runlist updates with the runlist lock
In reply to Namjae Jeonntfs_non_resident_attr_shrink() calls runlist helpers that require the runlist write lock, but did not hold it while freeing clusters and truncating the runlist. Serialize those operations and the resident conversion with the runlist lock.
ntfs_attr_map_cluster() can merge a newly allocated run before updating mapping pairs. If the update fails, free the clusters and restore both the in-memory runlist and on-disk mapping pairs from a saved runlist. Mark the volume in error if either rollback step fails.
--- fs/ntfs/attrib.c | 205 +++++++++++++++++++++++++++++++++++---------- fs/ntfs/attrib.h | 9 ++ fs/ntfs/attrlist.c | 46 ++++++---- fs/ntfs/attrlist.h | 2 + fs/ntfs/compress.c | 2 +- fs/ntfs/file.c | 3 +- fs/ntfs/inode.c | 2 +- fs/ntfs/mft.c | 13 +-- 8 files changed, 216 insertions(+), 66 deletions(-)
fs/ntfs/attrib.c +161 -44
@@ -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,11 +3853,17 @@ 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); + ctx =NULL ; +if (locked_ni == ni || defer_attrlist) { + err = -ENOSPC ; + goto put_err_out; + } err =ntfs_inode_free_space (base_ni, mp_size - cur_max_mp_size);if (err)return err; - err =ntfs_attrlist_update (base_ni); + err =ntfs_attrlist_update_locked ( + base_ni, locked_ni);if (err)return err; goto retry;
@@ -3856,6 +3871,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)/* 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; }
@@ -4064,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
@@ -4197,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;
@@ -4205,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizs64 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);
@@ -4250,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. */
@@ -4269,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizif (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);
@@ -4282,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizkvfree (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. */
@@ -4298,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizVFS_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. */
@@ -4344,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 )
@@ -4361,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; }/*
@@ -4369,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;
@@ -4573,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;
@@ -4617,11 +4681,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsizntfs_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) {/*
@@ -4635,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)) {
@@ -4744,7 +4808,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsimark_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;
@@ -4919,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) ?
@@ -4934,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 ;
@@ -4964,9 +5032,10 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloif (NInoNonResident (ni)) {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 , true); + 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) ?
@@ -4977,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloreturn 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
@@ -4989,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;
@@ -5023,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned intif (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 */
@@ -5049,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)
@@ -5145,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)) {
@@ -5168,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 );
@@ -5188,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; }
@@ -5431,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;
@@ -5516,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;
@@ -5594,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);
@@ -5770,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, boif (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
fs/ntfs/attrib.h +9 -0
@@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64int __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);/*
fs/ntfs/attrlist.c +32 -14
114 lines not on this page · raw message
fs/ntfs/attrlist.h +2 -0
@@ -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 */
fs/ntfs/compress.c +1 -1
@@ -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 ;
fs/ntfs/file.c +2 -1
@@ -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" );
fs/ntfs/inode.c +1 -1
@@ -2772,7 +2772,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);
fs/ntfs/mft.c +8 -5
@@ -1131,7 +1131,8 @@ static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol,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;
@@ -1152,7 +1153,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" );
@@ -1340,7 +1341,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 )
@@ -1451,7 +1452,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); }
@@ -1776,7 +1779,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 )
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: protect runlist updates with the runlist lock
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 6 lines
ntfs_non_resident_attr_shrink()calls runlist helpers that require the runlist write lock, but did not hold it while freeing clusters and truncating the runlist. Serialize those operations and the resident conversion with the runlist lock.
Quoted 7 lines
ntfs_attr_map_cluster()can merge a newly allocated run before updating mapping pairs. If the update fails, free the clusters and restore both the in-memory runlist and on-disk mapping pairs from a saved runlist. Mark the volume in error if either rollback step fails.Fixes 495e90fa3348 ("ntfs: update attrib operations")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 791 lines
--- fs/ntfs/attrib.c | 205 +++++++++++++++++++++++++++++++++++---------- fs/ntfs/attrib.h | 9 ++ fs/ntfs/attrlist.c | 46 ++++++---- fs/ntfs/attrlist.h | 2 + fs/ntfs/compress.c | 2 +- fs/ntfs/file.c | 3 +- fs/ntfs/inode.c | 2 +- fs/ntfs/mft.c | 13 +-- 8 files changed, 216 insertions(+), 66 deletions(-)fs/ntfs/attrib.c +161
-44@@ -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,11 +3853,17 @@ 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); + ctx =NULL ; +if (locked_ni == ni || defer_attrlist) { + err = -ENOSPC ; + goto put_err_out; + } err =ntfs_inode_free_space (base_ni, mp_size - cur_max_mp_size);if (err)return err; - err =ntfs_attrlist_update (base_ni); + err =ntfs_attrlist_update_locked ( + base_ni, locked_ni);if (err)return err; goto retry;@@ -3856,6 +3871,10 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)/* 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; }@@ -4064,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 @@ -4197,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;@@ -4205,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizs64 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);@@ -4250,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. */ @@ -4269,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizif (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);@@ -4282,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizkvfree (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. */ @@ -4298,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsizVFS_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. */ @@ -4344,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 )@@ -4361,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; }/* @@ -4369,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;@@ -4573,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;@@ -4617,11 +4681,11 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsizntfs_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) {/* @@ -4635,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)) {@@ -4744,7 +4808,8 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsimark_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;@@ -4919,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) ?@@ -4934,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 ;@@ -4964,9 +5032,10 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloif (NInoNonResident (ni)) {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 , true); + 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) ?@@ -4977,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloreturn 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 @@ -4989,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;@@ -5023,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned intif (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 */ @@ -5049,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)@@ -5145,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)) {@@ -5168,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 );@@ -5188,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; }@@ -5431,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;@@ -5516,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;@@ -5594,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);@@ -5770,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, boif (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 fs/ntfs/attrib.h +9
-0@@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64int __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);/* fs/ntfs/attrlist.c +32
-14@@ -68,7 +68,8 @@ int ntfs_attrlist_need(struct ntfs_inode *ni)* 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 *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;@@ -78,10 +79,12 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,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)@@ -131,7 +134,6 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, 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);@@ -152,7 +154,7 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, } } - err =ntfs_attr_update_mapping_pairs (attr_ni,0 ); + err =ntfs_attr_update_mapping_pairs_locked (attr_ni,0 , locked_ni);if (err) goto restore_old_runlist;@@ -177,7 +179,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi, 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 ); + 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);@@ -193,7 +196,8 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,return err; } -int ntfs_attrlist_update (struct ntfs_inode *base_ni) +int ntfs_attrlist_update_locked (struct ntfs_inode *base_ni, +struct ntfs_inode *locked_ni) {struct inode *attr_vi;struct ntfs_inode *attr_ni;@@ -215,20 +219,27 @@ 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 ); + 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); + 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 (attr_ni, base_ni->attr_list_size, -HOLES_NO ); + 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" );@@ -252,11 +263,13 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)*/ 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 ); + 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 ); + retry_err =ntfs_attrlist_repack ( + attr_vi, attr_ni, +NTFS_MAX_ATTR_LIST_SIZE , locked_ni);if (retry_err == -ENOSPC ) retry_err =0 ; }@@ -289,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 fs/ntfs/attrlist.h +2
-0@@ -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 */ fs/ntfs/compress.c +1
-1@@ -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 ;fs/ntfs/file.c +2
-1@@ -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" );fs/ntfs/inode.c +1
-1@@ -2772,7 +2772,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);fs/ntfs/mft.c +8
-6@@ -1131,7 +1131,8 @@ static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol,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;@@ -1152,7 +1153,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" );@@ -1340,7 +1341,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 )@@ -1451,7 +1452,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); }@@ -1776,7 +1779,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 ) --2.25.1
Signature · 2 lines
Thanks, Hyunchul
Namjae Jeon
maintainerPATCH 1/1 ntfs: propagate folio errors
In reply to Namjae JeonReturn the error from __filemap_get_folio() instead of replacing it with -ENOMEM.
--- fs/ntfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
fs/ntfs/inode.c +1 -1
@@ -3714,7 +3714,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 {
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: propagate folio errors
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 6 lines
Return the error from
__filemap_get_folio()instead of replacing it with-ENOMEM.Fixes af0db57d4293 ("ntfs: update inode operations")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 19 lines
--- fs/ntfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)fs/ntfs/inode.c +1
-2@@ -3714,7 +3714,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 { --2.25.1
Signature · 2 lines
Thanks, Hyunchul
Namjae Jeon
maintainerPATCH 1/1 ntfs: ignore interrupted inode reads as corruption
In reply to Namjae JeonDo not mark the volume in error or report an inode as corrupt when reading it was interrupted by a signal. -EINTR and -ERESTARTSYS indicate a transient read failure rather than on-disk NTFS corruption.
--- fs/ntfs/inode.c | 21 +++++++++++++-------- fs/ntfs/mft.c | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-)
fs/ntfs/inode.c +13 -8
@@ -1241,7 +1241,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 +1468,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 +1678,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 +1724,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; }
fs/ntfs/mft.c +2 -1
@@ -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; }
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: ignore interrupted inode reads as corruption
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 7 lines
Do not mark the volume in error or report an inode as corrupt when reading it was interrupted by a signal.
-EINTRand-ERESTARTSYSindicate a transient read failure rather than on-disk NTFS corruption.Fixes 115380f9a2f9 ("ntfs: update mft operations")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 79 lines
--- fs/ntfs/inode.c | 21 +++++++++++++-------- fs/ntfs/mft.c | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-)fs/ntfs/inode.c +13
-8@@ -1241,7 +1241,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 +1468,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 +1678,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 +1724,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; }fs/ntfs/mft.c +2
-2@@ -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; } --2.25.1
Signature · 2 lines
Thanks, Hyunchul
Namjae Jeon
maintainerPATCH 1/1 ntfs: discard inodes that fail initialization
In reply to Namjae JeonDiscard a newly allocated normal, attribute, or index inode when initialization fails. Keeping an incompletely initialized inode in the inode cache can expose stale sequence data to later directory lookups.
--- fs/ntfs/inode.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-)
fs/ntfs/inode.c +15 -12
@@ -170,17 +170,18 @@ 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) +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 +232,18 @@ 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) +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 +288,18 @@ 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) +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; }
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: discard inodes that fail initialization
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 7 lines
Discard a newly allocated normal, attribute, or index inode when initialization fails. Keeping an incompletely initialized inode in the inode cache can expose stale sequence data to later directory lookups.
Fixes d7aa04984f12 ("ntfs: return errors from inode initialization")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 79 lines
--- fs/ntfs/inode.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-)fs/ntfs/inode.c +15
-13@@ -170,17 +170,18 @@ 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) +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 +232,18 @@ 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) +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 +288,18 @@ 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) +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; } --2.25.1
Signature · 2 lines
Thanks, Hyunchul
Namjae Jeon
maintainerPATCH 1/1 ntfs: unhash failed inode reads
In reply to Namjae JeonRemove a newly allocated inode from the inode hash before discarding it. NTFS may set i_nlink before a later initialization step fails, in which case iput alone can retain the incomplete inode in the cache.
--- fs/ntfs/inode.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)
fs/ntfs/inode.c +9 -6
@@ -170,9 +170,10 @@ 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); -if (err) +if (err) { +remove_inode_hash (vi);discard_new_inode (vi); -else + }else unlock_new_inode (vi); }/*
@@ -232,9 +233,10 @@ 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); -if (err) +if (err) { +remove_inode_hash (vi);discard_new_inode (vi); -else + }else unlock_new_inode (vi); }/*
@@ -288,9 +290,10 @@ 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); -if (err) +if (err) { +remove_inode_hash (vi);discard_new_inode (vi); -else + }else unlock_new_inode (vi); }/*
Signature · 1 line
2.25.1
Re: [PATCH] ntfs: unhash failed inode reads
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 7 lines
Remove a newly allocated inode from the inode hash before discarding it. NTFS may set
i_nlinkbefore a later initialization step fails, in which case iput alone can retain the incomplete inode in the cache.Fixes bf6be898fbc5 ("ntfs: discard inodes that fail initialization")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 49 lines
--- fs/ntfs/inode.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)fs/ntfs/inode.c +9
-7@@ -170,9 +170,10 @@ 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); -if (err) +if (err) { +remove_inode_hash (vi);discard_new_inode (vi); -else + }else unlock_new_inode (vi); }/* @@ -232,9 +233,10 @@ 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); -if (err) +if (err) { +remove_inode_hash (vi);discard_new_inode (vi); -else + }else unlock_new_inode (vi); }/* @@ -288,9 +290,10 @@ 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); -if (err) +if (err) { +remove_inode_hash (vi);discard_new_inode (vi); -else + }else unlock_new_inode (vi); }/* -- 2.25.1
Signature · 2 lines
Thanks, Hyunchul
2026년 9월 8일 (화) 오후 6:44, Namjae Jeon <[email protected]>님이 작성:
Quoted 20 lines
The ntfs MFT allocator historically treated records below 64 as a permanent extension area and stopped searching for $MFT extent records after record 400. Windows and ntfs3 do not maintain that on-disk layout, so an NTFS volume can have free MFT records while ntfs returns
-ENOSPCwhen $MFT:$DATA needs another mapping-pairs extent.Use record 24 as the first normal record and maintain an in-memory tail reserve of up to four initialized records. Normal allocations skip the reserve, while $MFT metadata extent allocations consume it. When a new tail is initialized, allocate at least two records and reserve the following records to avoid recursive allocation during MFT extension. Existing free runs can seed the reserve on volumes mounted without one.
For $MFT/$DATA, constrain an extent record to a record whose byte offset is below the new extent lowest VCN byte offset. This preserves bootstrap reachability without an arbitrary record 400 limit. If no safe record is available, validate and use reserved records 15, 12, 13, and 14 as bootstrap candidates while keeping their MFT bitmap entries in use.
Quoted 6 lines
This allows existing Windows volumes to extend $MFT using their actual free records and prevents normal file allocation from consuming the metadata reserve.
Fixes 115380f9a2f9 ("ntfs: update mft operations")Signed-off-by Namjae Jeon <[email protected]>
Looks good to me.
Quoted 765 lines
--- fs/ntfs/attrib.c | 11 +- fs/ntfs/mft.c | 420 +++++++++++++++++++++++++++++++++++++---------- fs/ntfs/mft.h | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs/volume.h | 6 + 5 files changed, 349 insertions(+), 92 deletions(-)fs/ntfs/attrib.c +7
-4@@ -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;@@ -3976,7 +3976,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;@@ -4836,7 +4839,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;fs/ntfs/mft.c +334
-86@@ -841,12 +841,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 +976,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 +998,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *voread_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 +1062,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 +1106,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,6 +1117,18 @@ 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)@@ -1471,8 +1596,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 +1652,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)@@ -1928,6 +2052,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 +2080,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 +2132,12 @@ 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 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,6 +2489,12 @@ 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),@@ -2315,8 +2534,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 +2608,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 +2686,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 +2699,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 +2741,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 +2758,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; - -/* Mark the mft record as not in use. */ - ni_mrec->flags &= ~MFT_RECORD_IN_USE ; +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 ; + + 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,16 +2803,20 @@ 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 && 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);@@ -2595,6 +2842,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);fs/ntfs/mft.h +1
-1@@ -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);fs/ntfs/namei.c +1
-1@@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dmark_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);fs/ntfs/volume.h +6
-1@@ -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; --2.25.1
Signature · 2 lines
Thanks, Hyunchul