summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/ntfs/attrib.c259
-rw-r--r--fs/ntfs/attrib.h9
-rw-r--r--fs/ntfs/attrlist.c224
-rw-r--r--fs/ntfs/attrlist.h2
-rw-r--r--fs/ntfs/compress.c2
-rw-r--r--fs/ntfs/file.c3
-rw-r--r--fs/ntfs/inode.c65
-rw-r--r--fs/ntfs/mft.c457
-rw-r--r--fs/ntfs/mft.h2
-rw-r--r--fs/ntfs/namei.c2
-rw-r--r--fs/ntfs/super.c12
-rw-r--r--fs/ntfs/volume.h12
-rw-r--r--fs/smb/client/cifssmb.c2
-rw-r--r--fs/smb/client/connect.c9
-rw-r--r--fs/smb/client/file.c4
-rw-r--r--fs/smb/client/misc.c12
-rw-r--r--fs/smb/client/reparse.c58
-rw-r--r--fs/smb/client/reparse.h7
-rw-r--r--fs/smb/client/sess.c106
-rw-r--r--fs/smb/client/smb2inode.c11
-rw-r--r--fs/smb/client/smb2misc.c69
-rw-r--r--fs/smb/client/smb2ops.c51
-rw-r--r--fs/smb/client/smb2pdu.c13
-rw-r--r--fs/smb/client/trace.h1
-rw-r--r--fs/smb/server/connection.c72
-rw-r--r--fs/smb/server/connection.h3
-rw-r--r--fs/smb/server/mgmt/user_session.c124
-rw-r--r--fs/smb/server/mgmt/user_session.h8
-rw-r--r--fs/smb/server/proc.c2
-rw-r--r--fs/smb/server/server.c2
-rw-r--r--fs/smb/server/smb2pdu.c71
-rw-r--r--fs/smb/server/smb2pdu.h3
-rw-r--r--fs/smb/server/stats.h1
33 files changed, 1308 insertions, 370 deletions
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 848a0d338b89..c949ff765075 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -2917,7 +2917,7 @@ retry:
attr_ni = NULL;
/* Allocate new extent. */
- err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL);
+ err = ntfs_mft_record_alloc(ni->vol, 0, &attr_ni, ni, NULL, -1);
if (err) {
ntfs_error(sb, "Failed to allocate extent record");
goto err_out;
@@ -3550,7 +3550,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
* new extent and move attribute to it.
*/
ni = NULL;
- err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL);
+ err = ntfs_mft_record_alloc(base_ni->vol, 0, &ni, base_ni, NULL, -1);
if (err) {
ntfs_error(sb, "Couldn't allocate MFT record, err : %d", err);
return err;
@@ -3574,7 +3574,8 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
* update allocated and compressed size.
*/
static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
- struct mft_record *m, struct ntfs_attr_search_ctx *ctx)
+ struct mft_record *m, struct ntfs_attr_search_ctx *ctx,
+ struct ntfs_inode *locked_ni, bool defer_attrlist)
{
int sparse, err = 0;
struct ntfs_inode *base_ni;
@@ -3610,6 +3611,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) &&
!(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) {
+ if (defer_attrlist)
+ return -ENOSPC;
if (!NInoAttrList(base_ni)) {
err = ntfs_inode_add_attrlist(base_ni);
if (err)
@@ -3623,7 +3626,7 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
goto out;
}
- err = ntfs_attrlist_update(base_ni);
+ err = ntfs_attrlist_update_locked(base_ni, locked_ni);
if (err)
goto out;
err = -EAGAIN;
@@ -3703,6 +3706,8 @@ out:
* 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 @@ out:
* 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 @@ retry:
continue;
}
- err = ntfs_attr_update_meta(a, ni, m, ctx);
+ err = ntfs_attr_update_meta(a, ni, m, ctx, locked_ni,
+ defer_attrlist);
if (err < 0) {
if (err == -EAGAIN) {
ntfs_attr_put_search_ctx(ctx);
@@ -3844,18 +3853,28 @@ retry:
*/
if (ni->type == AT_ATTRIBUTE_LIST) {
ntfs_attr_put_search_ctx(ctx);
- if (ntfs_inode_free_space(base_ni, mp_size -
- cur_max_mp_size)) {
- ntfs_debug("Attribute list is too big. Defragment the volume\n");
- return -ENOSPC;
+ ctx = NULL;
+ if (locked_ni == ni || defer_attrlist) {
+ err = -ENOSPC;
+ goto put_err_out;
}
- if (ntfs_attrlist_update(base_ni))
- return -EIO;
+ err = ntfs_inode_free_space(base_ni, mp_size -
+ cur_max_mp_size);
+ if (err)
+ return err;
+ err = ntfs_attrlist_update_locked(
+ base_ni, locked_ni);
+ if (err)
+ return err;
goto retry;
}
/* Add attribute list if it isn't present, and retry. */
if (!NInoAttrList(base_ni)) {
+ if (defer_attrlist) {
+ err = -ENOSPC;
+ goto put_err_out;
+ }
ntfs_attr_put_search_ctx(ctx);
if (ntfs_inode_add_attrlist(base_ni)) {
ntfs_error(sb, "Can not add attrlist");
@@ -3883,13 +3902,21 @@ retry:
}
}
+ if (defer_attrlist &&
+ (ctx->ntfs_ino->nr_extents == -1 ||
+ NInoAttrList(ctx->ntfs_ino)) &&
+ ctx->attr->type != AT_ATTRIBUTE_LIST) {
+ err = -ENOSPC;
+ goto put_err_out;
+ }
+
/* Update lowest vcn. */
a->data.non_resident.lowest_vcn = cpu_to_le64(stop_vcn);
mark_mft_record_dirty(ctx->ntfs_ino);
if ((ctx->ntfs_ino->nr_extents == -1 || NInoAttrList(ctx->ntfs_ino)) &&
ctx->attr->type != AT_ATTRIBUTE_LIST) {
ctx->al_entry->lowest_vcn = cpu_to_le64(stop_vcn);
- err = ntfs_attrlist_update(base_ni);
+ err = ntfs_attrlist_update_locked(base_ni, locked_ni);
if (err)
goto put_err_out;
}
@@ -3976,7 +4003,10 @@ retry:
unsigned int de_cnt = 0;
/* Allocate new mft record. */
- err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL);
+ err = ntfs_mft_record_alloc(ni->vol, 0, &ext_ni, base_ni, NULL,
+ base_ni->mft_no == FILE_MFT &&
+ ni->type == AT_DATA &&
+ ni->name == AT_UNNAMED ? stop_vcn : -1);
if (err) {
ntfs_error(sb, "Failed to allocate extent record");
goto put_err_out;
@@ -4061,6 +4091,19 @@ put_err_out:
return err;
}
+int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni)
+{
+ return __ntfs_attr_update_mapping_pairs(ni, from_vcn, locked_ni,
+ false);
+}
+
+int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
+{
+ return ntfs_attr_update_mapping_pairs_locked(ni, from_vcn, NULL);
+}
+
/*
* ntfs_attr_make_resident - convert a non-resident to a resident attribute
* @ni: open ntfs attribute to make resident
@@ -4194,7 +4237,9 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
*
* Reduce the size of a non-resident, open ntfs attribute @na to @newsize bytes.
*/
-static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsize)
+static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni,
+ const s64 newsize,
+ struct ntfs_inode *locked_ni)
{
struct ntfs_volume *vol;
struct ntfs_attr_search_ctx *ctx;
@@ -4202,6 +4247,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
s64 nr_freed_clusters;
int err;
struct ntfs_inode *base_ni;
+ bool runlist_locked = locked_ni == ni;
ntfs_debug("Inode 0x%llx attr 0x%x new size %lld\n",
(unsigned long long)ni->mft_no, ni->type, (long long)newsize);
@@ -4247,18 +4293,24 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
* clusters if there is a change.
*/
if (ntfs_bytes_to_cluster(vol, ni->allocated_size) != first_free_vcn) {
- struct ntfs_attr_search_ctx *ctx;
+ /*
+ * ntfs_cluster_free() and ntfs_rl_truncate_nolock()
+ * both require this lock.
+ */
+ if (!runlist_locked)
+ down_write(&ni->runlist.lock);
err = ntfs_attr_map_whole_runlist(ni);
if (err) {
ntfs_debug("Eeek! ntfs_attr_map_whole_runlist failed.\n");
- return err;
+ goto unlock_runlist;
}
ctx = ntfs_attr_get_search_ctx(ni, NULL);
if (!ctx) {
ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
- return -ENOMEM;
+ err = -ENOMEM;
+ goto unlock_runlist;
}
/* Deallocate all clusters starting with the first free one. */
@@ -4266,7 +4318,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
if (nr_freed_clusters < 0) {
ntfs_debug("Eeek! Freeing of clusters failed. Aborting...\n");
ntfs_attr_put_search_ctx(ctx);
- return (int)nr_freed_clusters;
+ err = (int)nr_freed_clusters;
+ goto unlock_runlist;
}
ntfs_attr_put_search_ctx(ctx);
@@ -4279,7 +4332,8 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
kvfree(ni->runlist.rl);
ni->runlist.rl = NULL;
ntfs_error(vol->sb, "Eeek! Run list truncation failed.\n");
- return -EIO;
+ err = -EIO;
+ goto unlock_runlist;
}
/* Prepare to mapping pairs update. */
@@ -4295,11 +4349,13 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
VFS_I(base_ni)->i_blocks = ni->allocated_size >> 9;
/* Write mapping pairs for new runlist. */
- err = ntfs_attr_update_mapping_pairs(ni, 0 /*first_free_vcn*/);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
ntfs_debug("Eeek! Mapping pairs update failed. Leaving inconstant metadata. Run chkdsk.\n");
- return err;
+ goto unlock_runlist;
}
+ if (!runlist_locked)
+ up_write(&ni->runlist.lock);
}
/* Get the first attribute record. */
@@ -4341,7 +4397,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
/* If the attribute now has zero size, make it resident. */
if (!newsize && !NInoEncrypted(ni) && !NInoCompressed(ni)) {
+ if (!runlist_locked)
+ down_write(&ni->runlist.lock);
err = ntfs_attr_make_resident(ni, ctx);
+ if (!runlist_locked)
+ up_write(&ni->runlist.lock);
if (err) {
/* If couldn't make resident, just continue. */
if (err != -EPERM)
@@ -4358,6 +4418,11 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
put_err_out:
ntfs_attr_put_search_ctx(ctx);
return err;
+
+unlock_runlist:
+ if (!runlist_locked)
+ up_write(&ni->runlist.lock);
+ return err;
}
/*
@@ -4366,13 +4431,14 @@ put_err_out:
* @prealloc_size: preallocation size (in bytes) to which to expand the attribute
* @newsize: new size (in bytes) to which to expand the attribute
* @holes: how to create a hole if expanding
- * @need_lock: whether mrec lock is needed or not
+ * @locked_ni: inode whose runlist lock is already held
*
* Expand the size of a non-resident, open ntfs attribute @na to @newsize bytes,
* by allocating new clusters.
*/
static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsize,
- const s64 prealloc_size, unsigned int holes, bool need_lock)
+ const s64 prealloc_size, unsigned int holes,
+ struct ntfs_inode *locked_ni)
{
s64 lcn_seek_from;
s64 first_free_vcn;
@@ -4519,13 +4585,39 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
ntfs_bytes_to_cluster(vol, ni->allocated_size),
first_free_vcn -
ntfs_bytes_to_cluster(vol, ni->allocated_size),
- lcn_seek_from, DATA_ZONE, false, false, false);
+ lcn_seek_from, DATA_ZONE, false,
+ ni->type == AT_ATTRIBUTE_LIST, false);
if (IS_ERR(rl)) {
ntfs_debug("Cluster allocation failed (%lld)",
(long long)first_free_vcn -
ntfs_bytes_to_cluster(vol, ni->allocated_size));
return PTR_ERR(rl);
}
+ /*
+ * A contiguous ATTRIBUTE_LIST allocation keeps its mapping
+ * pairs small enough to fit in the base MFT record. The
+ * allocator can return a short run when contiguity was
+ * requested, so discard it and retry normally if necessary.
+ */
+ if (ni->type == AT_ATTRIBUTE_LIST &&
+ (rl->vcn != ntfs_bytes_to_cluster(vol,
+ ni->allocated_size) ||
+ rl->length != first_free_vcn -
+ ntfs_bytes_to_cluster(vol, ni->allocated_size) ||
+ rl[1].length)) {
+ ntfs_cluster_free_from_rl(vol, rl);
+ kvfree(rl);
+ rl = ntfs_cluster_alloc(vol,
+ ntfs_bytes_to_cluster(vol,
+ ni->allocated_size),
+ first_free_vcn -
+ ntfs_bytes_to_cluster(vol,
+ ni->allocated_size),
+ lcn_seek_from, DATA_ZONE, false,
+ false, false);
+ if (IS_ERR(rl))
+ return PTR_ERR(rl);
+ }
}
if (!NInoCompressed(ni)) {
@@ -4544,7 +4636,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
/* Prepare to mapping pairs update. */
ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(
+ ni, 0, locked_ni);
if (err) {
ntfs_debug("Mapping pairs update failed");
goto rollback;
@@ -4588,11 +4681,11 @@ rollback:
ntfs_debug("Leaking clusters");
/* Now, truncate the runlist itself. */
- if (need_lock)
+ if (ni != locked_ni)
down_write(&ni->runlist.lock);
err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist,
ntfs_bytes_to_cluster(vol, org_alloc_size));
- if (need_lock)
+ if (ni != locked_ni)
up_write(&ni->runlist.lock);
if (err2) {
/*
@@ -4606,11 +4699,11 @@ rollback:
/* Prepare to mapping pairs update. */
ni->allocated_size = org_alloc_size;
/* Restore mapping pairs. */
- if (need_lock)
+ if (ni != locked_ni)
down_read(&ni->runlist.lock);
- if (ntfs_attr_update_mapping_pairs(ni, 0))
+ if (__ntfs_attr_update_mapping_pairs(ni, 0, locked_ni, true))
ntfs_error(sb, "Failed to restore old mapping pairs");
- if (need_lock)
+ if (ni != locked_ni)
up_read(&ni->runlist.lock);
if (NInoSparse(ni) || NInoCompressed(ni)) {
@@ -4715,7 +4808,8 @@ attr_resize_again:
mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
/* Resize non-resident attribute */
- return ntfs_non_resident_attr_expand(attr_ni, newsize, prealloc_size, holes, true);
+ return ntfs_non_resident_attr_expand(
+ attr_ni, newsize, prealloc_size, holes, NULL);
} else if (err != -ENOSPC && err != -EPERM) {
ntfs_error(sb, "Failed to make attribute non-resident");
goto put_err_out;
@@ -4836,7 +4930,7 @@ attr_resize_again:
}
/* Allocate new mft record. */
- err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL);
+ err = ntfs_mft_record_alloc(base_ni->vol, 0, &ext_ni, base_ni, NULL, -1);
if (err) {
ntfs_error(sb, "Couldn't allocate MFT record");
goto put_err_out;
@@ -4890,13 +4984,14 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
if (NInoNonResident(ni)) {
if (newsize > i_size) {
down_write(&ni->runlist.lock);
- err = ntfs_non_resident_attr_expand(ni, newsize, 0,
- NVolDisableSparse(ni->vol) ?
- HOLES_NO : HOLES_OK,
- false);
+ err = ntfs_non_resident_attr_expand(
+ ni, newsize, 0,
+ NVolDisableSparse(ni->vol) ?
+ HOLES_NO : HOLES_OK, ni);
up_write(&ni->runlist.lock);
} else
- err = ntfs_non_resident_attr_shrink(ni, newsize);
+ err = ntfs_non_resident_attr_shrink(
+ ni, newsize, NULL);
} else
err = ntfs_resident_attr_resize(ni, newsize, 0,
NVolDisableSparse(ni->vol) ?
@@ -4905,7 +5000,9 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
return err;
}
-int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size)
+int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize,
+ const s64 prealloc_size,
+ struct ntfs_inode *locked_ni)
{
int err = 0;
@@ -4918,7 +5015,8 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
ntfs_debug("Entering for inode 0x%llx, attr 0x%x, size %lld\n",
(unsigned long long)ni->mft_no, ni->type, newsize);
- if (ni->data_size == newsize) {
+ if (ni->data_size == newsize &&
+ (!prealloc_size || prealloc_size <= ni->allocated_size)) {
ntfs_debug("Size is already ok\n");
return 0;
}
@@ -4933,10 +5031,11 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
}
if (NInoNonResident(ni)) {
- if (newsize > ni->data_size)
- err = ntfs_non_resident_attr_expand(ni, newsize, prealloc_size,
- NVolDisableSparse(ni->vol) ?
- HOLES_NO : HOLES_OK, true);
+ if (newsize > ni->data_size || prealloc_size > ni->allocated_size)
+ err = ntfs_non_resident_attr_expand(
+ ni, newsize, prealloc_size,
+ NVolDisableSparse(ni->vol) ?
+ HOLES_NO : HOLES_OK, locked_ni);
} else
err = ntfs_resident_attr_resize(ni, newsize, prealloc_size,
NVolDisableSparse(ni->vol) ?
@@ -4947,6 +5046,12 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
return err;
}
+int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize,
+ const s64 prealloc_size)
+{
+ return ntfs_attr_expand_locked(ni, newsize, prealloc_size, NULL);
+}
+
/*
* ntfs_attr_truncate_i - resize an ntfs attribute
* @ni: open ntfs inode to resize
@@ -4959,7 +5064,9 @@ int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 preallo
* newly allocated space is marked as not initialised and no real allocation
* on disk is performed.
*/
-int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes)
+int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize,
+ unsigned int holes,
+ struct ntfs_inode *locked_ni)
{
int err;
@@ -4993,15 +5100,23 @@ int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int
if (NInoNonResident(ni)) {
if (newsize > ni->data_size)
- err = ntfs_non_resident_attr_expand(ni, newsize, 0, holes, true);
+ err = ntfs_non_resident_attr_expand(
+ ni, newsize, 0, holes, locked_ni);
else
- err = ntfs_non_resident_attr_shrink(ni, newsize);
+ err = ntfs_non_resident_attr_shrink(
+ ni, newsize, locked_ni);
} else
err = ntfs_resident_attr_resize(ni, newsize, 0, holes);
ntfs_debug("Return status %d\n", err);
return err;
}
+int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize,
+ unsigned int holes)
+{
+ return ntfs_attr_truncate_i_locked(ni, newsize, holes, NULL);
+}
+
/*
* Resize an attribute, creating a hole if relevant
*/
@@ -5019,10 +5134,11 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
struct ntfs_volume *vol = ni->vol;
struct ntfs_attr_search_ctx *ctx;
struct runlist_element *rl, *rlc;
+ struct runlist_element *old_rl = NULL;
s64 vcn = vcn_start, lcn, clu_count;
s64 lcn_seek_from = -1;
int err = 0;
- size_t new_rl_count;
+ size_t new_rl_count, old_rl_count;
err = ntfs_attr_map_whole_runlist(ni);
if (err)
@@ -5115,6 +5231,19 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
WARN_ON(rlc->vcn != vcn);
lcn = rlc->lcn;
clu_count = rlc->length;
+ old_rl_count = ni->runlist.count;
+ old_rl = kmemdup(ni->runlist.rl,
+ old_rl_count * sizeof(*old_rl), GFP_NOFS);
+ if (!old_rl) {
+ err = -ENOMEM;
+ if (ntfs_cluster_free_from_rl(vol, rlc)) {
+ ntfs_error(vol->sb,
+ "Failed to free cluster allocation after runlist backup failure.");
+ NVolSetErrors(vol);
+ }
+ kvfree(rlc);
+ goto out;
+ }
rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count);
if (IS_ERR(rl)) {
@@ -5138,15 +5267,32 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
if (update_mp) {
ntfs_attr_reinit_search_ctx(ctx);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err) {
int err2;
err2 = ntfs_cluster_free(ni, vcn, clu_count, ctx);
- if (err2 < 0)
+ if (err2 < 0 || err2 != clu_count) {
ntfs_error(vol->sb,
- "Failed to free cluster allocation. Leaving inconstant metadata.\n");
- goto out;
+ "Failed to free cluster allocation. Leaving inconsistent metadata.\n");
+ NVolSetErrors(vol);
+ goto out;
+ }
+
+ /*
+ * Restore the runlist before repairing the on-disk
+ * mapping pairs.
+ */
+ kvfree(ni->runlist.rl);
+ ni->runlist.rl = old_rl;
+ ni->runlist.count = old_rl_count;
+ old_rl = NULL;
+ if (ntfs_attr_update_mapping_pairs_locked(
+ ni, 0, ni)) {
+ ntfs_error(vol->sb,
+ "Failed to restore mapping pairs after allocation rollback.\n");
+ NVolSetErrors(vol);
+ }
}
} else {
VFS_I(ni)->i_blocks += clu_count << (vol->cluster_size_bits - 9);
@@ -5158,6 +5304,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
*lcn_count = clu_count;
*balloc = true;
out:
+ kvfree(old_rl);
ntfs_attr_put_search_ctx(ctx);
return err;
}
@@ -5401,7 +5548,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
ni->data_size += ntfs_cluster_to_bytes(vol, len);
if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size)
ni->initialized_size += ntfs_cluster_to_bytes(vol, len);
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (ret)
return ret;
@@ -5486,7 +5633,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
}
if (ni->allocated_size > 0) {
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (ret) {
up_write(&ni->runlist.lock);
goto out_rl;
@@ -5564,7 +5711,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
ni->runlist.rl = rl;
ni->runlist.count = new_rl_count;
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
up_write(&ni->runlist.lock);
if (ret) {
kvfree(punch_rl);
@@ -5740,7 +5887,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
if (NInoRunlistDirty(ni)) {
mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
down_write(&ni->runlist.lock);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (err)
ntfs_error(ni->vol->sb, "Updating mapping pairs failed");
else
diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h
index e2224fbfaabe..6b4fa9f57640 100644
--- a/fs/ntfs/attrib.h
+++ b/fs/ntfs/attrib.h
@@ -112,7 +112,13 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
const s64 i_size);
int ntfs_attr_expand(struct ntfs_inode *ni, const s64 newsize, const s64 prealloc_size);
+int ntfs_attr_expand_locked(struct ntfs_inode *ni, const s64 newsize,
+ const s64 prealloc_size,
+ struct ntfs_inode *locked_ni);
int ntfs_attr_truncate_i(struct ntfs_inode *ni, const s64 newsize, unsigned int holes);
+int ntfs_attr_truncate_i_locked(struct ntfs_inode *ni, const s64 newsize,
+ unsigned int holes,
+ struct ntfs_inode *locked_ni);
int ntfs_attr_truncate(struct ntfs_inode *ni, const s64 newsize);
int ntfs_attr_rm(struct ntfs_inode *ni);
int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
@@ -133,6 +139,9 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
__le16 *name, u8 name_len, u8 *val, u32 size,
__le16 flags);
int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn);
+int ntfs_attr_update_mapping_pairs_locked(struct ntfs_inode *ni,
+ s64 from_vcn,
+ struct ntfs_inode *locked_ni);
struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
/*
diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
index be3086d34338..bb191953dcb1 100644
--- a/fs/ntfs/attrlist.c
+++ b/fs/ntfs/attrlist.c
@@ -12,6 +12,9 @@
#include "mft.h"
#include "attrib.h"
#include "attrlist.h"
+#include "lcnalloc.h"
+
+#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024)
/*
* ntfs_attrlist_need - check whether inode need attribute list
@@ -51,11 +54,155 @@ int ntfs_attrlist_need(struct ntfs_inode *ni)
return 0;
}
-int ntfs_attrlist_update(struct ntfs_inode *base_ni)
+/*
+ * Repack the $MFT/$ATTRIBUTE_LIST data into one run.
+ *
+ * The mapping pairs for an $ATTRIBUTE_LIST must remain in the base MFT
+ * record. Once that record has no room left, extending a fragmented list
+ * can require one more mapping-pairs byte than the record can hold. There
+ * is no attribute that can legally be moved out in that state: $STANDARD_
+ * INFORMATION, $ATTRIBUTE_LIST, and the first $MFT/$DATA extent all have to
+ * stay in the base record. Move the list data to one contiguous run. The
+ * caller supplies the minimum allocation size so a recovery can use the
+ * smallest useful run while normal updates can still request the maximum
+ * legal list size as a reserve.
+ */
+static int ntfs_attrlist_repack(struct inode *attr_vi,
+ struct ntfs_inode *attr_ni, s64 min_alloc_size,
+ struct ntfs_inode *locked_ni)
+{
+ struct ntfs_volume *vol = attr_ni->vol;
+ struct runlist_element *old_rl, *new_rl;
+ u8 *data = NULL;
+ s64 data_size, alloc_size, nr_clusters, written;
+ s64 old_alloc_size;
+ size_t old_rl_count, new_rl_count;
+ unsigned long flags;
+ int err, restore_err;
+ if (attr_ni->mft_no != FILE_MFT || !NInoNonResident(attr_ni) ||
+ min_alloc_size < 0)
+ return -EINVAL;
+ /* The buffered I/O below can reacquire the attribute runlist lock. */
+ if (attr_ni == locked_ni)
+ return -ENOSPC;
+
+ err = ntfs_attr_map_whole_runlist(attr_ni);
+ if (err)
+ return err;
+
+ data_size = attr_ni->data_size;
+ if (data_size < 0)
+ return -EIO;
+
+ if (data_size) {
+ data = kvmalloc(data_size, GFP_NOFS);
+ if (!data)
+ return -ENOMEM;
+
+ written = ntfs_inode_attr_pread(attr_vi, 0, data_size, data);
+ if (written != data_size) {
+ err = written < 0 ? (int)written : -EIO;
+ goto out_free_data;
+ }
+ }
+
+ old_alloc_size = attr_ni->allocated_size;
+ alloc_size = max_t(s64, old_alloc_size, min_alloc_size);
+ nr_clusters = ntfs_bytes_to_cluster(vol,
+ alloc_size + vol->cluster_size - 1);
+ if (nr_clusters <= 0) {
+ err = -EFBIG;
+ goto out_free_data;
+ }
+
+ /* A single run keeps the mapping pairs at the minimum size. */
+ new_rl = ntfs_cluster_alloc(vol, 0, nr_clusters, -1, DATA_ZONE,
+ true, true, false);
+ if (IS_ERR(new_rl)) {
+ err = PTR_ERR(new_rl);
+ goto out_free_data;
+ }
+
+ new_rl_count = 0;
+ if (new_rl->vcn == 0 && new_rl->length == nr_clusters &&
+ !new_rl[1].length)
+ new_rl_count = 2;
+
+ if (new_rl_count != 2) {
+ ntfs_cluster_free_from_rl(vol, new_rl);
+ kvfree(new_rl);
+ err = -ENOSPC;
+ goto out_free_data;
+ }
+ old_rl = attr_ni->runlist.rl;
+ old_rl_count = attr_ni->runlist.count;
+ down_write(&attr_ni->runlist.lock);
+ attr_ni->runlist.rl = new_rl;
+ attr_ni->runlist.count = new_rl_count;
+ up_write(&attr_ni->runlist.lock);
+
+ write_lock_irqsave(&attr_ni->size_lock, flags);
+ attr_ni->allocated_size = ntfs_cluster_to_bytes(vol, nr_clusters);
+ write_unlock_irqrestore(&attr_ni->size_lock, flags);
+
+ /* Populate the replacement extent before publishing its mapping pairs. */
+ if (data_size) {
+ written = ntfs_inode_attr_pwrite(attr_vi, 0, data_size, data, true);
+ if (written != data_size) {
+ err = written < 0 ? (int)written : -EIO;
+ goto restore_old_runlist;
+ }
+ }
+
+ err = ntfs_attr_update_mapping_pairs_locked(attr_ni, 0, locked_ni);
+ if (err)
+ goto restore_old_runlist;
+
+ /* The new mapping is now authoritative; release the old data runs. */
+ if (ntfs_cluster_free_from_rl(vol, old_rl)) {
+ ntfs_error(vol->sb,
+ "Failed to free old ATTRIBUTE_LIST extent: inode %#llx",
+ (long long)attr_ni->mft_no);
+ NVolSetErrors(vol);
+ }
+ kvfree(old_rl);
+ kvfree(data);
+ return 0;
+
+restore_old_runlist:
+ down_write(&attr_ni->runlist.lock);
+ attr_ni->runlist.rl = old_rl;
+ attr_ni->runlist.count = old_rl_count;
+ up_write(&attr_ni->runlist.lock);
+
+ write_lock_irqsave(&attr_ni->size_lock, flags);
+ attr_ni->allocated_size = old_alloc_size;
+ write_unlock_irqrestore(&attr_ni->size_lock, flags);
+
+ restore_err = ntfs_attr_update_mapping_pairs_locked(
+ attr_ni, 0, locked_ni);
+ if (restore_err) {
+ ntfs_error(vol->sb, "Failed to restore ATTRIBUTE_LIST mapping pairs (%d)",
+ restore_err);
+ NVolSetErrors(vol);
+ }
+
+ ntfs_cluster_free_from_rl(vol, new_rl);
+ kvfree(new_rl);
+ err = err ? err : restore_err;
+
+out_free_data:
+ kvfree(data);
+ return err;
+}
+
+int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
+ struct ntfs_inode *locked_ni)
{
struct inode *attr_vi;
struct ntfs_inode *attr_ni;
- int err;
+ s64 written;
+ int err, retry_err;
/*
* generic_shutdown_super() clears SB_ACTIVE before evicting cached
@@ -72,23 +219,66 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
return err;
}
attr_ni = NTFS_I(attr_vi);
+ /* Truncation and page-cache writes can reacquire this runlist lock. */
+ if (attr_ni == locked_ni) {
+ iput(attr_vi);
+ return -ENOSPC;
+ }
- err = ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO);
- if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT) {
- err = ntfs_attr_truncate(attr_ni, 0);
- if (err || ntfs_attr_truncate_i(attr_ni, base_ni->attr_list_size, HOLES_NO) != 0) {
+ err = ntfs_attr_truncate_i_locked(
+ attr_ni, base_ni->attr_list_size, HOLES_NO, locked_ni);
+ if (err == -ENOSPC && attr_ni->mft_no == FILE_MFT &&
+ NInoNonResident(attr_ni)) {
+ retry_err = ntfs_attrlist_repack(attr_vi, attr_ni,
+ base_ni->attr_list_size, locked_ni);
+ if (retry_err) {
+ ntfs_error(base_ni->vol->sb, "Failed to repack attribute list");
iput(attr_vi);
+ return retry_err;
+ }
+
+ retry_err = ntfs_attr_truncate_i_locked(
+ attr_ni, base_ni->attr_list_size,
+ HOLES_NO, locked_ni);
+ if (retry_err) {
ntfs_error(base_ni->vol->sb,
- "Failed to truncate attribute list of inode %#llx",
- (long long)base_ni->mft_no);
- return -EIO;
+ "Failed to resize attribute list after repack");
+ iput(attr_vi);
+ return retry_err;
}
} else if (err) {
iput(attr_vi);
ntfs_error(base_ni->vol->sb,
"Failed to truncate attribute list of inode %#llx",
(long long)base_ni->mft_no);
- return -EIO;
+ return err;
+ }
+
+ /*
+ * Reserve the maximum legal list size while the MFT metadata area is
+ * still easy to allocate contiguously. This prevents a later list entry
+ * from needing another mapping-pairs byte in the full base MFT record.
+ * Failure to obtain the optional reserve must not reject the current
+ * metadata update; the repack retry above remains available if needed.
+ */
+ if (base_ni->mft_no == FILE_MFT && NInoNonResident(attr_ni) &&
+ attr_ni->allocated_size < NTFS_MAX_ATTR_LIST_SIZE) {
+ retry_err = ntfs_attr_expand_locked(
+ attr_ni, base_ni->attr_list_size,
+ NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
+ if (retry_err == -ENOSPC) {
+ retry_err = ntfs_attrlist_repack(
+ attr_vi, attr_ni,
+ NTFS_MAX_ATTR_LIST_SIZE, locked_ni);
+ if (retry_err == -ENOSPC)
+ retry_err = 0;
+ }
+ if (retry_err) {
+ ntfs_error(base_ni->vol->sb,
+ "Failed to reserve attribute list space");
+ iput(attr_vi);
+ return retry_err;
+ }
}
i_size_write(attr_vi, base_ni->attr_list_size);
@@ -96,14 +286,15 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
NInoSetAttrListNonResident(base_ni);
- if (ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
- base_ni->attr_list, false) !=
- base_ni->attr_list_size) {
+ written = ntfs_inode_attr_pwrite(attr_vi, 0, base_ni->attr_list_size,
+ base_ni->attr_list, false);
+ if (written != base_ni->attr_list_size) {
+ err = written < 0 ? (int)written : -EIO;
iput(attr_vi);
ntfs_error(base_ni->vol->sb,
"Failed to write attribute list of inode %#llx",
(long long)base_ni->mft_no);
- return -EIO;
+ return err;
}
NInoSetAttrListDirty(base_ni);
@@ -111,6 +302,11 @@ int ntfs_attrlist_update(struct ntfs_inode *base_ni)
return 0;
}
+int ntfs_attrlist_update(struct ntfs_inode *base_ni)
+{
+ return ntfs_attrlist_update_locked(base_ni, NULL);
+}
+
/*
* ntfs_attrlist_entry_add - add an attribute list attribute entry
* @ni: opened ntfs inode, which contains that attribute
diff --git a/fs/ntfs/attrlist.h b/fs/ntfs/attrlist.h
index 1892a3934d3a..10cc2cc8e208 100644
--- a/fs/ntfs/attrlist.h
+++ b/fs/ntfs/attrlist.h
@@ -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 */
diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
index 99a3ea2b5c55..075b57fc1de6 100644
--- a/fs/ntfs/compress.c
+++ b/fs/ntfs/compress.c
@@ -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;
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index 8164326b7812..007d1614b9ac 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -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");
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 5aedc045f65a..a777de8a80c7 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -170,17 +170,19 @@ struct inode *ntfs_iget(struct super_block *sb, u64 mft_no)
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_inode(vi);
- unlock_new_inode(vi);
+ if (err) {
+ remove_inode_hash(vi);
+ discard_new_inode(vi);
+ } else
+ unlock_new_inode(vi);
}
/*
* There is no point in keeping bad inodes around. This also
* simplifies things in that we never need to check for bad inodes
* elsewhere.
*/
- if (unlikely(err)) {
- iput(vi);
+ if (unlikely(err))
vi = ERR_PTR(err);
- }
return vi;
}
@@ -231,17 +233,19 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, __le32 type,
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_attr_inode(base_vi, vi);
- unlock_new_inode(vi);
+ if (err) {
+ remove_inode_hash(vi);
+ discard_new_inode(vi);
+ } else
+ unlock_new_inode(vi);
}
/*
* There is no point in keeping bad attribute inodes around. This also
* simplifies things in that we never need to check for bad attribute
* inodes elsewhere.
*/
- if (unlikely(err)) {
- iput(vi);
+ if (unlikely(err))
vi = ERR_PTR(err);
- }
return vi;
}
@@ -286,17 +290,19 @@ struct inode *ntfs_index_iget(struct inode *base_vi, __le16 *name,
/* If this is a freshly allocated inode, need to read it now. */
if (inode_state_read_once(vi) & I_NEW) {
err = ntfs_read_locked_index_inode(base_vi, vi);
- unlock_new_inode(vi);
+ if (err) {
+ remove_inode_hash(vi);
+ discard_new_inode(vi);
+ } else
+ unlock_new_inode(vi);
}
/*
* There is no point in keeping bad index inodes around. This also
* simplifies things in that we never need to check for bad index
* inodes elsewhere.
*/
- if (unlikely(err)) {
- iput(vi);
+ if (unlikely(err))
vi = ERR_PTR(err);
- }
return vi;
}
@@ -1241,7 +1247,8 @@ unm_err_out:
if (m)
unmap_mft_record(ni);
err_out:
- if (err != -EOPNOTSUPP && err != -ENOMEM && vol_err == true) {
+ if (err != -EOPNOTSUPP && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS && vol_err == true) {
ntfs_error(vol->sb,
"Failed with error code %i. Marking corrupt inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no);
@@ -1467,12 +1474,13 @@ unm_err_out:
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(base_ni);
err_out:
- if (err != -ENOENT)
+ if (err != -ENOENT && err != -EINTR && err != -ERESTARTSYS)
ntfs_error(vol->sb,
"Failed with error code %i while reading attribute inode (mft_no 0x%llx, type 0x%x, name_len %i). Marking corrupt inode and base inode 0x%llx as bad. Run chkdsk.",
err, ni->mft_no, ni->type, ni->name_len,
base_ni->mft_no);
- if (err != -ENOENT && err != -ENOMEM)
+ if (err != -ENOENT && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS)
NVolSetErrors(vol);
return err;
}
@@ -1676,8 +1684,9 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
/* Get the index bitmap attribute inode. */
bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len);
if (IS_ERR(bvi)) {
- ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
err = PTR_ERR(bvi);
+ if (err != -EINTR && err != -ERESTARTSYS)
+ ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
goto unm_err_out;
}
bni = NTFS_I(bvi);
@@ -1721,10 +1730,12 @@ unm_err_out:
if (m)
unmap_mft_record(base_ni);
err_out:
- ntfs_error(vi->i_sb,
- "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
- err, ni->mft_no, ni->name_len);
- if (err != -EOPNOTSUPP && err != -ENOMEM)
+ if (err != -EINTR && err != -ERESTARTSYS)
+ ntfs_error(vi->i_sb,
+ "Failed with error code %i while reading index inode (mft_no 0x%llx, name_len %i.",
+ err, ni->mft_no, ni->name_len);
+ if (err != -EOPNOTSUPP && err != -ENOMEM &&
+ err != -EINTR && err != -ERESTARTSYS)
NVolSetErrors(vol);
return err;
}
@@ -2772,7 +2783,7 @@ int __ntfs_write_inode(struct inode *vi, int sync)
if (NInoNonResident(ni) && NInoRunlistDirty(ni)) {
down_write(&ni->runlist.lock);
- err = ntfs_attr_update_mapping_pairs(ni, 0);
+ err = ntfs_attr_update_mapping_pairs_locked(ni, 0, ni);
if (!err)
NInoClearRunlistDirty(ni);
up_write(&ni->runlist.lock);
@@ -3713,7 +3724,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
FGP_CREAT | FGP_LOCK,
mapping_gfp_mask(mapping));
if (IS_ERR(folio)) {
- ret = -ENOMEM;
+ ret = PTR_ERR(folio);
break;
}
} else {
@@ -3745,6 +3756,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
u64 rl_length = 0;
s64 vcn;
struct runlist_element *rl;
+ int bio_err;
lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
vcn = ntfs_pidx_to_cluster(vol, folio->index);
@@ -3787,8 +3799,15 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
goto err_unlock_folio;
}
- submit_bio_wait(bio);
+ bio_err = submit_bio_wait(bio);
bio_put(bio);
+ if (bio_err) {
+ ntfs_error(vi->i_sb,
+ "Synchronous attribute write failed (%d)",
+ bio_err);
+ ret = bio_err;
+ goto err_unlock_folio;
+ }
vcn += rl_length;
offset += length;
} while (lcn_count != 0);
diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index 98ab686a5ea2..4b7449495375 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -213,7 +213,8 @@ struct mft_record *map_mft_record(struct ntfs_inode *ni)
return m;
atomic_dec(&ni->count);
- ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m));
+ if (PTR_ERR(m) != -EINTR && PTR_ERR(m) != -ERESTARTSYS)
+ ntfs_error(ni->vol->sb, "Failed with error code %lu.", -PTR_ERR(m));
return m;
}
@@ -462,7 +463,7 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
{
u8 *kmirr;
struct folio *folio;
- unsigned int folio_ofs, lcn_folio_off = 0;
+ unsigned int folio_ofs;
int err = 0;
struct bio *bio;
@@ -492,15 +493,11 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
memcpy(kmirr, m, vol->mft_record_size);
kunmap_local(kmirr);
- if (vol->cluster_size_bits > PAGE_SHIFT) {
- lcn_folio_off = folio->index << PAGE_SHIFT;
- lcn_folio_off &= vol->cluster_size_mask;
- }
-
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector =
ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
- lcn_folio_off + folio_ofs);
+ ((u64)folio->index << PAGE_SHIFT) +
+ folio_ofs);
if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs))
err = submit_bio_wait(bio);
@@ -841,12 +838,126 @@ static bool ntfs_may_write_mft_record(struct ntfs_volume *vol, const u64 mft_no,
static const char *es = " Leaving inconsistent metadata. Unmount and run chkdsk.";
-#define RESERVED_MFT_RECORDS 64
+#define FIRST_NORMAL_MFT_RECORD 24
+#define MFT_RECORD_RESERVE 4
/*
- * ntfs_mft_bitmap_find_and_alloc_free_rec_nolock - see name
+ * Records 12-15 are marked in use by Windows but normally have no name
+ * and no links. Keep them as the last bootstrap option when a volume
+ * mounted without an in-memory tail reserve needs its first $MFT metadata
+ * extent.
+ */
+static bool mft_reserved_is_free(struct ntfs_volume *vol,
+ struct ntfs_inode *mft_ni, s64 mft_no)
+{
+ struct attr_record *a;
+ struct mft_record *m;
+ struct folio *folio;
+ void *mapped;
+ pgoff_t index = NTFS_MFT_NR_TO_PIDX(vol, mft_no);
+ unsigned int ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no);
+ u32 attrs_offset, bytes_in_use;
+ bool available = false, have_std = false;
+ int i;
+
+ for (i = 0; i < mft_ni->nr_extents; i++) {
+ if (mft_ni->ext.extent_ntfs_inos[i] &&
+ mft_ni->ext.extent_ntfs_inos[i]->mft_no == mft_no)
+ return false;
+ }
+ m = kmalloc(vol->mft_record_size, GFP_NOFS);
+ if (!m)
+ return false;
+
+ folio = read_mapping_folio(vol->mft_ino->i_mapping, index, NULL);
+ if (IS_ERR(folio))
+ goto free_m;
+
+ folio_lock(folio);
+ mapped = kmap_local_folio(folio, 0);
+ memcpy(m, (u8 *)mapped + ofs, vol->mft_record_size);
+ kunmap_local(mapped);
+ folio_unlock(folio);
+ folio_put(folio);
+ if (post_read_mst_fixup((struct ntfs_record *)m, vol->mft_record_size))
+ goto free_m;
+
+ if (!ntfs_is_mft_record(m->magic) ||
+ !(m->flags & MFT_RECORD_IN_USE) || m->base_mft_record ||
+ m->link_count)
+ goto out;
+
+ attrs_offset = le16_to_cpu(m->attrs_offset);
+ bytes_in_use = le32_to_cpu(m->bytes_in_use);
+ if (attrs_offset > bytes_in_use || bytes_in_use > vol->mft_record_size ||
+ bytes_in_use - attrs_offset < sizeof(a->type))
+ goto out;
+
+ for (a = (struct attr_record *)((u8 *)m + attrs_offset);
+ (u8 *)a + sizeof(a->type) <= (u8 *)m + bytes_in_use;) {
+ u32 len;
+
+ if (a->type == AT_END) {
+ if ((u8 *)a + sizeof(a->type) + sizeof(a->length) >
+ (u8 *)m + bytes_in_use)
+ break;
+ /* Also accept a record emptied by an earlier bootstrap. */
+ available = have_std ||
+ (u8 *)a == (u8 *)m + attrs_offset;
+ break;
+ }
+ if (a->type == AT_FILE_NAME)
+ break;
+ len = le32_to_cpu(a->length);
+ if (len < offsetof(struct attr_record, data) ||
+ (u8 *)a + len > (u8 *)m + bytes_in_use)
+ break;
+ if (a->type == AT_STANDARD_INFORMATION) {
+ u32 value_len, value_ofs;
+
+ if (have_std || a->non_resident ||
+ len < offsetof(struct attr_record,
+ data.resident.reserved) + 1)
+ break;
+ value_len = le32_to_cpu(a->data.resident.value_length);
+ value_ofs = le16_to_cpu(a->data.resident.value_offset);
+ if (value_ofs > len || value_len > len - value_ofs)
+ break;
+ have_std = true;
+ }
+ a = (struct attr_record *)((u8 *)a + len);
+ }
+out:
+ kfree(m);
+ return available;
+free_m:
+ kfree(m);
+ return false;
+}
+
+static s64 mft_reserve_end(const u8 *buf, s64 buf_start, s64 buf_end,
+ s64 start, s64 pass_end, s64 initialized_mft_records)
+{
+ s64 end = start + 1;
+ s64 limit = min_t(s64, start + MFT_RECORD_RESERVE, pass_end);
+
+ if (limit > initialized_mft_records)
+ limit = initialized_mft_records;
+ if (limit > buf_end)
+ limit = buf_end;
+ while (end < limit &&
+ !(buf[(end - buf_start) >> 3] &
+ (1 << ((end - buf_start) & 7))))
+ end++;
+ return end;
+}
+
+/*
+ * mft_bitmap_alloc_free_rec - find and allocate a free MFT record
* @vol: volume on which to search for a free mft record
* @base_ni: open base inode if allocating an extent mft record or NULL
+ * @max_mft_no: first record which must not be allocated, or -1
+ * @new_reserve_end: if not NULL, end of a free run starting after the result
*
* Search for a free mft record in the mft bitmap attribute on the ntfs volume
* @vol.
@@ -862,10 +973,12 @@ static const char *es = " Leaving inconsistent metadata. Unmount and run chkds
*
* Locking: Caller must hold vol->mftbmp_lock for writing.
*/
-static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vol,
- struct ntfs_inode *base_ni)
+static s64 mft_bitmap_alloc_free_rec(struct ntfs_volume *vol,
+ struct ntfs_inode *base_ni,
+ s64 max_mft_no, s64 *new_reserve_end)
{
s64 pass_end, ll, data_pos, pass_start, ofs, bit;
+ s64 initialized_mft_records;
unsigned long flags;
struct address_space *mftbmp_mapping;
u8 *buf = NULL, *byte;
@@ -882,30 +995,36 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
read_lock_irqsave(&NTFS_I(vol->mft_ino)->size_lock, flags);
pass_end = NTFS_I(vol->mft_ino)->allocated_size >>
vol->mft_record_size_bits;
+ initialized_mft_records = NTFS_I(vol->mft_ino)->initialized_size >>
+ vol->mft_record_size_bits;
read_unlock_irqrestore(&NTFS_I(vol->mft_ino)->size_lock, flags);
read_lock_irqsave(&NTFS_I(vol->mftbmp_ino)->size_lock, flags);
ll = NTFS_I(vol->mftbmp_ino)->initialized_size << 3;
read_unlock_irqrestore(&NTFS_I(vol->mftbmp_ino)->size_lock, flags);
if (pass_end > ll)
pass_end = ll;
- pass = 1;
- if (!base_ni)
- data_pos = vol->mft_data_pos;
- else
- data_pos = base_ni->mft_no + 1;
- if (data_pos < RESERVED_MFT_RECORDS)
- data_pos = RESERVED_MFT_RECORDS;
- if (data_pos >= pass_end) {
- data_pos = RESERVED_MFT_RECORDS;
+ if (max_mft_no >= 0 && pass_end > max_mft_no)
+ pass_end = max_mft_no;
+ if (base_ni && base_ni->mft_no == FILE_MFT) {
+ data_pos = FILE_first_user;
pass = 2;
- /* This happens on a freshly formatted volume. */
if (data_pos >= pass_end)
return -ENOSPC;
- }
-
- if (base_ni && base_ni->mft_no == FILE_MFT) {
- data_pos = 0;
- pass = 2;
+ } else {
+ pass = 1;
+ if (!base_ni)
+ data_pos = vol->mft_data_pos;
+ else
+ data_pos = base_ni->mft_no + 1;
+ if (data_pos < FIRST_NORMAL_MFT_RECORD)
+ data_pos = FIRST_NORMAL_MFT_RECORD;
+ if (data_pos >= pass_end) {
+ data_pos = FIRST_NORMAL_MFT_RECORD;
+ pass = 2;
+ /* This happens on a freshly formatted volume. */
+ if (data_pos >= pass_end)
+ return -ENOSPC;
+ }
}
pass_start = data_pos;
@@ -940,38 +1059,28 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
size, data_pos, bit);
for (; bit < size && data_pos + bit < pass_end;
bit &= ~7ull, bit += 8) {
- /*
- * If we're extending $MFT and running out of the first
- * mft record (base record) then give up searching since
- * no guarantee that the found record will be accessible.
- */
- if (base_ni && base_ni->mft_no == FILE_MFT && bit > 400) {
- folio_unlock(folio);
- kunmap_local(buf);
- folio_put(folio);
- return -ENOSPC;
- }
-
byte = buf + (bit >> 3);
if (*byte == 0xff)
continue;
- b = ffz((unsigned long)*byte);
- if (b < 8 && b >= (bit & 7)) {
+ b = bit & 7;
+ for (; b < 8; b++) {
+ if (*byte & (1 << b))
+ continue;
ll = data_pos + (bit & ~7ull) + b;
+ if (ll >= pass_end)
+ break;
+ /* Keep the dynamic tail reserve for $MFT metadata. */
+ if ((!base_ni || base_ni->mft_no != FILE_MFT) &&
+ ll >= vol->mft_record_reserve_pos &&
+ ll < vol->mft_record_reserve_end)
+ continue;
if (unlikely(ll >= (1ll << 32))) {
folio_unlock(folio);
kunmap_local(buf);
folio_put(folio);
return -ENOSPC;
}
- *byte |= 1 << b;
- folio_mark_dirty(folio);
- folio_unlock(folio);
- kunmap_local(buf);
- folio_put(folio);
- ntfs_debug("Done. (Found and allocated mft record 0x%llx.)",
- ll);
- return ll;
+ goto found;
}
}
ntfs_debug("After inner for loop: size 0x%x, data_pos 0x%llx, bit 0x%llx",
@@ -994,7 +1103,8 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
* part of the zone which we omitted earlier.
*/
pass_end = pass_start;
- data_pos = pass_start = RESERVED_MFT_RECORDS;
+ data_pos = FIRST_NORMAL_MFT_RECORD;
+ pass_start = FIRST_NORMAL_MFT_RECORD;
ntfs_debug("pass %i, pass_start 0x%llx, pass_end 0x%llx.",
pass, pass_start, pass_end);
if (data_pos >= pass_end)
@@ -1004,9 +1114,22 @@ static s64 ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(struct ntfs_volume *vo
/* No free mft records in currently initialized mft bitmap. */
ntfs_debug("Done. (No free mft records left in currently initialized mft bitmap.)");
return -ENOSPC;
+found:
+ if (new_reserve_end)
+ *new_reserve_end = mft_reserve_end(buf, data_pos,
+ data_pos + size, ll, pass_end,
+ initialized_mft_records);
+ *byte |= 1 << b;
+ folio_mark_dirty(folio);
+ folio_unlock(folio);
+ kunmap_local(buf);
+ folio_put(folio);
+ ntfs_debug("Done. (Found and allocated mft record 0x%llx.)", ll);
+ return ll;
}
-static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
+static int ntfs_mft_attr_extend(struct ntfs_inode *ni,
+ struct ntfs_inode *locked_ni)
{
int ret = 0;
struct ntfs_inode *base_ni;
@@ -1027,7 +1150,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
}
}
- ret = ntfs_attr_update_mapping_pairs(ni, 0);
+ ret = ntfs_attr_update_mapping_pairs_locked(ni, 0, locked_ni);
if (ret)
pr_err("MP update failed\n");
@@ -1215,7 +1338,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size +
le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
if (unlikely(ret)) {
- ret = ntfs_mft_attr_extend(mftbmp_ni);
+ ret = ntfs_mft_attr_extend(mftbmp_ni, mftbmp_ni);
if (!ret)
goto extended_ok;
if (ret != -EAGAIN)
@@ -1326,7 +1449,9 @@ undo_alloc:
NVolSetErrors(vol);
}
mark_mft_record_dirty(ctx->ntfs_ino);
- } else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) {
+ } else if (status.mp_extended &&
+ ntfs_attr_update_mapping_pairs_locked(mftbmp_ni, 0,
+ mftbmp_ni)) {
ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
NVolSetErrors(vol);
}
@@ -1414,7 +1539,6 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_set(mftbmp_ni, old_initialized_size, 8, 0);
if (likely(!ret)) {
ntfs_debug("Done. (Wrote eight initialized bytes to mft bitmap.");
- ntfs_inc_free_mft_records(vol, 8 * 8);
return 0;
}
ntfs_error(vol->sb, "Failed to write to mft bitmap.");
@@ -1471,8 +1595,9 @@ err_out:
* @vol: volume on which to extend the mft data attribute
*
* Extend the mft data attribute on the ntfs volume @vol by 16 mft records
- * worth of clusters or if not enough space for this by one mft record worth
- * of clusters.
+ * worth of clusters or if not enough space for this by two mft records worth
+ * of clusters. Keeping at least two new records breaks the recursion between
+ * extending $MFT and allocating a record for a new $MFT attribute extent.
*
* Note: Only changes allocated_size, i.e. does not touch initialized_size or
* data_size.
@@ -1526,10 +1651,8 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
}
lcn = rl->lcn + rl->length;
ntfs_debug("Last lcn of mft data attribute is 0x%llx.", lcn);
- /* Minimum allocation is one mft record worth of clusters. */
- min_nr = NTFS_B_TO_CLU(vol, vol->mft_record_size);
- if (!min_nr)
- min_nr = 1;
+ /* Keep room for the allocating record and at least one MFT reserve. */
+ min_nr = DIV_ROUND_UP_ULL((u64)vol->mft_record_size * 2, vol->cluster_size);
/* Want to allocate 16 mft records worth of clusters. */
nr = vol->mft_record_size << 4 >> vol->cluster_size_bits;
if (!nr)
@@ -1653,7 +1776,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
ret = ntfs_attr_record_resize(ctx->mrec, a, mp_size +
le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
if (unlikely(ret)) {
- ret = ntfs_mft_attr_extend(mft_ni);
+ ret = ntfs_mft_attr_extend(mft_ni, NULL);
if (!ret)
goto extended_ok;
if (ret != -EAGAIN)
@@ -1928,6 +2051,7 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
* @ni: [OUT] on success, set to the allocated ntfs inode
* @base_ni: [IN] open base inode if allocating an extent mft record or NULL
* @ni_mrec: [OUT] on successful return this is the mapped mft record
+ * @mft_data_vcn: [IN] lowest VCN of a new $MFT/$DATA extent, or -1
*
* Allocate an mft record in $MFT/$DATA of an open ntfs volume @vol.
*
@@ -1955,30 +2079,23 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
* optimize this we start scanning at the place specified by @base_ni or if
* @base_ni is NULL we start where we last stopped and we perform wrap around
* when we reach the end. Note, we do not try to allocate mft records below
- * number 64 because numbers 0 to 15 are the defined system files anyway and 16
- * to 64 are special in that they are used for storing extension mft records
- * for the $DATA attribute of $MFT. This is required to avoid the possibility
- * of creating a runlist with a circular dependency which once written to disk
- * can never be read in again. Windows will only use records 16 to 24 for
- * normal files if the volume is completely out of space. We never use them
- * which means that when the volume is really out of space we cannot create any
- * more files while Windows can still create up to 8 small files. We can start
- * doing this at some later time, it does not matter much for now.
+ * number 24 because numbers 0 to 15 are the defined system files and records
+ * 16 to 23 are kept for metadata compatibility. Records reserved dynamically
+ * at the initialized MFT tail are skipped by normal allocation and consumed by
+ * $MFT metadata extent allocation.
*
* When scanning the mft bitmap, we only search up to the last allocated mft
- * record. If there are no free records left in the range 64 to number of
+ * record. If there are no free records left in the range 24 to number of
* allocated mft records, then we extend the $MFT/$DATA attribute in order to
* create free mft records. We extend the allocated size of $MFT/$DATA by 16
* records at a time or one cluster, if cluster size is above 16kiB. If there
- * is not sufficient space to do this, we try to extend by a single mft record
- * or one cluster, if cluster size is above the mft record size.
+ * is not sufficient space to do this, we try to extend by two mft records or
+ * one cluster, if a cluster already contains at least two mft records.
*
- * No matter how many mft records we allocate, we initialize only the first
- * allocated mft record, incrementing mft data size and initialized size
- * accordingly, open an struct ntfs_inode for it and return it to the caller, unless
- * there are less than 64 mft records, in which case we allocate and initialize
- * mft records until we reach record 64 which we consider as the first free mft
- * record for use by normal files.
+ * When extending the initialized MFT tail, we also initialize up to four
+ * additional records and reserve them in memory for future $MFT metadata
+ * extents. If there are less than 24 mft records, records are initialized
+ * until record 24, which is the first record used for normal files.
*
* If during any stage we overflow the initialized data in the mft bitmap, we
* extend the initialized size (and data size) by 8 bytes, allocating another
@@ -2014,9 +2131,13 @@ static int ntfs_mft_record_format(const struct ntfs_volume *vol, const s64 mft_n
*/
int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
struct ntfs_inode **ni, struct ntfs_inode *base_ni,
- struct mft_record **ni_mrec)
+ struct mft_record **ni_mrec, const s64 mft_data_vcn)
{
s64 ll, bit, old_data_initialized, old_data_size;
+ s64 nr_new_mft_records = 0;
+ s64 max_mft_no = -1, reserve_start = -1, reserve_end = -1;
+ s64 candidate_reserve_end = -1;
+ s64 *reserve_endp;
unsigned long flags;
struct folio *folio;
struct ntfs_inode *mft_ni, *mftbmp_ni;
@@ -2027,7 +2148,9 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
unsigned int ofs;
int err;
__le16 seq_no, usn;
- bool record_formatted = false;
+ bool record_formatted = false, from_reserve = false, tail_alloc = false;
+ bool reserve_created = false;
+ bool forced_reserved_record = false;
unsigned int memalloc_flags;
if (base_ni && *ni)
@@ -2036,6 +2159,21 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
/* @mode and @base_ni are mutually exclusive. */
if (mode && base_ni)
return -EINVAL;
+ if (mft_data_vcn >= 0 &&
+ (!base_ni || base_ni->mft_no != FILE_MFT))
+ return -EINVAL;
+ if (mft_data_vcn >= 0) {
+ u64 vbo;
+
+ if ((u64)mft_data_vcn > (U64_MAX >> vol->cluster_size_bits))
+ return -EOVERFLOW;
+ vbo = (u64)mft_data_vcn << vol->cluster_size_bits;
+ /*
+ * The whole extent record must be reachable without this
+ * extent, including when an MFT record spans multiple clusters.
+ */
+ max_mft_no = vbo >> vol->mft_record_size_bits;
+ }
if (base_ni)
ntfs_debug("Entering (allocating an extent mft record for base mft record 0x%llx).",
@@ -2050,10 +2188,39 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
mutex_lock(&mft_ni->mrec_lock);
mftbmp_ni = NTFS_I(vol->mftbmp_ino);
search_free_rec:
+ from_reserve = false;
+ reserve_created = false;
+ candidate_reserve_end = -1;
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
- bit = ntfs_mft_bitmap_find_and_alloc_free_rec_nolock(vol, base_ni);
+ if (base_ni && base_ni->mft_no == FILE_MFT &&
+ vol->mft_record_reserve_pos < vol->mft_record_reserve_end &&
+ (max_mft_no < 0 || vol->mft_record_reserve_pos < max_mft_no)) {
+ bit = vol->mft_record_reserve_pos;
+ err = ntfs_bitmap_set_bit(vol->mftbmp_ino, bit);
+ if (unlikely(err)) {
+ ntfs_error(vol->sb,
+ "Failed to allocate reserved MFT record 0x%llx.",
+ bit);
+ goto err_out;
+ }
+ vol->mft_record_reserve_pos++;
+ from_reserve = true;
+ ntfs_debug("Allocated MFT metadata record 0x%llx from tail reserve.",
+ bit);
+ goto have_alloc_rec;
+ }
+ reserve_endp = vol->mft_record_reserve_pos >=
+ vol->mft_record_reserve_end ? &candidate_reserve_end : NULL;
+ bit = mft_bitmap_alloc_free_rec(vol, base_ni, max_mft_no, reserve_endp);
if (bit >= 0) {
+ if (candidate_reserve_end > bit + 1) {
+ vol->mft_record_reserve_pos = bit + 1;
+ vol->mft_record_reserve_end = candidate_reserve_end;
+ reserve_created = true;
+ ntfs_debug("Reserved free MFT records [0x%llx, 0x%llx) for metadata.",
+ bit + 1, candidate_reserve_end);
+ }
ntfs_debug("Found and allocated free record (#1), bit 0x%llx.",
(long long)bit);
goto have_alloc_rec;
@@ -2068,6 +2235,24 @@ search_free_rec:
}
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 @@ search_free_rec:
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 @@ have_alloc_rec:
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 @@ have_alloc_rec:
mft_ni->initialized_size);
}
read_unlock_irqrestore(&mft_ni->size_lock, flags);
+ if (tail_alloc) {
+ s64 bitmap_records;
+
+ read_lock_irqsave(&mft_ni->size_lock, flags);
+ reserve_end = mft_ni->allocated_size >>
+ vol->mft_record_size_bits;
+ read_unlock_irqrestore(&mft_ni->size_lock, flags);
+ read_lock_irqsave(&mftbmp_ni->size_lock, flags);
+ bitmap_records = mftbmp_ni->initialized_size << 3;
+ read_unlock_irqrestore(&mftbmp_ni->size_lock, flags);
+ if (reserve_end > bitmap_records)
+ reserve_end = bitmap_records;
+ if (reserve_end > bit + 1 + MFT_RECORD_RESERVE)
+ reserve_end = bit + 1 + MFT_RECORD_RESERVE;
+ reserve_start = bit + 1;
+ if (reserve_end > reserve_start) {
+ ll = reserve_end << vol->mft_record_size_bits;
+ } else {
+ reserve_start = -1;
+ reserve_end = -1;
+ ll = (bit + 1) << vol->mft_record_size_bits;
+ }
+ }
} else if (ll > mft_ni->allocated_size) {
err = -ENOSPC;
goto undo_mftbmp_alloc_nolock;
@@ -2276,14 +2489,25 @@ have_alloc_rec:
mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(mft_ni);
+ if (reserve_start >= 0 && reserve_end > reserve_start) {
+ vol->mft_record_reserve_pos = reserve_start;
+ vol->mft_record_reserve_end = reserve_end;
+ ntfs_debug("Reserved MFT records [0x%llx, 0x%llx) for metadata.",
+ reserve_start, reserve_end);
+ }
read_lock_irqsave(&mft_ni->size_lock, flags);
ntfs_debug("Status of mft data after mft record initialization: allocated_size 0x%llx, data_size 0x%llx, initialized_size 0x%llx.",
mft_ni->allocated_size, i_size_read(vol->mft_ino),
mft_ni->initialized_size);
WARN_ON(i_size_read(vol->mft_ino) > mft_ni->allocated_size);
WARN_ON(mft_ni->initialized_size > i_size_read(vol->mft_ino));
+ nr_new_mft_records = (i_size_read(vol->mft_ino) - old_data_size) >>
+ vol->mft_record_size_bits;
read_unlock_irqrestore(&mft_ni->size_lock, flags);
mft_rec_already_initialized:
+ /* Account for newly visible MFT records before dropping the lock. */
+ if (nr_new_mft_records > 0)
+ ntfs_inc_free_mft_records(vol, nr_new_mft_records);
/*
* We can finally drop the mft bitmap lock as the mft data attribute
* has been fully updated. The only disparity left is that the
@@ -2315,8 +2539,8 @@ mft_rec_already_initialized:
/* If we just formatted the mft record no need to do it again. */
if (!record_formatted) {
/* Sanity check that the mft record is really not in use. */
- if (ntfs_is_file_record(m->magic) &&
- (m->flags & MFT_RECORD_IN_USE)) {
+ if (!forced_reserved_record && ntfs_is_file_record(m->magic) &&
+ (m->flags & MFT_RECORD_IN_USE)) {
ntfs_warning(vol->sb,
"Mft record 0x%llx was marked free in mft bitmap but is marked used itself. Unmount and run chkdsk.",
bit);
@@ -2389,9 +2613,13 @@ mft_rec_already_initialized:
ntfs_error(vol->sb, "Failed to map allocated extent mft record 0x%llx.",
bit);
err = PTR_ERR(m_tmp);
- /* Set the mft record itself not in use. */
- m->flags &= cpu_to_le16(
- ~le16_to_cpu(MFT_RECORD_IN_USE));
+ if (forced_reserved_record) {
+ m->base_mft_record = 0;
+ m->flags |= MFT_RECORD_IN_USE;
+ } else {
+ /* Set the mft record itself not in use. */
+ m->flags &= cpu_to_le16(~le16_to_cpu(MFT_RECORD_IN_USE));
+ }
/* Make sure the mft record is written out to disk. */
ntfs_mft_mark_dirty(folio);
folio_unlock(folio);
@@ -2463,7 +2691,8 @@ mft_rec_already_initialized:
(*ni)->mft_no = bit;
if (ni_mrec)
*ni_mrec = (*ni)->mrec;
- ntfs_dec_free_mft_records(vol, 1);
+ if (!forced_reserved_record)
+ ntfs_dec_free_mft_records(vol, 1);
return 0;
undo_data_init:
write_lock_irqsave(&mft_ni->size_lock, flags);
@@ -2475,10 +2704,13 @@ undo_mftbmp_alloc:
if (!base_ni || base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
undo_mftbmp_alloc_nolock:
- if (ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
+ if (!forced_reserved_record && ntfs_bitmap_clear_bit(vol->mftbmp_ino, bit)) {
ntfs_error(vol->sb, "Failed to clear bit in mft bitmap.%s", es);
NVolSetErrors(vol);
}
+ if ((from_reserve || reserve_created) &&
+ vol->mft_record_reserve_pos == bit + 1)
+ vol->mft_record_reserve_pos = bit;
if (!base_ni || base_ni->mft_no != FILE_MFT)
up_write(&vol->mftbmp_lock);
err_out:
@@ -2514,9 +2746,11 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
int err;
u16 seq_no;
__le16 old_seq_no;
+ __le64 old_base_mft_record;
struct mft_record *ni_mrec;
unsigned int memalloc_flags;
struct ntfs_inode *base_ni;
+ bool keep_reserved;
if (!vol || !ni)
return -EINVAL;
@@ -2529,9 +2763,23 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
/* Cache the mft reference for later. */
mft_no = ni->mft_no;
-
- /* 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,24 +2808,28 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
if (err)
goto sync_rollback;
- if (likely(ni->nr_extents >= 0))
- base_ni = ni;
- else
- base_ni = ni->ext.base_ntfs_ino;
+ if (keep_reserved) {
+ unmap_mft_record(ni);
+ return 0;
+ }
/* Clear the bit in the $MFT/$BITMAP corresponding to this record. */
memalloc_flags = memalloc_nofs_save();
if (base_ni->mft_no != FILE_MFT)
down_write(&vol->mftbmp_lock);
err = ntfs_bitmap_clear_bit(vol->mftbmp_ino, mft_no);
+ if (!err)
+ ntfs_inc_free_mft_records(vol, 1);
+ if (!err && base_ni->mft_no == FILE_MFT &&
+ mft_no + 1 == vol->mft_record_reserve_pos &&
+ mft_no < vol->mft_record_reserve_end)
+ vol->mft_record_reserve_pos = mft_no;
if (base_ni->mft_no != FILE_MFT)
up_write(&vol->mftbmp_lock);
memalloc_nofs_restore(memalloc_flags);
if (err)
goto bitmap_rollback;
-
unmap_mft_record(ni);
- ntfs_inc_free_mft_records(vol, 1);
return 0;
/* Rollback what we did... */
@@ -2595,6 +2847,7 @@ sync_rollback:
"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);
diff --git a/fs/ntfs/mft.h b/fs/ntfs/mft.h
index 75a51a98d0f6..ed5c1d595c0d 100644
--- a/fs/ntfs/mft.h
+++ b/fs/ntfs/mft.h
@@ -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);
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 7091b2496fac..fdf52fac4329 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -480,7 +480,7 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
mark_inode_dirty(dir);
err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL,
- &ni_mrec);
+ &ni_mrec, -1);
if (err) {
iput(vi);
return ERR_PTR(err);
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 5aad2d2a36bb..4066bacabe37 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -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
diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h
index 65fd3908af26..bc85a9592245 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -55,6 +55,10 @@
* @attrdef_size: Size of the attribute definition table in bytes.
* @attrdef: Table of attribute definitions. Obtained from FILE_AttrDef.
* @mft_data_pos: Mft record number at which to allocate the next mft record.
+ * @mft_record_reserve_pos: First record in the in-memory MFT metadata reserve
+ * (protected by mftbmp_lock).
+ * @mft_record_reserve_end: First record beyond the MFT metadata reserve
+ * (protected by mftbmp_lock).
* @mft_zone_start: First cluster of the mft zone.
* @mft_zone_end: First cluster beyond the mft zone.
* @mft_zone_pos: Current position in the mft zone.
@@ -119,6 +123,8 @@ struct ntfs_volume {
s32 attrdef_size;
struct attr_def *attrdef;
s64 mft_data_pos;
+ s64 mft_record_reserve_pos;
+ s64 mft_record_reserve_end;
s64 mft_zone_start;
s64 mft_zone_end;
s64 mft_zone_pos;
@@ -252,17 +258,11 @@ static inline void ntfs_dec_free_clusters(struct ntfs_volume *vol, s64 nr)
static inline void ntfs_inc_free_mft_records(struct ntfs_volume *vol, s64 nr)
{
- if (!NVolFreeClusterKnown(vol))
- return;
-
atomic64_add(nr, &vol->free_mft_records);
}
static inline void ntfs_dec_free_mft_records(struct ntfs_volume *vol, s64 nr)
{
- if (!NVolFreeClusterKnown(vol))
- return;
-
atomic64_sub(nr, &vol->free_mft_records);
}
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index f9aff0712794..6dddbd84b93b 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -3080,7 +3080,7 @@ int cifs_query_reparse_point(const unsigned int xid,
end = 2 + get_bcc(&io_rsp->hdr) + (__u8 *)&io_rsp->ByteCount;
start = (__u8 *)&io_rsp->hdr.Protocol + data_offset;
- if (start >= end) {
+ if (start >= end || (size_t)(end - start) < sizeof(*buf)) {
rc = smb_EIO2(smb_eio_trace_qreparse_data_area,
(unsigned long)start - (unsigned long)io_rsp,
(unsigned long)end - (unsigned long)io_rsp);
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index b6e98eb31673..28e1ddeb6182 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -174,6 +174,8 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
nserver = ses->chans[i].server;
if (!nserver)
continue;
+ if (!list_empty(&nserver->rlist))
+ continue;
nserver->srv_count++;
list_add(&nserver->rlist, &reco);
}
@@ -182,11 +184,15 @@ cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
}
}
+ spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry_safe(server, nserver, &reco, rlist) {
list_del_init(&server->rlist);
set_need_reco(server);
+ spin_unlock(&cifs_tcp_ses_lock);
cifs_put_tcp_session(server, 0);
+ spin_lock(&cifs_tcp_ses_lock);
}
+ spin_unlock(&cifs_tcp_ses_lock);
}
/*
@@ -1067,6 +1073,7 @@ clean_demultiplex_info(struct TCP_Server_Info *server)
spin_unlock(&server->srv_lock);
cancel_delayed_work_sync(&server->echo);
+ cancel_delayed_work_sync(&server->reconnect);
spin_lock(&server->srv_lock);
server->tcpStatus = CifsExiting;
@@ -1823,6 +1830,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
spin_lock_init(&tcp_ses->mid_counter_lock);
INIT_LIST_HEAD(&tcp_ses->tcp_ses_list);
INIT_LIST_HEAD(&tcp_ses->smb_ses_list);
+ INIT_LIST_HEAD(&tcp_ses->rlist);
INIT_DELAYED_WORK(&tcp_ses->echo, cifs_echo_request);
INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
mutex_init(&tcp_ses->reconnect_mutex);
@@ -1926,6 +1934,7 @@ out_err:
kfree(tcp_ses->leaf_fullpath);
if (tcp_ses->ssocket)
sock_release(tcp_ses->ssocket);
+ smbd_destroy(tcp_ses);
kfree(tcp_ses);
}
return ERR_PTR(rc);
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index 1aa4844f8b8a..0d428517f454 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -3354,8 +3354,8 @@ void cifs_oplock_break(struct work_struct *work)
wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
TASK_UNINTERRUPTIBLE);
- tlink = cifs_sb_tlink(cifs_sb);
- if (IS_ERR(tlink)) {
+ tlink = cifs_get_tlink(cfile->tlink);
+ if (IS_ERR_OR_NULL(tlink)) {
/* drop the reference taken when the break was queued */
_cifsFileInfo_put(cfile, false /* do not wait for ourself */, false);
goto out;
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 945194fe7a97..05168284f205 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -788,7 +788,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
/* copy DfsPath */
- if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
+ if (le16_to_cpu(ref->DfsPathOffset) < sizeof(*ref) ||
+ le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
+ cifs_dbg(VFS, "%s: DfsPathOffset %u out of range [%zu, %td]\n",
+ __func__, le16_to_cpu(ref->DfsPathOffset),
+ sizeof(*ref), data_end - (char *)ref);
rc = -EINVAL;
goto parse_DFS_referrals_exit;
}
@@ -802,7 +806,11 @@ parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
}
/* copy link target UNC */
- if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
+ if (le16_to_cpu(ref->NetworkAddressOffset) < sizeof(*ref) ||
+ le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
+ cifs_dbg(VFS, "%s: NetworkAddressOffset %u out of range [%zu, %td]\n",
+ __func__, le16_to_cpu(ref->NetworkAddressOffset),
+ sizeof(*ref), data_end - (char *)ref);
rc = -EINVAL;
goto parse_DFS_referrals_exit;
}
diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c
index 8a1b9e8be5ba..3a27773186ae 100644
--- a/fs/smb/client/reparse.c
+++ b/fs/smb/client/reparse.c
@@ -3,6 +3,7 @@
* Copyright (c) 2024 Paulo Alcantara <pc@manguebit.com>
*/
+#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/slab.h>
@@ -159,15 +160,24 @@ static int create_native_symlink(const unsigned int xid, struct inode *inode,
convert_delimiter(sym, sep);
/*
- * For absolute NT symlinks it is required to pass also leading
- * backslash and to not mangle NT object prefix "\\??\\" and not to
- * mangle colon in drive letter. But cifs_convert_path_to_utf16()
- * removes leading backslash and replaces '?' and ':'. So temporary
- * mask these characters in NT object prefix by '_' and then change
- * them back.
+ * Absolute NT symlinks must retain the leading backslash, "\\??\\"
+ * prefix and drive-letter colon. cifs_convert_path_to_utf16() strips
+ * the leading backslash and maps '?' and ':', so temporarily mask
+ * these characters with '_' and restore them after conversion.
+ *
+ * When symlinkroot is unset, sym comes directly from the caller.
+ * Validate the complete "\\??\\X:" prefix before using fixed offsets
+ * or subtracting the NT prefix length below. Require an ASCII drive
+ * letter so the prefix occupies six characters in UTF-16 too.
*/
- if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/')
+ if (!(sbflags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') {
+ if (!strstarts(sym, "\\??\\") || !isascii(sym[4]) ||
+ !isalpha(sym[4]) || sym[5] != ':') {
+ rc = -EINVAL;
+ goto out;
+ }
sym[0] = sym[1] = sym[2] = sym[5] = '_';
+ }
/*
* On a POSIX paths mount the symlink target is stored verbatim, so
@@ -1139,29 +1149,30 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data,
u32 tag, struct cifs_fattr *fattr)
{
unsigned int sbflags = cifs_sb_flags(cifs_sb);
+ kuid_t uid = cifs_sb->ctx->linux_uid;
+ kgid_t gid = cifs_sb->ctx->linux_gid;
struct smb2_file_full_ea_info *ea;
bool have_xattr_dev = false;
+ dev_t rdev = 0;
+ umode_t mode;
u32 next = 0;
- fattr->cf_uid = cifs_sb->ctx->linux_uid;
- fattr->cf_gid = cifs_sb->ctx->linux_gid;
-
- fattr->cf_mode &= ~S_IFMT;
+ mode = fattr->cf_mode & ~S_IFMT;
switch (tag) {
case IO_REPARSE_TAG_LX_SYMLINK:
- fattr->cf_mode |= S_IFLNK;
+ mode |= S_IFLNK;
break;
case IO_REPARSE_TAG_LX_FIFO:
- fattr->cf_mode |= S_IFIFO;
+ mode |= S_IFIFO;
break;
case IO_REPARSE_TAG_AF_UNIX:
- fattr->cf_mode |= S_IFSOCK;
+ mode |= S_IFSOCK;
break;
case IO_REPARSE_TAG_LX_CHR:
- fattr->cf_mode |= S_IFCHR;
+ mode |= S_IFCHR;
break;
case IO_REPARSE_TAG_LX_BLK:
- fattr->cf_mode |= S_IFBLK;
+ mode |= S_IFBLK;
break;
}
@@ -1185,26 +1196,29 @@ static bool wsl_to_fattr(struct cifs_open_info_data *data,
if (!strncmp(name, SMB2_WSL_XATTR_UID, nlen)) {
if (!(sbflags & CIFS_MOUNT_OVERR_UID))
- fattr->cf_uid = wsl_make_kuid(cifs_sb, v);
+ uid = wsl_make_kuid(cifs_sb, v);
} else if (!strncmp(name, SMB2_WSL_XATTR_GID, nlen)) {
if (!(sbflags & CIFS_MOUNT_OVERR_GID))
- fattr->cf_gid = wsl_make_kgid(cifs_sb, v);
+ gid = wsl_make_kgid(cifs_sb, v);
} else if (!strncmp(name, SMB2_WSL_XATTR_MODE, nlen)) {
/* File type in reparse point tag and in xattr mode must match. */
- if (S_DT(fattr->cf_mode) != S_DT(le32_to_cpu(*(__le32 *)v)))
+ if (S_DT(mode) != S_DT(get_unaligned_le32(v)))
return false;
- fattr->cf_mode = (umode_t)le32_to_cpu(*(__le32 *)v);
+ mode = get_unaligned_le32(v);
} else if (!strncmp(name, SMB2_WSL_XATTR_DEV, nlen)) {
- fattr->cf_rdev = reparse_mkdev(v);
+ rdev = reparse_mkdev(v);
have_xattr_dev = true;
}
} while (next);
out:
-
/* Major and minor numbers for char and block devices are mandatory. */
if (!have_xattr_dev && (tag == IO_REPARSE_TAG_LX_CHR || tag == IO_REPARSE_TAG_LX_BLK))
return false;
+ fattr->cf_uid = uid;
+ fattr->cf_gid = gid;
+ fattr->cf_mode = mode;
+ fattr->cf_rdev = rdev;
return true;
}
diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h
index 49efd85b1e94..05b2cecb4495 100644
--- a/fs/smb/client/reparse.h
+++ b/fs/smb/client/reparse.h
@@ -9,6 +9,7 @@
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/uidgid.h>
+#include <linux/unaligned.h>
#include "fs_context.h"
#include "cifsglob.h"
#include "../common/smbfsctl.h"
@@ -23,7 +24,7 @@
static inline dev_t reparse_mkdev(void *ptr)
{
- u64 v = le64_to_cpu(*(__le64 *)ptr);
+ u64 v = get_unaligned_le64(ptr);
return MKDEV(v & 0xffffffff, v >> 32);
}
@@ -31,7 +32,7 @@ static inline dev_t reparse_mkdev(void *ptr)
static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb,
void *ptr)
{
- u32 uid = le32_to_cpu(*(__le32 *)ptr);
+ u32 uid = get_unaligned_le32(ptr);
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_UID)
return cifs_sb->ctx->linux_uid;
@@ -41,7 +42,7 @@ static inline kuid_t wsl_make_kuid(struct cifs_sb_info *cifs_sb,
static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb,
void *ptr)
{
- u32 gid = le32_to_cpu(*(__le32 *)ptr);
+ u32 gid = get_unaligned_le32(ptr);
if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_OVERR_GID)
return cifs_sb->ctx->linux_gid;
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index 7cf7dd104f7c..e095f41b5882 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -149,9 +149,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
int old_chan_count, new_chan_count;
int left;
int rc = 0;
- int tries = 0;
+ int tries = 0, attempts;
size_t iface_weight = 0, iface_min_speed = 0;
- struct cifs_server_iface *iface = NULL, *niface = NULL;
+ struct cifs_server_iface *iface = NULL, *candidate = NULL;
struct cifs_server_iface *last_iface = NULL;
spin_lock(&ses->chan_lock);
@@ -197,67 +197,89 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
break;
}
- if (!iface)
- iface = list_first_entry(&ses->iface_list, struct cifs_server_iface,
- iface_head);
last_iface = list_last_entry(&ses->iface_list, struct cifs_server_iface,
iface_head);
iface_min_speed = last_iface->speed;
+ spin_unlock(&ses->iface_lock);
- list_for_each_entry_safe_from(iface, niface, &ses->iface_list,
- iface_head) {
- /* do not mix rdma and non-rdma interfaces */
- if (iface->rdma_capable != ses->server->rdma)
- continue;
-
- /* skip ifaces that are unusable */
- if (!iface->is_active ||
- (is_ses_using_iface(ses, iface) &&
- !iface->rss_capable))
- continue;
+ attempts = 0;
+ while (left > 0) {
+ spin_lock(&ses->iface_lock);
- /* check if we already allocated enough channels */
- iface_weight = iface->speed / iface_min_speed;
+ /*
+ * iface_lock must be dropped while opening a channel,
+ * and a concurrent interface refresh may remove and
+ * free entries during that window, so no list entry
+ * may be kept across it without a reference. Scan
+ * the list from the beginning each time and only pass
+ * a referenced candidate to cifs_ses_add_channel();
+ * weight_fulfilled tracks the progress so that no
+ * iface is selected beyond its weight.
+ */
+ candidate = NULL;
+ list_for_each_entry(iface, &ses->iface_list, iface_head) {
+ /* do not mix rdma and non-rdma interfaces */
+ if (iface->rdma_capable != ses->server->rdma)
+ continue;
+
+ /* skip ifaces that are unusable */
+ if (!iface->is_active ||
+ (is_ses_using_iface(ses, iface) &&
+ !iface->rss_capable))
+ continue;
+
+ /* check if we already allocated enough channels */
+ iface_weight = iface->speed / iface_min_speed;
+
+ if (iface->weight_fulfilled >= iface_weight)
+ continue;
+
+ /* take ref before unlock */
+ kref_get(&iface->refcount);
+ candidate = iface;
+ break;
+ }
- if (iface->weight_fulfilled >= iface_weight)
- continue;
+ if (!candidate) {
+ /* no usable iface. reset weight_fulfilled and start over */
+ list_for_each_entry(iface, &ses->iface_list, iface_head)
+ iface->weight_fulfilled = 0;
+ spin_unlock(&ses->iface_lock);
+ break;
+ }
- /* take ref before unlock */
- kref_get(&iface->refcount);
+ attempts++;
+ if (attempts > 3 * ses->chan_max) {
+ kref_put(&candidate->refcount, release_iface);
+ spin_unlock(&ses->iface_lock);
+ break;
+ }
spin_unlock(&ses->iface_lock);
- rc = cifs_ses_add_channel(ses, iface);
+ rc = cifs_ses_add_channel(ses, candidate);
spin_lock(&ses->iface_lock);
if (rc) {
cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n",
- &iface->sockaddr,
+ &candidate->sockaddr,
rc);
/* failure to add chan should increase weight */
- iface->weight_fulfilled++;
- kref_put(&iface->refcount, release_iface);
+ candidate->weight_fulfilled++;
+ kref_put(&candidate->refcount, release_iface);
+ spin_unlock(&ses->iface_lock);
continue;
}
- iface->num_channels++;
- iface->weight_fulfilled++;
+ candidate->num_channels++;
+ candidate->weight_fulfilled++;
cifs_info("successfully opened new channel on iface:%pIS\n",
- &iface->sockaddr);
- break;
- }
-
- /* reached end of list. reset weight_fulfilled and start over */
- if (list_entry_is_head(iface, &ses->iface_list, iface_head)) {
- list_for_each_entry(iface, &ses->iface_list, iface_head)
- iface->weight_fulfilled = 0;
+ &candidate->sockaddr);
spin_unlock(&ses->iface_lock);
- iface = NULL;
- continue;
- }
- spin_unlock(&ses->iface_lock);
- left--;
- new_chan_count++;
+ left--;
+ new_chan_count++;
+ break;
+ }
}
return new_chan_count - old_chan_count;
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index 96063e355186..13fe8e3b48f3 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -77,6 +77,17 @@ static int parse_posix_sids(struct cifs_open_info_data *data,
sidsbuf = (u8 *)qi + le16_to_cpu(qi->OutputBufferOffset) + qi_len;
sidsbuf_end = sidsbuf + out_len - qi_len;
+ if (sidsbuf_end < sidsbuf) {
+ cifs_dbg(VFS, "%s: server-supplied out_len %u caused pointer wraparound\n",
+ __func__, out_len);
+ return -EINVAL;
+ }
+ if (sidsbuf_end > (u8 *)rsp_iov->iov_base + rsp_iov->iov_len) {
+ cifs_dbg(VFS, "%s: server-supplied out_len %u overruns iov by %td bytes\n",
+ __func__, out_len,
+ sidsbuf_end - ((u8 *)rsp_iov->iov_base + rsp_iov->iov_len));
+ return -EINVAL;
+ }
owner_len = posix_info_sid_size(sidsbuf, sidsbuf_end);
if (owner_len == -1)
diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c
index 9068175e57cd..0cfe60ae42c3 100644
--- a/fs/smb/client/smb2misc.c
+++ b/fs/smb/client/smb2misc.c
@@ -85,6 +85,36 @@ static const __le16 smb2_rsp_struct_sizes[NUMBER_OF_SMB2_COMMANDS] = {
/* SMB2_OPLOCK_BREAK */ cpu_to_le16(24)
};
+/*
+ * Minimum received PDU size for commands whose response carries a
+ * variable-length data area. A non-zero entry marks the command as
+ * having one, and gives the length smb2_check_message() requires
+ * before smb2_get_data_area_len() reads the offset and length fields
+ * out of the fixed response struct.
+ */
+static const size_t smb2_min_pdu_len[NUMBER_OF_SMB2_COMMANDS] = {
+ /* SMB2_NEGOTIATE */ sizeof(struct smb2_negotiate_rsp),
+ /* SMB2_SESSION_SETUP */ sizeof(struct smb2_sess_setup_rsp),
+ /* SMB2_LOGOFF */ 0,
+ /* SMB2_TREE_CONNECT */ 0,
+ /* SMB2_TREE_DISCONNECT */ 0,
+ /* SMB2_CREATE */ sizeof(struct smb2_create_rsp),
+ /* SMB2_CLOSE */ 0,
+ /* SMB2_FLUSH */ 0,
+ /* SMB2_READ */ sizeof(struct smb2_read_rsp),
+ /* SMB2_WRITE */ 0,
+ /* SMB2_LOCK */ 0,
+ /* SMB2_IOCTL */ sizeof(struct smb2_ioctl_rsp),
+ /* SMB2_CANCEL */ 0,
+ /* SMB2_ECHO */ 0,
+ /* SMB2_QUERY_DIRECTORY */ sizeof(struct smb2_query_directory_rsp),
+ /* SMB2_CHANGE_NOTIFY */ sizeof(struct smb2_change_notify_rsp),
+ /* SMB2_QUERY_INFO */ sizeof(struct smb2_query_info_rsp),
+ /* SMB2_SET_INFO */ 0,
+ /* SMB2_OPLOCK_BREAK */ 0,
+};
+
+#define smb2_has_data_area(cmd) (smb2_min_pdu_len[cmd] != 0)
#define SMB311_NEGPROT_BASE_SIZE (sizeof(struct smb2_hdr) + sizeof(struct smb2_negotiate_rsp))
static __u32 get_neg_ctxt_len(struct smb2_hdr *hdr, __u32 len,
@@ -233,6 +263,16 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
}
}
+ if ((shdr->Status == STATUS_SUCCESS ||
+ shdr->Status == STATUS_MORE_PROCESSING_REQUIRED ||
+ pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2_LE) &&
+ smb2_has_data_area(command) &&
+ len < smb2_min_pdu_len[command]) {
+ cifs_server_dbg(VFS, "SMB2 command %d response too short: %u < %zu\n",
+ command, len, smb2_min_pdu_len[command]);
+ return 1;
+ }
+
have_data = false;
data_area_overlap = false;
calc_len = __smb2_calc_size(buf, &have_data, &data_area_overlap);
@@ -299,33 +339,6 @@ smb2_check_message(char *buf, unsigned int pdu_len, unsigned int len,
}
/*
- * The size of the variable area depends on the offset and length fields
- * located in different fields for various SMB2 responses. SMB2 responses
- * with no variable length info, show an offset of zero for the offset field.
- */
-static const bool has_smb2_data_area[NUMBER_OF_SMB2_COMMANDS] = {
- /* SMB2_NEGOTIATE */ true,
- /* SMB2_SESSION_SETUP */ true,
- /* SMB2_LOGOFF */ false,
- /* SMB2_TREE_CONNECT */ false,
- /* SMB2_TREE_DISCONNECT */ false,
- /* SMB2_CREATE */ true,
- /* SMB2_CLOSE */ false,
- /* SMB2_FLUSH */ false,
- /* SMB2_READ */ true,
- /* SMB2_WRITE */ false,
- /* SMB2_LOCK */ false,
- /* SMB2_IOCTL */ true,
- /* SMB2_CANCEL */ false, /* BB CHECK this not listed in documentation */
- /* SMB2_ECHO */ false,
- /* SMB2_QUERY_DIRECTORY */ true,
- /* SMB2_CHANGE_NOTIFY */ true,
- /* SMB2_QUERY_INFO */ true,
- /* SMB2_SET_INFO */ false,
- /* SMB2_OPLOCK_BREAK */ false
-};
-
-/*
* Returns the pointer to the beginning of the data area. Length of the data
* area and the offset to it (from the beginning of the smb are also returned.
*/
@@ -451,7 +464,7 @@ __smb2_calc_size(void *buf, bool *have_data, bool *data_area_overlap)
*/
len += le16_to_cpu(pdu->StructureSize2);
- if (has_smb2_data_area[le16_to_cpu(shdr->Command)] == false)
+ if (!smb2_has_data_area(le16_to_cpu(shdr->Command)))
goto calc_size_exit;
smb2_get_data_area_len(&offset, &data_length, shdr);
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index cb4fd09f996e..3464470d3297 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -785,9 +785,9 @@ next_iface:
break;
}
/* Validate that Next doesn't point beyond the buffer */
- if (next > bytes_left) {
- cifs_dbg(VFS, "%s: invalid Next pointer %zu > %zd\n",
- __func__, next, bytes_left);
+ if (next < sizeof(*p) || next > bytes_left) {
+ cifs_dbg(VFS, "%s: invalid Next pointer %zu out of range [%zu, %zd]\n",
+ __func__, next, sizeof(*p), bytes_left);
rc = -EINVAL;
goto out;
}
@@ -1053,8 +1053,9 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
char *name, *value;
size_t buf_size = dst_size;
size_t name_len, value_len, user_name_len;
+ u32 next_off;
- while (src_size > 0) {
+ while (src_size >= sizeof(*src)) {
name_len = (size_t)src->ea_name_length;
value_len = (size_t)le16_to_cpu(src->ea_value_length);
@@ -1110,14 +1111,22 @@ move_smb2_ea_to_cifs(char *dst, size_t dst_size,
if (!src->next_entry_offset)
break;
- if (src_size < le32_to_cpu(src->next_entry_offset)) {
- /* stop before overrun buffer */
- rc = -ERANGE;
- break;
+ next_off = le32_to_cpu(src->next_entry_offset);
+ if (next_off < sizeof(*src) || src_size < next_off) {
+ cifs_dbg(FYI, "EA next_entry_offset %u out of range [%zu, %zu]\n",
+ next_off, sizeof(*src), src_size);
+ rc = smb_EIO2(smb_eio_trace_ea_next_offset,
+ next_off, src_size);
+ goto out;
+ }
+ src_size -= next_off;
+ src = (void *)((char *)src + next_off);
+ if (src_size > 0 && src_size < sizeof(*src)) {
+ cifs_dbg(FYI, "EA next_entry_offset %u left truncated entry (%zu bytes)\n",
+ next_off, src_size);
+ rc = smb_EIO2(smb_eio_trace_ea_next_offset, next_off, src_size);
+ goto out;
}
- src_size -= le32_to_cpu(src->next_entry_offset);
- src = (void *)((char *)src +
- le32_to_cpu(src->next_entry_offset));
}
/* didn't find the named attribute */
@@ -2454,8 +2463,14 @@ smb3_enum_snapshots(const unsigned int xid, struct cifs_tcon *tcon,
* and retry the ioctl again with larger array size sufficient
* to hold all of the snapshot GMT tokens on the second try.
*/
- if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE)
+ if (snapshot_in.snapshot_array_size < GMT_TOKEN_SIZE) {
+ if (ret_data_len < sizeof(struct smb_snapshot_array)) {
+ rc = -EIO;
+ kfree(retbuf);
+ return rc;
+ }
ret_data_len = sizeof(struct smb_snapshot_array);
+ }
/*
* We return struct SRV_SNAPSHOT_ARRAY, followed by
@@ -5365,11 +5380,13 @@ receive_encrypted_standard(struct TCP_Server_Info *server,
length = decrypt_raw_data(server, buf, buf_size, NULL, false);
if (length)
return length;
+ pdu_length = buf_size;
next_is_large = server->large_buf;
one_more:
shdr = (struct smb2_hdr *)buf;
next_cmd = le32_to_cpu(shdr->NextCommand);
+ server->total_read = next_cmd ? next_cmd : pdu_length;
if (*num_mids >= MAX_COMPOUND) {
cifs_server_dbg(VFS, "too many PDUs in compound\n");
@@ -5377,8 +5394,15 @@ one_more:
}
if (next_cmd) {
- if (WARN_ON_ONCE(next_cmd > pdu_length))
+ if (next_cmd < MID_HEADER_SIZE(server) ||
+ next_cmd > pdu_length ||
+ pdu_length - next_cmd < MID_HEADER_SIZE(server)) {
+ unsigned int max_next = pdu_length > (unsigned int)MID_HEADER_SIZE(server) ?
+ pdu_length - (unsigned int)MID_HEADER_SIZE(server) : 0;
+ cifs_server_dbg(VFS, "invalid NextCommand offset %u out of range [%zu, %u]\n",
+ next_cmd, MID_HEADER_SIZE(server), max_next);
return -1;
+ }
if (next_is_large)
next_buffer = (char *)cifs_buf_get();
else
@@ -5414,6 +5438,7 @@ one_more:
server->bigbuf = buf = next_buffer;
else
server->smallbuf = buf = next_buffer;
+ next_buffer = NULL;
goto one_more;
} else if (ret != 0) {
/*
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index dea05aeb53a1..880ce12f50c4 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -189,18 +189,19 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses,
spin_unlock(&ses->chan_lock);
/*
- * the above reference of server by channel
- * needs to be dropped without holding chan_lock
- * as cifs_put_tcp_session takes a higher lock
- * i.e. cifs_tcp_ses_lock
+ * signal the channel and its primary server to
+ * reconnect before dropping the above reference of
+ * server by channel, which is done without holding
+ * chan_lock as cifs_put_tcp_session takes a higher
+ * lock i.e. cifs_tcp_ses_lock
*/
- cifs_put_tcp_session(server, from_reconnect);
-
cifs_signal_cifsd_for_reconnect(server, false);
/* mark primary server as needing reconnect */
pserver = server->primary_server;
cifs_signal_cifsd_for_reconnect(pserver, false);
+
+ cifs_put_tcp_session(server, from_reconnect);
skip_terminate:
return -EHOSTDOWN;
}
diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
index b442cccd1530..bb8d0197cb54 100644
--- a/fs/smb/client/trace.h
+++ b/fs/smb/client/trace.h
@@ -27,6 +27,7 @@
EM(smb_eio_trace_copychunk_overcopy_c, "copychunk_overcopy_c") \
EM(smb_eio_trace_create_rsp_too_small, "create_rsp_too_small") \
EM(smb_eio_trace_dfsref_no_rsp, "dfsref_no_rsp") \
+ EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \
EM(smb_eio_trace_ea_overrun, "ea_overrun") \
EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \
EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 4cb92d6599ee..d211861ff86f 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -22,6 +22,8 @@
static DEFINE_MUTEX(init_lock);
static struct ksmbd_conn_ops default_conn_ops;
+static struct delayed_work session_expiration_work;
+static bool stopping_session_expiration_work;
DEFINE_HASHTABLE(conn_list, CONN_HASH_BITS);
DECLARE_RWSEM(conn_list_lock);
@@ -158,18 +160,28 @@ static void delete_proc_clients(void) {}
static struct workqueue_struct *ksmbd_conn_wq;
+static void ksmbd_session_expiration_worker(struct work_struct *work);
+
int ksmbd_conn_wq_init(void)
{
ksmbd_conn_wq = alloc_workqueue("ksmbd-conn-release",
WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
if (!ksmbd_conn_wq)
return -ENOMEM;
+
+ WRITE_ONCE(stopping_session_expiration_work, false);
+ INIT_DELAYED_WORK(&session_expiration_work,
+ ksmbd_session_expiration_worker);
+ queue_delayed_work(ksmbd_conn_wq, &session_expiration_work,
+ KSMBD_SESSION_EXPIRATION_INTERVAL);
return 0;
}
void ksmbd_conn_wq_destroy(void)
{
if (ksmbd_conn_wq) {
+ WRITE_ONCE(stopping_session_expiration_work, true);
+ cancel_delayed_work_sync(&session_expiration_work);
destroy_workqueue(ksmbd_conn_wq);
ksmbd_conn_wq = NULL;
}
@@ -279,6 +291,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
return NULL;
conn->need_neg = true;
+ conn->creation_time = jiffies;
ksmbd_conn_set_new(conn);
conn->local_nls = load_nls("utf8");
if (!conn->local_nls)
@@ -588,6 +601,20 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn)
if (kthread_should_stop())
return false;
+ /*
+ * Stale connections that have not completed NEGOTIATE and SESSION_SETUP
+ * must be disconnected. Do not race a request that is currently
+ * completing authentication.
+ */
+ if (!atomic_read(&conn->req_running) &&
+ time_after(jiffies, conn->creation_time +
+ KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) &&
+ (READ_ONCE(conn->need_neg) ||
+ !ksmbd_conn_has_valid_or_expired_session(conn))) {
+ ksmbd_debug(CONN, "Connection setup timed out\n");
+ return false;
+ }
+
if (atomic_read(&conn->stats.open_files_count) > 0)
return true;
@@ -605,6 +632,51 @@ bool ksmbd_conn_alive(struct ksmbd_conn *conn)
return true;
}
+static void ksmbd_session_expiration_worker(struct work_struct *work)
+{
+ struct ksmbd_conn *conn, *target;
+ int bkt;
+
+ if (!ksmbd_server_running())
+ goto reschedule;
+
+ ksmbd_expire_sessions();
+
+ /*
+ * An old connection without a Valid or Expired session must be
+ * disconnected. Process one connection at a time without holding
+ * conn_list_lock across transport shutdown.
+ */
+again:
+ target = NULL;
+ down_read(&conn_list_lock);
+ hash_for_each(conn_list, bkt, conn, hlist) {
+ if (ksmbd_conn_exiting(conn) || ksmbd_conn_releasing(conn) ||
+ atomic_read(&conn->req_running) ||
+ time_before_eq(jiffies, conn->creation_time +
+ KSMBD_UNAUTHENTICATED_CONN_TIMEOUT) ||
+ (!READ_ONCE(conn->need_neg) &&
+ ksmbd_conn_has_valid_or_expired_session(conn)))
+ continue;
+
+ target = ksmbd_conn_get(conn);
+ break;
+ }
+ up_read(&conn_list_lock);
+
+ if (target) {
+ ksmbd_debug(CONN, "Connection setup timed out\n");
+ ksmbd_conn_abort(target);
+ ksmbd_conn_put(target);
+ goto again;
+ }
+
+reschedule:
+ if (!READ_ONCE(stopping_session_expiration_work))
+ queue_delayed_work(ksmbd_conn_wq, &session_expiration_work,
+ KSMBD_SESSION_EXPIRATION_INTERVAL);
+}
+
/* "+2" for BCC field (ByteCount, 2 bytes) */
#define SMB1_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb_hdr) + 2)
#define SMB2_MIN_SUPPORTED_PDU_SIZE (sizeof(struct smb2_pdu))
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 63484c8efbbd..371f17b4f02a 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -77,6 +77,7 @@ struct ksmbd_conn {
struct rw_semaphore session_lock;
/* smb session 1 per user */
struct xarray sessions;
+ unsigned long creation_time;
unsigned long last_active;
/* How many request are running currently */
atomic_t req_running;
@@ -192,6 +193,8 @@ struct ksmbd_transport {
#define KSMBD_TCP_RECV_TIMEOUT (7 * HZ)
#define KSMBD_TCP_SEND_TIMEOUT (5 * HZ)
+#define KSMBD_SESSION_EXPIRATION_INTERVAL (5 * HZ)
+#define KSMBD_UNAUTHENTICATED_CONN_TIMEOUT (45 * HZ)
#define KSMBD_TCP_PEER_SOCKADDR(c) ((struct sockaddr *)&((c)->peer_addr))
#define CONN_HASH_BITS 12
diff --git a/fs/smb/server/mgmt/user_session.c b/fs/smb/server/mgmt/user_session.c
index 2eb8f730e99e..44dc3f800cd4 100644
--- a/fs/smb/server/mgmt/user_session.c
+++ b/fs/smb/server/mgmt/user_session.c
@@ -22,6 +22,7 @@
static DEFINE_IDA(session_ida);
#define SESSION_HASH_BITS 12
+#define KSMBD_MAX_PENDING_SESSIONS 1
static DEFINE_HASHTABLE(sessions_table, SESSION_HASH_BITS);
static DECLARE_RWSEM(sessions_table_lock);
@@ -432,26 +433,31 @@ struct ksmbd_session *__session_lookup(unsigned long long id)
return NULL;
}
-static void ksmbd_expire_session(struct ksmbd_conn *conn)
+static bool ksmbd_too_many_session_setups(struct ksmbd_conn *conn)
{
unsigned long id;
struct ksmbd_session *sess;
+ unsigned int pending = 0;
down_write(&sessions_table_lock);
down_write(&conn->session_lock);
xa_for_each(&conn->sessions, id, sess) {
+ if (READ_ONCE(sess->state) != SMB2_SESSION_IN_PROGRESS)
+ continue;
+
if (atomic_read(&sess->refcnt) <= 1 &&
- (sess->state != SMB2_SESSION_VALID ||
- time_after(jiffies,
- sess->last_active + SMB2_SESSION_TIMEOUT))) {
+ time_after(jiffies, sess->last_active +
+ KSMBD_UNAUTHENTICATED_CONN_TIMEOUT)) {
xa_erase(&conn->sessions, sess->id);
ksmbd_session_remove_from_table(sess);
ksmbd_session_destroy(sess);
continue;
}
+ pending++;
}
up_write(&conn->session_lock);
up_write(&sessions_table_lock);
+ return pending >= KSMBD_MAX_PENDING_SESSIONS;
}
int ksmbd_session_register(struct ksmbd_conn *conn,
@@ -461,9 +467,12 @@ int ksmbd_session_register(struct ksmbd_conn *conn,
sess->dialect = conn->dialect;
memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
- ksmbd_expire_session(conn);
- ret = xa_err(xa_store(&conn->sessions, sess->id, sess,
- KSMBD_DEFAULT_GFP));
+ /* Bound abandoned SessionId-zero authentication exchanges. */
+ if (ksmbd_too_many_session_setups(conn))
+ ret = -ENOSPC;
+ else
+ ret = xa_err(xa_store(&conn->sessions, sess->id, sess,
+ KSMBD_DEFAULT_GFP));
if (ret) {
down_write(&sessions_table_lock);
ksmbd_session_remove_from_table(sess);
@@ -474,6 +483,105 @@ int ksmbd_session_register(struct ksmbd_conn *conn,
return ret;
}
+void ksmbd_session_unregister(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess)
+{
+ struct ksmbd_conn *session_conns[KSMBD_MAX_CHANNELS];
+ struct channel *chann;
+ unsigned long index;
+ unsigned int nr_conns = 0, i;
+ bool removed = false;
+
+ down_write(&sessions_table_lock);
+ if (!hlist_unhashed(&sess->hlist)) {
+ /* Keep each channel connection stable under sessions_table_lock. */
+ down_read(&sess->chann_lock);
+ xa_for_each(&sess->ksmbd_chann_list, index, chann) {
+ if (nr_conns == ARRAY_SIZE(session_conns))
+ break;
+ session_conns[nr_conns++] = chann->conn;
+ }
+ up_read(&sess->chann_lock);
+
+ ksmbd_session_remove_from_table(sess);
+ removed = true;
+ }
+
+ down_write(&conn->session_lock);
+ if (xa_load(&conn->sessions, sess->id) == sess)
+ xa_erase(&conn->sessions, sess->id);
+ up_write(&conn->session_lock);
+ for (i = 0; i < nr_conns; i++) {
+ if (session_conns[i] == conn)
+ continue;
+ down_write(&session_conns[i]->session_lock);
+ if (xa_load(&session_conns[i]->sessions, sess->id) == sess)
+ xa_erase(&session_conns[i]->sessions, sess->id);
+ up_write(&session_conns[i]->session_lock);
+ }
+ up_write(&sessions_table_lock);
+
+ if (removed)
+ ksmbd_user_session_put(sess);
+}
+
+bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn)
+{
+ struct ksmbd_session *sess;
+ unsigned long id;
+ int state, bkt;
+ bool found = false;
+
+ down_read(&conn->session_lock);
+ xa_for_each(&conn->sessions, id, sess) {
+ state = READ_ONCE(sess->state);
+ if (state == SMB2_SESSION_VALID ||
+ state == SMB2_SESSION_EXPIRED) {
+ found = true;
+ break;
+ }
+ }
+ up_read(&conn->session_lock);
+ if (found)
+ return true;
+
+ /* A session bound through SMB3 multichannel is not in conn->sessions. */
+ down_read(&sessions_table_lock);
+ hash_for_each(sessions_table, bkt, sess, hlist) {
+ state = READ_ONCE(sess->state);
+ if (state != SMB2_SESSION_VALID &&
+ state != SMB2_SESSION_EXPIRED)
+ continue;
+
+ down_read(&sess->chann_lock);
+ found = xa_load(&sess->ksmbd_chann_list, (long)conn);
+ up_read(&sess->chann_lock);
+ if (found)
+ break;
+ }
+ up_read(&sessions_table_lock);
+ return found;
+}
+
+void ksmbd_expire_sessions(void)
+{
+ struct ksmbd_session *sess;
+ u64 now = ktime_get_real_seconds();
+ int bkt;
+
+ down_read(&sessions_table_lock);
+ hash_for_each(sessions_table, bkt, sess, hlist) {
+ if (READ_ONCE(sess->state) != SMB2_SESSION_VALID ||
+ !sess->kerberos_expiry || now < sess->kerberos_expiry)
+ continue;
+
+ if (cmpxchg(&sess->state, SMB2_SESSION_VALID,
+ SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID)
+ ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS);
+ }
+ up_read(&sessions_table_lock);
+}
+
static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
{
struct channel *chann;
@@ -488,7 +596,7 @@ static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
return 0;
}
-void ksmbd_sessions_deregister(struct ksmbd_conn *conn)
+void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn)
{
struct ksmbd_session *sess;
unsigned long id;
diff --git a/fs/smb/server/mgmt/user_session.h b/fs/smb/server/mgmt/user_session.h
index 3e52d4cc1324..217258551d6d 100644
--- a/fs/smb/server/mgmt/user_session.h
+++ b/fs/smb/server/mgmt/user_session.h
@@ -72,6 +72,8 @@ struct ksmbd_session {
struct rw_semaphore rpc_lock;
};
+#define KSMBD_MAX_CHANNELS 32
+
static inline int test_session_flag(struct ksmbd_session *sess, int bit)
{
return sess->flags & bit;
@@ -98,7 +100,11 @@ bool is_ksmbd_session_in_connection(struct ksmbd_conn *conn,
unsigned long long id);
int ksmbd_session_register(struct ksmbd_conn *conn,
struct ksmbd_session *sess);
-void ksmbd_sessions_deregister(struct ksmbd_conn *conn);
+void ksmbd_session_unregister(struct ksmbd_conn *conn,
+ struct ksmbd_session *sess);
+void ksmbd_conn_sessions_cleanup(struct ksmbd_conn *conn);
+bool ksmbd_conn_has_valid_or_expired_session(struct ksmbd_conn *conn);
+void ksmbd_expire_sessions(void);
struct ksmbd_session *__session_lookup(unsigned long long id);
struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
unsigned long long id);
diff --git a/fs/smb/server/proc.c b/fs/smb/server/proc.c
index 826353ed0553..19f0f2cfbf54 100644
--- a/fs/smb/server/proc.c
+++ b/fs/smb/server/proc.c
@@ -178,6 +178,8 @@ static int proc_show_ksmbd_stats(struct seq_file *m, void *v)
proc_show_runtime_totals(m);
seq_printf(m, "sessions:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_SESSIONS));
+ seq_printf(m, "session_timeouts:\t%lld\n",
+ ksmbd_counter_sum(KSMBD_COUNTER_SESSION_TIMEOUTS));
seq_printf(m, "tree_connects:\t%lld\n",
ksmbd_counter_sum(KSMBD_COUNTER_TREE_CONNS));
seq_printf(m, "requests:\t%lld\n",
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 0069d4e6a60a..0827c8c51006 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -414,7 +414,7 @@ static int ksmbd_server_process_request(struct ksmbd_conn *conn)
static int ksmbd_server_terminate_conn(struct ksmbd_conn *conn)
{
- ksmbd_sessions_deregister(conn);
+ ksmbd_conn_sessions_cleanup(conn);
destroy_lease_table(conn);
return 0;
}
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index b7ce67094626..6b8809f67b92 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -85,8 +85,6 @@ struct channel *lookup_chann_list(struct ksmbd_session *sess, struct ksmbd_conn
return chann;
}
-#define KSMBD_MAX_CHANNELS 32
-
static int register_session_channel(struct ksmbd_session *sess,
struct ksmbd_conn *conn,
const char *sess_key)
@@ -933,8 +931,14 @@ static bool smb2_session_expired_cmd_allowed(struct ksmbd_work *work,
static bool smb2_session_kerberos_expired(struct ksmbd_session *sess)
{
- return sess->kerberos_expiry &&
- ktime_get_real_seconds() >= sess->kerberos_expiry;
+ if (!sess->kerberos_expiry ||
+ ktime_get_real_seconds() < sess->kerberos_expiry)
+ return false;
+
+ if (cmpxchg(&sess->state, SMB2_SESSION_VALID,
+ SMB2_SESSION_EXPIRED) == SMB2_SESSION_VALID)
+ ksmbd_counter_inc(KSMBD_COUNTER_SESSION_TIMEOUTS);
+ return true;
}
/**
@@ -969,9 +973,8 @@ int smb2_check_user_session(struct ksmbd_work *work)
if (!work->next_smb2_rcv_hdr_off && sess_id)
work->sess = ksmbd_session_lookup_all_states(conn, sess_id);
if (work->sess) {
- if (smb2_session_kerberos_expired(work->sess)) {
- work->sess->state = SMB2_SESSION_EXPIRED;
- } else if (work->sess->state != SMB2_SESSION_VALID) {
+ if (!smb2_session_kerberos_expired(work->sess) &&
+ work->sess->state != SMB2_SESSION_VALID) {
ksmbd_user_session_put(work->sess);
work->sess = NULL;
}
@@ -996,8 +999,7 @@ int smb2_check_user_session(struct ksmbd_work *work)
sess_id, work->sess->id);
return -EINVAL;
}
- if (smb2_session_kerberos_expired(work->sess))
- work->sess->state = SMB2_SESSION_EXPIRED;
+ smb2_session_kerberos_expired(work->sess);
if (work->sess->state != SMB2_SESSION_VALID) {
pr_err("compound request on a non-valid session (state %d)\n",
work->sess->state);
@@ -1014,7 +1016,6 @@ int smb2_check_user_session(struct ksmbd_work *work)
work->sess = ksmbd_session_lookup_all_states(conn, sess_id);
if (work->sess) {
if (smb2_session_kerberos_expired(work->sess)) {
- work->sess->state = SMB2_SESSION_EXPIRED;
return smb2_session_expired_cmd_allowed(work, cmd) ?
1 : -EKEYEXPIRED;
}
@@ -2436,7 +2437,7 @@ int smb2_sess_setup(struct ksmbd_work *work)
struct ksmbd_conn *conn = work->conn;
struct smb2_sess_setup_req *req;
struct smb2_sess_setup_rsp *rsp;
- struct ksmbd_session *sess;
+ struct ksmbd_session *sess = NULL;
struct negotiate_message *negblob;
unsigned int negblob_len, negblob_off;
int rc = 0;
@@ -2592,6 +2593,9 @@ int smb2_sess_setup(struct ksmbd_work *work)
goto out_err;
}
+ if (work->session_setup_reauth)
+ WRITE_ONCE(sess->state, SMB2_SESSION_IN_PROGRESS);
+
conn->binding = false;
}
work->sess = sess;
@@ -2703,6 +2707,14 @@ out_err:
}
if (rc < 0) {
+ bool setup_in_progress = sess &&
+ READ_ONCE(sess->state) == SMB2_SESSION_IN_PROGRESS &&
+ !(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING);
+
+ /* Authentication errors must not leave the new session published. */
+ if (setup_in_progress)
+ ksmbd_session_unregister(conn, sess);
+
if (sess && conn->dialect == SMB311_PROT_ID &&
(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
struct preauth_session *preauth_sess;
@@ -2736,7 +2748,8 @@ out_err:
* For binding requests, session belongs to another
* connection. Do not expire it.
*/
- if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING)) {
+ if (!(req->Flags & SMB2_SESSION_REQ_FLAG_BINDING) &&
+ !setup_in_progress) {
sess->last_active = jiffies;
sess->kerberos_expiry = 0;
sess->state = SMB2_SESSION_EXPIRED;
@@ -6262,21 +6275,18 @@ err_out2:
* @reqOutputBufferLength: max buffer length expected in command response
* @fixed_len: minimum fixed response length
* @rsp: query info response buffer contains output buffer length
- * @rsp_org: base response buffer pointer in case of chained response
*
* Return: 0 on success, otherwise error
*/
static int buffer_check_err(int reqOutputBufferLength,
unsigned int fixed_len,
- struct smb2_query_info_rsp *rsp,
- void *rsp_org)
+ struct smb2_query_info_rsp *rsp)
{
unsigned int output_len = le32_to_cpu(rsp->OutputBufferLength);
if (reqOutputBufferLength < fixed_len) {
pr_err("Invalid Buffer Size Requested\n");
rsp->hdr.Status = STATUS_INFO_LENGTH_MISMATCH;
- *(__be32 *)rsp_org = cpu_to_be32(sizeof(struct smb2_hdr));
return -EINVAL;
}
@@ -6287,8 +6297,7 @@ static int buffer_check_err(int reqOutputBufferLength,
return 0;
}
-static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp,
- void *rsp_org)
+static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp)
{
struct smb2_file_standard_info *sinfo;
@@ -6303,8 +6312,7 @@ static void get_standard_info_pipe(struct smb2_query_info_rsp *rsp,
cpu_to_le32(sizeof(struct smb2_file_standard_info));
}
-static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num,
- void *rsp_org)
+static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num)
{
struct smb2_file_internal_info *file_info;
@@ -6318,8 +6326,7 @@ static void get_internal_info_pipe(struct smb2_query_info_rsp *rsp, u64 num,
static int smb2_get_info_file_pipe(struct ksmbd_session *sess,
struct smb2_query_info_req *req,
- struct smb2_query_info_rsp *rsp,
- void *rsp_org)
+ struct smb2_query_info_rsp *rsp)
{
u64 id;
int rc;
@@ -6344,16 +6351,16 @@ static int smb2_get_info_file_pipe(struct ksmbd_session *sess,
switch (req->FileInfoClass) {
case FILE_STANDARD_INFORMATION:
- get_standard_info_pipe(rsp, rsp_org);
+ get_standard_info_pipe(rsp);
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
le32_to_cpu(rsp->OutputBufferLength),
- rsp, rsp_org);
+ rsp);
break;
case FILE_INTERNAL_INFORMATION:
- get_internal_info_pipe(rsp, id, rsp_org);
+ get_internal_info_pipe(rsp, id);
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
le32_to_cpu(rsp->OutputBufferLength),
- rsp, rsp_org);
+ rsp);
break;
default:
ksmbd_debug(SMB, "smb2_info_file_pipe for %u not supported\n",
@@ -7189,8 +7196,7 @@ static int smb2_get_info_file(struct ksmbd_work *work,
if (test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_PIPE)) {
/* smb2 info file called for pipe */
- rc = smb2_get_info_file_pipe(work->sess, req, rsp,
- work->response_buf);
+ rc = smb2_get_info_file_pipe(work->sess, req, rsp);
goto iov_pin_out;
}
@@ -7299,13 +7305,16 @@ static int smb2_get_info_file(struct ksmbd_work *work,
case FILE_ALTERNATE_NAME_INFORMATION:
fixed_len = FILE_ALTERNATE_NAME_INFORMATION_SIZE;
break;
+ case FILE_NORMALIZED_NAME_INFORMATION:
+ fixed_len = FILE_NORMALIZED_NAME_INFORMATION_SIZE;
+ break;
case FILE_STREAM_INFORMATION:
fixed_len = FILE_STREAM_INFORMATION_SIZE;
break;
}
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
fixed_len,
- rsp, work->response_buf);
+ rsp);
}
ksmbd_fd_put(work, fp);
@@ -7576,7 +7585,7 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
}
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
fixed_len,
- rsp, work->response_buf);
+ rsp);
path_put(&path);
if (!rc)
@@ -7690,7 +7699,7 @@ release_acl:
rsp->OutputBufferLength = cpu_to_le32(secdesclen);
rc = buffer_check_err(le32_to_cpu(req->OutputBufferLength),
le32_to_cpu(rsp->OutputBufferLength),
- rsp, work->response_buf);
+ rsp);
if (rc)
goto err_out;
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index 3f08d1ca5a38..ca8e27f7b712 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -61,8 +61,6 @@ struct preauth_integrity_info {
#define SMB2_SESSION_IN_PROGRESS BIT(0)
#define SMB2_SESSION_VALID BIT(1)
-#define SMB2_SESSION_TIMEOUT (10 * HZ)
-
/* Apple Defined Contexts */
#define SMB2_CREATE_AAPL "AAPL"
@@ -214,6 +212,7 @@ struct file_sparse {
#define FILE_ALLOCATION_INFORMATION_SIZE 19
#define FILE_END_OF_FILE_INFORMATION_SIZE 20
#define FILE_ALTERNATE_NAME_INFORMATION_SIZE 8
+#define FILE_NORMALIZED_NAME_INFORMATION_SIZE 8
#define FILE_STREAM_INFORMATION_SIZE 32
#define FILE_PIPE_INFORMATION_SIZE 23
#define FILE_PIPE_LOCAL_INFORMATION_SIZE 24
diff --git a/fs/smb/server/stats.h b/fs/smb/server/stats.h
index bc864efa0d46..8b32b8b4e8be 100644
--- a/fs/smb/server/stats.h
+++ b/fs/smb/server/stats.h
@@ -15,6 +15,7 @@
enum {
KSMBD_COUNTER_SESSIONS = 0,
+ KSMBD_COUNTER_SESSION_TIMEOUTS,
KSMBD_COUNTER_TREE_CONNS,
KSMBD_COUNTER_REQUESTS,
KSMBD_COUNTER_STATUS_SUCCESS,