summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve French <stfrench@microsoft.com>2024-05-01 08:56:13 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-05-25 17:22:47 +0300
commitb6e27f7fc2456b8d652b7d43c9af2c1eae3a68fc (patch)
treedfaf875cc76de0eca0ec563ca9b42a95bbe6300e
parentf93d145f90028a508d68c08b2b0f0a36063d02cb (diff)
downloadlinux-b6e27f7fc2456b8d652b7d43c9af2c1eae3a68fc.tar.xz
smb: client: do not defer close open handles to deleted files
[ Upstream commit ffceb7640cbfe6ea60e7769e107451d63a2fe3d3 ] When a file/dentry has been deleted before closing all its open handles, currently, closing them can add them to the deferred close list. This can lead to problems in creating file with the same name when the file is re-created before the deferred close completes. This issue was seen while reusing a client's already existing lease on a file for compound operations and xfstest 591 failed because of the deferred close handle that remained valid even after the file was deleted and was being reused to create a file with the same name. The server in this case returns an error on open with STATUS_DELETE_PENDING. Recreating the file would fail till the deferred handles are closed (duration specified in closetimeo). This patch fixes the issue by flagging all open handles for the deleted file (file path to be precise) by setting status_file_deleted to true in the cifsFileInfo structure. As per the information classes specified in MS-FSCC, SMB2 query info response from the server has a DeletePending field, set to true to indicate that deletion has been requested on that file. If this is the case, flag the open handles for this file too. When doing close in cifs_close for each of these handles, check the value of this boolean field and do not defer close these handles if the corresponding filepath has been deleted. Signed-off-by: Meetakshi Setiya <msetiya@microsoft.com> Signed-off-by: Steve French <stfrench@microsoft.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
-rw-r--r--fs/smb/client/cifsglob.h1
-rw-r--r--fs/smb/client/cifsproto.h4
-rw-r--r--fs/smb/client/file.c3
-rw-r--r--fs/smb/client/inode.c28
-rw-r--r--fs/smb/client/misc.c34
-rw-r--r--fs/smb/client/smb2inode.c9
6 files changed, 74 insertions, 5 deletions
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index a149579910ad..fdadda4024f4 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -1427,6 +1427,7 @@ struct cifsFileInfo {
bool invalidHandle:1; /* file closed via session abend */
bool swapfile:1;
bool oplock_break_cancelled:1;
+ bool status_file_deleted:1; /* file has been deleted */
bool offload:1; /* offload final part of _put to a wq */
unsigned int oplock_epoch; /* epoch from the lease break */
__u32 oplock_level; /* oplock/lease level from the lease break */
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
index e9b38b279a6c..50040990e70b 100644
--- a/fs/smb/client/cifsproto.h
+++ b/fs/smb/client/cifsproto.h
@@ -298,6 +298,10 @@ extern void cifs_close_all_deferred_files(struct cifs_tcon *cifs_tcon);
extern void cifs_close_deferred_file_under_dentry(struct cifs_tcon *cifs_tcon,
const char *path);
+
+extern void cifs_mark_open_handles_for_deleted_file(struct inode *inode,
+ const char *path);
+
extern struct TCP_Server_Info *
cifs_get_tcp_session(struct smb3_fs_context *ctx,
struct TCP_Server_Info *primary_server);
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index 751ae89cefe3..8eaf195ef560 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -501,6 +501,7 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->uid = current_fsuid();
cfile->dentry = dget(dentry);
cfile->f_flags = file->f_flags;
+ cfile->status_file_deleted = false;
cfile->invalidHandle = false;
cfile->deferred_close_scheduled = false;
cfile->tlink = cifs_get_tlink(tlink);
@@ -1167,7 +1168,7 @@ int cifs_close(struct inode *inode, struct file *file)
if ((cifs_sb->ctx->closetimeo && cinode->oplock == CIFS_CACHE_RHW_FLG)
&& cinode->lease_granted &&
!test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags) &&
- dclose) {
+ dclose && !(cfile->status_file_deleted)) {
if (test_and_clear_bit(CIFS_INO_MODIFIED_ATTR, &cinode->flags)) {
inode_set_mtime_to_ts(inode,
inode_set_ctime_current(inode));
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index 156713186b3c..2739cb839080 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -894,6 +894,9 @@ cifs_get_file_info(struct file *filp)
struct cifsFileInfo *cfile = filp->private_data;
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
struct TCP_Server_Info *server = tcon->ses->server;
+ struct dentry *dentry = filp->f_path.dentry;
+ void *page = alloc_dentry_path();
+ const unsigned char *path;
if (!server->ops->query_file_info)
return -ENOSYS;
@@ -908,7 +911,14 @@ cifs_get_file_info(struct file *filp)
data.symlink = true;
data.reparse.tag = IO_REPARSE_TAG_SYMLINK;
}
+ path = build_path_from_dentry(dentry, page);
+ if (IS_ERR(path)) {
+ free_dentry_path(page);
+ return PTR_ERR(path);
+ }
cifs_open_info_to_fattr(&fattr, &data, inode->i_sb);
+ if (fattr.cf_flags & CIFS_FATTR_DELETE_PENDING)
+ cifs_mark_open_handles_for_deleted_file(inode, path);
break;
case -EREMOTE:
cifs_create_junction_fattr(&fattr, inode->i_sb);
@@ -938,6 +948,7 @@ cifs_get_file_info(struct file *filp)
rc = cifs_fattr_to_inode(inode, &fattr, false);
cgfi_exit:
cifs_free_open_info(&data);
+ free_dentry_path(page);
free_xid(xid);
return rc;
}
@@ -1076,6 +1087,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
struct kvec rsp_iov, *iov = NULL;
int rsp_buftype = CIFS_NO_BUFFER;
u32 tag = data->reparse.tag;
+ struct inode *inode = NULL;
int rc = 0;
if (!tag && server->ops->query_reparse_point) {
@@ -1115,8 +1127,12 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data,
if (tcon->posix_extensions)
smb311_posix_info_to_fattr(fattr, data, sb);
- else
+ else {
cifs_open_info_to_fattr(fattr, data, sb);
+ inode = cifs_iget(sb, fattr);
+ if (inode && fattr->cf_flags & CIFS_FATTR_DELETE_PENDING)
+ cifs_mark_open_handles_for_deleted_file(inode, full_path);
+ }
out:
fattr->cf_cifstag = data->reparse.tag;
free_rsp_buf(rsp_buftype, rsp_iov.iov_base);
@@ -1171,6 +1187,8 @@ static int cifs_get_fattr(struct cifs_open_info_data *data,
full_path, fattr);
} else {
cifs_open_info_to_fattr(fattr, data, sb);
+ if (fattr->cf_flags & CIFS_FATTR_DELETE_PENDING)
+ cifs_mark_open_handles_for_deleted_file(*inode, full_path);
}
break;
case -EREMOTE:
@@ -1853,16 +1871,20 @@ retry_std_delete:
psx_del_no_retry:
if (!rc) {
- if (inode)
+ if (inode) {
+ cifs_mark_open_handles_for_deleted_file(inode, full_path);
cifs_drop_nlink(inode);
+ }
} else if (rc == -ENOENT) {
d_drop(dentry);
} else if (rc == -EBUSY) {
if (server->ops->rename_pending_delete) {
rc = server->ops->rename_pending_delete(full_path,
dentry, xid);
- if (rc == 0)
+ if (rc == 0) {
+ cifs_mark_open_handles_for_deleted_file(inode, full_path);
cifs_drop_nlink(inode);
+ }
}
} else if ((rc == -EACCES) && (dosattr == 0) && inode) {
attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index d56959d02e36..669d27b4d414 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -853,6 +853,40 @@ cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon, const char *path)
free_dentry_path(page);
}
+/*
+ * If a dentry has been deleted, all corresponding open handles should know that
+ * so that we do not defer close them.
+ */
+void cifs_mark_open_handles_for_deleted_file(struct inode *inode,
+ const char *path)
+{
+ struct cifsFileInfo *cfile;
+ void *page;
+ const char *full_path;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+
+ page = alloc_dentry_path();
+ spin_lock(&cinode->open_file_lock);
+
+ /*
+ * note: we need to construct path from dentry and compare only if the
+ * inode has any hardlinks. When number of hardlinks is 1, we can just
+ * mark all open handles since they are going to be from the same file.
+ */
+ if (inode->i_nlink > 1) {
+ list_for_each_entry(cfile, &cinode->openFileList, flist) {
+ full_path = build_path_from_dentry(cfile->dentry, page);
+ if (!IS_ERR(full_path) && strcmp(full_path, path) == 0)
+ cfile->status_file_deleted = true;
+ }
+ } else {
+ list_for_each_entry(cfile, &cinode->openFileList, flist)
+ cfile->status_file_deleted = true;
+ }
+ spin_unlock(&cinode->open_file_lock);
+ free_dentry_path(page);
+}
+
/* parses DFS referral V3 structure
* caller is responsible for freeing target_nodes
* returns:
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index e452c59c13e2..0b7b08335291 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -561,8 +561,15 @@ finished:
case SMB2_OP_DELETE:
if (rc)
trace_smb3_delete_err(xid, ses->Suid, tcon->tid, rc);
- else
+ else {
+ /*
+ * If dentry (hence, inode) is NULL, lease break is going to
+ * take care of degrading leases on handles for deleted files.
+ */
+ if (inode)
+ cifs_mark_open_handles_for_deleted_file(inode, full_path);
trace_smb3_delete_done(xid, ses->Suid, tcon->tid);
+ }
break;
case SMB2_OP_MKDIR:
if (rc)