forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge tag '5.20-rc-smb3-client-fixes-part2' of git://git.samba.org/sf…
…rench/cifs-2.6 Pull more cifs updates from Steve French: - two fixes for stable, one for a lock length miscalculation, and another fixes a lease break timeout bug - improvement to handle leases, allows the close timeout to be configured more safely - five restructuring/cleanup patches * tag '5.20-rc-smb3-client-fixes-part2' of git://git.samba.org/sfrench/cifs-2.6: cifs: Do not access tcon->cfids->cfid directly from is_path_accessible cifs: Add constructor/destructors for tcon->cfid SMB3: fix lease break timeout when multiple deferred close handles for the same file. smb3: allow deferred close timeout to be configurable cifs: Do not use tcon->cfid directly, use the cfid we get from open_cached_dir cifs: Move cached-dir functions into a separate file cifs: Remove {cifs,nfs}_fscache_release_page() cifs: fix lock length calculation
- Loading branch information
Showing
19 changed files
with
528 additions
and
445 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,388 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Functions to handle the cached directory entries | ||
* | ||
* Copyright (c) 2022, Ronnie Sahlberg <[email protected]> | ||
*/ | ||
|
||
#include "cifsglob.h" | ||
#include "cifsproto.h" | ||
#include "cifs_debug.h" | ||
#include "smb2proto.h" | ||
#include "cached_dir.h" | ||
|
||
/* | ||
* Open the and cache a directory handle. | ||
* If error then *cfid is not initialized. | ||
*/ | ||
int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, | ||
const char *path, | ||
struct cifs_sb_info *cifs_sb, | ||
bool lookup_only, struct cached_fid **ret_cfid) | ||
{ | ||
struct cifs_ses *ses; | ||
struct TCP_Server_Info *server; | ||
struct cifs_open_parms oparms; | ||
struct smb2_create_rsp *o_rsp = NULL; | ||
struct smb2_query_info_rsp *qi_rsp = NULL; | ||
int resp_buftype[2]; | ||
struct smb_rqst rqst[2]; | ||
struct kvec rsp_iov[2]; | ||
struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; | ||
struct kvec qi_iov[1]; | ||
int rc, flags = 0; | ||
__le16 utf16_path = 0; /* Null - since an open of top of share */ | ||
u8 oplock = SMB2_OPLOCK_LEVEL_II; | ||
struct cifs_fid *pfid; | ||
struct dentry *dentry; | ||
struct cached_fid *cfid; | ||
|
||
if (tcon == NULL || tcon->nohandlecache || | ||
is_smb1_server(tcon->ses->server)) | ||
return -EOPNOTSUPP; | ||
|
||
ses = tcon->ses; | ||
server = ses->server; | ||
|
||
if (cifs_sb->root == NULL) | ||
return -ENOENT; | ||
|
||
if (strlen(path)) | ||
return -ENOENT; | ||
|
||
dentry = cifs_sb->root; | ||
|
||
cfid = tcon->cfid; | ||
mutex_lock(&cfid->fid_mutex); | ||
if (cfid->is_valid) { | ||
cifs_dbg(FYI, "found a cached root file handle\n"); | ||
*ret_cfid = cfid; | ||
kref_get(&cfid->refcount); | ||
mutex_unlock(&cfid->fid_mutex); | ||
return 0; | ||
} | ||
|
||
/* | ||
* We do not hold the lock for the open because in case | ||
* SMB2_open needs to reconnect, it will end up calling | ||
* cifs_mark_open_files_invalid() which takes the lock again | ||
* thus causing a deadlock | ||
*/ | ||
mutex_unlock(&cfid->fid_mutex); | ||
|
||
if (lookup_only) | ||
return -ENOENT; | ||
|
||
if (smb3_encryption_required(tcon)) | ||
flags |= CIFS_TRANSFORM_REQ; | ||
|
||
if (!server->ops->new_lease_key) | ||
return -EIO; | ||
|
||
pfid = &cfid->fid; | ||
server->ops->new_lease_key(pfid); | ||
|
||
memset(rqst, 0, sizeof(rqst)); | ||
resp_buftype[0] = resp_buftype[1] = CIFS_NO_BUFFER; | ||
memset(rsp_iov, 0, sizeof(rsp_iov)); | ||
|
||
/* Open */ | ||
memset(&open_iov, 0, sizeof(open_iov)); | ||
rqst[0].rq_iov = open_iov; | ||
rqst[0].rq_nvec = SMB2_CREATE_IOV_SIZE; | ||
|
||
oparms.tcon = tcon; | ||
oparms.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE); | ||
oparms.desired_access = FILE_READ_ATTRIBUTES; | ||
oparms.disposition = FILE_OPEN; | ||
oparms.fid = pfid; | ||
oparms.reconnect = false; | ||
|
||
rc = SMB2_open_init(tcon, server, | ||
&rqst[0], &oplock, &oparms, &utf16_path); | ||
if (rc) | ||
goto oshr_free; | ||
smb2_set_next_command(tcon, &rqst[0]); | ||
|
||
memset(&qi_iov, 0, sizeof(qi_iov)); | ||
rqst[1].rq_iov = qi_iov; | ||
rqst[1].rq_nvec = 1; | ||
|
||
rc = SMB2_query_info_init(tcon, server, | ||
&rqst[1], COMPOUND_FID, | ||
COMPOUND_FID, FILE_ALL_INFORMATION, | ||
SMB2_O_INFO_FILE, 0, | ||
sizeof(struct smb2_file_all_info) + | ||
PATH_MAX * 2, 0, NULL); | ||
if (rc) | ||
goto oshr_free; | ||
|
||
smb2_set_related(&rqst[1]); | ||
|
||
rc = compound_send_recv(xid, ses, server, | ||
flags, 2, rqst, | ||
resp_buftype, rsp_iov); | ||
mutex_lock(&cfid->fid_mutex); | ||
|
||
/* | ||
* Now we need to check again as the cached root might have | ||
* been successfully re-opened from a concurrent process | ||
*/ | ||
|
||
if (cfid->is_valid) { | ||
/* work was already done */ | ||
|
||
/* stash fids for close() later */ | ||
struct cifs_fid fid = { | ||
.persistent_fid = pfid->persistent_fid, | ||
.volatile_fid = pfid->volatile_fid, | ||
}; | ||
|
||
/* | ||
* caller expects this func to set the fid in cfid to valid | ||
* cached root, so increment the refcount. | ||
*/ | ||
kref_get(&cfid->refcount); | ||
|
||
mutex_unlock(&cfid->fid_mutex); | ||
|
||
if (rc == 0) { | ||
/* close extra handle outside of crit sec */ | ||
SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); | ||
} | ||
rc = 0; | ||
goto oshr_free; | ||
} | ||
|
||
/* Cached root is still invalid, continue normaly */ | ||
|
||
if (rc) { | ||
if (rc == -EREMCHG) { | ||
tcon->need_reconnect = true; | ||
pr_warn_once("server share %s deleted\n", | ||
tcon->treeName); | ||
} | ||
goto oshr_exit; | ||
} | ||
|
||
atomic_inc(&tcon->num_remote_opens); | ||
|
||
o_rsp = (struct smb2_create_rsp *)rsp_iov[0].iov_base; | ||
oparms.fid->persistent_fid = o_rsp->PersistentFileId; | ||
oparms.fid->volatile_fid = o_rsp->VolatileFileId; | ||
#ifdef CONFIG_CIFS_DEBUG2 | ||
oparms.fid->mid = le64_to_cpu(o_rsp->hdr.MessageId); | ||
#endif /* CIFS_DEBUG2 */ | ||
|
||
cfid->tcon = tcon; | ||
cfid->is_valid = true; | ||
cfid->dentry = dentry; | ||
dget(dentry); | ||
kref_init(&cfid->refcount); | ||
|
||
/* BB TBD check to see if oplock level check can be removed below */ | ||
if (o_rsp->OplockLevel == SMB2_OPLOCK_LEVEL_LEASE) { | ||
/* | ||
* See commit 2f94a3125b87. Increment the refcount when we | ||
* get a lease for root, release it if lease break occurs | ||
*/ | ||
kref_get(&cfid->refcount); | ||
cfid->has_lease = true; | ||
smb2_parse_contexts(server, o_rsp, | ||
&oparms.fid->epoch, | ||
oparms.fid->lease_key, &oplock, | ||
NULL, NULL); | ||
} else | ||
goto oshr_exit; | ||
|
||
qi_rsp = (struct smb2_query_info_rsp *)rsp_iov[1].iov_base; | ||
if (le32_to_cpu(qi_rsp->OutputBufferLength) < sizeof(struct smb2_file_all_info)) | ||
goto oshr_exit; | ||
if (!smb2_validate_and_copy_iov( | ||
le16_to_cpu(qi_rsp->OutputBufferOffset), | ||
sizeof(struct smb2_file_all_info), | ||
&rsp_iov[1], sizeof(struct smb2_file_all_info), | ||
(char *)&cfid->file_all_info)) | ||
cfid->file_all_info_is_valid = true; | ||
|
||
cfid->time = jiffies; | ||
|
||
oshr_exit: | ||
mutex_unlock(&cfid->fid_mutex); | ||
oshr_free: | ||
SMB2_open_free(&rqst[0]); | ||
SMB2_query_info_free(&rqst[1]); | ||
free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base); | ||
free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base); | ||
if (rc == 0) | ||
*ret_cfid = cfid; | ||
|
||
return rc; | ||
} | ||
|
||
int open_cached_dir_by_dentry(struct cifs_tcon *tcon, | ||
struct dentry *dentry, | ||
struct cached_fid **ret_cfid) | ||
{ | ||
struct cached_fid *cfid; | ||
|
||
cfid = tcon->cfid; | ||
|
||
mutex_lock(&cfid->fid_mutex); | ||
if (cfid->dentry == dentry) { | ||
cifs_dbg(FYI, "found a cached root file handle by dentry\n"); | ||
*ret_cfid = cfid; | ||
kref_get(&cfid->refcount); | ||
mutex_unlock(&cfid->fid_mutex); | ||
return 0; | ||
} | ||
mutex_unlock(&cfid->fid_mutex); | ||
return -ENOENT; | ||
} | ||
|
||
static void | ||
smb2_close_cached_fid(struct kref *ref) | ||
{ | ||
struct cached_fid *cfid = container_of(ref, struct cached_fid, | ||
refcount); | ||
struct cached_dirent *dirent, *q; | ||
|
||
if (cfid->is_valid) { | ||
cifs_dbg(FYI, "clear cached root file handle\n"); | ||
SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid, | ||
cfid->fid.volatile_fid); | ||
} | ||
|
||
/* | ||
* We only check validity above to send SMB2_close, | ||
* but we still need to invalidate these entries | ||
* when this function is called | ||
*/ | ||
cfid->is_valid = false; | ||
cfid->file_all_info_is_valid = false; | ||
cfid->has_lease = false; | ||
if (cfid->dentry) { | ||
dput(cfid->dentry); | ||
cfid->dentry = NULL; | ||
} | ||
/* | ||
* Delete all cached dirent names | ||
*/ | ||
mutex_lock(&cfid->dirents.de_mutex); | ||
list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) { | ||
list_del(&dirent->entry); | ||
kfree(dirent->name); | ||
kfree(dirent); | ||
} | ||
cfid->dirents.is_valid = 0; | ||
cfid->dirents.is_failed = 0; | ||
cfid->dirents.ctx = NULL; | ||
cfid->dirents.pos = 0; | ||
mutex_unlock(&cfid->dirents.de_mutex); | ||
|
||
} | ||
|
||
void close_cached_dir(struct cached_fid *cfid) | ||
{ | ||
mutex_lock(&cfid->fid_mutex); | ||
kref_put(&cfid->refcount, smb2_close_cached_fid); | ||
mutex_unlock(&cfid->fid_mutex); | ||
} | ||
|
||
void close_cached_dir_lease_locked(struct cached_fid *cfid) | ||
{ | ||
if (cfid->has_lease) { | ||
cfid->has_lease = false; | ||
kref_put(&cfid->refcount, smb2_close_cached_fid); | ||
} | ||
} | ||
|
||
void close_cached_dir_lease(struct cached_fid *cfid) | ||
{ | ||
mutex_lock(&cfid->fid_mutex); | ||
close_cached_dir_lease_locked(cfid); | ||
mutex_unlock(&cfid->fid_mutex); | ||
} | ||
|
||
/* | ||
* Called from cifs_kill_sb when we unmount a share | ||
*/ | ||
void close_all_cached_dirs(struct cifs_sb_info *cifs_sb) | ||
{ | ||
struct rb_root *root = &cifs_sb->tlink_tree; | ||
struct rb_node *node; | ||
struct cached_fid *cfid; | ||
struct cifs_tcon *tcon; | ||
struct tcon_link *tlink; | ||
|
||
for (node = rb_first(root); node; node = rb_next(node)) { | ||
tlink = rb_entry(node, struct tcon_link, tl_rbnode); | ||
tcon = tlink_tcon(tlink); | ||
if (IS_ERR(tcon)) | ||
continue; | ||
cfid = tcon->cfid; | ||
mutex_lock(&cfid->fid_mutex); | ||
if (cfid->dentry) { | ||
dput(cfid->dentry); | ||
cfid->dentry = NULL; | ||
} | ||
mutex_unlock(&cfid->fid_mutex); | ||
} | ||
} | ||
|
||
/* | ||
* Invalidate and close all cached dirs when a TCON has been reset | ||
* due to a session loss. | ||
*/ | ||
void invalidate_all_cached_dirs(struct cifs_tcon *tcon) | ||
{ | ||
mutex_lock(&tcon->cfid->fid_mutex); | ||
tcon->cfid->is_valid = false; | ||
/* cached handle is not valid, so SMB2_CLOSE won't be sent below */ | ||
close_cached_dir_lease_locked(tcon->cfid); | ||
memset(&tcon->cfid->fid, 0, sizeof(struct cifs_fid)); | ||
mutex_unlock(&tcon->cfid->fid_mutex); | ||
} | ||
|
||
static void | ||
smb2_cached_lease_break(struct work_struct *work) | ||
{ | ||
struct cached_fid *cfid = container_of(work, | ||
struct cached_fid, lease_break); | ||
|
||
close_cached_dir_lease(cfid); | ||
} | ||
|
||
int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]) | ||
{ | ||
if (tcon->cfid->is_valid && | ||
!memcmp(lease_key, | ||
tcon->cfid->fid.lease_key, | ||
SMB2_LEASE_KEY_SIZE)) { | ||
tcon->cfid->time = 0; | ||
INIT_WORK(&tcon->cfid->lease_break, | ||
smb2_cached_lease_break); | ||
queue_work(cifsiod_wq, | ||
&tcon->cfid->lease_break); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
struct cached_fid *init_cached_dir(void) | ||
{ | ||
struct cached_fid *cfid; | ||
|
||
cfid = kzalloc(sizeof(*cfid), GFP_KERNEL); | ||
if (!cfid) | ||
return NULL; | ||
INIT_LIST_HEAD(&cfid->dirents.entries); | ||
mutex_init(&cfid->dirents.de_mutex); | ||
mutex_init(&cfid->fid_mutex); | ||
return cfid; | ||
} | ||
|
||
void free_cached_dir(struct cifs_tcon *tcon) | ||
{ | ||
kfree(tcon->cfid); | ||
} |
Oops, something went wrong.