diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c index b53912ce5e2a..e014c880b88b 100644 --- a/fs/smb/server/vfs_cache.c +++ b/fs/smb/server/vfs_cache.c @@ -229,6 +229,22 @@ bool ksmbd_inode_pending_delete(struct ksmbd_file *fp) struct ksmbd_inode *ci = fp->f_ci; int ret; + /* + * Stream delete-pending is tracked per-handle (see + * ksmbd_fd_set_delete_pending()), not on the shared inode -- the + * whole-file flags checked below would never see it set, and would + * also incorrectly report a whole-file pending-delete as applying + * to an unrelated stream handle on the same inode. + */ + if (ksmbd_stream_fd(fp)) { + bool pending; + + spin_lock(&fp->f_lock); + pending = fp->stream_del_pending; + spin_unlock(&fp->f_lock); + return pending; + } + down_read(&ci->m_lock); ret = (ci->m_flags & S_DEL_PENDING); up_read(&ci->m_lock); @@ -299,15 +315,19 @@ void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp, * mark the stream for deletion, not the whole file -- otherwise * deleting a single alternate data stream (e.g. AFP_AfpInfo) deletes * the entire file's data along with it. + * + * This is tracked on fp itself (stream_del_pending), not the shared + * ksmbd_inode: the inode-wide S_DEL_ON_CLS_STREAM flag used by + * ksmbd_fd_set_delete_on_close() can't record *which* stream should be + * deleted, so if a different stream handle on the same file closed + * first, it would delete the wrong stream. */ void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp) { - struct ksmbd_inode *ci = fp->f_ci; - if (ksmbd_stream_fd(fp)) { - down_write(&ci->m_lock); - ci->m_flags |= S_DEL_ON_CLS_STREAM; - up_write(&ci->m_lock); + spin_lock(&fp->f_lock); + fp->stream_del_pending = true; + spin_unlock(&fp->f_lock); } else { ksmbd_set_inode_pending_delete(fp); } @@ -315,12 +335,10 @@ void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp) void ksmbd_fd_clear_delete_pending(struct ksmbd_file *fp) { - struct ksmbd_inode *ci = fp->f_ci; - if (ksmbd_stream_fd(fp)) { - down_write(&ci->m_lock); - ci->m_flags &= ~S_DEL_ON_CLS_STREAM; - up_write(&ci->m_lock); + spin_lock(&fp->f_lock); + fp->stream_del_pending = false; + spin_unlock(&fp->f_lock); } else { ksmbd_clear_inode_pending_delete(fp); } @@ -446,6 +464,19 @@ static void __ksmbd_inode_close(struct ksmbd_file *fp) } up_write(&ci->m_lock); + /* + * Per-handle delete-pending from ksmbd_fd_set_delete_pending() + * (FileDispositionInformation on this stream) -- separate from + * the inode-wide flag above, which only ever meant "some + * stream on this file" with no way to say which one. + */ + spin_lock(&fp->f_lock); + if (fp->stream_del_pending) { + fp->stream_del_pending = false; + remove_stream_xattr = true; + } + spin_unlock(&fp->f_lock); + if (remove_stream_xattr) { const struct cred *saved_cred; diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h index 111a4e315499..f796b6edc69b 100644 --- a/fs/smb/server/vfs_cache.h +++ b/fs/smb/server/vfs_cache.h @@ -118,6 +118,13 @@ struct ksmbd_file { struct list_head node; struct list_head blocked_works; struct list_head lock_list; + /* + * Per-handle FileDispositionInformation delete-pending state for a + * stream handle -- separate from ksmbd_inode's inode-wide m_flags, + * which have no way to record which stream on a multi-stream file + * was actually marked for deletion. See ksmbd_fd_set_delete_pending(). + */ + bool stream_del_pending; unsigned int durable_timeout; unsigned int durable_scavenger_timeout;