Re: [PATCH v3] ntfs: serialize attribute-list replacement with lookups

From: Hyunchul Lee

Date: Wed Jul 01 2026 - 04:16:58 EST


Hi Cen,

Thank you for the patch, but it has conflicts and needs to be rebased onto
ntfs-for-7.2-rc1 from the repository below:

git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git

I left a few comments below.

On Mon, Jun 29, 2026 at 12:57:53PM +0800, Cen Zhang wrote:
> ntfs_external_attr_find() walks base_ni->attr_list with raw
> attr_list_entry cursors stored in ctx->al_entry and local variables. Other
> metadata paths can replace base_ni->attr_list and free the old buffer while
> a lookup still carries one of those cursors.
>
> The buggy scenario involves two paths, with each column showing the order
> within that path:
>
> attribute lookup path: attribute-list mutation path:
> 1. enters ntfs_attr_lookup() 1. enters an attr-list add/remove
> 2. reads base_ni->attr_list 2. builds a replacement buffer
> 3. keeps ctx->al_entry/al_entry 3. publishes base_ni->attr_list
> pointing into that buffer 4. frees the previous buffer
> 4. continues the attribute search
> 5. dereferences the saved cursor
>
> Validation reproduced this kernel report:
> BUG: KASAN: slab-use-after-free in ntfs_attr_lookup+0x24b2/0x2bf0
>
> Call Trace:
> <TASK>
> dump_stack_lvl+0x66/0xa0
> print_report+0xce/0x630
> ? ntfs_attr_lookup+0x24b2/0x2bf0
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? __virt_addr_valid+0x20d/0x410
> ? ntfs_attr_lookup+0x24b2/0x2bf0
> kasan_report+0xe0/0x110
> ? ntfs_attr_lookup+0x24b2/0x2bf0
> ntfs_attr_lookup+0x24b2/0x2bf0
> ? ntfs_attr_lookup+0x9/0x2bf0
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? 0xffffffffc00000da
> ? __pfx_ntfs_attr_lookup+0x10/0x10
> ntfs_attr_map_whole_runlist+0x488/0x8b0
> ? __pfx_ntfs_attr_map_whole_runlist+0x10/0x10
> ? __pfx_do_raw_spin_trylock+0x10/0x10
> ntfs_attr_vcn_to_rl+0x13a/0x1d0
> ? filemap_fault+0xf03/0x2080
> ntfs_read_iomap_begin_non_resident+0x142/0x920
> ? __pfx_ntfs_read_iomap_begin_non_resident+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? __pfx_ntfs_read_iomap_begin+0x10/0x10
> iomap_iter+0x6de/0x11e0
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? prepare_alloc_pages+0x1c4/0x4d0
> iomap_readahead+0x239/0x910
> ? folios_put_refs+0x1ed/0x4f0
> ? __pfx_iomap_readahead+0x10/0x10
> ? __pfx_folios_put_refs+0x10/0x10
> ? lock_release+0x1e0/0x280
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? xas_store+0x871/0x1650
> ? xas_find_conflict+0x64b/0xb90
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? lock_release+0x1e0/0x280
> ntfs_readahead+0x193/0x1d0
> ? __pfx_ntfs_readahead+0x10/0x10
> ? __filemap_add_folio+0x869/0xa60
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? lock_acquire+0x2b8/0x2f0
> ? __pfx___filemap_add_folio+0x10/0x10
> read_pages+0x18f/0x890
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? folio_alloc_noprof+0x69/0xb0
> ? __pfx_read_pages+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> page_cache_ra_unbounded+0x389/0x6c0
> do_page_cache_ra+0x109/0x170
> filemap_fault+0xf03/0x2080
> ? __pfx_filemap_fault+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? lock_acquire+0x2b8/0x2f0
> ? lockdep_init_map_type+0x5c/0x230
> __do_fault+0xf0/0x260
> __handle_mm_fault+0xdcf/0x1ca0
> ? __pfx___handle_mm_fault+0x10/0x10
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? srso_alias_return_thunk+0x5/0xfbef5
> ? lock_release+0x1e0/0x280
> handle_mm_fault+0x19c/0x470
> do_user_addr_fault+0x23b/0x9c0
> exc_page_fault+0x5c/0xc0
> asm_exc_page_fault+0x26/0x30
>
> Allocated by task 511:
> kasan_save_stack+0x33/0x60
> kasan_save_track+0x14/0x30
> __kasan_kmalloc+0xaa/0xb0
> __kvmalloc_node_noprof+0x353/0x920
> ntfs_attrlist_entry_rm+0x28d/0x510
> ntfs_attr_record_rm+0x4dd/0xda0
> ntfs_delete+0x50a/0xf90
> ntfs_unlink+0x212/0x4d0
> vfs_unlink+0x259/0xb00
> filename_unlinkat+0x2db/0x5f0
> __x64_sys_unlink+0x46/0x70
> do_syscall_64+0x115/0x6a0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Freed by task 511:
> kasan_save_stack+0x33/0x60
> kasan_save_track+0x14/0x30
> kasan_save_free_info+0x3b/0x60
> __kasan_slab_free+0x5f/0x80
> kfree+0x307/0x580
> ntfs_attrlist_entry_add+0xc1b/0x1110
> ntfs_resident_attr_record_add+0x6a8/0xa10
> ntfs_attr_add+0x682/0xcd0
> __ntfs_link+0x815/0xde0
> ntfs_link+0x223/0x3d0
> vfs_link+0x465/0xcf0
> filename_linkat+0x316/0x540
> __x64_sys_link+0x81/0xb0
> do_syscall_64+0x115/0x6a0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Add a per-ntfs_inode attr_list_lock and couple it to
> ntfs_attr_search_ctx. Search contexts that may carry attr-list cursors
> take the read side for their full lifetime, so ctx->al_entry remains
> protected until ntfs_attr_put_search_ctx(). Attribute-list mutation paths
> use the write side while deriving offsets from the old list and publishing
> the replacement list. Contexts used under runlist.lock use an explicit
> NONE mode so the ordering stays:
>
> mrec_lock => attr_list_lock => runlist.lock
>
> Fixes: 495e90fa3348 ("ntfs: update attrib operations")
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
> ---
> v3:
> - Keep attr_list_lock tied to ntfs_attr_search_ctx lifetime instead of
> taking it only around ntfs_attr_lookup().
> - Add nolock helpers for paths that already hold attr_list_lock before
> runlist.lock, preserving the mrec_lock => attr_list_lock => runlist.lock
> order.
> - Protect the remaining direct attr_list walker in
> ntfs_inode_attach_all_extents().
>
> fs/ntfs/aops.c | 3 +
> fs/ntfs/attrib.c | 287 ++++++++++++++++++++++++++++++++++++---------
> fs/ntfs/attrib.h | 27 ++++-
> fs/ntfs/attrlist.c | 30 +++--
> fs/ntfs/compress.c | 6 +-
> fs/ntfs/dir.c | 8 +-
> fs/ntfs/ea.c | 2 +-
> fs/ntfs/file.c | 13 +-
> fs/ntfs/index.c | 5 +-
> fs/ntfs/inode.c | 88 +++++++++++---
> fs/ntfs/inode.h | 7 ++
> fs/ntfs/iomap.c | 16 ++-
> fs/ntfs/mft.c | 63 +++++++---
> fs/ntfs/namei.c | 6 +-
> fs/ntfs/super.c | 6 +-
> 15 files changed, 445 insertions(+), 122 deletions(-)
>
> diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
> index 1fbf832ad1654..981777ff24279 100644
> --- a/fs/ntfs/aops.c
> +++ b/fs/ntfs/aops.c
> @@ -131,6 +131,7 @@ static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
> s64 lcn;
> unsigned long blocksize, flags;
> struct ntfs_inode *ni = NTFS_I(mapping->host);
> + struct ntfs_inode *attr_lock_ni;
> struct ntfs_volume *vol = ni->vol;
> unsigned int delta;
> unsigned char blocksize_bits;
> @@ -160,10 +161,12 @@ static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
> */
> if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size)))
> goto hole;
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> down_read(&ni->runlist.lock);
> lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ntfs_bytes_to_cluster(vol, ofs),
> false);
> up_read(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(lcn < LCN_HOLE)) {
> /*
> * Step down to an integer to avoid gcc doing a long long
> diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
> index 97b660eaa00c1..21662be1594ff 100644
> --- a/fs/ntfs/attrib.c
> +++ b/fs/ntfs/attrib.c
> @@ -107,7 +107,7 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
> m = map_mft_record(base_ni);
> if (IS_ERR(m))
> return PTR_ERR(m);
> - ctx = ntfs_attr_get_search_ctx(base_ni, m);
> + ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_NONE);
> if (unlikely(!ctx)) {
> err = -ENOMEM;
> goto err_out;
> @@ -299,14 +299,17 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
> */
> int ntfs_map_runlist(struct ntfs_inode *ni, s64 vcn)
> {
> + struct ntfs_inode *attr_lock_ni;
> int err = 0;
>
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> down_write(&ni->runlist.lock);
> /* Make sure someone else didn't do the work while we were sleeping. */
> if (likely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) <=
> LCN_RL_NOT_MAPPED))
> err = ntfs_map_runlist_nolock(ni, vcn, NULL);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> return err;
> }
>
> @@ -317,7 +320,7 @@ struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64
> bool is_retry = false;
>
> if (!rl) {
> - err = ntfs_attr_map_whole_runlist(ni);
> + err = ntfs_attr_map_whole_runlist_nolock(ni);
> if (err)
> return ERR_PTR(-ENOENT);
> rl = ni->runlist.rl;
> @@ -1436,6 +1439,81 @@ int ntfs_attr_lookup(const __le32 type, const __le16 *name,
> val, val_len, ctx);
> }
>
> +static struct ntfs_inode *ntfs_attr_search_lock_inode(struct ntfs_inode *ni)
> +{
> + if (!ni)
> + return NULL;
> + if (NInoAttr(ni)) {
> + if (ni->type == AT_ATTRIBUTE_LIST)
> + return NULL;
> + return ni->ext.base_ntfs_ino;
> + }
> + return ni;
> +}
> +
> +struct ntfs_inode *ntfs_attr_list_lock(struct ntfs_inode *ni,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode)
> +{
> + struct ntfs_inode *lock_ni;
> +
> + if (lock_mode == NTFS_ATTR_CTX_LOCK_NONE)
> + return NULL;

For the NONE case, it would be better to call rwsem_assert_held().

> +
> + lock_ni = ntfs_attr_search_lock_inode(ni);
> + if (!lock_ni)
> + return NULL;
> +
> + if (lock_mode == NTFS_ATTR_CTX_LOCK_WRITE)
> + down_write(&lock_ni->attr_list_lock);
> + else
> + down_read(&lock_ni->attr_list_lock);
> +
> + return lock_ni;
> +}
> +
> +void ntfs_attr_list_unlock(struct ntfs_inode *lock_ni,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode)
> +{
> + if (!lock_ni || lock_mode == NTFS_ATTR_CTX_LOCK_NONE)
> + return;
> +
> + if (lock_mode == NTFS_ATTR_CTX_LOCK_WRITE)
> + up_write(&lock_ni->attr_list_lock);
> + else
> + up_read(&lock_ni->attr_list_lock);
> +}
> +
> +void ntfs_attr_lock_search_ctx(struct ntfs_attr_search_ctx *ctx,
> + struct ntfs_inode *ni,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode)
> +{
> + struct ntfs_inode *lock_ni;
> +
> + ctx->attr_list_lock_ni = NULL;
> + ctx->attr_list_lock_mode = NTFS_ATTR_CTX_LOCK_NONE;
> +
> + if (lock_mode == NTFS_ATTR_CTX_LOCK_NONE)
> + return;

As mentioned above, a call to rwsem_assert_held() would be better.

> +
> + lock_ni = ntfs_attr_list_lock(ni, lock_mode);
> + if (!lock_ni)
> + return;
> +
> + ctx->attr_list_lock_ni = lock_ni;
> + ctx->attr_list_lock_mode = lock_mode;
> +}
> +
> +void ntfs_attr_unlock_search_ctx(struct ntfs_attr_search_ctx *ctx)
> +{
> + if (!ctx->attr_list_lock_ni)
> + return;
> +
> + ntfs_attr_list_unlock(ctx->attr_list_lock_ni, ctx->attr_list_lock_mode);
> +
> + ctx->attr_list_lock_ni = NULL;
> + ctx->attr_list_lock_mode = NTFS_ATTR_CTX_LOCK_NONE;
> +}
> +
> /**
> * ntfs_attr_init_search_ctx - initialize an attribute search context
> * @ctx: attribute search context to initialize
> @@ -1466,6 +1544,8 @@ static bool ntfs_attr_init_search_ctx(struct ntfs_attr_search_ctx *ctx,
> ctx->base_mrec = NULL;
> ctx->base_attr = NULL;
> ctx->mapped_base_mrec = false;
> + ctx->attr_list_lock_ni = NULL;
> + ctx->attr_list_lock_mode = NTFS_ATTR_CTX_LOCK_NONE;
> return true;
> }
>
> @@ -1481,6 +1561,9 @@ static bool ntfs_attr_init_search_ctx(struct ntfs_attr_search_ctx *ctx,
> */
> void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx)
> {
> + struct ntfs_inode *attr_list_lock_ni = ctx->attr_list_lock_ni;
> + enum ntfs_attr_search_ctx_lock_mode attr_list_lock_mode =
> + ctx->attr_list_lock_mode;
> bool mapped_mrec;
>
> if (likely(!ctx->base_ntfs_ino)) {
> @@ -1502,6 +1585,8 @@ void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx)
> mapped_mrec = ctx->mapped_base_mrec;
> ntfs_attr_init_search_ctx(ctx, ctx->base_ntfs_ino, ctx->base_mrec);
> ctx->mapped_mrec = mapped_mrec;
> + ctx->attr_list_lock_ni = attr_list_lock_ni;
> + ctx->attr_list_lock_mode = attr_list_lock_mode;
> }
>
> /*
> @@ -1513,7 +1598,8 @@ void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx)
> * and return it. Return NULL if allocation failed.
> */
> struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
> - struct mft_record *mrec)
> + struct mft_record *mrec,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode)
> {
> struct ntfs_attr_search_ctx *ctx;
> bool init;
> @@ -1524,6 +1610,8 @@ struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
> if (init == false) {
> kmem_cache_free(ntfs_attr_ctx_cache, ctx);
> ctx = NULL;
> + } else {
> + ntfs_attr_lock_search_ctx(ctx, ni, lock_mode);
> }
> }
>
> @@ -1539,6 +1627,8 @@ struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
> */
> void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx)
> {
> + ntfs_attr_unlock_search_ctx(ctx);
> +
> if (ctx->mapped_mrec)
> unmap_mft_record(ctx->ntfs_ino);
>
> @@ -1808,7 +1898,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
> ctx = NULL;
> goto err_out;
> }
> - ctx = ntfs_attr_get_search_ctx(base_ni, m);
> + ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> err = -ENOMEM;
> goto err_out;
> @@ -2147,7 +2237,7 @@ int ntfs_attr_set_initialized_size(struct ntfs_inode *ni, loff_t new_size)
> if (!NInoNonResident(ni))
> return -EINVAL;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx)
> return -ENOMEM;
>
> @@ -2247,7 +2337,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
> }
>
> /* Locate place where record should be. */
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> if (!ctx) {
> ntfs_error(ni->vol->sb, "%s: Failed to get search context",
> __func__);
> @@ -2369,7 +2459,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
> }
>
> /* Locate place where record should be. */
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> if (!ctx) {
> pr_err("%s: Failed to get search context\n", __func__);
> return -ENOMEM;
> @@ -2479,6 +2569,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
> int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx)
> {
> struct ntfs_inode *base_ni, *ni;
> + u8 *old_al = NULL;
> __le32 type;
> int err;
>
> @@ -2517,9 +2608,11 @@ int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx)
> /* Post $ATTRIBUTE_LIST delete setup. */
> if (type == AT_ATTRIBUTE_LIST) {
> if (NInoAttrList(base_ni) && base_ni->attr_list)
> - kvfree(base_ni->attr_list);
> + old_al = base_ni->attr_list;
> base_ni->attr_list = NULL;
> + base_ni->attr_list_size = 0;
> NInoClearAttrList(base_ni);
> + kvfree(old_al);
> }
>
> /* Free MFT record, if it doesn't contain attributes. */
> @@ -2944,7 +3037,7 @@ int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type,
> newname = name;
> }
>
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> pr_err("%s: Failed to get search context\n", __func__);
> @@ -3096,7 +3189,7 @@ void ntfs_attr_close(struct ntfs_inode *ni)
> * will map the runlist fragments from each of the extents thus giving access
> * to the entirety of the disk allocation of an attribute.
> */
> -int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
> +int ntfs_attr_map_whole_runlist_nolock(struct ntfs_inode *ni)
> {
> s64 next_vcn, last_vcn, highest_vcn;
> struct ntfs_attr_search_ctx *ctx;
> @@ -3119,7 +3212,7 @@ int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
> else
> base_ni = ni;
>
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> if (!ctx) {
> ntfs_error(sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -3211,6 +3304,18 @@ int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
> return err;
> }
>
> +int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
> +{
> + struct ntfs_inode *attr_lock_ni;
> + int err;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> + err = ntfs_attr_map_whole_runlist_nolock(ni);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> +
> + return err;
> +}
> +
> /*
> * ntfs_attr_record_move_to - move attribute record to target inode
> * @ctx: attribute search context describing the attribute record
> @@ -3245,7 +3350,7 @@ int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode
>
> /* Find place in MFT record where attribute will be moved. */
> a = ctx->attr;
> - nctx = ntfs_attr_get_search_ctx(ni, NULL);
> + nctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> if (!nctx) {
> ntfs_error(sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -3332,7 +3437,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
> return -EINVAL;
> }
>
> - err = ntfs_inode_attach_all_extents(ctx->ntfs_ino);
> + err = ntfs_inode_attach_all_extents_nolock(ctx->ntfs_ino);
> if (err) {
> ntfs_error(sb, "Couldn't attach extents, inode=%llu",
> (unsigned long long)base_ni->mft_no);
> @@ -3431,9 +3536,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
> if ((le32_to_cpu(a->length) -
> 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 (!NInoAttrList(base_ni)) {
> - err = ntfs_inode_add_attrlist(base_ni);
> + err = ntfs_inode_add_attrlist_nolock(base_ni);
> if (err)
> goto out;
> err = -EAGAIN;
> @@ -3534,7 +3638,8 @@ 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,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode)

All callers pass NONE, so is the lock_mode still necessary?

> {
> struct ntfs_attr_search_ctx *ctx;
> struct ntfs_inode *base_ni;
> @@ -3566,7 +3671,7 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
> else
> base_ni = ni;
>
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, lock_mode);
> if (!ctx) {
> ntfs_error(sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -3666,8 +3771,8 @@ 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)) {
> + if (ntfs_inode_free_space_nolock(base_ni, mp_size -
> + cur_max_mp_size)) {
> ntfs_debug("Attribute list is too big. Defragment the volume\n");
> return -ENOSPC;
> }
> @@ -3679,7 +3784,7 @@ 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)) {
> ntfs_attr_put_search_ctx(ctx);
> - if (ntfs_inode_add_attrlist(base_ni)) {
> + if (ntfs_inode_add_attrlist_nolock(base_ni)) {
> ntfs_error(sb, "Can not add attrlist");
> return -EIO;
> }
> @@ -3883,6 +3988,24 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
> return err;
> }
>
> +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
> +{
> + struct ntfs_inode *attr_lock_ni;
> + int err;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> + err = __ntfs_attr_update_mapping_pairs(ni, from_vcn, NTFS_ATTR_CTX_LOCK_NONE);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +
> + return err;
> +}
> +
> +int ntfs_attr_update_mapping_pairs_nolock(struct ntfs_inode *ni, s64 from_vcn)
> +{
> + return __ntfs_attr_update_mapping_pairs(ni, from_vcn,
> + NTFS_ATTR_CTX_LOCK_NONE);
> +}
> +
> /*
> * ntfs_attr_make_resident - convert a non-resident to a resident attribute
> * @ni: open ntfs attribute to make resident
> @@ -3940,7 +4063,7 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
> }
>
> /* Read and cache the whole runlist if not already done. */
> - err = ntfs_attr_map_whole_runlist(ni);
> + err = ntfs_attr_map_whole_runlist_nolock(ni);
> if (err)
> return err;
>
> @@ -4077,7 +4200,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
> return err;
> }
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -4125,7 +4248,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
> }
>
> /* Get the first attribute record. */
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -4237,7 +4360,8 @@ static int ntfs_non_resident_attr_expand(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) {
> - err = ntfs_attr_map_whole_runlist(ni);
> + err = need_lock ? ntfs_attr_map_whole_runlist(ni) :
> + ntfs_attr_map_whole_runlist_nolock(ni);
> if (err) {
> ntfs_error(sb, "ntfs_attr_map_whole_runlist failed");
> return err;
> @@ -4356,14 +4480,15 @@ 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 = need_lock ? ntfs_attr_update_mapping_pairs(ni, 0) :
> + ntfs_attr_update_mapping_pairs_nolock(ni, 0);
> if (err) {
> ntfs_debug("Mapping pairs update failed");
> goto rollback;
> }
> }
>
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> if (ni->allocated_size == org_alloc_size)
> @@ -4414,16 +4539,22 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
> kvfree(ni->runlist.rl);
> ni->runlist.rl = NULL;
> ntfs_error(sb, "Couldn't truncate runlist. Rollback failed");
> - } else {
> - /* Prepare to mapping pairs update. */
> - ni->allocated_size = org_alloc_size;
> - /* Restore mapping pairs. */
> - if (need_lock)
> - down_read(&ni->runlist.lock);
> - if (ntfs_attr_update_mapping_pairs(ni, 0))
> - ntfs_error(sb, "Failed to restore old mapping pairs");
> - if (need_lock)
> - up_read(&ni->runlist.lock);
> + } else {
> + /* Prepare to mapping pairs update. */
> + ni->allocated_size = org_alloc_size;
> + /* Restore mapping pairs. */
> + if (need_lock) {
> + struct ntfs_inode *attr_lock_ni;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> + down_read(&ni->runlist.lock);
> + if (ntfs_attr_update_mapping_pairs_nolock(ni, 0))
> + ntfs_error(sb, "Failed to restore old mapping pairs");
> + up_read(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> + } else if (ntfs_attr_update_mapping_pairs_nolock(ni, 0)) {
> + ntfs_error(sb, "Failed to restore old mapping pairs");
> + }
>
> if (NInoSparse(ni) || NInoCompressed(ni)) {
> ni->itype.compressed.size = org_compressed_size;
> @@ -4469,7 +4600,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
> base_ni = attr_ni;
>
> /* Get the attribute record that needs modification. */
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);

attr_list_lock can be acquired recursively. this function calls
ntfs_attr_make_non_resident(), which also calls
ntfs_attr_get_search_ctx().

> if (!ctx) {
> ntfs_error(sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -4698,12 +4829,16 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
>
> if (NInoNonResident(ni)) {
> if (newsize > i_size) {
> + struct ntfs_inode *attr_lock_ni;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> err = ntfs_non_resident_attr_expand(ni, newsize, 0,
> NVolDisableSparse(ni->vol) ?
> HOLES_NO : HOLES_OK,
> false);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> } else
> err = ntfs_non_resident_attr_shrink(ni, newsize);
> } else
> @@ -4833,14 +4968,17 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
> int err = 0;
> size_t new_rl_count;
>
> - err = ntfs_attr_map_whole_runlist(ni);
> + err = ntfs_attr_map_whole_runlist_nolock(ni);
> if (err)
> return err;
>
> - if (NInoAttr(ni))
> - ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL);
> - else
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + if (NInoAttr(ni)) {
> + struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino;
> +
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> + } else {
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> + }
> if (!ctx) {
> ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -4947,7 +5085,7 @@ 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_nolock(ni, 0);
> if (err) {
> int err2;
>
> @@ -5000,7 +5138,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni)
> err = ntfs_attr_map_whole_runlist(ni);
> if (err)
> return err;
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -5014,7 +5152,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni)
> }
>
> /* Search for attribute extents and remove them all. */
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
> if (!ctx) {
> ntfs_error(sb, "%s: Failed to get search context", __func__);
> return -ENOMEM;
> @@ -5046,7 +5184,7 @@ int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
>
> ntfs_debug("Entering\n");
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(ni->vol->sb, "%s: Failed to get search context",
> __func__);
> @@ -5155,6 +5293,7 @@ void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type,
> int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s64 len)
> {
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> struct runlist_element *hole_rl, *rl;
> struct ntfs_attr_search_ctx *ctx;
> int ret;
> @@ -5175,16 +5314,19 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
> hole_rl[1].lcn = LCN_ENOENT;
> hole_rl[1].length = 0;
>
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> - ret = ntfs_attr_map_whole_runlist(ni);
> + ret = ntfs_attr_map_whole_runlist_nolock(ni);
> if (ret) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return ret;
> }
>
> rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn);
> if (!rl) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> kfree(hole_rl);
> return -EIO;
> }
> @@ -5193,6 +5335,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
> hole_rl, 1, &new_rl_count);
> if (IS_ERR(rl)) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> kfree(hole_rl);
> return PTR_ERR(rl);
> }
> @@ -5203,12 +5346,13 @@ 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_nolock(ni, 0);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> if (ret)
> return ret;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ret = -ENOMEM;
> return ret;
> @@ -5233,6 +5377,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
> int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, s64 len)
> {
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> struct runlist_element *punch_rl, *rl;
> struct ntfs_attr_search_ctx *ctx = NULL;
> s64 end_vcn;
> @@ -5247,10 +5392,12 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
> if (start_vcn >= end_vcn)
> return -EINVAL;
>
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> - ret = ntfs_attr_map_whole_runlist(ni);
> + ret = ntfs_attr_map_whole_runlist_nolock(ni);
> if (ret) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return ret;
> }
>
> @@ -5260,6 +5407,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
> rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn);
> if (!rl) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return -EIO;
> }
>
> @@ -5267,6 +5415,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
> start_vcn, len, &punch_rl, &new_rl_cnt);
> if (IS_ERR(rl)) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return PTR_ERR(rl);
> }
> ni->runlist.rl = rl;
> @@ -5288,15 +5437,17 @@ 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_nolock(ni, 0);
> if (ret) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> goto out_rl;
> }
> }
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ret = -ENOMEM;
> goto out_rl;
> @@ -5328,6 +5479,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
> int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 len)
> {
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> struct runlist_element *punch_rl, *rl;
> s64 end_vcn;
> int dst_cnt;
> @@ -5341,10 +5493,12 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
> if (start_vcn >= end_vcn)
> return -EINVAL;
>
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> - ret = ntfs_attr_map_whole_runlist(ni);
> + ret = ntfs_attr_map_whole_runlist_nolock(ni);
> if (ret) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return ret;
> }
>
> @@ -5354,6 +5508,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
> rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn);
> if (!rl) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return -EIO;
> }
>
> @@ -5361,13 +5516,15 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
> start_vcn, len, &punch_rl, &new_rl_count);
> if (IS_ERR(rl)) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return PTR_ERR(rl);
> }
> ni->runlist.rl = rl;
> ni->runlist.count = new_rl_count;
>
> - ret = ntfs_attr_update_mapping_pairs(ni, 0);
> + ret = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> if (ret) {
> kvfree(punch_rl);
> return ret;
> @@ -5398,9 +5555,13 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
> return -EINVAL;
>
> if (NInoNonResident(ni) && !NInoFullyMapped(ni)) {
> + struct ntfs_inode *attr_lock_ni;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> down_write(&ni->runlist.lock);
> - err = ntfs_attr_map_whole_runlist(ni);
> + err = ntfs_attr_map_whole_runlist_nolock(ni);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> if (err)
> return err;
> }
> @@ -5412,7 +5573,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
> return PTR_ERR(mrec);
> }
>
> - ctx = ntfs_attr_get_search_ctx(ni, mrec);
> + ctx = ntfs_attr_get_search_ctx(ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto out_unmap;
> @@ -5487,11 +5648,15 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
> }
>
> while (try_alloc_cnt > 0) {
> + struct ntfs_inode *attr_lock_ni;
> +
> mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt,
> try_alloc_cnt, &balloc, false, false);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> mutex_unlock(&ni->mrec_lock);
> if (err)
> goto out;
> @@ -5517,11 +5682,15 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
> /* allocate clusters outside of initialized_size */
> try_alloc_cnt = vcn_end - vcn;
> while (try_alloc_cnt > 0) {
> + struct ntfs_inode *attr_lock_ni;
> +
> mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt,
> try_alloc_cnt, &balloc, false, false);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> mutex_unlock(&ni->mrec_lock);
> if (err || signal_pending(current))
> goto out;
> @@ -5532,14 +5701,18 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
> }
>
> if (NInoRunlistDirty(ni)) {
> + struct ntfs_inode *attr_lock_ni;
> +
> mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> - err = ntfs_attr_update_mapping_pairs(ni, 0);
> + err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
> if (err)
> ntfs_error(ni->vol->sb, "Updating mapping pairs failed");
> else
> NInoClearRunlistDirty(ni);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> mutex_unlock(&ni->mrec_lock);
> }
> return err;
> diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h
> index f7acc7986b090..b4d457b267797 100644
> --- a/fs/ntfs/attrib.h
> +++ b/fs/ntfs/attrib.h
> @@ -26,6 +26,8 @@ extern __le16 AT_UNNAMED[];
> * @base_ntfs_ino: Base inode
> * @mapped_base_mrec: true if @base_mrec was mapped by the search
> * @base_attr: Base attribute record pointer
> + * @attr_list_lock_ni: Base inode whose attr_list_lock is held
> + * @attr_list_lock_mode: attr_list_lock mode held by this context
> *
> * Structure must be initialized to zero before the first call to one of the
> * attribute search functions. Initialize @mrec to point to the mft record to
> @@ -39,6 +41,12 @@ extern __le16 AT_UNNAMED[];
> * any modification of the search context, to automagically get the next
> * matching attribute.
> */
> +enum ntfs_attr_search_ctx_lock_mode {
> + NTFS_ATTR_CTX_LOCK_NONE,
> + NTFS_ATTR_CTX_LOCK_READ,
> + NTFS_ATTR_CTX_LOCK_WRITE,
> +};
> +
> struct ntfs_attr_search_ctx {
> struct mft_record *mrec;
> bool mapped_mrec;
> @@ -50,6 +58,8 @@ struct ntfs_attr_search_ctx {
> struct mft_record *base_mrec;
> bool mapped_base_mrec;
> struct attr_record *base_attr;
> + struct ntfs_inode *attr_list_lock_ni;
> + enum ntfs_attr_search_ctx_lock_mode attr_list_lock_mode;
> };
>
> enum { /* ways of processing holes when expanding */
> @@ -66,6 +76,7 @@ struct runlist_element *ntfs_attr_find_vcn_nolock(struct ntfs_inode *ni,
> const s64 vcn, struct ntfs_attr_search_ctx *ctx);
> struct runlist_element *__ntfs_attr_find_vcn_nolock(struct runlist *runlist,
> const s64 vcn);
> +int ntfs_attr_map_whole_runlist_nolock(struct ntfs_inode *ni);
> int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni);
> int ntfs_attr_lookup(const __le32 type, const __le16 *name,
> const u32 name_len, const u32 ic,
> @@ -83,7 +94,16 @@ static inline s64 ntfs_attr_size(const struct attr_record *a)
>
> void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx);
> struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
> - struct mft_record *mrec);
> + struct mft_record *mrec,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +struct ntfs_inode *ntfs_attr_list_lock(struct ntfs_inode *ni,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +void ntfs_attr_list_unlock(struct ntfs_inode *lock_ni,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +void ntfs_attr_lock_search_ctx(struct ntfs_attr_search_ctx *ctx,
> + struct ntfs_inode *ni,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +void ntfs_attr_unlock_search_ctx(struct ntfs_attr_search_ctx *ctx);
> void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx);
> int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol,
> const __le32 type, const s64 size);
> @@ -129,6 +149,7 @@ 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_nolock(struct ntfs_inode *ni, s64 from_vcn);
> struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
>
> /*
> @@ -144,7 +165,9 @@ struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64
> * @ni, you can simply do:
> *
> * int err;
> - * struct ntfs_attr_search_ctx *ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + * struct ntfs_attr_search_ctx *ctx;
> + *
> + * ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> * if (!ctx)
> * // Error code is in errno. Handle this case.
> * while (!(err = ntfs_attrs_walk(ctx))) {
> diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
> index bd501e8a628c4..56967aca8c7a2 100644
> --- a/fs/ntfs/attrlist.c
> +++ b/fs/ntfs/attrlist.c
> @@ -153,7 +153,7 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
> return -ENOMEM;
>
> /* Find place for the new entry. */
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
> if (!ctx) {
> err = -ENOMEM;
> ntfs_error(ni->vol->sb, "Failed to get search context");
> @@ -172,7 +172,6 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
> if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) {
> err = -EEXIST;
> ntfs_debug("Such attribute already present in the attribute list.\n");
> - ntfs_attr_put_search_ctx(ctx);
> goto err_out;
> }
> /* Add new entry after this extent. */
> @@ -182,15 +181,11 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
> /* Check for real errors. */
> if (err != -ENOENT) {
> ntfs_debug("Attribute lookup failed.\n");
> - ntfs_attr_put_search_ctx(ctx);
> goto err_out;
> }
> /* No previous extents found. */
> ale = ctx->al_entry;
> }
> - /* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */
> - ntfs_attr_put_search_ctx(ctx);
> -
> /* Determine new entry offset. */
> entry_offset = ((u8 *)ale - ni->attr_list);
> /* Set pointer to new entry. */
> @@ -219,16 +214,22 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
> old_al = ni->attr_list;
> ni->attr_list = new_al;
> ni->attr_list_size = ni->attr_list_size + entry_len;
> + ntfs_attr_put_search_ctx(ctx);
> + ctx = NULL;
>
> err = ntfs_attrlist_update(ni);
> if (err) {
> + down_write(&ni->attr_list_lock);
> ni->attr_list = old_al;
> ni->attr_list_size -= entry_len;
> + up_write(&ni->attr_list_lock);
> goto err_out;
> }
> kvfree(old_al);
> return 0;
> err_out:
> + if (ctx)
> + ntfs_attr_put_search_ctx(ctx);
> kvfree(new_al);
> return err;
> }
> @@ -244,9 +245,12 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
> int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
> {
> u8 *new_al;
> - int new_al_len;
> + int err, new_al_len;
> struct ntfs_inode *base_ni;
> struct attr_list_entry *ale;
> + enum ntfs_attr_search_ctx_lock_mode lock_mode;
> + bool ctx_locked;
> + u8 *old_al;
>
> if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) {
> ntfs_debug("Invalid arguments.\n");
> @@ -281,9 +285,17 @@ int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
> ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list));
>
> /* Set new runlist. */
> - kvfree(base_ni->attr_list);
> + old_al = base_ni->attr_list;
> base_ni->attr_list = new_al;
> base_ni->attr_list_size = new_al_len;
> + kvfree(old_al);
>
> - return ntfs_attrlist_update(base_ni);
> + ctx_locked = ctx->attr_list_lock_ni;
> + lock_mode = ctx->attr_list_lock_mode;
> + if (ctx_locked)
> + ntfs_attr_unlock_search_ctx(ctx);
> + err = ntfs_attrlist_update(base_ni);
> + if (ctx_locked)
> + ntfs_attr_lock_search_ctx(ctx, base_ni, lock_mode);
> + return err;
> }
> diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
> index 76bd806b41edd..bac6b32069475 100644
> --- a/fs/ntfs/compress.c
> +++ b/fs/ntfs/compress.c
> @@ -1287,6 +1287,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
> /* more compressed zeroes, to be followed by some count */
> static char morezeroes[] = {0x03, 0xb0, 0x02, 0x00};
> struct page **pages_disk = NULL, *pg;
> + struct ntfs_inode *attr_lock_ni;
> s64 bio_lcn;
> struct runlist_element *rlc, *rl;
> int i, err;
> @@ -1390,10 +1391,12 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
> }
>
> bio_lcn = rlc->lcn;
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count);
> if (IS_ERR(rl)) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb, "Failed to merge runlists");
> err = PTR_ERR(rl);
> if (ntfs_cluster_free_from_rl(vol, rlc))
> @@ -1405,8 +1408,9 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
> ni->runlist.count = new_rl_count;
> ni->runlist.rl = rl;
>
> - err = ntfs_attr_update_mapping_pairs(ni, 0);
> + err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> if (err) {
> err = -EIO;
> goto out;
> diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
> index 20f5c7074bdd1..84ca11227d060 100644
> --- a/fs/ntfs/dir.c
> +++ b/fs/ntfs/dir.c
> @@ -91,7 +91,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
> -PTR_ERR(m));
> return ERR_MREF(PTR_ERR(m));
> }
> - ctx = ntfs_attr_get_search_ctx(dir_ni, m);
> + ctx = ntfs_attr_get_search_ctx(dir_ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> err = -ENOMEM;
> goto err_out;
> @@ -854,7 +854,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
> file->private_data = private;
> }
>
> - ctx = ntfs_attr_get_search_ctx(ndir, NULL);
> + ctx = ntfs_attr_get_search_ctx(ndir, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto out;
> @@ -1061,7 +1061,7 @@ int ntfs_check_empty_dir(struct ntfs_inode *ni, struct mft_record *ni_mrec)
> if (!(ni_mrec->flags & MFT_RECORD_IS_DIRECTORY))
> return 0;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(ni->vol->sb, "Failed to get search context");
> return -ENOMEM;
> @@ -1162,7 +1162,7 @@ static int ntfs_dir_fsync(struct file *filp, loff_t start, loff_t end,
> if (NVolShutdown(vol))
> return -EIO;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx)
> return -ENOMEM;
>
> diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
> index c4a4a3e3e5996..3cba0ff9606cb 100644
> --- a/fs/ntfs/ea.c
> +++ b/fs/ntfs/ea.c
> @@ -587,7 +587,7 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
> if (IS_ERR(m))
> return PTR_ERR(m);
>
> - ctx = ntfs_attr_get_search_ctx(ni, m);
> + ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto err_out;
> diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
> index e8bea22b81a75..038ed723c4bff 100644
> --- a/fs/ntfs/file.c
> +++ b/fs/ntfs/file.c
> @@ -22,6 +22,7 @@
> #include "ea.h"
> #include "iomap.h"
> #include "bitmap.h"
> +#include "attrib.h"
>
> #include <linux/filelock.h>
>
> @@ -78,6 +79,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
> {
> struct ntfs_inode *ni = NTFS_I(vi);
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> struct runlist_element *rl;
> s64 aligned_data_size;
> s64 vcn_ds, vcn_tr;
> @@ -86,6 +88,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
>
> inode_lock(vi);
> mutex_lock(&ni->mrec_lock);
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
>
> aligned_data_size = round_up(ni->data_size, vol->cluster_size);
> @@ -110,7 +113,7 @@ 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_nolock(ni, 0);
> if (err)
> ntfs_error(vol->sb,
> "Failed to rollback mapping pairs for prealloc");
> @@ -119,6 +122,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
>
> out_unlock:
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> mutex_unlock(&ni->mrec_lock);
> inode_unlock(vi);
>
> @@ -176,7 +180,7 @@ static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end,
> ret = __ntfs_write_inode(vi, 1);
> write_inode_now(vi, !datasync);
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx)
> return -ENOMEM;
>
> @@ -1028,6 +1032,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
> struct inode *vi = file_inode(file);
> struct ntfs_inode *ni = NTFS_I(vi);
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> int err = 0;
> loff_t old_size;
> bool map_locked = false;
> @@ -1042,9 +1047,11 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
> return -ENOSPC;
>
> if (NInoNonResident(ni) && !NInoFullyMapped(ni)) {
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> down_write(&ni->runlist.lock);
> - err = ntfs_attr_map_whole_runlist(ni);
> + err = ntfs_attr_map_whole_runlist_nolock(ni);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> if (err)
> return err;
> }
> diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
> index a547bdcfa4561..f6d31465c7b93 100644
> --- a/fs/ntfs/index.c
> +++ b/fs/ntfs/index.c
> @@ -509,7 +509,7 @@ static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name,
> struct index_root *ir = NULL;
>
> ntfs_debug("Entering\n");
> - *ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + *ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
> if (!*ctx) {
> ntfs_error(ni->vol->sb, "%s, Failed to get search context", __func__);
> return NULL;
> @@ -1240,7 +1240,8 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
> * When there is no space to build a non-resident
> * index, we may have to move the root to an extent
> */
> - if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) {
> + if ((ret == -ENOSPC) &&
> + (ctx->al_entry || !ntfs_inode_add_attrlist_nolock(icx->idx_ni))) {
> ntfs_attr_put_search_ctx(ctx);
> ctx = NULL;
> ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx);
> diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
> index 16890d411194d..1cbeb6a68e915 100644
> --- a/fs/ntfs/inode.c
> +++ b/fs/ntfs/inode.c
> @@ -326,7 +326,7 @@ static int ntfs_non_resident_dealloc_clusters(struct ntfs_inode *ni)
> struct ntfs_attr_search_ctx *actx;
> int err = 0;
>
> - actx = ntfs_attr_get_search_ctx(ni, NULL);
> + actx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!actx)
> return -ENOMEM;
> WARN_ON(actx->mrec->link_count != 0);
> @@ -475,6 +475,7 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni)
> ni->folio = NULL;
> ni->folio_ofs = 0;
> ni->mrec = NULL;
> + init_rwsem(&ni->attr_list_lock);
> ni->attr_list_size = 0;
> ni->attr_list = NULL;
> ni->itype.index.block_size = 0;
> @@ -711,7 +712,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
> goto err_out;
> }
>
> - ctx = ntfs_attr_get_search_ctx(ni, m);
> + ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto unm_err_out;
> @@ -1265,7 +1266,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
> err = PTR_ERR(m);
> goto err_out;
> }
> - ctx = ntfs_attr_get_search_ctx(base_ni, m);
> + ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto unm_err_out;
> @@ -1506,7 +1507,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
> err = PTR_ERR(m);
> goto err_out;
> }
> - ctx = ntfs_attr_get_search_ctx(base_ni, m);
> + ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto unm_err_out;
> @@ -1897,7 +1898,7 @@ int ntfs_read_inode_mount(struct inode *vi)
> /* Provides read_folio() for map_mft_record(). */
> vi->i_mapping->a_ops = &ntfs_mft_aops;
>
> - ctx = ntfs_attr_get_search_ctx(ni, m);
> + ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto err_out;
> @@ -2447,7 +2448,7 @@ static int ntfs_inode_sync_standard_information(struct inode *vi, struct mft_rec
> bool modified = false;
>
> /* Update the access times in the standard information attribute. */
> - ctx = ntfs_attr_get_search_ctx(ni, m);
> + ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx))
> return -ENOMEM;
> err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0,
> @@ -2548,7 +2549,7 @@ int ntfs_inode_sync_filename(struct ntfs_inode *ni)
>
> ntfs_debug("Entering for inode %llu\n", ni->mft_no);
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx)
> return -ENOMEM;
>
> @@ -2740,11 +2741,15 @@ int __ntfs_write_inode(struct inode *vi, int sync)
> }
>
> if (NInoNonResident(ni) && NInoRunlistDirty(ni)) {
> + struct ntfs_inode *attr_lock_ni;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> - err = ntfs_attr_update_mapping_pairs(ni, 0);
> + err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
> if (!err)
> NInoClearRunlistDirty(ni);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> }
>
> err = ntfs_inode_sync_standard_information(vi, m);
> @@ -2963,7 +2968,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni,
> *
> * Return 0 on success and error.
> */
> -int ntfs_inode_attach_all_extents(struct ntfs_inode *ni)
> +int ntfs_inode_attach_all_extents_nolock(struct ntfs_inode *ni)
> {
> struct attr_list_entry *ale;
> u64 prev_attached = 0;
> @@ -3003,13 +3008,25 @@ int ntfs_inode_attach_all_extents(struct ntfs_inode *ni)
> return 0;
> }
>
> +int ntfs_inode_attach_all_extents(struct ntfs_inode *ni)
> +{
> + struct ntfs_inode *attr_lock_ni;
> + int err;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> + err = ntfs_inode_attach_all_extents_nolock(ni);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> +
> + return err;
> +}
> +
> /*
> * ntfs_inode_add_attrlist - add attribute list to inode and fill it
> * @ni: opened ntfs inode to which add attribute list
> *
> * Return 0 on success or error.
> */
> -int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> +int ntfs_inode_add_attrlist_nolock(struct ntfs_inode *ni)
> {
> int err;
> struct ntfs_attr_search_ctx *ctx;
> @@ -3034,7 +3051,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> return -EIO;
>
> /* Form attribute list. */
> - ctx = ntfs_attr_get_search_ctx(ni, ni_mrec);
> + ctx = ntfs_attr_get_search_ctx(ni, ni_mrec, NTFS_ATTR_CTX_LOCK_NONE);
> if (!ctx) {
> err = -ENOMEM;
> goto err_out;
> @@ -3092,7 +3109,6 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> goto put_err_out;
> }
>
> - /* Set in-memory attribute list. */
> ni->attr_list = al;
> ni->attr_list_size = al_len;
> NInoSetAttrList(ni);
> @@ -3102,7 +3118,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> /* Free space if there is not enough it for $ATTRIBUTE_LIST. */
> if (le32_to_cpu(ni_mrec->bytes_allocated) -
> le32_to_cpu(ni_mrec->bytes_in_use) < attr_al_len) {
> - if (ntfs_inode_free_space(ni, (int)attr_al_len)) {
> + if (ntfs_inode_free_space_nolock(ni, (int)attr_al_len)) {
> /* Failed to free space. */
> err = -ENOSPC;
> ntfs_error(ni->vol->sb, "Failed to free space for attrlist");
> @@ -3129,6 +3145,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> remove_attrlist_record:
> /* Prevent ntfs_attr_recorm_rm from freeing attribute list. */
> ni->attr_list = NULL;
> + ni->attr_list_size = 0;
> NInoClearAttrList(ni);
> /* Remove $ATTRIBUTE_LIST record. */
> ntfs_attr_reinit_search_ctx(ctx);
> @@ -3182,6 +3199,18 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> return err;
> }
>
> +int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> +{
> + struct ntfs_inode *attr_lock_ni;
> + int err;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> + err = ntfs_inode_add_attrlist_nolock(ni);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +
> + return err;
> +}
> +
> /*
> * ntfs_inode_close - close an ntfs inode and free all associated memory
> * @ni: ntfs inode to close
> @@ -3325,7 +3354,8 @@ static int ntfs_attr_position(__le32 type, struct ntfs_attr_search_ctx *ctx)
> *
> * Return 0 on success or error.
> */
> -int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
> +static int __ntfs_inode_free_space(struct ntfs_inode *ni, int size,
> + enum ntfs_attr_search_ctx_lock_mode lock_mode)
> {
> struct ntfs_attr_search_ctx *ctx;
> int freed, err;
> @@ -3349,7 +3379,7 @@ int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
> if (size <= freed)
> return 0;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, lock_mode);
> if (!ctx) {
> ntfs_error(sb, "%s, Failed to get search context", __func__);
> return -ENOMEM;
> @@ -3435,6 +3465,23 @@ int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
> return err;
> }
>
> +int ntfs_inode_free_space_nolock(struct ntfs_inode *ni, int size)
> +{
> + return __ntfs_inode_free_space(ni, size, NTFS_ATTR_CTX_LOCK_NONE);
> +}
> +
> +int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
> +{
> + struct ntfs_inode *attr_lock_ni;
> + int err;
> +
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> + err = __ntfs_inode_free_space(ni, size, NTFS_ATTR_CTX_LOCK_NONE);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +
> + return err;
> +}
> +
> s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf)
> {
> struct address_space *mapping = vi->i_mapping;
> @@ -3459,10 +3506,11 @@ s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf)
> count = isize - pos;
>
> if (!NInoNonResident(ni)) {
> + struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino;
> struct ntfs_attr_search_ctx *ctx;
> u8 *attr;
>
> - ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(vi->i_sb, "Failed to get attr search ctx");
> err = -ENOMEM;
> @@ -3650,6 +3698,8 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
>
> if (sync) {
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> + enum ntfs_attr_search_ctx_lock_mode mode;
> s64 lcn, lcn_count;
> unsigned int lcn_folio_off = 0;
> struct bio *bio;
> @@ -3659,13 +3709,16 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
>
> lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
> vcn = ntfs_pidx_to_cluster(vol, folio->index);
> + mode = NTFS_ATTR_CTX_LOCK_READ;
>
> do {
> + attr_lock_ni = ntfs_attr_list_lock(ni, mode);
> down_write(&ni->runlist.lock);
> rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn);
> if (IS_ERR(rl)) {
> ret = PTR_ERR(rl);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, mode);
> goto err_unlock_folio;
> }
>
> @@ -3677,6 +3730,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
> lcn_count = 0;
> }
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, mode);
>
> if (vol->cluster_size_bits > PAGE_SHIFT) {
> lcn_folio_off = folio->index << PAGE_SHIFT;
> @@ -3737,7 +3791,7 @@ s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool s
>
> WARN_ON(!NInoAttr(ni));
>
> - ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(vi->i_sb, "Failed to get attr search ctx");
> return -ENOMEM;
> diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h
> index 67942b97fac6d..43783e77c4f0e 100644
> --- a/fs/ntfs/inode.h
> +++ b/fs/ntfs/inode.h
> @@ -10,6 +10,8 @@
> #ifndef _LINUX_NTFS_INODE_H
> #define _LINUX_NTFS_INODE_H
>
> +#include <linux/rwsem.h>
> +
> #include "debug.h"
>
> enum ntfs_inode_mutex_lock_class {
> @@ -66,6 +68,7 @@ enum ntfs_inode_mutex_lock_class {
> * Attribute list support (only for use by the attribute lookup
> * functions). Setup during read_inode for all inodes with attribute
> * lists. Only valid if NI_AttrList is set in state.
> + * @attr_list_lock: Serializes attribute list pointer updates with lookups.
> * @attr_list_size: Length of attribute list value in bytes.
> * @attr_list: Attribute list value itself.
> *
> @@ -118,6 +121,7 @@ struct ntfs_inode {
> int folio_ofs;
> s64 mft_lcn[2];
> unsigned int mft_lcn_count;
> + struct rw_semaphore attr_list_lock;
> u32 attr_list_size;
> u8 *attr_list;
> union {
> @@ -334,9 +338,12 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
>
> int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni);
> int __ntfs_write_inode(struct inode *vi, int sync);
> +int ntfs_inode_attach_all_extents_nolock(struct ntfs_inode *ni);
> int ntfs_inode_attach_all_extents(struct ntfs_inode *ni);
> +int ntfs_inode_add_attrlist_nolock(struct ntfs_inode *ni);
> int ntfs_inode_add_attrlist(struct ntfs_inode *ni);
> void ntfs_destroy_ext_inode(struct ntfs_inode *ni);
> +int ntfs_inode_free_space_nolock(struct ntfs_inode *ni, int size);
> int ntfs_inode_free_space(struct ntfs_inode *ni, int size);
> s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf);
> s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf,
> diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
> index dc7d8c893a699..f2bed2e0c8c8b 100644
> --- a/fs/ntfs/iomap.c
> +++ b/fs/ntfs/iomap.c
> @@ -96,7 +96,7 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
> else
> base_ni = ni;
>
> - ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto out;
> @@ -196,6 +196,7 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
> bool need_unwritten)
> {
> struct ntfs_inode *ni = NTFS_I(inode);
> + struct ntfs_inode *attr_lock_ni;
> s64 vcn;
> s64 lcn;
> struct runlist_element *rl;
> @@ -206,20 +207,24 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
> vcn = ntfs_bytes_to_cluster(vol, offset);
> vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
>
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> down_write(&ni->runlist.lock);
> rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn);
> if (IS_ERR(rl)) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> return PTR_ERR(rl);
> }
>
> if (flags & IOMAP_REPORT) {
> if (lcn < LCN_HOLE) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> return -ENOENT;
> }
> } else if (lcn < LCN_ENOENT) {
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> return -EINVAL;
> }
>
> @@ -247,6 +252,7 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
> "runlist(vcn : %lld, length : %lld, lcn : %lld) is corrupted\n",
> rl->vcn, rl->length, rl->lcn);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> return -EIO;
> }
>
> @@ -255,6 +261,7 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
> else
> iomap->length = length;
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>
> if (!(flags & IOMAP_ZERO) &&
> iomap->type == IOMAP_MAPPED &&
> @@ -572,6 +579,7 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
> {
> struct ntfs_inode *ni = NTFS_I(inode);
> struct ntfs_volume *vol = ni->vol;
> + struct ntfs_inode *attr_lock_ni;
> loff_t vcn_ofs, rl_length;
> s64 vcn, start_lcn, lcn_count;
> bool balloc = false, update_mp;
> @@ -584,11 +592,13 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
>
> update_mp = ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_DIO | NTFS_IOMAP_FLAGS_MKWRITE) ||
> NInoAttr(ni) || ni->mft_no < FILE_first_user;
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> down_write(&ni->runlist.lock);
> err = ntfs_attr_map_cluster(ni, vcn, &start_lcn, &lcn_count,
> max_clu_count, &balloc, update_mp,
> ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK);
> up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> mutex_unlock(&ni->mrec_lock);
> if (err) {
> ni->i_dealloc_clusters = 0;
> @@ -661,7 +671,7 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
> char *kattr;
> struct page *ipage;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto out;
> @@ -785,7 +795,7 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
> struct page *ipage = iomap->private;
>
> mutex_lock(&ni->mrec_lock);
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> written = -ENOMEM;
> goto err_out;
> diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
> index 7d989267a82b9..e2b2e21abb7f5 100644
> --- a/fs/ntfs/mft.c
> +++ b/fs/ntfs/mft.c
> @@ -12,6 +12,7 @@
> #include <linux/bio.h>
> #include <linux/iomap.h>
>
> +#include "attrib.h"
> #include "bitmap.h"
> #include "lcnalloc.h"
> #include "mft.h"
> @@ -1093,7 +1094,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
> base_ni = ni;
>
> if (!NInoAttrList(base_ni)) {
> - ret = ntfs_inode_add_attrlist(base_ni);
> + ret = ntfs_inode_add_attrlist_nolock(base_ni);
> if (ret) {
> pr_err("Can not add attrlist\n");
> goto out;
> @@ -1103,7 +1104,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_nolock(ni, 0);
> if (ret)
> pr_err("MP update failed\n");
>
> @@ -1135,6 +1136,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> unsigned long flags;
> struct folio *folio;
> struct ntfs_inode *mft_ni, *mftbmp_ni;
> + struct ntfs_inode *attr_lock_ni;
> struct runlist_element *rl, *rl2 = NULL;
> struct ntfs_attr_search_ctx *ctx = NULL;
> struct mft_record *mrec;
> @@ -1153,6 +1155,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_debug("Extending mft bitmap allocation.");
> mft_ni = NTFS_I(vol->mft_ino);
> mftbmp_ni = NTFS_I(vol->mftbmp_ino);
> + attr_lock_ni = ntfs_attr_list_lock(mftbmp_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> /*
> * Determine the last lcn of the mft bitmap. The allocated size of the
> * mft bitmap cannot be zero so we are ok to do this.
> @@ -1165,6 +1168,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> NTFS_B_TO_CLU(vol, ll - 1), NULL);
> if (IS_ERR(rl) || unlikely(!rl->length || rl->lcn < 0)) {
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb,
> "Failed to determine last allocated cluster of mft bitmap attribute.");
> if (!IS_ERR(rl))
> @@ -1186,6 +1190,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> ll >> PAGE_SHIFT, NULL);
> if (IS_ERR(folio)) {
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb, "Failed to read from lcn bitmap.");
> return PTR_ERR(folio);
> }
> @@ -1217,6 +1222,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> true, false, false);
> if (IS_ERR(rl2)) {
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb,
> "Failed to allocate a cluster for the mft bitmap.");
> return PTR_ERR(rl2);
> @@ -1224,6 +1230,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> rl = ntfs_runlists_merge(&mftbmp_ni->runlist, rl2, 0, &new_rl_count);
> if (IS_ERR(rl)) {
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb, "Failed to merge runlists for mft bitmap.");
> if (ntfs_cluster_free_from_rl(vol, rl2)) {
> ntfs_error(vol->sb, "Failed to deallocate allocated cluster.%s",
> @@ -1251,7 +1258,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> ret = PTR_ERR(mrec);
> goto undo_alloc;
> }
> - ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_NONE);
> if (unlikely(!ctx)) {
> ntfs_error(vol->sb, "Failed to get search context.");
> ret = -ENOMEM;
> @@ -1343,6 +1350,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_attr_put_search_ctx(ctx);
> unmap_mft_record(mft_ni);
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_debug("Done.");
> return 0;
>
> @@ -1359,6 +1367,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_attr_put_search_ctx(ctx);
> unmap_mft_record(mft_ni);
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> /*
> * The only thing that is now wrong is ->allocated_size of the
> * base attribute extent which chkdsk should be able to fix.
> @@ -1402,7 +1411,8 @@ 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_nolock(mftbmp_ni, 0)) {
> ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
> NVolSetErrors(vol);
> }
> @@ -1411,6 +1421,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
> if (!IS_ERR(mrec))
> unmap_mft_record(mft_ni);
> up_write(&mftbmp_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return ret;
> }
>
> @@ -1449,7 +1460,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
> ntfs_error(vol->sb, "Failed to map mft record.");
> return PTR_ERR(mrec);
> }
> - ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> ntfs_error(vol->sb, "Failed to get search context.");
> ret = -ENOMEM;
> @@ -1501,7 +1512,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
> NVolSetErrors(vol);
> return ret;
> }
> - ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> ntfs_error(vol->sb, "Failed to get search context.%s", es);
> NVolSetErrors(vol);
> @@ -1568,6 +1579,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> s64 min_nr, nr, ll;
> unsigned long flags;
> struct ntfs_inode *mft_ni;
> + struct ntfs_inode *attr_lock_ni;
> struct runlist_element *rl, *rl2;
> struct ntfs_attr_search_ctx *ctx = NULL;
> struct mft_record *mrec;
> @@ -1579,6 +1591,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>
> ntfs_debug("Extending mft data allocation.");
> mft_ni = NTFS_I(vol->mft_ino);
> + attr_lock_ni = ntfs_attr_list_lock(mft_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> /*
> * Determine the preferred allocation location, i.e. the last lcn of
> * the mft data attribute. The allocated size of the mft data
> @@ -1592,6 +1605,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> NTFS_B_TO_CLU(vol, ll - 1), NULL);
> if (IS_ERR(rl) || unlikely(!rl->length || rl->lcn < 0)) {
> up_write(&mft_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb,
> "Failed to determine last allocated cluster of mft data attribute.");
> if (!IS_ERR(rl))
> @@ -1622,6 +1636,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_warning(vol->sb,
> "Cannot allocate mft record because the maximum number of inodes (2^32) has already been reached.");
> up_write(&mft_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return -ENOSPC;
> }
> }
> @@ -1646,6 +1661,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_error(vol->sb,
> "Failed to allocate the minimal number of clusters (%lli) for the mft data attribute.",
> nr);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return PTR_ERR(rl2);
> }
> /*
> @@ -1661,6 +1677,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> rl = ntfs_runlists_merge(&mft_ni->runlist, rl2, 0, &new_rl_count);
> if (IS_ERR(rl)) {
> up_write(&mft_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> ntfs_error(vol->sb, "Failed to merge runlists for mft data attribute.");
> if (ntfs_cluster_free_from_rl(vol, rl2)) {
> ntfs_error(vol->sb,
> @@ -1686,7 +1703,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> down_write(&mft_ni->runlist.lock);
> goto undo_alloc;
> }
> - ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> + ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_NONE);
> if (unlikely(!ctx)) {
> ntfs_error(vol->sb, "Failed to get search context.");
> ret = -ENOMEM;
> @@ -1782,6 +1799,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_attr_put_search_ctx(ctx);
> unmap_mft_record(mft_ni);
> ntfs_debug("Done.");
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return 0;
> restore_undo_alloc:
> ntfs_attr_reinit_search_ctx(ctx);
> @@ -1795,6 +1813,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_attr_put_search_ctx(ctx);
> unmap_mft_record(mft_ni);
> up_write(&mft_ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> /*
> * The only thing that is now wrong is ->allocated_size of the
> * base attribute extent which chkdsk should be able to fix.
> @@ -1814,7 +1833,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es);
> NVolSetErrors(vol);
> }
> - if (mp_extended && ntfs_attr_update_mapping_pairs(mft_ni, 0)) {
> + if (mp_extended && ntfs_attr_update_mapping_pairs_nolock(mft_ni, 0)) {
> ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
> es);
> NVolSetErrors(vol);
> @@ -1843,6 +1862,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
> }
> if (!IS_ERR(mrec))
> unmap_mft_record(mft_ni);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> return ret;
> }
>
> @@ -2326,7 +2346,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
> err = PTR_ERR(m);
> goto undo_data_init;
> }
> - ctx = ntfs_attr_get_search_ctx(mft_ni, m);
> + ctx = ntfs_attr_get_search_ctx(mft_ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> ntfs_error(vol->sb, "Failed to get search context.");
> err = -ENOMEM;
> @@ -2671,14 +2691,17 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
> static s64 lcn_from_index(struct ntfs_volume *vol, struct ntfs_inode *ni,
> unsigned long index)
> {
> + struct ntfs_inode *attr_lock_ni;
> s64 vcn;
> s64 lcn;
>
> vcn = ntfs_pidx_to_cluster(vol, index);
>
> + attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> down_read(&ni->runlist.lock);
> lcn = ntfs_attr_vcn_to_lcn_nolock(ni, vcn, false);
> up_read(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>
> return lcn;
> }
> @@ -2788,14 +2811,20 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
> bio = NULL;
> }
>
> - if (vol->cluster_size < folio_size(folio)) {
> - down_write(&ni->runlist.lock);
> - rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
> - up_write(&ni->runlist.lock);
> - if (IS_ERR(rl) || lcn < 0) {
> - err = -EIO;
> - goto unm_done;
> - }
> + if (vol->cluster_size < folio_size(folio)) {
> + struct ntfs_inode *attr_lock_ni;
> + enum ntfs_attr_search_ctx_lock_mode mode;
> +
> + mode = NTFS_ATTR_CTX_LOCK_READ;
> + attr_lock_ni = ntfs_attr_list_lock(ni, mode);
> + down_write(&ni->runlist.lock);
> + rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
> + up_write(&ni->runlist.lock);
> + ntfs_attr_list_unlock(attr_lock_ni, mode);
> + if (IS_ERR(rl) || lcn < 0) {
> + err = -EIO;
> + goto unm_done;
> + }
>
> if (bio &&
> (bio_end_sector(bio) >> (vol->cluster_size_bits - 9)) !=
> diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
> index 96c450e62efca..6dd72071eb25c 100644
> --- a/fs/ntfs/namei.c
> +++ b/fs/ntfs/namei.c
> @@ -267,7 +267,7 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent,
> ctx = NULL;
> goto err_out;
> }
> - ctx = ntfs_attr_get_search_ctx(ni, m);
> + ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> err = -ENOMEM;
> goto err_out;
> @@ -856,7 +856,7 @@ static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
> * If filename in DOS or in WIN32 namespace, then remove DOS name first,
> * only then remove WIN32 name.
> */
> - actx = ntfs_attr_get_search_ctx(ni, NULL);
> + actx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
> if (!actx) {
> ntfs_error(sb, "%s, Failed to get search context", __func__);
> if (need_lock) {
> @@ -1618,7 +1618,7 @@ static struct dentry *ntfs_get_parent(struct dentry *child_dent)
> if (IS_ERR(mrec))
> return ERR_CAST(mrec);
> /* Find the first file name attribute in the mft record. */
> - ctx = ntfs_attr_get_search_ctx(ni, mrec);
> + ctx = ntfs_attr_get_search_ctx(ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
> if (unlikely(!ctx)) {
> unmap_mft_record(ni);
> return ERR_PTR(-ENOMEM);
> diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
> index 22dc7865eca79..4b558c058934a 100644
> --- a/fs/ntfs/super.c
> +++ b/fs/ntfs/super.c
> @@ -350,7 +350,7 @@ static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags)
> if (vol->vol_flags == flags)
> goto done;
>
> - ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> err = -ENOMEM;
> goto put_unm_err_out;
> @@ -434,7 +434,7 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
> }
>
> mutex_lock(&vol_ni->mrec_lock);
> - ctx = ntfs_attr_get_search_ctx(vol_ni, NULL);
> + ctx = ntfs_attr_get_search_ctx(vol_ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
> if (!ctx) {
> ret = -ENOMEM;
> goto out;
> @@ -1492,7 +1492,7 @@ static bool load_system_files(struct ntfs_volume *vol)
> goto volume_failed;
> }
>
> - ctx = ntfs_attr_get_search_ctx(NTFS_I(vol->vol_ino), m);
> + ctx = ntfs_attr_get_search_ctx(NTFS_I(vol->vol_ino), m, NTFS_ATTR_CTX_LOCK_READ);
> if (!ctx) {
> ntfs_error(sb, "Failed to get attribute search context.");
> goto get_ctx_vol_failed;
> --
> 2.43.0

--
Thanks,
Hyunchul