summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-06 23:34:15 +0100
committerMark Brown <broonie@kernel.org>2026-07-06 23:34:15 +0100
commitbf2cecaae4fb165639deaed6983124d0602264f3 (patch)
treedb05a6a3ef710d2b8806d71bfd303fc4bab3f30b /fs
parentf29a8bf6fde130313ec77c89d7612100ff5bafb2 (diff)
parentd37664f99ef94f7a7bbca4b5ddcb7ab5c801f2e1 (diff)
regulator: qcom_usb_vbus: add pm4125 VBUS regulator support
Rakesh Kota <rakesh.kota@oss.qualcomm.com> says: Add support for PM4125 USB VBUS regulator. Unlike PM8150B which uses a current-limit selector, PM4125 uses a 2-bit VBOOST voltage selector supporting 4.25 V, 4.5 V, 4.75 V and 5.0 V output. Link: https://patch.msgid.link/20260706-add_pm4125-vbus-reg-v3-0-999d78a87b81@oss.qualcomm.com
Diffstat (limited to 'fs')
-rw-r--r--fs/afs/callback.c17
-rw-r--r--fs/afs/cell.c27
-rw-r--r--fs/afs/cmservice.c7
-rw-r--r--fs/afs/dir.c40
-rw-r--r--fs/afs/dynroot.c2
-rw-r--r--fs/afs/fs_operation.c2
-rw-r--r--fs/afs/inode.c15
-rw-r--r--fs/afs/internal.h3
-rw-r--r--fs/afs/super.c5
-rw-r--r--fs/afs/symlink.c4
-rw-r--r--fs/afs/vl_list.c24
-rw-r--r--fs/afs/volume.c2
-rw-r--r--fs/bpf_fs_kfuncs.c23
-rw-r--r--fs/cachefiles/namei.c4
-rw-r--r--fs/exec.c2
-rw-r--r--fs/exfat/iomap.c5
-rw-r--r--fs/fat/dir.c44
-rw-r--r--fs/fhandle.c2
-rw-r--r--fs/freevxfs/vxfs_bmap.c3
-rw-r--r--fs/fuse/file.c14
-rw-r--r--fs/iomap/bio.c15
-rw-r--r--fs/iomap/buffered-io.c16
-rw-r--r--fs/iomap/direct-io.c7
-rw-r--r--fs/iomap/ioend.c8
-rw-r--r--fs/minix/minix.h2
-rw-r--r--fs/namei.c4
-rw-r--r--fs/netfs/buffered_read.c2
-rw-r--r--fs/netfs/buffered_write.c2
-rw-r--r--fs/netfs/direct_write.c18
-rw-r--r--fs/netfs/internal.h12
-rw-r--r--fs/netfs/locking.c95
-rw-r--r--fs/netfs/read_retry.c7
-rw-r--r--fs/netfs/write_collect.c10
-rw-r--r--fs/netfs/write_issue.c55
-rw-r--r--fs/netfs/write_retry.c7
-rw-r--r--fs/ntfs/aops.c6
-rw-r--r--fs/ntfs3/inode.c5
-rw-r--r--fs/orangefs/dir.c7
-rw-r--r--fs/overlayfs/copy_up.c12
-rw-r--r--fs/overlayfs/inode.c4
-rw-r--r--fs/proc/generic.c9
-rw-r--r--fs/resctrl/monitor.c37
-rw-r--r--fs/smb/client/cifsfs.h4
-rw-r--r--fs/smb/client/file.c1
-rw-r--r--fs/smb/client/smb2pdu.c8
-rw-r--r--fs/smb/server/ksmbd_work.h7
-rw-r--r--fs/smb/server/oplock.c109
-rw-r--r--fs/smb/server/server.c14
-rw-r--r--fs/smb/server/smb2misc.c18
-rw-r--r--fs/smb/server/smb2pdu.c17
-rw-r--r--fs/smb/server/smbacl.c15
-rw-r--r--fs/smb/server/vfs.c14
-rw-r--r--fs/smb/server/vfs_cache.c79
-rw-r--r--fs/xfs/libxfs/xfs_dquot_buf.c14
-rw-r--r--fs/xfs/scrub/agheader_repair.c2
-rw-r--r--fs/xfs/xfs_aops.c3
-rw-r--r--fs/xfs/xfs_buf.c190
-rw-r--r--fs/xfs/xfs_buf.h2
-rw-r--r--fs/xfs/xfs_buf_item.c3
-rw-r--r--fs/xfs/xfs_inode.c3
-rw-r--r--fs/xfs/xfs_log_recover.c6
-rw-r--r--fs/xfs/xfs_qm.c5
-rw-r--r--fs/xfs/xfs_super.c3
63 files changed, 750 insertions, 352 deletions
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
index 894d2bad6b6c..61354003c006 100644
--- a/fs/afs/callback.c
+++ b/fs/afs/callback.c
@@ -113,16 +113,12 @@ static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
{
struct afs_volume *volume = NULL;
struct rb_node *p;
- int seq = 1;
- for (;;) {
+ scoped_seqlock_read(&cell->volume_lock, ss_lock) {
/* Unfortunately, rbtree walking doesn't give reliable results
* under just the RCU read lock, so we have to check for
* changes.
*/
- seq++; /* 2 on the 1st/lockless path, otherwise odd */
- read_seqbegin_or_lock(&cell->volume_lock, &seq);
-
p = rcu_dereference_raw(cell->volumes.rb_node);
while (p) {
volume = rb_entry(p, struct afs_volume, cell_node);
@@ -138,12 +134,9 @@ static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
if (volume && afs_try_get_volume(volume, afs_volume_trace_get_callback))
break;
- if (!need_seqretry(&cell->volume_lock, seq))
- break;
- seq |= 1; /* Want a lock next time */
+ volume = NULL;
}
- done_seqretry(&cell->volume_lock, seq);
return volume;
}
@@ -221,7 +214,11 @@ static void afs_break_some_callbacks(struct afs_server *server,
rcu_read_lock();
volume = afs_lookup_volume_rcu(server->cell, vid);
- if (cbb->fid.vnode == 0 && cbb->fid.unique == 0) {
+ if (!volume) {
+ /* Ignore breaks on unknown volumes. */
+ rcu_read_unlock();
+ *_count = 0;
+ } else if (cbb->fid.vnode == 0 && cbb->fid.unique == 0) {
afs_break_volume_callback(server, volume);
*_count -= 1;
if (*_count)
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index 9738684dbdd2..47a2645768d7 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -206,11 +206,6 @@ static struct afs_cell *afs_alloc_cell(struct afs_net *net,
cell->dns_status = vllist->status;
smp_store_release(&cell->dns_lookup_count, 1); /* vs source/status */
atomic_inc(&net->cells_outstanding);
- ret = idr_alloc_cyclic(&net->cells_dyn_ino, cell,
- 2, INT_MAX / 2, GFP_KERNEL);
- if (ret < 0)
- goto error;
- cell->dynroot_ino = ret;
cell->debug_id = atomic_inc_return(&cell_debug_id);
trace_afs_cell(cell->debug_id, 1, 0, afs_cell_trace_alloc);
@@ -304,6 +299,13 @@ struct afs_cell *afs_lookup_cell(struct afs_net *net,
goto cell_already_exists;
}
+ ret = idr_alloc_cyclic(&net->cells_dyn_ino, candidate,
+ 2, INT_MAX / 2, GFP_KERNEL);
+ if (ret < 0)
+ goto cant_alloc_ino;
+ candidate->dynroot_ino = ret;
+ set_bit(AFS_CELL_FL_HAVE_INO, &candidate->flags);
+
cell = candidate;
candidate = NULL;
afs_use_cell(cell, trace);
@@ -378,6 +380,11 @@ no_wait:
_leave(" = %p [cell]", cell);
return cell;
+cant_alloc_ino:
+ up_write(&net->cells_lock);
+ afs_put_cell(candidate, afs_cell_trace_put_candidate);
+ goto error_noput;
+
cell_already_exists:
_debug("cell exists");
cell = cursor;
@@ -547,6 +554,8 @@ static int afs_update_cell(struct afs_cell *cell)
rcu_assign_pointer(cell->vl_servers, vllist);
cell->dns_source = vllist->source;
old = p;
+ } else {
+ old = vllist;
}
write_unlock(&cell->vl_servers_lock);
afs_put_vlserverlist(cell->net, old);
@@ -577,7 +586,6 @@ static void afs_cell_destroy(struct rcu_head *rcu)
afs_put_vlserverlist(net, rcu_access_pointer(cell->vl_servers));
afs_unuse_cell(cell->alias_of, afs_cell_trace_unuse_alias);
key_put(cell->anonymous_key);
- idr_remove(&net->cells_dyn_ino, cell->dynroot_ino);
kfree(cell->name - 1);
kfree(cell);
@@ -592,6 +600,13 @@ static void afs_destroy_cell_work(struct work_struct *work)
afs_see_cell(cell, afs_cell_trace_destroy);
timer_delete_sync(&cell->management_timer);
cancel_work_sync(&cell->manager);
+
+ if (test_bit(AFS_CELL_FL_HAVE_INO, &cell->flags)) {
+ down_write(&cell->net->cells_lock);
+ idr_remove(&cell->net->cells_dyn_ino, cell->dynroot_ino);
+ up_write(&cell->net->cells_lock);
+ }
+
call_rcu(&cell->rcu, afs_cell_destroy);
}
diff --git a/fs/afs/cmservice.c b/fs/afs/cmservice.c
index 5540ae1cad59..db394f101fc6 100644
--- a/fs/afs/cmservice.c
+++ b/fs/afs/cmservice.c
@@ -334,7 +334,6 @@ static int afs_deliver_cb_init_call_back_state3(struct afs_call *call)
ret = afs_extract_data(call, false);
switch (ret) {
case 0: break;
- case -EAGAIN: return 0;
default: return ret;
}
@@ -364,6 +363,11 @@ static int afs_deliver_cb_init_call_back_state3(struct afs_call *call)
if (!afs_check_call_state(call, AFS_CALL_SV_REPLYING))
return afs_io_error(call, afs_io_error_cm_reply);
+ if (!call->server) {
+ trace_afs_cm_no_server_u(call, call->request);
+ return 0;
+ }
+
if (memcmp(call->request, &call->server->_uuid, sizeof(call->server->_uuid)) != 0) {
pr_notice("Callback UUID does not match fileserver UUID\n");
trace_afs_cm_no_server_u(call, call->request);
@@ -451,7 +455,6 @@ static int afs_deliver_cb_probe_uuid(struct afs_call *call)
ret = afs_extract_data(call, false);
switch (ret) {
case 0: break;
- case -EAGAIN: return 0;
default: return ret;
}
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 498b99ccdf0e..6df56fe9163f 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -28,9 +28,11 @@ static int afs_d_revalidate(struct inode *dir, const struct qstr *name,
static int afs_d_delete(const struct dentry *dentry);
static void afs_d_iput(struct dentry *dentry, struct inode *inode);
static bool afs_lookup_one_filldir(struct dir_context *ctx, const char *name, int nlen,
- loff_t fpos, u64 ino, unsigned dtype);
+ u64 ino, u32 uniquifier);
+#define AFS_LOOKUP_ONE ((filldir_t)0x123UL)
static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nlen,
- loff_t fpos, u64 ino, unsigned dtype);
+ u64 ino, u32 uniquifier);
+#define AFS_LOOKUP ((filldir_t)0x137UL)
static int afs_create(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, umode_t mode, bool excl);
static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
@@ -421,11 +423,18 @@ static int afs_dir_iterate_block(struct afs_vnode *dvnode,
}
/* found the next entry */
- if (!dir_emit(ctx, dire->u.name, nlen,
- ntohl(dire->u.vnode),
- (ctx->actor == afs_lookup_filldir ||
- ctx->actor == afs_lookup_one_filldir)?
- ntohl(dire->u.unique) : DT_UNKNOWN)) {
+ if (ctx->actor == AFS_LOOKUP) {
+ if (!afs_lookup_filldir(ctx, dire->u.name, nlen,
+ ntohl(dire->u.vnode),
+ ntohl(dire->u.unique)))
+ return 0;
+ } else if (ctx->actor == AFS_LOOKUP_ONE) {
+ if (!afs_lookup_one_filldir(ctx, dire->u.name, nlen,
+ ntohl(dire->u.vnode),
+ ntohl(dire->u.unique)))
+ return 0;
+ } else if (!dir_emit(ctx, dire->u.name, nlen,
+ ntohl(dire->u.vnode), DT_UNKNOWN)) {
_leave(" = 0 [full]");
return 0;
}
@@ -545,6 +554,7 @@ static int afs_readdir(struct file *file, struct dir_context *ctx)
{
afs_dataversion_t dir_version;
+ ctx->dt_flags_mask = UINT_MAX;
return afs_dir_iterate(file_inode(file), ctx, file, &dir_version);
}
@@ -554,14 +564,14 @@ static int afs_readdir(struct file *file, struct dir_context *ctx)
* uniquifier through dtype
*/
static bool afs_lookup_one_filldir(struct dir_context *ctx, const char *name,
- int nlen, loff_t fpos, u64 ino, unsigned dtype)
+ int nlen, u64 ino, u32 uniquifier)
{
struct afs_lookup_one_cookie *cookie =
container_of(ctx, struct afs_lookup_one_cookie, ctx);
_enter("{%s,%u},%s,%u,,%llu,%u",
cookie->name.name, cookie->name.len, name, nlen,
- (unsigned long long) ino, dtype);
+ (unsigned long long) ino, uniquifier);
/* insanity checks first */
BUILD_BUG_ON(sizeof(union afs_xdr_dir_block) != 2048);
@@ -574,7 +584,7 @@ static bool afs_lookup_one_filldir(struct dir_context *ctx, const char *name,
}
cookie->fid.vnode = ino;
- cookie->fid.unique = dtype;
+ cookie->fid.unique = uniquifier;
cookie->found = 1;
_leave(" = false [found]");
@@ -591,7 +601,7 @@ static int afs_do_lookup_one(struct inode *dir, const struct qstr *name,
{
struct afs_super_info *as = dir->i_sb->s_fs_info;
struct afs_lookup_one_cookie cookie = {
- .ctx.actor = afs_lookup_one_filldir,
+ .ctx.actor = AFS_LOOKUP_ONE,
.name = *name,
.fid.vid = as->volume->vid
};
@@ -622,14 +632,14 @@ static int afs_do_lookup_one(struct inode *dir, const struct qstr *name,
* uniquifier through dtype
*/
static bool afs_lookup_filldir(struct dir_context *ctx, const char *name,
- int nlen, loff_t fpos, u64 ino, unsigned dtype)
+ int nlen, u64 ino, u32 uniquifier)
{
struct afs_lookup_cookie *cookie =
container_of(ctx, struct afs_lookup_cookie, ctx);
_enter("{%s,%u},%s,%u,,%llu,%u",
cookie->name.name, cookie->name.len, name, nlen,
- (unsigned long long) ino, dtype);
+ (unsigned long long) ino, uniquifier);
/* insanity checks first */
BUILD_BUG_ON(sizeof(union afs_xdr_dir_block) != 2048);
@@ -637,7 +647,7 @@ static bool afs_lookup_filldir(struct dir_context *ctx, const char *name,
if (cookie->nr_fids < 50) {
cookie->fids[cookie->nr_fids].vnode = ino;
- cookie->fids[cookie->nr_fids].unique = dtype;
+ cookie->fids[cookie->nr_fids].unique = uniquifier;
cookie->nr_fids++;
}
@@ -778,7 +788,7 @@ static struct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry)
for (i = 0; i < ARRAY_SIZE(cookie->fids); i++)
cookie->fids[i].vid = dvnode->fid.vid;
- cookie->ctx.actor = afs_lookup_filldir;
+ cookie->ctx.actor = AFS_LOOKUP;
cookie->name = dentry->d_name;
cookie->nr_fids = 2; /* slot 1 is saved for the fid we actually want
* and slot 0 for the directory */
diff --git a/fs/afs/dynroot.c b/fs/afs/dynroot.c
index 1d5e33bc7502..6e3c8c691ba9 100644
--- a/fs/afs/dynroot.c
+++ b/fs/afs/dynroot.c
@@ -278,7 +278,7 @@ static struct dentry *afs_lookup_atcell(struct inode *dir, struct dentry *dentry
}
/*
- * Transcribe the cell database into readdir content under the RCU read lock.
+ * Transcribe the cell database into readdir content under net->cells_lock.
* Each cell produces two entries, one prefixed with a dot and one not.
*/
static int afs_dynroot_readdir_cells(struct afs_net *net, struct dir_context *ctx)
diff --git a/fs/afs/fs_operation.c b/fs/afs/fs_operation.c
index c0dbbc6d3716..20801b29521d 100644
--- a/fs/afs/fs_operation.c
+++ b/fs/afs/fs_operation.c
@@ -348,7 +348,7 @@ int afs_put_operation(struct afs_operation *op)
for (i = 0; i < op->nr_files - 2; i++)
if (op->more_files[i].put_vnode)
iput(&op->more_files[i].vnode->netfs.inode);
- kfree(op->more_files);
+ kvfree(op->more_files);
}
if (op->estate) {
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 3f48458694ba..14f39a9bea6c 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -52,9 +52,9 @@ static noinline void dump_vnode(struct afs_vnode *vnode, struct afs_vnode *paren
/*
* Set parameters for the netfs library
*/
-static void afs_set_netfs_context(struct afs_vnode *vnode)
+static void afs_set_netfs_context(struct afs_vnode *vnode, bool is_file)
{
- netfs_inode_init(&vnode->netfs, &afs_req_ops, true);
+ netfs_inode_init(&vnode->netfs, &afs_req_ops, is_file);
}
/*
@@ -93,6 +93,10 @@ static int afs_inode_init_from_status(struct afs_operation *op,
inode->i_gid = make_kgid(&init_user_ns, status->group);
set_nlink(&vnode->netfs.inode, status->nlink);
+ i_size_write(inode, status->size);
+ inode_set_bytes(inode, status->size);
+ afs_set_netfs_context(vnode, status->type == AFS_FTYPE_FILE);
+
switch (status->type) {
case AFS_FTYPE_FILE:
inode->i_mode = S_IFREG | (status->mode & S_IALLUGO);
@@ -126,7 +130,6 @@ static int afs_inode_init_from_status(struct afs_operation *op,
}
inode->i_mapping->a_ops = &afs_symlink_aops;
inode_nohighmem(inode);
- mapping_set_release_always(inode->i_mapping);
break;
default:
dump_vnode(vnode, op->file[0].vnode != vnode ? op->file[0].vnode : NULL);
@@ -134,10 +137,6 @@ static int afs_inode_init_from_status(struct afs_operation *op,
return afs_protocol_error(NULL, afs_eproto_file_type);
}
- i_size_write(inode, status->size);
- inode_set_bytes(inode, status->size);
- afs_set_netfs_context(vnode);
-
vnode->invalid_before = status->data_version;
trace_afs_set_dv(vnode, status->data_version);
inode_set_iversion_raw(&vnode->netfs.inode, status->data_version);
@@ -566,7 +565,6 @@ struct inode *afs_root_iget(struct super_block *sb, struct key *key)
vnode = AFS_FS_I(inode);
vnode->cb_v_check = atomic_read(&as->volume->cb_v_break);
- afs_set_netfs_context(vnode);
op = afs_alloc_operation(key, as->volume);
if (IS_ERR(op)) {
@@ -682,6 +680,7 @@ void afs_evict_inode(struct inode *inode)
inode->i_mapping->a_ops->writepages(inode->i_mapping, &wbc);
}
+ flush_delayed_work(&vnode->lock_work);
netfs_wait_for_outstanding_io(inode);
truncate_inode_pages_final(&inode->i_data);
netfs_free_folioq_buffer(vnode->directory);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 0b72a8566299..601f01e5c15f 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -388,6 +388,7 @@ struct afs_cell {
#define AFS_CELL_FL_NO_GC 0 /* The cell was added manually, don't auto-gc */
#define AFS_CELL_FL_DO_LOOKUP 1 /* DNS lookup requested */
#define AFS_CELL_FL_CHECK_ALIAS 2 /* Need to check for aliases */
+#define AFS_CELL_FL_HAVE_INO 3 /* Have dynroot_ino */
enum afs_cell_state state;
short error;
enum dns_record_source dns_source:8; /* Latest source of data from lookup */
@@ -750,8 +751,6 @@ static inline void afs_vnode_set_cache(struct afs_vnode *vnode,
{
#ifdef CONFIG_AFS_FSCACHE
vnode->netfs.cache = cookie;
- if (cookie)
- mapping_set_release_always(vnode->netfs.inode.i_mapping);
#endif
}
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 942f3e9800d7..82bb713825a0 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -587,7 +587,8 @@ static int afs_get_tree(struct fs_context *fc)
}
fc->root = dget(sb->s_root);
- trace_afs_get_tree(as->cell, as->volume);
+ if (!ctx->dyn_root)
+ trace_afs_get_tree(as->cell, as->volume);
_leave(" = 0 [%p]", sb);
return 0;
@@ -659,7 +660,6 @@ static void afs_i_init_once(void *_vnode)
INIT_LIST_HEAD(&vnode->wb_keys);
INIT_LIST_HEAD(&vnode->pending_locks);
INIT_LIST_HEAD(&vnode->granted_locks);
- INIT_DELAYED_WORK(&vnode->lock_work, afs_lock_work);
INIT_LIST_HEAD(&vnode->cb_mmap_link);
seqlock_init(&vnode->cb_lock);
}
@@ -693,6 +693,7 @@ static struct inode *afs_alloc_inode(struct super_block *sb)
init_rwsem(&vnode->rmdir_lock);
INIT_WORK(&vnode->cb_work, afs_invalidate_mmap_work);
+ INIT_DELAYED_WORK(&vnode->lock_work, afs_lock_work);
_leave(" = %p", &vnode->netfs.inode);
return &vnode->netfs.inode;
diff --git a/fs/afs/symlink.c b/fs/afs/symlink.c
index ed5868369f37..16b4823cb7b7 100644
--- a/fs/afs/symlink.c
+++ b/fs/afs/symlink.c
@@ -255,11 +255,11 @@ int afs_symlink_writepages(struct address_space *mapping,
}
if (ret == 0) {
- mutex_lock(&vnode->netfs.wb_lock);
+ netfs_wb_begin(&vnode->netfs, false);
netfs_free_folioq_buffer(vnode->directory);
vnode->directory = NULL;
vnode->directory_size = 0;
- mutex_unlock(&vnode->netfs.wb_lock);
+ netfs_wb_end(&vnode->netfs);
} else if (ret == 1) {
ret = 0; /* Skipped write due to lock conflict. */
}
diff --git a/fs/afs/vl_list.c b/fs/afs/vl_list.c
index 3e4966915ea4..c1dac5dbed0d 100644
--- a/fs/afs/vl_list.c
+++ b/fs/afs/vl_list.c
@@ -92,7 +92,7 @@ static struct afs_addr_list *afs_extract_vl_addrs(struct afs_net *net,
{
struct afs_addr_list *alist;
const u8 *b = *_b;
- int ret = -EINVAL;
+ int ret;
alist = afs_alloc_addrlist(nr_addrs);
if (!alist)
@@ -110,6 +110,7 @@ static struct afs_addr_list *afs_extract_vl_addrs(struct afs_net *net,
case DNS_ADDRESS_IS_IPV4:
if (end - b < 4) {
_leave(" = -EINVAL [short inet]");
+ ret = -EINVAL;
goto error;
}
memcpy(x, b, 4);
@@ -122,6 +123,7 @@ static struct afs_addr_list *afs_extract_vl_addrs(struct afs_net *net,
case DNS_ADDRESS_IS_IPV6:
if (end - b < 16) {
_leave(" = -EINVAL [short inet6]");
+ ret = -EINVAL;
goto error;
}
memcpy(x, b, 16);
@@ -198,6 +200,8 @@ struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
b += sizeof(*hdr);
while (end - b >= sizeof(bs)) {
+ int nlen;
+
bs.name_len = afs_extract_le16(&b);
bs.priority = afs_extract_le16(&b);
bs.weight = afs_extract_le16(&b);
@@ -207,10 +211,12 @@ struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
bs.protocol = *b++;
bs.nr_addrs = *b++;
+ nlen = min3(bs.name_len, end - b, 255);
+
_debug("extract %u %u %u %u %u %u %*.*s",
bs.name_len, bs.priority, bs.weight,
bs.port, bs.protocol, bs.nr_addrs,
- bs.name_len, bs.name_len, b);
+ bs.name_len, nlen, b);
if (end - b < bs.name_len)
break;
@@ -287,8 +293,20 @@ struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
afs_put_addrlist(old, afs_alist_trace_put_vlserver_old);
}
+ /* Check for duplicates in the server list */
+ for (j = 0; j < vllist->nr_servers; j++) {
+ struct afs_vlserver *s = vllist->servers[j].server;
- /* TODO: Might want to check for duplicates */
+ if (s->name_len == server->name_len &&
+ s->port == server->port &&
+ strncasecmp(s->name, server->name, server->name_len) == 0) {
+ afs_put_vlserver(cell->net, server);
+ server = NULL;
+ break;
+ }
+ }
+ if (!server)
+ continue;
/* Insertion-sort by priority and weight */
for (j = 0; j < vllist->nr_servers; j++) {
diff --git a/fs/afs/volume.c b/fs/afs/volume.c
index 9ae5c8ad2e04..4f79d25ec37f 100644
--- a/fs/afs/volume.c
+++ b/fs/afs/volume.c
@@ -40,7 +40,7 @@ static struct afs_volume *afs_insert_volume_into_cell(struct afs_cell *cell,
goto found;
}
- set_bit(AFS_VOLUME_RM_TREE, &volume->flags);
+ set_bit(AFS_VOLUME_RM_TREE, &p->flags);
rb_replace_node_rcu(&p->cell_node, &volume->cell_node, &cell->volumes);
}
}
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 768aca2dc0f0..f1863a891db6 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -360,18 +360,23 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s
#endif /* CONFIG_CGROUPS */
/**
- * bpf_real_inode - get the real inode backing a dentry
- * @dentry: dentry to resolve
+ * bpf_real_data_inode - get the real inode hosting a file's data
+ * @file: file to resolve
*
- * If the dentry is on a union/overlay filesystem, return the underlying, real
- * inode that hosts the data. Otherwise return the inode attached to the
- * dentry itself.
+ * Resolve @file to the inode that hosts its data. For a regular file on a
+ * union/overlay filesystem this is the underlying (upper or lower) inode that
+ * stores the data, not the overlay inode.
*
- * Return: The real inode backing the dentry, or NULL for a negative dentry.
+ * Data resolution only applies to regular files. For a non-regular file (e.g.
+ * a device node, fifo or socket) on a union/overlay filesystem the overlay
+ * inode itself is returned; for any file on a non-union filesystem the inode
+ * attached to @file is returned.
+ *
+ * Return: The inode hosting @file's data, or NULL.
*/
-__bpf_kfunc struct inode *bpf_real_inode(struct dentry *dentry)
+__bpf_kfunc struct inode *bpf_real_data_inode(struct file *file)
{
- return d_real_inode(dentry);
+ return d_real_inode(file_dentry(file));
}
__bpf_kfunc_end_defs();
@@ -384,7 +389,7 @@ BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_real_inode, KF_SLEEPABLE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_real_data_inode, KF_SLEEPABLE | KF_RET_NULL)
BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index 2937db690b40..8a9f6be15828 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -209,7 +209,6 @@ lookup_error:
return ERR_PTR(ret);
nomem_d_alloc:
- inode_unlock(d_inode(dir));
_leave(" = -ENOMEM");
return ERR_PTR(-ENOMEM);
}
@@ -375,7 +374,7 @@ try_again:
"Rename failed with error %d", ret);
}
- __cachefiles_unmark_inode_in_use(object, d_inode(rep));
+ cachefiles_do_unmark_inode_in_use(object, d_inode(rep));
end_renaming(&rd);
_leave(" = 0");
return 0;
@@ -467,7 +466,6 @@ struct file *cachefiles_create_tmpfile(struct cachefiles_object *object)
ret = -EINVAL;
if (unlikely(!file->f_op->read_iter) ||
unlikely(!file->f_op->write_iter)) {
- fput(file);
pr_notice("Cache does not support read_iter and write_iter\n");
goto err_unuse;
}
diff --git a/fs/exec.c b/fs/exec.c
index b92fe7db176c..d5993cedc829 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1717,7 +1717,7 @@ static int exec_binprm(struct linux_binprm *bprm)
old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
rcu_read_unlock();
- /* This allows 4 levels of binfmt rewrites before failing hard. */
+ /* This allows 5 levels of binfmt rewrites before failing hard. */
for (depth = 0;; depth++) {
struct file *exec;
if (depth > 5)
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index 1aac38e63fe6..190fc6471f84 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -253,10 +253,7 @@ static void exfat_iomap_read_end_io(struct bio *bio)
static void exfat_iomap_bio_submit_read(const struct iomap_iter *iter,
struct iomap_read_folio_ctx *ctx)
{
- struct bio *bio = ctx->read_ctx;
-
- bio->bi_end_io = exfat_iomap_read_end_io;
- submit_bio(bio);
+ iomap_bio_submit_read_endio(iter, ctx, exfat_iomap_read_end_io);
}
const struct iomap_read_ops exfat_iomap_bio_read_ops = {
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index 4f6f42f33613..c6cca5d00ffd 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -131,6 +131,31 @@ static inline int fat_get_entry(struct inode *dir, loff_t *pos,
}
/*
+ * Like fat_get_entry(), but honour the FAT end-of-directory marker:
+ * a dirent whose first name byte is NUL terminates iteration per the
+ * spec, which also guarantees that every following slot is zeroed.
+ * Skip straight to the end of the directory so the next call returns
+ * -1 from fat_bmap() without re-reading the trailing zero slots, and
+ * so callers that persist *pos across invocations (e.g. readdir's
+ * ctx->pos) keep reporting EOD. Release *bh and set it to NULL to
+ * match fat_get_entry()'s contract that *bh is NULL on the -1 return.
+ */
+static int fat_get_entry_eod(struct inode *dir, loff_t *pos,
+ struct buffer_head **bh,
+ struct msdos_dir_entry **de)
+{
+ int err = fat_get_entry(dir, pos, bh, de);
+
+ if (err == 0 && (*de)->name[0] == 0) {
+ brelse(*bh);
+ *bh = NULL;
+ *pos = dir->i_size;
+ return -1;
+ }
+ return err;
+}
+
+/*
* Convert Unicode 16 to UTF-8, translated Unicode, or ASCII.
* If uni_xlate is enabled and we can't get a 1:1 conversion, use a
* colon as an escape character since it is normally invalid on the vfat
@@ -327,7 +352,7 @@ parse_long:
if (ds->id & 0x40)
(*unicode)[offset + 13] = 0;
- if (fat_get_entry(dir, pos, bh, de) < 0)
+ if (fat_get_entry_eod(dir, pos, bh, de) < 0)
return PARSE_EOF;
if (slot == 0)
break;
@@ -489,7 +514,7 @@ int fat_search_long(struct inode *inode, const unsigned char *name,
err = -ENOENT;
while (1) {
- if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
+ if (fat_get_entry_eod(inode, &cpos, &bh, &de) == -1)
goto end_of_dir;
parse_record:
nr_slots = 0;
@@ -601,7 +626,7 @@ static int __fat_readdir(struct inode *inode, struct file *file,
bh = NULL;
get_new:
- if (fat_get_entry(inode, &cpos, &bh, &de) == -1)
+ if (fat_get_entry_eod(inode, &cpos, &bh, &de) == -1)
goto end_of_dir;
parse_record:
nr_slots = 0;
@@ -885,7 +910,7 @@ static int fat_get_short_entry(struct inode *dir, loff_t *pos,
struct buffer_head **bh,
struct msdos_dir_entry **de)
{
- while (fat_get_entry(dir, pos, bh, de) >= 0) {
+ while (fat_get_entry_eod(dir, pos, bh, de) >= 0) {
/* free entry or long name entry or volume label */
if (!IS_FREE((*de)->name) && !((*de)->attr & ATTR_VOLUME))
return 0;
@@ -1302,6 +1327,7 @@ int fat_add_entries(struct inode *dir, void *slots, int nr_slots,
struct msdos_dir_entry *de;
int err, free_slots, i, nr_bhs;
loff_t pos;
+ bool saw_eod;
sinfo->nr_slots = nr_slots;
@@ -1310,12 +1336,15 @@ int fat_add_entries(struct inode *dir, void *slots, int nr_slots,
bh = prev = NULL;
pos = 0;
err = -ENOSPC;
+ saw_eod = false;
while (fat_get_entry(dir, &pos, &bh, &de) > -1) {
/* check the maximum size of directory */
if (pos >= FAT_MAX_DIR_SIZE)
goto error;
if (IS_FREE(de->name)) {
+ if (de->name[0] == 0)
+ saw_eod = true;
if (prev != bh) {
get_bh(bh);
bhs[nr_bhs] = prev = bh;
@@ -1325,6 +1354,13 @@ int fat_add_entries(struct inode *dir, void *slots, int nr_slots,
if (free_slots == nr_slots)
goto found;
} else {
+ if (saw_eod) {
+ fat_fs_error_ratelimit(sb,
+ "allocated dir entry found after end-of-directory marker (i_pos %lld)",
+ MSDOS_I(dir)->i_pos);
+ err = -EIO;
+ goto error;
+ }
for (i = 0; i < nr_bhs; i++)
brelse(bhs[i]);
prev = NULL;
diff --git a/fs/fhandle.c b/fs/fhandle.c
index 1ca7eb3a6cb5..f8829231e3d7 100644
--- a/fs/fhandle.c
+++ b/fs/fhandle.c
@@ -295,7 +295,7 @@ static bool capable_wrt_mount(struct mount *mount)
*/
guard(rcu)();
mnt_ns = READ_ONCE(mount->mnt_ns);
- return ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN);
+ return mnt_ns && ns_capable(mnt_ns->user_ns, CAP_SYS_ADMIN);
}
static inline int may_decode_fh(struct handle_to_path_ctx *ctx,
diff --git a/fs/freevxfs/vxfs_bmap.c b/fs/freevxfs/vxfs_bmap.c
index e85222892038..1b8216eb1d90 100644
--- a/fs/freevxfs/vxfs_bmap.c
+++ b/fs/freevxfs/vxfs_bmap.c
@@ -227,7 +227,8 @@ vxfs_bmap_typed(struct inode *ip, long iblock)
return 0;
}
default:
- BUG();
+ WARN_ON_ONCE(1);
+ return 0;
}
}
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index e052a0d44dee..ceada75310b8 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -981,19 +981,8 @@ static int fuse_iomap_read_folio_range_async(const struct iomap_iter *iter,
return ret;
}
-static void fuse_iomap_submit_read(const struct iomap_iter *iter,
- struct iomap_read_folio_ctx *ctx)
-{
- struct fuse_fill_read_data *data = ctx->read_ctx;
-
- if (data->ia)
- fuse_send_readpages(data->ia, data->file, data->nr_bytes,
- data->fc->async_read);
-}
-
static const struct iomap_read_ops fuse_iomap_read_ops = {
.read_folio_range = fuse_iomap_read_folio_range_async,
- .submit_read = fuse_iomap_submit_read,
};
static int fuse_read_folio(struct file *file, struct folio *folio)
@@ -1116,6 +1105,9 @@ static void fuse_readahead(struct readahead_control *rac)
return;
iomap_readahead(&fuse_iomap_ops, &ctx, NULL);
+ if (data.ia)
+ fuse_send_readpages(data.ia, data.file, data.nr_bytes,
+ fc->async_read);
}
static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to)
diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c
index 4504f4633f17..dc8ac7e370a5 100644
--- a/fs/iomap/bio.c
+++ b/fs/iomap/bio.c
@@ -78,14 +78,24 @@ u32 iomap_finish_ioend_buffered_read(struct iomap_ioend *ioend)
return __iomap_read_end_io(&ioend->io_bio, ioend->io_error);
}
-static void iomap_bio_submit_read(const struct iomap_iter *iter,
- struct iomap_read_folio_ctx *ctx)
+void iomap_bio_submit_read_endio(const struct iomap_iter *iter,
+ struct iomap_read_folio_ctx *ctx, bio_end_io_t end_io)
{
struct bio *bio = ctx->read_ctx;
+ bio->bi_end_io = end_io;
if (iter->iomap.flags & IOMAP_F_INTEGRITY)
fs_bio_integrity_alloc(bio);
submit_bio(bio);
+
+ ctx->read_ctx = NULL;
+}
+EXPORT_SYMBOL_GPL(iomap_bio_submit_read_endio);
+
+static void iomap_bio_submit_read(const struct iomap_iter *iter,
+ struct iomap_read_folio_ctx *ctx)
+{
+ return iomap_bio_submit_read_endio(iter, ctx, iomap_read_end_io);
}
static struct bio_set *iomap_read_bio_set(struct iomap_read_folio_ctx *ctx)
@@ -127,7 +137,6 @@ static void iomap_read_alloc_bio(const struct iomap_iter *iter,
if (ctx->rac)
bio->bi_opf |= REQ_RAHEAD;
bio->bi_iter.bi_sector = iomap_sector(iomap, iter->pos);
- bio->bi_end_io = iomap_read_end_io;
bio_add_folio_nofail(bio, folio, plen,
offset_in_folio(folio, iter->pos));
ctx->read_ctx = bio;
diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index 8d4806dc46d4..276720bc18dc 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -642,12 +642,12 @@ void iomap_read_folio(const struct iomap_ops *ops,
fsverity_readahead(ctx->vi, folio->index,
folio_nr_pages(folio));
- while ((ret = iomap_iter(&iter, ops)) > 0)
+ while ((ret = iomap_iter(&iter, ops)) > 0) {
iter.status = iomap_read_folio_iter(&iter, ctx,
&bytes_submitted);
-
- if (ctx->read_ctx && ctx->ops->submit_read)
- ctx->ops->submit_read(&iter, ctx);
+ if (ctx->read_ctx && ctx->ops->submit_read)
+ ctx->ops->submit_read(&iter, ctx);
+ }
if (ctx->cur_folio)
iomap_read_end(ctx->cur_folio, bytes_submitted);
@@ -718,12 +718,12 @@ void iomap_readahead(const struct iomap_ops *ops,
fsverity_readahead(ctx->vi, readahead_index(rac),
readahead_count(rac));
- while (iomap_iter(&iter, ops) > 0)
+ while (iomap_iter(&iter, ops) > 0) {
iter.status = iomap_readahead_iter(&iter, ctx,
&cur_bytes_submitted);
-
- if (ctx->read_ctx && ctx->ops->submit_read)
- ctx->ops->submit_read(&iter, ctx);
+ if (ctx->read_ctx && ctx->ops->submit_read)
+ ctx->ops->submit_read(&iter, ctx);
+ }
if (ctx->cur_folio)
iomap_read_end(ctx->cur_folio, cur_bytes_submitted);
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index b485e3b191da..e2cd5f92babe 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -369,7 +369,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
*/
if ((op & REQ_ATOMIC) && WARN_ON_ONCE(ret != iomap_length(iter))) {
ret = -EINVAL;
- goto out_put_bio;
+ goto out_bio_release_pages;
}
if (iter->iomap.flags & IOMAP_F_INTEGRITY) {
@@ -393,6 +393,11 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
iomap_dio_submit_bio(iter, dio, bio, pos);
return ret;
+out_bio_release_pages:
+ if (dio->flags & IOMAP_DIO_BOUNCE)
+ bio_iov_iter_unbounce(bio, true, false);
+ else
+ bio_release_pages(bio, false);
out_put_bio:
bio_put(bio);
return ret;
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index f7c3e0c70fd7..0565328764c1 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -298,8 +298,12 @@ new_ioend:
* appending writes.
*/
ioend->io_size += map_len;
- if (ioend->io_offset + ioend->io_size > end_pos)
- ioend->io_size = end_pos - ioend->io_offset;
+ if (ioend->io_offset + ioend->io_size > end_pos) {
+ if (ioend->io_offset >= end_pos)
+ ioend->io_size = 0;
+ else
+ ioend->io_size = end_pos - ioend->io_offset;
+ }
wbc_account_cgroup_owner(wpc->wbc, folio, map_len);
return map_len;
diff --git a/fs/minix/minix.h b/fs/minix/minix.h
index f2025c9b5825..9e52d4302f0d 100644
--- a/fs/minix/minix.h
+++ b/fs/minix/minix.h
@@ -97,7 +97,7 @@ static inline struct minix_inode_info *minix_i(struct inode *inode)
static inline unsigned minix_blocks_needed(unsigned bits, unsigned blocksize)
{
- return DIV_ROUND_UP(bits, blocksize * 8);
+ return DIV_ROUND_UP_POW2(bits, blocksize * 8);
}
#if defined(CONFIG_MINIX_FS_NATIVE_ENDIAN) && \
diff --git a/fs/namei.c b/fs/namei.c
index 5cc9f0f466b8..19ce43c9a6e6 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -4736,6 +4736,10 @@ int vfs_tmpfile(struct mnt_idmap *idmap,
int error;
int open_flag = file->f_flags;
+ /* A tmpfile is I_LINKABLE, so guard its owner like may_o_create(). */
+ if (!fsuidgid_has_mapping(dir->i_sb, idmap))
+ return -EOVERFLOW;
+
/* we want directory to be writable */
error = inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC);
if (error)
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 76d0f6a29aba..24a8a5418e31 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -659,7 +659,7 @@ retry:
* within the cache granule containing the EOF, in which case we need
* to preload the granule.
*/
- if (!netfs_is_cache_enabled(ctx) &&
+ if (!netfs_is_cache_maybe_enabled(ctx) &&
netfs_skip_folio_read(folio, pos, len, false)) {
netfs_stat(&netfs_n_rh_write_zskip);
goto have_folio_no_wait;
diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c
index 6bde3320bcec..2cdb68e6b16f 100644
--- a/fs/netfs/buffered_write.c
+++ b/fs/netfs/buffered_write.c
@@ -277,7 +277,7 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
* caching service temporarily because the backing store got
* culled.
*/
- if (netfs_is_cache_enabled(ctx)) {
+ if (netfs_is_cache_maybe_enabled(ctx)) {
if (finfo) {
netfs_stat(&netfs_n_wh_wstream_conflict);
goto flush_content;
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
index 25f8ceb15fad..c16fbad286a1 100644
--- a/fs/netfs/direct_write.c
+++ b/fs/netfs/direct_write.c
@@ -166,13 +166,16 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
*/
subreq->error = -EAGAIN;
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
- if (subreq->transferred > 0)
+ if (subreq->transferred > 0) {
iov_iter_advance(&wreq->buffer.iter, subreq->transferred);
+ wreq->transferred += subreq->transferred;
+ }
if (stream->source == NETFS_UPLOAD_TO_SERVER &&
wreq->netfs_ops->retry_request)
wreq->netfs_ops->retry_request(wreq, stream);
+ __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
__clear_bit(NETFS_SREQ_BOUNDARY, &subreq->flags);
__clear_bit(NETFS_SREQ_FAILED, &subreq->flags);
@@ -186,17 +189,10 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
- if (stream->prepare_write) {
+ if (stream->prepare_write)
stream->prepare_write(subreq);
- __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
- netfs_stat(&netfs_n_wh_retry_write_subreq);
- } else {
- struct iov_iter source;
-
- netfs_reset_iter(subreq);
- source = subreq->io_iter;
- netfs_reissue_write(stream, subreq, &source);
- }
+ __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
+ netfs_stat(&netfs_n_wh_retry_write_subreq);
}
netfs_unbuffered_write_done(wreq);
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index 645996ecfc80..d889caa401dc 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -239,6 +239,18 @@ static inline bool netfs_is_cache_enabled(struct netfs_inode *ctx)
#endif
}
+static inline bool netfs_is_cache_maybe_enabled(struct netfs_inode *ctx)
+{
+#if IS_ENABLED(CONFIG_FSCACHE)
+ struct fscache_cookie *cookie = ctx->cache;
+
+ return fscache_cookie_valid(cookie) &&
+ test_bit(FSCACHE_COOKIE_IS_CACHING, &cookie->flags);
+#else
+ return false;
+#endif
+}
+
/*
* Get a ref on a netfs group attached to a dirty page (e.g. a ceph snap).
*/
diff --git a/fs/netfs/locking.c b/fs/netfs/locking.c
index 2249ecd09d0a..4e3be2b81504 100644
--- a/fs/netfs/locking.c
+++ b/fs/netfs/locking.c
@@ -9,6 +9,11 @@
#include <linux/netfs.h>
#include "internal.h"
+struct netfs_wb_waiter {
+ struct list_head link; /* Link in ictx->wb_queue */
+ struct task_struct *waiter; /* Waiter task; cleared when lock granted */
+};
+
/*
* inode_dio_wait_interruptible - wait for outstanding DIO requests to finish
* @inode: inode to wait for
@@ -203,3 +208,93 @@ void netfs_end_io_direct(struct inode *inode)
up_read(&inode->i_rwsem);
}
EXPORT_SYMBOL(netfs_end_io_direct);
+
+/*
+ * Wait to have exclusive access to writeback.
+ */
+static bool netfs_wb_begin_wait(struct netfs_inode *ictx)
+{
+ struct netfs_wb_waiter waiter = {};
+ struct task_struct *tsk = current;
+ bool got = false;
+
+ spin_lock(&ictx->lock);
+
+ if (test_and_set_bit_lock(NETFS_ICTX_WB_LOCK, &ictx->flags)) {
+ get_task_struct(tsk);
+ waiter.waiter = tsk;
+ list_add_tail(&waiter.link, &ictx->wb_queue);
+ } else {
+ got = true;
+ }
+ spin_unlock(&ictx->lock);
+
+ if (!got) {
+ for (;;) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ /* Read waiter before accessing inode state. */
+ if (smp_load_acquire(&waiter.waiter) == NULL)
+ break;
+ schedule();
+ }
+ }
+ __set_current_state(TASK_RUNNING);
+ return true;
+}
+
+/**
+ * netfs_wb_begin - Begin writeback, waiting if need be
+ * @ictx: The inode to get writeback access on
+ * @nowait: Return failure immediately rather than waiting if true
+ *
+ * Begin writeback to an inode, waiting for exclusive access if @nowait is
+ * false. This prevents collection from being done out of order with respect
+ * to the issuance of write subrequests.
+ *
+ * Note that writeback may be ended in a different process (e.g. the collection
+ * function on a workqueue) than started it.
+ *
+ * Return: True if can proceed, false if denied.
+ */
+bool netfs_wb_begin(struct netfs_inode *ictx, bool nowait)
+{
+ if (!test_and_set_bit_lock(NETFS_ICTX_WB_LOCK, &ictx->flags))
+ return true;
+ if (nowait) {
+ netfs_stat(&netfs_n_wb_lock_skip);
+ return false;
+ }
+ netfs_stat(&netfs_n_wb_lock_wait);
+ return netfs_wb_begin_wait(ictx);
+}
+EXPORT_SYMBOL(netfs_wb_begin);
+
+/* netfs_wb_end - End writeback
+ * @ictx: The inode we have writeback access to
+ *
+ * End writeback access on an inode, waking up the next writeback request.
+ */
+void netfs_wb_end(struct netfs_inode *ictx)
+{
+ struct netfs_wb_waiter *waiter;
+ struct task_struct *tsk;
+
+ WARN_ON_ONCE(!test_bit(NETFS_ICTX_WB_LOCK, &ictx->flags));
+
+ spin_lock(&ictx->lock);
+
+ waiter = list_first_entry_or_null(&ictx->wb_queue, struct netfs_wb_waiter, link);
+ if (waiter) {
+ list_del(&waiter->link);
+ tsk = waiter->waiter;
+ /* Write inode state before clearing waiter. */
+ smp_store_release(&waiter->waiter, NULL);
+ wake_up_process(tsk);
+ put_task_struct(tsk);
+ } else {
+ clear_bit_unlock(NETFS_ICTX_WB_LOCK, &ictx->flags);
+ }
+
+ spin_unlock(&ictx->lock);
+}
+EXPORT_SYMBOL(netfs_wb_end);
diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c
index f59a70f3a086..2b42758e01ec 100644
--- a/fs/netfs/read_retry.c
+++ b/fs/netfs/read_retry.c
@@ -98,7 +98,12 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
goto abandon;
}
- list_for_each_continue(next, &stream->subrequests) {
+ for (;;) {
+ /* Read pointer to subreq before reading subreq state. */
+ next = smp_load_acquire(&next->next);
+ if (next == &stream->subrequests)
+ break;
+
subreq = list_entry(next, struct netfs_io_subrequest, rreq_link);
if (subreq->start + subreq->transferred != start + len ||
test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) ||
diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c
index 24fc2bb2f8a4..210eb8f3958d 100644
--- a/fs/netfs/write_collect.c
+++ b/fs/netfs/write_collect.c
@@ -408,6 +408,16 @@ bool netfs_write_collection(struct netfs_io_request *wreq)
netfs_wake_rreq_flag(wreq, NETFS_RREQ_IN_PROGRESS, netfs_rreq_trace_wake_ip);
/* As we cleared NETFS_RREQ_IN_PROGRESS, we acquired its ref. */
+ switch (wreq->origin) {
+ case NETFS_WRITEBACK:
+ case NETFS_WRITEBACK_SINGLE:
+ case NETFS_WRITETHROUGH:
+ netfs_wb_end(ictx);
+ break;
+ default:
+ break;
+ }
+
if (wreq->iocb) {
size_t written = min(wreq->transferred, wreq->len);
wreq->iocb->ki_pos += written;
diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c
index c03c7cc45e47..f2761c99795a 100644
--- a/fs/netfs/write_issue.c
+++ b/fs/netfs/write_issue.c
@@ -106,7 +106,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
_enter("R=%x", wreq->debug_id);
ictx = netfs_inode(wreq->inode);
- if (is_cacheable && netfs_is_cache_enabled(ictx))
+ if (is_cacheable)
fscache_begin_write_operation(&wreq->cache_resources, netfs_i_cookie(ictx));
if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE) < 0)
goto nomem;
@@ -551,14 +551,8 @@ int netfs_writepages(struct address_space *mapping,
struct folio *folio;
int error = 0;
- if (!mutex_trylock(&ictx->wb_lock)) {
- if (wbc->sync_mode == WB_SYNC_NONE) {
- netfs_stat(&netfs_n_wb_lock_skip);
- return 0;
- }
- netfs_stat(&netfs_n_wb_lock_wait);
- mutex_lock(&ictx->wb_lock);
- }
+ if (!netfs_wb_begin(ictx, wbc->sync_mode == WB_SYNC_NONE))
+ return 0;
/* Need the first folio to be able to set up the op. */
folio = writeback_iter(mapping, wbc, NULL, &error);
@@ -588,13 +582,13 @@ int netfs_writepages(struct address_space *mapping,
}
error = netfs_write_folio(wreq, wbc, folio);
- if (error < 0)
- break;
+ if (error == -ENOMEM) {
+ folio_redirty_for_writepage(wbc, folio);
+ folio_unlock(folio);
+ }
} while ((folio = writeback_iter(mapping, wbc, folio, &error)));
netfs_end_issue_write(wreq);
-
- mutex_unlock(&ictx->wb_lock);
netfs_wake_collector(wreq);
netfs_put_request(wreq, netfs_rreq_trace_put_return);
@@ -602,9 +596,16 @@ int netfs_writepages(struct address_space *mapping,
return error;
couldnt_start:
- netfs_kill_dirty_pages(mapping, wbc, folio);
+ if (error == -ENOMEM) {
+ folio_redirty_for_writepage(wbc, folio);
+ folio_unlock(folio);
+ folio = writeback_iter(mapping, wbc, folio, &error);
+ WARN_ON_ONCE(folio != NULL);
+ } else {
+ netfs_kill_dirty_pages(mapping, wbc, folio);
+ }
out:
- mutex_unlock(&ictx->wb_lock);
+ netfs_wb_end(ictx);
_leave(" = %d", error);
return error;
}
@@ -618,16 +619,17 @@ struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size_t len
struct netfs_io_request *wreq = NULL;
struct netfs_inode *ictx = netfs_inode(file_inode(iocb->ki_filp));
- mutex_lock(&ictx->wb_lock);
+ netfs_wb_begin(ictx, false);
wreq = netfs_create_write_req(iocb->ki_filp->f_mapping, iocb->ki_filp,
iocb->ki_pos, NETFS_WRITETHROUGH);
if (IS_ERR(wreq)) {
- mutex_unlock(&ictx->wb_lock);
+ netfs_wb_end(ictx);
return wreq;
}
wreq->io_streams[0].avail = true;
+ __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags);
trace_netfs_write(wreq, netfs_write_trace_writethrough);
return wreq;
}
@@ -685,7 +687,6 @@ int netfs_advance_writethrough(struct netfs_io_request *wreq, struct writeback_c
ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct writeback_control *wbc,
struct folio *writethrough_cache)
{
- struct netfs_inode *ictx = netfs_inode(wreq->inode);
ssize_t ret;
_enter("R=%x", wreq->debug_id);
@@ -699,8 +700,6 @@ ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct writeback_c
netfs_end_issue_write(wreq);
- mutex_unlock(&ictx->wb_lock);
-
if (wreq->iocb)
ret = -EIOCBQUEUED;
else
@@ -847,15 +846,10 @@ int netfs_writeback_single(struct address_space *mapping,
if (WARN_ON_ONCE(!iov_iter_is_folioq(iter)))
return -EIO;
- if (!mutex_trylock(&ictx->wb_lock)) {
- if (wbc->sync_mode == WB_SYNC_NONE) {
- /* The VFS will have undirtied the inode. */
- netfs_single_mark_inode_dirty(&ictx->inode);
- netfs_stat(&netfs_n_wb_lock_skip);
- return 1;
- }
- netfs_stat(&netfs_n_wb_lock_wait);
- mutex_lock(&ictx->wb_lock);
+ if (!netfs_wb_begin(ictx, wbc->sync_mode == WB_SYNC_NONE)) {
+ /* The VFS will have undirtied the inode. */
+ netfs_single_mark_inode_dirty(&ictx->inode);
+ return 1;
}
wreq = netfs_create_write_req(mapping, NULL, 0, NETFS_WRITEBACK_SINGLE);
@@ -893,7 +887,6 @@ stop:
smp_wmb(); /* Write lists before ALL_QUEUED. */
set_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags);
- mutex_unlock(&ictx->wb_lock);
netfs_wake_collector(wreq);
netfs_put_request(wreq, netfs_rreq_trace_put_return);
@@ -901,7 +894,7 @@ stop:
return ret;
couldnt_start:
- mutex_unlock(&ictx->wb_lock);
+ netfs_wb_end(ictx);
_leave(" = %d", ret);
return ret;
}
diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c
index 32735abfa03f..058bc7a166a5 100644
--- a/fs/netfs/write_retry.c
+++ b/fs/netfs/write_retry.c
@@ -72,7 +72,12 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
!test_bit(NETFS_SREQ_NEED_RETRY, &from->flags))
return;
- list_for_each_continue(next, &stream->subrequests) {
+ for (;;) {
+ /* Read pointer to subreq before reading subreq state. */
+ next = smp_load_acquire(&next->next);
+ if (next == &stream->subrequests)
+ break;
+
subreq = list_entry(next, struct netfs_io_subrequest, rreq_link);
if (subreq->start + subreq->transferred != start + len ||
test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) ||
diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
index 1fbf832ad165..f2bb56506046 100644
--- a/fs/ntfs/aops.c
+++ b/fs/ntfs/aops.c
@@ -38,11 +38,9 @@ static void ntfs_iomap_read_end_io(struct bio *bio)
}
static void ntfs_iomap_bio_submit_read(const struct iomap_iter *iter,
- struct iomap_read_folio_ctx *ctx)
+ struct iomap_read_folio_ctx *ctx)
{
- struct bio *bio = ctx->read_ctx;
- bio->bi_end_io = ntfs_iomap_read_end_io;
- submit_bio(bio);
+ iomap_bio_submit_read_endio(iter, ctx, ntfs_iomap_read_end_io);
}
static const struct iomap_read_ops ntfs_iomap_bio_read_ops = {
diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c
index c43101cc064d..0c9bd669117d 100644
--- a/fs/ntfs3/inode.c
+++ b/fs/ntfs3/inode.c
@@ -608,10 +608,7 @@ static void ntfs_iomap_read_end_io(struct bio *bio)
static void ntfs_iomap_bio_submit_read(const struct iomap_iter *iter,
struct iomap_read_folio_ctx *ctx)
{
- struct bio *bio = ctx->read_ctx;
-
- bio->bi_end_io = ntfs_iomap_read_end_io;
- submit_bio(bio);
+ iomap_bio_submit_read_endio(iter, ctx, ntfs_iomap_read_end_io);
}
static const struct iomap_read_ops ntfs_iomap_bio_read_ops = {
diff --git a/fs/orangefs/dir.c b/fs/orangefs/dir.c
index 6e2ebc8b9867..115b2c2f5269 100644
--- a/fs/orangefs/dir.c
+++ b/fs/orangefs/dir.c
@@ -191,7 +191,8 @@ static int fill_from_part(struct orangefs_dir_part *part,
{
const int offset = sizeof(struct orangefs_readdir_response_s);
struct orangefs_khandle *khandle;
- __u32 *len, padlen;
+ __u32 *len;
+ u64 padlen;
loff_t i;
char *s;
i = ctx->pos & ~PART_MASK;
@@ -215,8 +216,8 @@ static int fill_from_part(struct orangefs_dir_part *part,
* len is the size of the string itself. padlen is the
* total size of the encoded string.
*/
- padlen = (sizeof *len + *len + 1) +
- (8 - (sizeof *len + *len + 1)%8)%8;
+ padlen = (u64)sizeof *len + *len + 1;
+ padlen += (8 - padlen % 8) % 8;
if (part->len < i + padlen + sizeof *khandle)
goto next;
s = (void *)part + offset + i + sizeof *len;
diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 13cb60b52bd6..e963701b4c87 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -853,7 +853,7 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
{
struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
struct inode *udir = d_inode(c->destdir);
- struct dentry *temp, *upper;
+ struct dentry *temp, *upper, *newdentry = NULL;
struct file *tmpfile;
int err;
@@ -889,6 +889,14 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
err = PTR_ERR(upper);
if (!IS_ERR(upper)) {
err = ovl_do_link(ofs, temp, udir, upper);
+ if (!err) {
+ /*
+ * Record the linked dentry -- not the disconnected
+ * O_TMPFILE dentry -- so that ->d_revalidate() on
+ * the upper fs sees the real parent/name.
+ */
+ newdentry = dget(upper);
+ }
end_creating(upper);
}
@@ -903,7 +911,7 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
if (!c->metacopy)
ovl_set_upperdata(d_inode(c->dentry));
- ovl_inode_update(d_inode(c->dentry), dget(temp));
+ ovl_inode_update(d_inode(c->dentry), newdentry);
out:
ovl_end_write(c->dentry);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 00c69707bda9..bc71231cad53 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -783,8 +783,8 @@ static const struct address_space_operations ovl_aops = {
*
* This chain is valid:
* - inode->i_rwsem (inode_lock[2])
- * - upper_mnt->mnt_sb->s_writers (ovl_want_write[0])
* - OVL_I(inode)->lock (ovl_inode_lock[2])
+ * - upper_mnt->mnt_sb->s_writers (ovl_want_write[0])
* - OVL_I(lowerinode)->lock (ovl_inode_lock[1])
*
* And this chain is valid:
@@ -797,8 +797,8 @@ static const struct address_space_operations ovl_aops = {
* held, because it is in reverse order of the non-nested case using the same
* upper fs:
* - inode->i_rwsem (inode_lock[1])
- * - upper_mnt->mnt_sb->s_writers (ovl_want_write[0])
* - OVL_I(inode)->lock (ovl_inode_lock[1])
+ * - upper_mnt->mnt_sb->s_writers (ovl_want_write[0])
*/
#define OVL_MAX_NESTING FILESYSTEM_MAX_STACK_DEPTH
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index adc9b9a092b0..26086a283672 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -112,6 +112,8 @@ static bool pde_subdir_insert(struct proc_dir_entry *dir,
/* Add new node and rebalance tree. */
rb_link_node(&de->subdir_node, parent, new);
rb_insert_color(&de->subdir_node, root);
+ if (S_ISDIR(de->mode))
+ dir->nlink++;
return true;
}
@@ -404,7 +406,6 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
write_unlock(&proc_subdir_lock);
goto out_free_inum;
}
- dir->nlink++;
write_unlock(&proc_subdir_lock);
return dp;
@@ -706,6 +707,8 @@ static void pde_erase(struct proc_dir_entry *pde, struct proc_dir_entry *parent)
{
rb_erase(&pde->subdir_node, &parent->subdir);
RB_CLEAR_NODE(&pde->subdir_node);
+ if (S_ISDIR(pde->mode))
+ parent->nlink--;
}
/*
@@ -731,8 +734,6 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
de = NULL;
} else {
pde_erase(de, parent);
- if (S_ISDIR(de->mode))
- parent->nlink--;
}
}
write_unlock(&proc_subdir_lock);
@@ -791,8 +792,6 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)
continue;
}
next = de->parent;
- if (S_ISDIR(de->mode))
- next->nlink--;
write_unlock(&proc_subdir_lock);
proc_entry_rundown(de);
diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index 0e6a389a16bf..a932a1fea818 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -135,10 +135,10 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
u32 idx_limit = resctrl_arch_system_num_rmid_idx();
struct rmid_entry *entry;
+ bool rmid_dirty = true;
u32 idx, cur_idx = 1;
void *arch_mon_ctx;
void *arch_priv;
- bool rmid_dirty;
u64 val = 0;
arch_priv = mon_event_all[QOS_L3_OCCUP_EVENT_ID].arch_priv;
@@ -161,22 +161,27 @@ void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
break;
entry = __rmid_entry(idx);
- if (resctrl_arch_rmid_read(r, &d->hdr, entry->closid, entry->rmid,
- QOS_L3_OCCUP_EVENT_ID, arch_priv, &val,
- arch_mon_ctx)) {
- rmid_dirty = true;
- } else {
- rmid_dirty = (val >= resctrl_rmid_realloc_threshold);
+ if (!force_free) {
+ if (resctrl_arch_rmid_read(r, &d->hdr, entry->closid,
+ entry->rmid, QOS_L3_OCCUP_EVENT_ID,
+ arch_priv, &val, arch_mon_ctx)) {
+ rmid_dirty = true;
+ } else {
+ rmid_dirty = (val >= resctrl_rmid_realloc_threshold);
- /*
- * x86's CLOSID and RMID are independent numbers, so the entry's
- * CLOSID is an empty CLOSID (X86_RESCTRL_EMPTY_CLOSID). On Arm the
- * RMID (PMG) extends the CLOSID (PARTID) space with bits that aren't
- * used to select the configuration. It is thus necessary to track both
- * CLOSID and RMID because there may be dependencies between them
- * on some architectures.
- */
- trace_mon_llc_occupancy_limbo(entry->closid, entry->rmid, d->hdr.id, val);
+ /*
+ * x86's CLOSID and RMID are independent numbers,
+ * so the entry's CLOSID is an empty CLOSID
+ * (X86_RESCTRL_EMPTY_CLOSID). On Arm the RMID
+ * (PMG) extends the CLOSID (PARTID) space with
+ * bits that aren't used to select the configuration.
+ * It is thus necessary to track both CLOSID and
+ * RMID because there may be dependencies between
+ * them on some architectures.
+ */
+ trace_mon_llc_occupancy_limbo(entry->closid, entry->rmid,
+ d->hdr.id, val);
+ }
}
if (force_free || !rmid_dirty) {
diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h
index 901e1340c986..854e672a4e37 100644
--- a/fs/smb/client/cifsfs.h
+++ b/fs/smb/client/cifsfs.h
@@ -166,6 +166,6 @@ extern const struct export_operations cifs_export_ops;
#endif /* CONFIG_CIFS_NFSD_EXPORT */
/* when changing internal version - update following two lines at same time */
-#define SMB3_PRODUCT_BUILD 60
-#define CIFS_VERSION "2.60"
+#define SMB3_PRODUCT_BUILD 61
+#define CIFS_VERSION "2.61"
#endif /* _CIFSFS_H */
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index 8b25d6c9ec5e..5a25635bc62a 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -241,6 +241,7 @@ static void cifs_issue_read(struct netfs_io_subrequest *subreq)
return;
failed:
+ add_credits_and_wake_if(rdata->server, &rdata->credits, 0);
subreq->error = rc;
netfs_read_subreq_terminated(subreq);
}
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index d058584b8f05..95c0efe9d43b 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2396,9 +2396,9 @@ parse_posix_ctxt(struct create_context *cc, struct smb2_file_all_info *info,
memset(posix, 0, sizeof(*posix));
- posix->nlink = le32_to_cpu(*(__le32 *)(beg + 0));
- posix->reparse_tag = le32_to_cpu(*(__le32 *)(beg + 4));
- posix->mode = le32_to_cpu(*(__le32 *)(beg + 8));
+ posix->nlink = get_unaligned_le32(beg);
+ posix->reparse_tag = get_unaligned_le32(beg + 4);
+ posix->mode = get_unaligned_le32(beg + 8);
sid = beg + 12;
sid_len = posix_info_sid_size(sid, end);
@@ -5405,7 +5405,7 @@ int posix_info_sid_size(const void *beg, const void *end)
size_t subauth;
int total;
- if (beg + 1 > end)
+ if (beg + 2 > end)
return -1;
subauth = *(u8 *)(beg+1);
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index df0554a2c50d..88104f0cf363 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -67,6 +67,13 @@ struct ksmbd_work {
/* Number of granted credits */
unsigned int credits_granted;
+ /*
+ * Credit charge added to conn->outstanding_credits at receive time
+ * for the SMB2 PDU currently being processed, pending release. Zero
+ * once the charge has been returned (on the response or error path).
+ */
+ unsigned short credit_charge;
+
/* response smb header size */
unsigned int response_sz;
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 31dd9f3479b2..3c55ae5d6a11 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -278,10 +278,24 @@ struct oplock_info *opinfo_get(struct ksmbd_file *fp)
return opinfo;
}
-static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
+struct oplock_snapshot {
+ bool durable_open;
+ bool durable_detached;
+ unsigned long long fid;
+};
+
+static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci,
+ struct ksmbd_file *skip_fp,
+ struct oplock_snapshot *snapshot)
{
struct oplock_info *opinfo;
+ if (snapshot) {
+ snapshot->durable_open = false;
+ snapshot->durable_detached = false;
+ snapshot->fid = KSMBD_NO_FID;
+ }
+
down_read(&ci->m_lock);
opinfo = list_first_entry_or_null(&ci->m_op_list, struct oplock_info,
op_entry);
@@ -295,6 +309,16 @@ static struct oplock_info *opinfo_get_list(struct ksmbd_inode *ci)
opinfo = NULL;
}
}
+
+ if (opinfo && snapshot && opinfo->o_fp &&
+ opinfo->o_fp != skip_fp &&
+ READ_ONCE(opinfo->o_fp->is_durable)) {
+ snapshot->durable_open = true;
+ snapshot->durable_detached =
+ !READ_ONCE(opinfo->o_fp->conn) ||
+ !READ_ONCE(opinfo->o_fp->tcon);
+ snapshot->fid = opinfo->fid;
+ }
}
up_read(&ci->m_lock);
@@ -314,7 +338,7 @@ void opinfo_put(struct oplock_info *opinfo)
static bool ksmbd_inode_has_lease(struct ksmbd_inode *ci)
{
- struct oplock_info *opinfo = opinfo_get_list(ci);
+ struct oplock_info *opinfo = opinfo_get_list(ci, NULL, NULL);
bool is_lease;
if (!opinfo)
@@ -1180,6 +1204,36 @@ again:
return err;
}
+struct oplock_break_entry {
+ struct list_head list;
+ struct oplock_info *opinfo;
+};
+
+static int oplock_break_add(struct list_head *head, struct oplock_info *opinfo)
+{
+ struct oplock_break_entry *ent;
+
+ ent = kmalloc_obj(struct oplock_break_entry, KSMBD_DEFAULT_GFP);
+ if (!ent)
+ return -ENOMEM;
+
+ ent->opinfo = opinfo;
+ list_add_tail(&ent->list, head);
+ return 0;
+}
+
+static void oplock_break_drain_none(struct list_head *head)
+{
+ struct oplock_break_entry *ent, *tmp;
+
+ list_for_each_entry_safe(ent, tmp, head, list) {
+ oplock_break(ent->opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
+ list_del(&ent->list);
+ opinfo_put(ent->opinfo);
+ kfree(ent);
+ }
+}
+
void destroy_lease_table(struct ksmbd_conn *conn)
{
struct lease_table *lb, *lbtmp;
@@ -1289,6 +1343,7 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
{
struct oplock_info *opinfo;
struct ksmbd_inode *p_ci = NULL;
+ LIST_HEAD(brk_list);
if (lctx->version != 2)
return;
@@ -1314,12 +1369,14 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
continue;
}
- oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
- opinfo_put(opinfo);
+ if (oplock_break_add(&brk_list, opinfo))
+ opinfo_put(opinfo);
}
}
up_read(&p_ci->m_lock);
+ oplock_break_drain_none(&brk_list);
+
ksmbd_inode_put(p_ci);
}
@@ -1327,6 +1384,7 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
{
struct oplock_info *opinfo;
struct ksmbd_inode *p_ci = NULL;
+ LIST_HEAD(brk_list);
rcu_read_lock();
opinfo = rcu_dereference(fp->f_opinfo);
@@ -1355,12 +1413,14 @@ void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
continue;
}
- oplock_break(opinfo, SMB2_OPLOCK_LEVEL_NONE, NULL, false);
- opinfo_put(opinfo);
+ if (oplock_break_add(&brk_list, opinfo))
+ opinfo_put(opinfo);
}
}
up_read(&p_ci->m_lock);
+ oplock_break_drain_none(&brk_list);
+
ksmbd_inode_put(p_ci);
}
@@ -1385,6 +1445,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
struct oplock_info *opinfo = NULL, *prev_opinfo = NULL;
struct ksmbd_inode *ci = fp->f_ci;
struct lease_table *new_lb = NULL;
+ struct oplock_snapshot prev_op_snapshot;
bool prev_op_has_lease;
bool prev_durable_open = false;
bool prev_durable_detached = false;
@@ -1452,7 +1513,7 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto out;
}
}
- prev_opinfo = opinfo_get_list(ci);
+ prev_opinfo = opinfo_get_list(ci, fp, &prev_op_snapshot);
if (!prev_opinfo ||
(prev_opinfo->level == SMB2_OPLOCK_LEVEL_NONE && lctx)) {
opinfo_put(prev_opinfo);
@@ -1474,13 +1535,9 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
goto op_break_not_needed;
}
- if (prev_opinfo->o_fp && prev_opinfo->o_fp != fp &&
- prev_opinfo->o_fp->is_durable) {
- prev_durable_open = true;
- prev_durable_detached = !prev_opinfo->o_fp->conn ||
- !prev_opinfo->o_fp->tcon;
- prev_fid = prev_opinfo->fid;
- }
+ prev_durable_open = prev_op_snapshot.durable_open;
+ prev_durable_detached = prev_op_snapshot.durable_detached;
+ prev_fid = prev_op_snapshot.fid;
err = oplock_break(prev_opinfo, break_level, work,
share_ret < 0 && prev_opinfo->is_lease);
@@ -1571,7 +1628,7 @@ static bool smb_break_all_write_oplock(struct ksmbd_work *work,
struct oplock_info *brk_opinfo;
bool sent_break = false;
- brk_opinfo = opinfo_get_list(fp->f_ci);
+ brk_opinfo = opinfo_get_list(fp->f_ci, NULL, NULL);
if (!brk_opinfo)
return false;
if (brk_opinfo->level != SMB2_OPLOCK_LEVEL_BATCH &&
@@ -1602,9 +1659,11 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
bool send_interim, bool send_oplock_break)
{
struct oplock_info *op, *brk_op;
+ struct oplock_break_entry *ent, *tmp;
struct ksmbd_inode *ci;
struct ksmbd_conn *conn = work->conn;
bool sent_interim = false;
+ LIST_HEAD(brk_list);
if (!test_share_config_flag(work->tcon->share_conf,
KSMBD_SHARE_FLAG_OPLOCKS))
@@ -1646,6 +1705,22 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
SMB2_LEASE_KEY_SIZE))
goto next;
brk_op->open_trunc = is_trunc;
+
+ /*
+ * Defer the break until ci->m_lock is released: oplock_break()
+ * may block waiting for the lease break acknowledgment, and the
+ * close that wakes that wait needs ci->m_lock for write.
+ */
+ if (!oplock_break_add(&brk_list, brk_op))
+ continue;
+next:
+ opinfo_put(brk_op);
+ }
+ up_read(&ci->m_lock);
+
+ list_for_each_entry_safe(ent, tmp, &brk_list, list) {
+ brk_op = ent->opinfo;
+
if (!brk_op->is_lease && !send_oplock_break) {
brk_op->level = SMB2_OPLOCK_LEVEL_NONE;
brk_op->op_state = OPLOCK_STATE_NONE;
@@ -1657,10 +1732,10 @@ static void __smb_break_all_levII_oplock(struct ksmbd_work *work,
false);
}
sent_interim = true;
-next:
+ list_del(&ent->list);
opinfo_put(brk_op);
+ kfree(ent);
}
- up_read(&ci->m_lock);
if (op)
opinfo_put(op);
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 36feda7e0942..36a5ea4828ad 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -242,6 +242,20 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
} while (is_chained == true);
send:
+ /*
+ * Release any credit charge still outstanding for this request. On
+ * the normal path smb2_set_rsp_credits() already returned it, but the
+ * abort, error and send-no-response paths skip that call, so the
+ * charge would otherwise leak and eventually exhaust the connection's
+ * outstanding credit window.
+ */
+ if (work->credit_charge) {
+ spin_lock(&conn->credits_lock);
+ conn->outstanding_credits -= work->credit_charge;
+ work->credit_charge = 0;
+ spin_unlock(&conn->credits_lock);
+ }
+
if (work->tcon)
ksmbd_tree_connect_put(work->tcon);
smb3_preauth_hash_rsp(work);
diff --git a/fs/smb/server/smb2misc.c b/fs/smb/server/smb2misc.c
index a1ddca21c47b..c0c4edd092c2 100644
--- a/fs/smb/server/smb2misc.c
+++ b/fs/smb/server/smb2misc.c
@@ -261,8 +261,12 @@ calc_size_exit:
static inline int smb2_query_info_req_len(struct smb2_query_info_req *h)
{
- return le32_to_cpu(h->InputBufferLength) +
- le32_to_cpu(h->OutputBufferLength);
+ return le32_to_cpu(h->InputBufferLength);
+}
+
+static inline int smb2_query_info_resp_len(struct smb2_query_info_req *h)
+{
+ return le32_to_cpu(h->OutputBufferLength);
}
static inline int smb2_set_info_req_len(struct smb2_set_info_req *h)
@@ -297,9 +301,10 @@ static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h)
le32_to_cpu(h->MaxOutputResponse);
}
-static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
+static int smb2_validate_credit_charge(struct ksmbd_work *work,
struct smb2_hdr *hdr)
{
+ struct ksmbd_conn *conn = work->conn;
unsigned int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len;
unsigned short credit_charge = le16_to_cpu(hdr->CreditCharge);
void *__hdr = hdr;
@@ -308,6 +313,7 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
switch (hdr->Command) {
case SMB2_QUERY_INFO:
req_len = smb2_query_info_req_len(__hdr);
+ expect_resp_len = smb2_query_info_resp_len(__hdr);
break;
case SMB2_SET_INFO:
req_len = smb2_set_info_req_len(__hdr);
@@ -356,8 +362,10 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
ksmbd_debug(SMB, "Limits exceeding the maximum allowable outstanding requests, given : %u, pending : %u\n",
credit_charge, conn->outstanding_credits);
ret = 1;
- } else
+ } else {
conn->outstanding_credits += credit_charge;
+ work->credit_charge = credit_charge;
+ }
spin_unlock(&conn->credits_lock);
@@ -460,7 +468,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
validate_credit:
if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
- smb2_validate_credit_charge(work->conn, hdr))
+ smb2_validate_credit_charge(work, hdr))
return 1;
return 0;
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 5859fa68bb84..097f51fc7ed6 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -363,6 +363,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
conn->total_credits -= credit_charge;
conn->outstanding_credits -= credit_charge;
+ work->credit_charge = 0;
credits_requested = max_t(unsigned short,
le16_to_cpu(req_hdr->CreditRequest), 1);
@@ -6920,16 +6921,18 @@ static int smb2_set_info_file(struct ksmbd_work *work, struct ksmbd_file *fp,
}
case FILE_LINK_INFORMATION:
{
- if (!(fp->daccess & FILE_DELETE_LE)) {
- pr_err("no right to delete : 0x%x\n", fp->daccess);
- return -EACCES;
- }
+ struct smb2_file_link_info *file_info;
if (buf_len < sizeof(struct smb2_file_link_info))
return -EMSGSIZE;
- return smb2_create_link(work, work->tcon->share_conf,
- (struct smb2_file_link_info *)buffer,
+ file_info = (struct smb2_file_link_info *)buffer;
+ if (file_info->ReplaceIfExists && !(fp->daccess & FILE_DELETE_LE)) {
+ pr_err("no right to delete : 0x%x\n", fp->daccess);
+ return -EACCES;
+ }
+
+ return smb2_create_link(work, work->tcon->share_conf, file_info,
buf_len, fp->filp,
work->conn->local_nls);
}
@@ -7324,7 +7327,7 @@ int smb2_read(struct ksmbd_work *work)
ksmbd_debug(SMB, "filename %pD, offset %lld, len %zu\n",
fp->filp, offset, length);
- aux_payload_buf = kvzalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
+ aux_payload_buf = kvmalloc(ALIGN(length, 8), KSMBD_DEFAULT_GFP);
if (!aux_payload_buf) {
err = -ENOMEM;
goto out;
diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c
index 340ea98fa494..9c59c8f73b66 100644
--- a/fs/smb/server/smbacl.c
+++ b/fs/smb/server/smbacl.c
@@ -374,6 +374,7 @@ static void parse_dacl(struct mnt_idmap *idmap,
{
int i, ret;
u16 num_aces = 0;
+ u16 dacl_size;
unsigned int acl_size;
char *acl_base;
struct smb_ace **ppace;
@@ -403,7 +404,11 @@ static void parse_dacl(struct mnt_idmap *idmap,
if (num_aces <= 0)
return;
- if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
+ dacl_size = le16_to_cpu(pdacl->size);
+ if (dacl_size < sizeof(struct smb_acl))
+ return;
+
+ if (num_aces > (dacl_size - sizeof(struct smb_acl)) /
(offsetof(struct smb_ace, sid) +
offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
return;
@@ -740,12 +745,18 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap,
if (nt_ace_size > aces_size)
break;
+ if (ntace->sid.num_subauth == 0 ||
+ ntace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES)
+ goto next_ace;
+
memcpy((char *)pndace + size, ntace, nt_ace_size);
if (check_add_overflow(size, nt_ace_size, &size))
break;
+ num_aces++;
+
+next_ace:
aces_size -= nt_ace_size;
ntace = (struct smb_ace *)((char *)ntace + nt_ace_size);
- num_aces++;
}
}
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index f5fa22d87603..d0a0ad15d803 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1487,8 +1487,8 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn,
if (rc < 0)
pr_err("Failed to store XATTR ntacl :%d\n", rc);
- kfree(sd_ndr.data);
out:
+ kfree(sd_ndr.data);
kfree(acl_ndr.data);
kfree(smb_acl);
kfree(def_smb_acl);
@@ -1504,7 +1504,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
struct ndr n;
struct inode *inode = d_inode(dentry);
struct ndr acl_ndr = {0};
- struct xattr_ntacl acl;
+ struct xattr_ntacl acl = {0};
struct xattr_smb_acl *smb_acl = NULL, *def_smb_acl = NULL;
__u8 cmp_hash[XATTR_SD_HASH_SIZE] = {0};
@@ -1515,7 +1515,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
n.length = rc;
rc = ndr_decode_v4_ntacl(&n, &acl);
if (rc)
- goto free_n_data;
+ goto out_free;
smb_acl = ksmbd_vfs_make_xattr_posix_acl(idmap, inode,
ACL_TYPE_ACCESS);
@@ -1541,6 +1541,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn,
*pntsd = acl.sd_buf;
if (acl.sd_size < sizeof(struct smb_ntsd)) {
pr_err("sd size is invalid\n");
+ rc = -EINVAL;
goto out_free;
}
@@ -1560,8 +1561,6 @@ out_free:
kfree(acl.sd_buf);
*pntsd = NULL;
}
-
-free_n_data:
kfree(n.data);
return rc;
}
@@ -1576,14 +1575,15 @@ int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap,
err = ndr_encode_dos_attr(&n, da);
if (err)
- return err;
+ goto out;
err = ksmbd_vfs_setxattr(idmap, path, XATTR_NAME_DOS_ATTRIBUTE,
(void *)n.data, n.offset, 0, get_write);
if (err)
ksmbd_debug(SMB, "failed to store dos attribute in xattr\n");
- kfree(n.data);
+out:
+ kfree(n.data);
return err;
}
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index fde22742d193..d95c405eab11 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -581,7 +581,20 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
if (fp->conn || !fp->is_durable ||
fp->f_state != FP_INITED)
continue;
- list_move_tail(&fp->node, &dispose);
+
+ /*
+ * Claim the close before unlinking fp from m_fp_list.
+ * refcount == 1 means only the durable lifetime ref is
+ * left. Add a transient ref so final close can drop both.
+ */
+ write_lock(&global_ft.lock);
+ if (atomic_read(&fp->refcount) == 1) {
+ atomic_inc(&fp->refcount);
+ __ksmbd_remove_durable_fd(fp);
+ ksmbd_mark_fp_closed(fp);
+ list_move_tail(&fp->node, &dispose);
+ }
+ write_unlock(&global_ft.lock);
}
}
up_write(&ci->m_lock);
@@ -589,16 +602,18 @@ bool ksmbd_close_disconnected_durable_delete_on_close(struct dentry *dentry)
/*
* Drop our lookup reference before closing so the last __ksmbd_close_fd()
* can drop m_count to zero and unlink the delete-on-close file. The
- * collected handles still hold references, so ci stays valid until they
- * are closed below.
+ * collected handles still hold the transient reference taken above, so
+ * ci stays valid until they are closed below.
*/
ksmbd_inode_put(ci);
while (!list_empty(&dispose)) {
fp = list_first_entry(&dispose, struct ksmbd_file, node);
list_del_init(&fp->node);
- __ksmbd_close_fd(NULL, fp);
- closed = true;
+ if (atomic_sub_and_test(2, &fp->refcount)) {
+ __ksmbd_close_fd(NULL, fp);
+ closed = true;
+ }
}
return closed;
@@ -838,19 +853,24 @@ int ksmbd_close_fd_app_instance_id(char *app_instance_id)
return 0;
opinfo = opinfo_get(fp);
- if (!opinfo || !opinfo->sess)
+ if (!opinfo)
goto out;
+ down_read(&fp->f_ci->m_lock);
+ if (!opinfo->conn) {
+ up_read(&fp->f_ci->m_lock);
+ goto out;
+ }
+
ft = &opinfo->sess->file_table;
write_lock(&ft->lock);
- if (fp->f_state == FP_INITED) {
- if (has_file_id(fp->volatile_id)) {
- idr_remove(ft->idr, fp->volatile_id);
- fp->volatile_id = KSMBD_NO_FID;
- }
+ if (fp->f_state == FP_INITED && has_file_id(fp->volatile_id)) {
+ idr_remove(ft->idr, fp->volatile_id);
+ fp->volatile_id = KSMBD_NO_FID;
n_to_drop = ksmbd_mark_fp_closed(fp);
}
write_unlock(&ft->lock);
+ up_read(&fp->f_ci->m_lock);
opinfo_put(opinfo);
opinfo = NULL;
@@ -1461,16 +1481,21 @@ void ksmbd_stop_durable_scavenger(void)
static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
{
+ char *name;
+
if (!user)
return -EINVAL;
/* Duplicate the user name to ensure identity persistence */
- fp->owner.name = kstrdup(user->name, GFP_KERNEL);
- if (!fp->owner.name)
+ name = kstrdup(user->name, GFP_KERNEL);
+ if (!name)
return -ENOMEM;
+ spin_lock(&fp->f_lock);
fp->owner.uid = user->uid;
fp->owner.gid = user->gid;
+ fp->owner.name = name;
+ spin_unlock(&fp->f_lock);
return 0;
}
@@ -1488,18 +1513,24 @@ static int ksmbd_vfs_copy_durable_owner(struct ksmbd_file *fp,
bool ksmbd_vfs_compare_durable_owner(struct ksmbd_file *fp,
struct ksmbd_user *user)
{
- if (!user || !fp->owner.name)
+ bool ret = false;
+
+ if (!user)
return false;
+ spin_lock(&fp->f_lock);
+ if (!fp->owner.name)
+ goto out;
+
/* Check if the UID and GID match first (fast path) */
if (fp->owner.uid != user->uid || fp->owner.gid != user->gid)
- return false;
+ goto out;
/* Validate the account name to ensure the same SecurityContext */
- if (strcmp(fp->owner.name, user->name))
- return false;
-
- return true;
+ ret = (strcmp(fp->owner.name, user->name) == 0);
+out:
+ spin_unlock(&fp->f_lock);
+ return ret;
}
static bool session_fd_check(struct ksmbd_tree_connect *tcon,
@@ -1530,11 +1561,13 @@ static bool session_fd_check(struct ksmbd_tree_connect *tcon,
conn = fp->conn;
ci = fp->f_ci;
down_write(&ci->m_lock);
- list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry,
+ lockdep_is_held(&ci->m_lock)) {
if (op->conn != conn)
continue;
ksmbd_conn_put(op->conn);
op->conn = NULL;
+ op->sess = NULL;
}
up_write(&ci->m_lock);
@@ -1685,16 +1718,20 @@ int ksmbd_reopen_durable_fd(struct ksmbd_work *work, struct ksmbd_file *fp)
ci = fp->f_ci;
down_write(&ci->m_lock);
- list_for_each_entry_rcu(op, &ci->m_op_list, op_entry) {
+ list_for_each_entry_rcu(op, &ci->m_op_list, op_entry,
+ lockdep_is_held(&ci->m_lock)) {
if (op->conn)
continue;
op->conn = ksmbd_conn_get(fp->conn);
+ op->sess = work->sess;
}
up_write(&ci->m_lock);
+ spin_lock(&fp->f_lock);
fp->owner.uid = fp->owner.gid = 0;
kfree(fp->owner.name);
fp->owner.name = NULL;
+ spin_unlock(&fp->f_lock);
return 0;
}
diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c
index ce767b40482f..bbada0d3cc08 100644
--- a/fs/xfs/libxfs/xfs_dquot_buf.c
+++ b/fs/xfs/libxfs/xfs_dquot_buf.c
@@ -436,17 +436,27 @@ xfs_dqinode_metadir_create(
error = xfs_metadir_create(&upd, S_IFREG);
if (error)
- return error;
+ goto out_cancel;
xfs_trans_log_inode(upd.tp, upd.ip, XFS_ILOG_CORE);
error = xfs_metadir_commit(&upd);
if (error)
- return error;
+ goto out_irele;
xfs_finish_inode_setup(upd.ip);
*ipp = upd.ip;
return 0;
+
+out_cancel:
+ xfs_metadir_cancel(&upd, error);
+out_irele:
+ /* Have to finish setting up the inode to ensure it's deleted. */
+ if (upd.ip) {
+ xfs_finish_inode_setup(upd.ip);
+ xfs_irele(upd.ip);
+ }
+ return error;
}
#ifndef __KERNEL__
diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c
index 7114c9a44182..2554494847ff 100644
--- a/fs/xfs/scrub/agheader_repair.c
+++ b/fs/xfs/scrub/agheader_repair.c
@@ -652,7 +652,7 @@ xrep_agfl_fill(
while (agbno < start + len && af->fl_off < af->flcount)
af->agfl_bno[af->fl_off++] = cpu_to_be32(agbno++);
- error = xagb_bitmap_set(&af->used_extents, start, agbno - 1);
+ error = xagb_bitmap_set(&af->used_extents, start, agbno - start);
if (error)
return error;
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 2a0c54256e93..51293b6f331f 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -764,8 +764,7 @@ xfs_bio_submit_read(
/* defer read completions to the ioend workqueue */
iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, 0);
- bio->bi_end_io = xfs_end_bio;
- submit_bio(bio);
+ iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);
}
static const struct iomap_read_ops xfs_iomap_read_ops = {
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 3ce12fe1c307..e1465e950acc 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -121,6 +121,22 @@ xfs_buf_free(
}
static int
+xfs_buf_alloc_folio(
+ struct xfs_buf *bp,
+ size_t size,
+ gfp_t gfp_mask)
+{
+ struct folio *folio;
+
+ folio = folio_alloc(gfp_mask, get_order(size));
+ if (!folio)
+ return -ENOMEM;
+ bp->b_addr = folio_address(folio);
+ trace_xfs_buf_backing_folio(bp, _RET_IP_);
+ return 0;
+}
+
+static int
xfs_buf_alloc_kmem(
struct xfs_buf *bp,
size_t size,
@@ -129,7 +145,7 @@ xfs_buf_alloc_kmem(
ASSERT(is_power_of_2(size));
ASSERT(size < PAGE_SIZE);
- bp->b_addr = kmalloc(size, gfp_mask | __GFP_NOFAIL);
+ bp->b_addr = kmalloc(size, gfp_mask);
if (!bp->b_addr)
return -ENOMEM;
@@ -148,6 +164,26 @@ xfs_buf_alloc_kmem(
return 0;
}
+static int
+xfs_buf_alloc_vmalloc(
+ struct xfs_buf *bp,
+ size_t size,
+ gfp_t gfp_mask)
+{
+ for (;;) {
+ bp->b_addr = __vmalloc(size, gfp_mask);
+ if (bp->b_addr)
+ break;
+ if (gfp_mask & __GFP_NORETRY)
+ return -ENOMEM;
+ XFS_STATS_INC(bp->b_mount, xb_page_retries);
+ memalloc_retry_wait(gfp_mask);
+ }
+
+ trace_xfs_buf_backing_vmalloc(bp, _RET_IP_);
+ return 0;
+}
+
/*
* Allocate backing memory for a buffer.
*
@@ -175,7 +211,6 @@ xfs_buf_alloc_backing_mem(
{
size_t size = BBTOB(bp->b_length);
gfp_t gfp_mask = GFP_KERNEL | __GFP_NOLOCKDEP | __GFP_NOWARN;
- struct folio *folio;
if (xfs_buftarg_is_mem(bp->b_target))
return xmbuf_map_backing_mem(bp);
@@ -188,22 +223,6 @@ xfs_buf_alloc_backing_mem(
gfp_mask |= __GFP_NORETRY;
/*
- * For buffers smaller than PAGE_SIZE use a kmalloc allocation if that
- * is properly aligned. The slab allocator now guarantees an aligned
- * allocation for all power of two sizes, which matches most of the
- * smaller than PAGE_SIZE buffers used by XFS.
- */
- if (size < PAGE_SIZE && is_power_of_2(size))
- return xfs_buf_alloc_kmem(bp, size, gfp_mask);
-
- /*
- * Don't bother with the retry loop for single PAGE allocations: vmalloc
- * won't do any better.
- */
- if (size <= PAGE_SIZE)
- gfp_mask |= __GFP_NOFAIL;
-
- /*
* Optimistically attempt a single high order folio allocation for
* larger than PAGE_SIZE buffers.
*
@@ -215,35 +234,31 @@ xfs_buf_alloc_backing_mem(
* path for them instead of wasting memory here.
*/
if (size > PAGE_SIZE) {
- if (!is_power_of_2(size))
- goto fallback;
- gfp_mask &= ~__GFP_DIRECT_RECLAIM;
- gfp_mask |= __GFP_NORETRY;
- }
- folio = folio_alloc(gfp_mask, get_order(size));
- if (!folio) {
- if (size <= PAGE_SIZE)
- return -ENOMEM;
- trace_xfs_buf_backing_fallback(bp, _RET_IP_);
- goto fallback;
- }
- bp->b_addr = folio_address(folio);
- trace_xfs_buf_backing_folio(bp, _RET_IP_);
- return 0;
+ if (is_power_of_2(size)) {
+ gfp_t folio_gfp = gfp_mask;
-fallback:
- for (;;) {
- bp->b_addr = __vmalloc(size, gfp_mask);
- if (bp->b_addr)
- break;
- if (flags & XBF_READ_AHEAD)
- return -ENOMEM;
- XFS_STATS_INC(bp->b_mount, xb_page_retries);
- memalloc_retry_wait(gfp_mask);
+ folio_gfp &= ~__GFP_DIRECT_RECLAIM;
+ folio_gfp |= __GFP_NORETRY;
+ if (xfs_buf_alloc_folio(bp, size, folio_gfp) == 0)
+ return 0;
+ trace_xfs_buf_backing_fallback(bp, _RET_IP_);
+ }
+ return xfs_buf_alloc_vmalloc(bp, size, gfp_mask);
}
- trace_xfs_buf_backing_vmalloc(bp, _RET_IP_);
- return 0;
+ /*
+ * The slab allocator now guarantees aligned allocations for all power
+ * of two sizes. This covers most smaller XFS buffers, so just use
+ * kmalloc in this case.
+ *
+ * Don't bother with the vmalloc fallback for allocations of page size
+ * or less: vmalloc won't do any better.
+ */
+ if (!(gfp_mask & __GFP_NORETRY))
+ gfp_mask |= __GFP_NOFAIL;
+ if (size < PAGE_SIZE && is_power_of_2(size))
+ return xfs_buf_alloc_kmem(bp, size, gfp_mask);
+ return xfs_buf_alloc_folio(bp, size, gfp_mask);
}
static int
@@ -1083,11 +1098,17 @@ out_stale:
return false;
}
-/* returns false if the caller needs to resubmit the I/O, else true */
-static bool
-__xfs_buf_ioend(
+/*
+ * Complete a buffer read or write.
+ *
+ * Releases the buffer if the I/O was asynchronous.
+ */
+static void
+xfs_buf_ioend(
struct xfs_buf *bp)
{
+ bool async = bp->b_flags & XBF_ASYNC;
+
trace_xfs_buf_iodone(bp, _RET_IP_);
if (bp->b_flags & XBF_READ) {
@@ -1101,14 +1122,16 @@ __xfs_buf_ioend(
if (bp->b_flags & XBF_READ_AHEAD)
percpu_counter_dec(&bp->b_target->bt_readahead_count);
} else {
- if (!bp->b_error) {
+ if (unlikely(bp->b_error)) {
+ if (xfs_buf_ioend_handle_error(bp)) {
+ ASSERT(async);
+ return;
+ }
+ } else {
bp->b_flags &= ~XBF_WRITE_FAIL;
bp->b_flags |= XBF_DONE;
}
- if (unlikely(bp->b_error) && xfs_buf_ioend_handle_error(bp))
- return false;
-
/* clear the retry state */
bp->b_last_error = 0;
bp->b_retries = 0;
@@ -1128,30 +1151,15 @@ __xfs_buf_ioend(
bp->b_flags &= ~(XBF_READ | XBF_WRITE | XBF_READ_AHEAD |
_XBF_LOGRECOVERY);
- return true;
-}
-
-static void
-xfs_buf_ioend(
- struct xfs_buf *bp)
-{
- if (!__xfs_buf_ioend(bp))
- return;
- if (bp->b_flags & XBF_ASYNC)
+ if (async)
xfs_buf_relse(bp);
- else
- complete(&bp->b_iowait);
}
static void
xfs_buf_ioend_work(
struct work_struct *work)
{
- struct xfs_buf *bp =
- container_of(work, struct xfs_buf, b_ioend_work);
-
- if (__xfs_buf_ioend(bp))
- xfs_buf_relse(bp);
+ xfs_buf_ioend(container_of(work, struct xfs_buf, b_ioend_work));
}
void
@@ -1177,17 +1185,18 @@ xfs_buf_ioerror_alert(
}
/*
- * To simulate an I/O failure, the buffer must be locked and held with at least
- * two references.
+ * Fail a locked and referenced buffer outside the I/O path.
*
- * The buf item reference is dropped via ioend processing. The second reference
- * is owned by the caller and is dropped on I/O completion if the buffer is
- * XBF_ASYNC.
+ * The caller transfers a reference which will be released after processing the
+ * error.
*/
void
-xfs_buf_ioend_fail(
+xfs_buf_fail(
struct xfs_buf *bp)
{
+ ASSERT(xfs_buf_islocked(bp));
+
+ bp->b_flags |= XBF_ASYNC;
bp->b_flags &= ~XBF_DONE;
xfs_buf_stale(bp);
xfs_buf_ioerror(bp, -EIO);
@@ -1300,12 +1309,11 @@ xfs_buf_iowait(
{
ASSERT(!(bp->b_flags & XBF_ASYNC));
- do {
- trace_xfs_buf_iowait(bp, _RET_IP_);
- wait_for_completion(&bp->b_iowait);
- trace_xfs_buf_iowait_done(bp, _RET_IP_);
- } while (!__xfs_buf_ioend(bp));
+ trace_xfs_buf_iowait(bp, _RET_IP_);
+ wait_for_completion(&bp->b_iowait);
+ trace_xfs_buf_iowait_done(bp, _RET_IP_);
+ xfs_buf_ioend(bp);
return bp->b_error;
}
@@ -1369,8 +1377,8 @@ xfs_buf_submit(
* on shutdown.
*/
if (bp->b_mount->m_log && xlog_is_shutdown(bp->b_mount->m_log)) {
- xfs_buf_ioend_fail(bp);
- return;
+ xfs_buf_ioerror(bp, -EIO);
+ goto ioerror;
}
if (bp->b_flags & XBF_WRITE)
@@ -1383,18 +1391,26 @@ xfs_buf_submit(
bp->b_error = 0;
if ((bp->b_flags & XBF_WRITE) && !xfs_buf_verify_write(bp)) {
+ /* ->verify_write should have set b_error already */
xfs_force_shutdown(bp->b_mount, SHUTDOWN_CORRUPT_INCORE);
- xfs_buf_ioend(bp);
- return;
+ goto ioerror;
}
/* In-memory targets are directly mapped, no I/O required. */
- if (xfs_buftarg_is_mem(bp->b_target)) {
- xfs_buf_ioend(bp);
- return;
- }
+ if (xfs_buftarg_is_mem(bp->b_target))
+ goto end_io;
xfs_buf_submit_bio(bp);
+ return;
+
+ioerror:
+ bp->b_flags &= ~XBF_DONE;
+ xfs_buf_stale(bp);
+end_io:
+ if (bp->b_flags & XBF_ASYNC)
+ xfs_buf_ioend(bp);
+ else
+ complete(&bp->b_iowait);
}
/*
diff --git a/fs/xfs/xfs_buf.h b/fs/xfs/xfs_buf.h
index b3cd1c7029f1..79cc9c3f0254 100644
--- a/fs/xfs/xfs_buf.h
+++ b/fs/xfs/xfs_buf.h
@@ -290,7 +290,7 @@ extern void __xfs_buf_ioerror(struct xfs_buf *bp, int error,
xfs_failaddr_t failaddr);
#define xfs_buf_ioerror(bp, err) __xfs_buf_ioerror((bp), (err), __this_address)
extern void xfs_buf_ioerror_alert(struct xfs_buf *bp, xfs_failaddr_t fa);
-void xfs_buf_ioend_fail(struct xfs_buf *);
+void xfs_buf_fail(struct xfs_buf *bp);
void __xfs_buf_mark_corrupt(struct xfs_buf *bp, xfs_failaddr_t fa);
#define xfs_buf_mark_corrupt(bp) __xfs_buf_mark_corrupt((bp), __this_address)
diff --git a/fs/xfs/xfs_buf_item.c b/fs/xfs/xfs_buf_item.c
index 8487635579e5..1f055cd6732e 100644
--- a/fs/xfs/xfs_buf_item.c
+++ b/fs/xfs/xfs_buf_item.c
@@ -549,8 +549,7 @@ xfs_buf_item_unpin(
* wait for the lock and then run the IO failure completion.
*/
xfs_buf_lock(bp);
- bp->b_flags |= XBF_ASYNC;
- xfs_buf_ioend_fail(bp);
+ xfs_buf_fail(bp);
return;
}
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 317eb57f989f..15279d22a894 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -2646,8 +2646,7 @@ xfs_iflush_cluster(
* inode cluster buffers.
*/
xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE);
- bp->b_flags |= XBF_ASYNC;
- xfs_buf_ioend_fail(bp);
+ xfs_buf_fail(bp);
return error;
}
diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c
index 09e6678ca487..5f984bf5698a 100644
--- a/fs/xfs/xfs_log_recover.c
+++ b/fs/xfs/xfs_log_recover.c
@@ -1028,7 +1028,7 @@ xlog_verify_head(
{
struct xlog_rec_header *tmp_rhead;
char *tmp_buffer;
- xfs_daddr_t first_bad;
+ xfs_daddr_t first_bad = XFS_BUF_DADDR_NULL;
xfs_daddr_t tmp_rhead_blk;
int found;
int error;
@@ -1057,7 +1057,8 @@ xlog_verify_head(
*/
error = xlog_do_recovery_pass(log, *head_blk, tmp_rhead_blk,
XLOG_RECOVER_CRCPASS, &first_bad);
- if ((error == -EFSBADCRC || error == -EFSCORRUPTED) && first_bad) {
+ if ((error == -EFSBADCRC || error == -EFSCORRUPTED) &&
+ first_bad != XFS_BUF_DADDR_NULL) {
/*
* We've hit a potential torn write. Reset the error and warn
* about it.
@@ -3575,4 +3576,3 @@ xlog_recover_cancel(
if (xlog_recovery_needed(log))
xlog_recover_cancel_intents(log);
}
-
diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c
index aa0d2976f1c3..896b24f87ac9 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -166,10 +166,9 @@ xfs_qm_dqpurge(
* does it on success.
*/
error = xfs_qm_dqflush(dqp, bp);
- if (!error) {
+ if (!error)
error = xfs_bwrite(bp);
- xfs_buf_relse(bp);
- }
+ xfs_buf_relse(bp);
xfs_dqflock(dqp);
}
xfs_dquot_detach_buf(dqp);
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index eac7f9503805..8531d526fc44 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -534,8 +534,11 @@ xfs_open_devices(
out_free_rtdev_targ:
if (mp->m_rtdev_targp)
xfs_free_buftarg(mp->m_rtdev_targp);
+ mp->m_rtdev_targp = NULL;
+ rtdev_file = NULL; /* released by xfs_free_buftarg() */
out_free_ddev_targ:
xfs_free_buftarg(mp->m_ddev_targp);
+ mp->m_ddev_targp = NULL;
out_close_rtdev:
if (rtdev_file)
bdev_fput(rtdev_file);