Skip to content

Commit

Permalink
smb: client: do not defer close open handles to deleted files
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Meetakshi Setiya authored and Steve French committed Mar 11, 2024
1 parent 2c7d399 commit ffceb76
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 5 deletions.
1 change: 1 addition & 0 deletions fs/smb/client/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,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 */
unsigned int oplock_epoch; /* epoch from the lease break */
__u32 oplock_level; /* oplock/lease level from the lease break */
int count;
Expand Down
4 changes: 4 additions & 0 deletions fs/smb/client/cifsproto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion fs/smb/client/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,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);
Expand Down Expand Up @@ -1088,7 +1089,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));
Expand Down
28 changes: 25 additions & 3 deletions fs/smb/client/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1851,16 +1869,20 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)

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);
Expand Down
34 changes: 34 additions & 0 deletions fs/smb/client/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 8 additions & 1 deletion fs/smb/client/smb2inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,8 +561,15 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
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)
Expand Down

0 comments on commit ffceb76

Please sign in to comment.