Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion fs/ntfs/aops.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ static int ntfs_writepages(struct address_space *mapping,
.wbc = wbc,
.ops = &ntfs_writeback_ops,
};
bool need_iput = false;
int ret;

if (NVolShutdown(ni->vol))
return -EIO;
Expand All @@ -353,7 +355,20 @@ static int ntfs_writepages(struct address_space *mapping,
return -EOPNOTSUPP;
}

return iomap_writepages(&wpc);
/*
* Prevent eviction in writeback to avoid deadlock in
* ntfs_drop_big_inode().
*/
if ((ni->type == AT_DATA || ni->type == AT_INDEX_ALLOCATION) &&
igrab(inode))
need_iput = true;

ret = iomap_writepages(&wpc);

if (need_iput)
iput(inode);

return ret;
}

static int ntfs_swap_activate(struct swap_info_struct *sis,
Expand Down
47 changes: 35 additions & 12 deletions fs/ntfs/attrib.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
err = -EIO;
goto err_out;
}
WARN_ON(!ctx->attr->non_resident);
if (unlikely(!ctx->attr->non_resident)) {
err = -EIO;
goto err_out;
}
}
a = ctx->attr;
/*
Expand Down Expand Up @@ -4191,6 +4194,16 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
return err;
}

/*
* Drop any page-cache folios that lie beyond the new size before
* freeing the backing clusters and truncating the runlist. Otherwise
* a writeback that races with the truncation can start on a stale
* folio and deadlock, or map a vcn past the new allocation and lose
* the write. The MM truncation contract requires dropping page-cache
* contents before releasing their backing resources.
*/
truncate_inode_pages(VFS_I(ni)->i_mapping, newsize);

/* The first cluster outside the new allocation. */
if (NInoCompressed(ni))
/*
Expand Down Expand Up @@ -4290,6 +4303,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
ni->initialized_size = newsize;
ctx->attr->data.non_resident.initialized_size = cpu_to_le64(newsize);
}

/* Update data size in the index. */
if (ni->type == AT_DATA && ni->name == AT_UNNAMED)
NInoSetFileNameDirty(ni);
Expand Down Expand Up @@ -5325,6 +5339,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
ret = ntfs_attr_map_whole_runlist(ni);
if (ret) {
up_write(&ni->runlist.lock);
kfree(hole_rl);
return ret;
}

Expand Down Expand Up @@ -5536,6 +5551,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
s64 old_data_size;
s64 vcn_start, vcn_end, vcn_uninit, vcn, try_alloc_cnt;
s64 lcn, alloc_cnt;
s64 rl_lcn, rl_length, rl_vcn;
int err = 0;
struct runlist_element *rl;
bool balloc;
Expand Down Expand Up @@ -5615,19 +5631,23 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
while (vcn < vcn_uninit) {
down_read(&ni->runlist.lock);
rl = ntfs_attr_find_vcn_nolock(ni, vcn, NULL);
up_read(&ni->runlist.lock);
if (IS_ERR(rl)) {
up_read(&ni->runlist.lock);
err = PTR_ERR(rl);
goto out;
}
rl_lcn = rl->lcn;
rl_length = rl->length;
rl_vcn = rl->vcn;
up_read(&ni->runlist.lock);

if (rl->lcn > 0) {
vcn += rl->length - (vcn - rl->vcn);
} else if (rl->lcn == LCN_DELALLOC || rl->lcn == LCN_HOLE) {
try_alloc_cnt = min(rl->length - (vcn - rl->vcn),
if (rl_lcn > 0) {
vcn += rl_length - (vcn - rl_vcn);
} else if (rl_lcn == LCN_DELALLOC || rl_lcn == LCN_HOLE) {
try_alloc_cnt = min(rl_length - (vcn - rl_vcn),
vcn_uninit - vcn);

if (rl->lcn == LCN_DELALLOC) {
if (rl_lcn == LCN_DELALLOC) {
vcn += try_alloc_cnt;
continue;
}
Expand All @@ -5642,11 +5662,14 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
if (err)
goto out;

err = ntfs_dio_zero_range(VFS_I(ni),
lcn << vol->cluster_size_bits,
alloc_cnt << vol->cluster_size_bits);
if (err > 0)
goto out;
if (balloc) {
err = ntfs_dio_zero_range(VFS_I(ni),
lcn << vol->cluster_size_bits,
alloc_cnt <<
vol->cluster_size_bits);
if (err)
goto out;
Comment on lines +5670 to +5671
}

if (signal_pending(current))
goto out;
Expand Down
9 changes: 9 additions & 0 deletions fs/ntfs/attrlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
struct ntfs_inode *attr_ni;
int err;

/*
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
* inodes. Do not look up the attribute-list inode after SB_ACTIVE has
* been cleared; it may already be I_FREEING, and waiting on it can
* self-deadlock.
*/
if (!(VFS_I(base_ni)->i_sb->s_flags & SB_ACTIVE))
return -EIO;

attr_vi = ntfs_attr_iget(VFS_I(base_ni), AT_ATTRIBUTE_LIST, AT_UNNAMED, 0);
if (IS_ERR(attr_vi)) {
err = PTR_ERR(attr_vi);
Expand Down
15 changes: 11 additions & 4 deletions fs/ntfs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
__le16 I30[5] = { cpu_to_le16('$'), cpu_to_le16('I'),
cpu_to_le16('3'), cpu_to_le16('0'), 0 };

static inline u64 ntfs_check_mref(u64 mref)
{
if (IS_ERR_MREF(mref))
return ERR_MREF(-EIO);
return mref;
}

/*
* ntfs_lookup_inode_by_name - find an inode in a directory given its name
* @dir_ni: ntfs inode of the directory in which to search for the name
Expand Down Expand Up @@ -178,7 +185,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
mref = le64_to_cpu(ie->data.dir.indexed_file);
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(dir_ni);
return mref;
return ntfs_check_mref(mref);
}
/*
* For a case insensitive mount, we also perform a case
Expand Down Expand Up @@ -273,7 +280,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
if (name) {
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(dir_ni);
return name->mref;
return ntfs_check_mref(name->mref);
}
ntfs_debug("Entry not found.");
err = -ENOENT;
Expand Down Expand Up @@ -413,7 +420,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
mref = le64_to_cpu(ie->data.dir.indexed_file);
kfree(kaddr);
iput(ia_vi);
return mref;
return ntfs_check_mref(mref);
}
/*
* For a case insensitive mount, we also perform a case
Expand Down Expand Up @@ -538,7 +545,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
if (name) {
kfree(kaddr);
iput(ia_vi);
return name->mref;
return ntfs_check_mref(name->mref);
}
ntfs_debug("Entry not found.");
err = -ENOENT;
Expand Down
5 changes: 4 additions & 1 deletion fs/ntfs/index.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib)
ret = ntfs_inode_attr_pwrite(VFS_I(icx->ia_ni),
ntfs_ib_vcn_to_pos(icx, vcn), icx->block_size,
(u8 *)ib, icx->sync_write);

/* Perform data restoration before returning */
post_write_mst_fixup((struct ntfs_record *)ib);

if (ret != icx->block_size) {
ntfs_debug("Failed to write index block %lld, inode %llu",
vcn, (unsigned long long)icx->idx_ni->mft_no);
Expand Down Expand Up @@ -147,7 +151,6 @@ int ntfs_icx_ib_sync_write(struct ntfs_index_context *icx)
icx->ib = NULL;
icx->ib_dirty = false;
} else {
post_write_mst_fixup((struct ntfs_record *)icx->ib);
icx->sync_write = false;
}

Expand Down
9 changes: 9 additions & 0 deletions fs/ntfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,15 @@ static int ntfs_read_locked_inode(struct inode *vi)
!S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && !S_ISLNK(vi->i_mode))
vi->i_flags |= S_IMMUTABLE;

/*
* System files such as $Bitmap and $MFT are maintained by the driver
* itself, and writing them from userspace corrupts the volume.
* Always make them immutable regardless of the sys_immutable option.
* Directories are skipped so the root and $Extend stay usable.
*/
if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode))
vi->i_flags |= S_IMMUTABLE;

/*
* The number of 512-byte blocks used on disk (for stat). This is in so
* far inaccurate as it doesn't account for any named streams or other
Expand Down
8 changes: 6 additions & 2 deletions fs/ntfs/mft.c
Original file line number Diff line number Diff line change
Expand Up @@ -2641,7 +2641,6 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
s64 vcn = ntfs_pidx_to_cluster(vol, folio->index);
s64 end_vcn = ntfs_bytes_to_cluster(vol, ni->allocated_size);
unsigned int folio_sz;
struct runlist_element *rl = NULL;
loff_t i_size = i_size_read(vi);

ntfs_debug("Entering for inode 0x%llx, attribute type 0x%x, folio index 0x%lx.",
Expand Down Expand Up @@ -2686,6 +2685,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
&tni, &ref_inos[nr_ref_inos])) {
unsigned int mft_record_off = 0;
s64 vcn_off = vcn;
s64 rl_len = 0;

/*
* The record should be written. If a locked ntfs
Expand All @@ -2705,8 +2705,12 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
}

if (vol->cluster_size < folio_size(folio)) {
struct runlist_element *rl;

down_write(&ni->runlist.lock);
rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
if (!IS_ERR(rl))
rl_len = rl->length - (vcn_off - rl->vcn);
up_write(&ni->runlist.lock);
if (IS_ERR(rl) || lcn < 0) {
err = -EIO;
Expand Down Expand Up @@ -2737,7 +2741,7 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w

if (vol->cluster_size == NTFS_BLOCK_SIZE &&
(mft_record_off ||
(rl && rl->length - (vcn_off - rl->vcn) == 1) ||
rl_len == 1 ||
mft_ofs + NTFS_BLOCK_SIZE >= PAGE_SIZE))
folio_sz = NTFS_BLOCK_SIZE;
else
Expand Down
68 changes: 36 additions & 32 deletions fs/ntfs/namei.c
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,8 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
mutex_unlock(&dir_ni->mrec_lock);
mutex_unlock(&ni->mrec_lock);

ni->flags = fn->file_attributes;
ni->flags = fn->file_attributes |
(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN);
/* Set the sequence number. */
vi->i_generation = ni->seq_no;
set_nlink(vi, 1);
Expand Down Expand Up @@ -1086,6 +1087,9 @@ static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);

/* vfs_mkdir() may pass a mode with the S_IFMT bits cleared, so make
* sure __ntfs_create() sees this as a directory.
*/
ni = __ntfs_create(idmap, dir, uname, uname_len, S_IFDIR | mode, 0, NULL, 0);
kmem_cache_free(ntfs_name_cache, uname);
if (IS_ERR(ni)) {
Expand Down Expand Up @@ -1270,6 +1274,7 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct ntfs_volume *vol = NTFS_SB(sb);
struct ntfs_inode *old_ni, *new_ni = NULL;
struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir);
bool new_dir_first = false;

if (NVolShutdown(old_dir_ni->vol))
return -EIO;
Expand Down Expand Up @@ -1305,36 +1310,39 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
old_inode = old_dentry->d_inode;
new_inode = new_dentry->d_inode;
old_ni = NTFS_I(old_inode);
if (new_inode)
new_ni = NTFS_I(new_inode);
if (old_dir != new_dir)
new_dir_first = is_subdir(new_dentry->d_parent,
old_dentry->d_parent);

if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);

mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
if (new_ni)
mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);

if (old_dir == new_dir) {
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
} else if (new_dir_first) {
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
} else {
mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
}

if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) {
if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni) ||
(new_ni && NInoBeingDeleted(new_ni)) ||
(old_dir != new_dir && NInoBeingDeleted(new_dir_ni))) {
err = -ENOENT;
goto unlock_old;
goto err_out;
}

is_dir = S_ISDIR(old_inode->i_mode);

if (new_inode) {
new_ni = NTFS_I(new_inode);
mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
if (old_dir != new_dir) {
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
if (NInoBeingDeleted(new_dir_ni)) {
err = -ENOENT;
goto err_out;
}
}

if (NInoBeingDeleted(new_ni)) {
err = -ENOENT;
goto err_out;
}

if (is_dir) {
struct mft_record *ni_mrec;

Expand All @@ -1352,14 +1360,6 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false);
if (err)
goto err_out;
} else {
if (old_dir != new_dir) {
mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
if (NInoBeingDeleted(new_dir_ni)) {
err = -ENOENT;
goto err_out;
}
}
}

err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len);
Expand Down Expand Up @@ -1390,13 +1390,17 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
inode_inc_iversion(new_dir);

err_out:
if (old_dir != new_dir)
if (old_dir == new_dir) {
mutex_unlock(&old_dir_ni->mrec_lock);
} else if (new_dir_first) {
mutex_unlock(&old_dir_ni->mrec_lock);
mutex_unlock(&new_dir_ni->mrec_lock);
if (new_inode)
} else {
mutex_unlock(&new_dir_ni->mrec_lock);
mutex_unlock(&old_dir_ni->mrec_lock);
}
if (new_ni)
mutex_unlock(&new_ni->mrec_lock);

unlock_old:
mutex_unlock(&old_dir_ni->mrec_lock);
mutex_unlock(&old_ni->mrec_lock);
if (uname_new)
kmem_cache_free(ntfs_name_cache, uname_new);
Expand Down
Loading
Loading