summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEnzo Matsumiya <ematsumiya@suse.de>2025-07-07 14:22:18 -0300
committerEnzo Matsumiya <ematsumiya@suse.de>2025-07-09 02:48:10 -0300
commite835f5ff89e9106fe8c8d1fe8ea3917cea67c016 (patch)
treed94e108c09b6ba2224bb123968c3b4f9cb9bc82a
parent556055e008ee0054151085fbf3f9fbf182cf0b98 (diff)
downloadlinux-e835f5ff89e9106fe8c8d1fe8ea3917cea67c016.tar.gz
linux-e835f5ff89e9106fe8c8d1fe8ea3917cea67c016.tar.bz2
linux-e835f5ff89e9106fe8c8d1fe8ea3917cea67c016.zip
smb: client: don't break parent lease on mkdir
When creating a new directory, if we have its parent cached (i.e. we have a lease), set ParentLeaseKey for the target new dir, while also already requesting a lease for it. If it's possible to cache the new dir, try a single Create request first (without Close). On success, cache it, otherwise, fallback to previous behaviour (compound Create + Close). Add some helpers to cached_dir: - can_cache_dir() - init_cached_dir() - just allocate the object in the simplest way - add_cached_dir() - turn cfid available, but up to callers to make it valid Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
-rw-r--r--fs/smb/client/cached_dir.c88
-rw-r--r--fs/smb/client/cached_dir.h3
-rw-r--r--fs/smb/client/cifsglob.h2
-rw-r--r--fs/smb/client/cifsproto.h12
-rw-r--r--fs/smb/client/cifssmb.c2
-rw-r--r--fs/smb/client/inode.c11
-rw-r--r--fs/smb/client/smb2inode.c149
-rw-r--r--fs/smb/client/smb2pdu.c108
-rw-r--r--fs/smb/client/smb2proto.h2
9 files changed, 295 insertions, 82 deletions
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 696a1d224dee..d28855a1edc1 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -12,7 +12,6 @@
#include "smb2proto.h"
#include "cached_dir.h"
-static struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char *path);
static void free_cached_dir(struct cached_fid *cfid);
static void smb2_close_cached_fid(struct kref *ref);
@@ -192,9 +191,6 @@ replay_again:
return 0;
}
- if (cfids->num_entries >= tcon->max_cached_dirs)
- return -ENOENT;
-
server = cifs_pick_channel(ses);
if (!server->ops->new_lease_key)
return -EIO;
@@ -203,10 +199,10 @@ replay_again:
if (!utf16_path)
return -ENOMEM;
- cfid = init_cached_dir(cfids, path);
- if (!cfid) {
+ rc = add_cached_dir(tcon, path, NULL);
+ if (rc) {
kfree(utf16_path);
- return -ENOMEM;
+ return rc;
}
pfid = &cfid->fid;
@@ -234,7 +230,6 @@ replay_again:
}
}
cfid->dentry = dentry;
- cfid->tcon = tcon;
/*
* We do not hold the lock for the open because in case
@@ -617,13 +612,48 @@ bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
return false;
}
-static struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char *path)
+bool can_cache_dir(struct cifs_tcon *tcon)
+{
+ struct cached_fids *cfids;
+ bool ret;
+
+ if (!tcon)
+ return false;
+
+ cfids = tcon->cfids;
+ if (!cfids)
+ return false;
+
+ spin_lock(&cfids->cfid_list_lock);
+ ret = (cfids->num_entries >= tcon->max_cached_dirs);
+ spin_unlock(&cfids->cfid_list_lock);
+
+ return ret;
+}
+
+/*
+ * Bare init a cfid.
+ * Doesn't add to the list nor set it as valid, use add_cached_dir() later on successful open.
+ *
+ * To make the returned cfid valid, callers must add it to the list and set accordingly:
+ * ->tcon
+ * ->has_lease
+ * ->dentry
+ * ->time
+ * ->is_open
+ * ->on_list
+ * ->file_all_info
+ *
+ * Return: new cfid or NULL on error.
+ */
+struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char *path)
{
struct cached_fid *cfid;
cfid = kzalloc(sizeof(*cfid), GFP_ATOMIC);
if (!cfid)
return NULL;
+
cfid->path = kstrdup(path, GFP_ATOMIC);
if (!cfid->path) {
kfree(cfid);
@@ -639,10 +669,30 @@ static struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char
kref_init(&cfid->refcount);
cfid->cfids = cfids;
- cfids->num_entries++;
- list_add(&cfid->entry, &cfids->entries);
- cfid->on_list = true;
- kref_get(&cfid->refcount);
+
+ return cfid;
+}
+
+int add_cached_dir(struct cifs_tcon *tcon, const char *path, struct cached_fid *cfid)
+{
+ struct cached_fids *cfids = tcon->cfids;
+ int ret = -ENOENT;
+
+ if (!can_cache_dir(tcon))
+ return ret;
+
+ if (!cfid) {
+ if (!path) {
+ cifs_dbg(FYI, "no cfid or path to cache, skipping\n");
+ goto out;
+ }
+
+ cfid = init_cached_dir(cfids, path);
+ if (!cfid) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
/*
* Set @cfid->has_lease to true during construction so that the lease
@@ -652,9 +702,17 @@ static struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char
* Concurrent processes won't be to use it yet due to @cfid->time being
* zero.
*/
+ spin_lock(&cfids->cfid_list_lock);
+ cfid->on_list = true;
cfid->has_lease = true;
-
- return cfid;
+ kref_get(&cfid->refcount);
+ list_add(&cfid->entry, &cfids->entries);
+ cfids->num_entries++;
+ cfid->tcon = tcon;
+ ret = 0;
+out:
+ spin_unlock(&cfids->cfid_list_lock);
+ return ret;
}
static void free_cached_dir(struct cached_fid *cfid)
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index fd314e7a18ee..4390860fc45c 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -71,6 +71,9 @@ enum {
extern struct cached_fids *init_cached_dirs(void);
extern void free_cached_dirs(struct cached_fids *cfids);
+extern bool can_cache_dir(struct cifs_tcon *tcon);
+extern struct cached_fid *init_cached_dir(struct cached_fids *cfids, const char *path);
+extern int add_cached_dir(struct cifs_tcon *tcon, const char *path, struct cached_fid *cfid);
extern struct cached_fid *lookup_cached_dir(struct cached_fids *cfids,
const void *key, int type);
extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index fdd95e5100cd..ca0b6aecd530 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -439,7 +439,7 @@ struct smb_version_operations {
struct cifs_sb_info *cifs_sb);
int (*mkdir)(const unsigned int xid, struct inode *inode, umode_t mode,
struct cifs_tcon *tcon, const char *name,
- struct cifs_sb_info *sb);
+ struct cifs_sb_info *sb, struct dentry *dentry);
/* set info on created directory */
void (*mkdir_setinfo)(struct inode *, const char *,
struct cifs_sb_info *, struct cifs_tcon *,
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
index 045227ed4efc..2deabf300025 100644
--- a/fs/smb/client/cifsproto.h
+++ b/fs/smb/client/cifsproto.h
@@ -214,6 +214,15 @@ extern int cifs_fattr_to_inode(struct inode *inode, struct cifs_fattr *fattr,
extern struct inode *cifs_iget(struct super_block *sb,
struct cifs_fattr *fattr);
+void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
+ struct cifs_open_info_data *data,
+ struct super_block *sb);
+int cifs_get_fattr(struct cifs_open_info_data *data,
+ struct super_block *sb, int xid,
+ const struct cifs_fid *fid,
+ struct cifs_fattr *fattr,
+ struct inode **inode,
+ const char *full_path);
int cifs_get_inode_info(struct inode **inode, const char *full_path,
struct cifs_open_info_data *data, struct super_block *sb, int xid,
const struct cifs_fid *fid);
@@ -441,7 +450,8 @@ extern int CIFSSMBUnixSetPathInfo(const unsigned int xid,
extern int CIFSSMBMkDir(const unsigned int xid, struct inode *inode,
umode_t mode, struct cifs_tcon *tcon,
- const char *name, struct cifs_sb_info *cifs_sb);
+ const char *name, struct cifs_sb_info *cifs_sb,
+ struct dentry *dentry);
extern int CIFSSMBRmDir(const unsigned int xid, struct cifs_tcon *tcon,
const char *name, struct cifs_sb_info *cifs_sb);
extern int CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon,
diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c
index 7216fcec79e8..20f001266aad 100644
--- a/fs/smb/client/cifssmb.c
+++ b/fs/smb/client/cifssmb.c
@@ -846,7 +846,7 @@ RmDirRetry:
int
CIFSSMBMkDir(const unsigned int xid, struct inode *inode, umode_t mode,
struct cifs_tcon *tcon, const char *name,
- struct cifs_sb_info *cifs_sb)
+ struct cifs_sb_info *cifs_sb, struct dentry *dentry)
{
int rc = 0;
CREATE_DIRECTORY_REQ *pSMB = NULL;
diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c
index d8728da54512..50f926aefd68 100644
--- a/fs/smb/client/inode.c
+++ b/fs/smb/client/inode.c
@@ -887,7 +887,7 @@ out_reparse:
fattr->cf_mode, fattr->cf_uniqueid, fattr->cf_nlink);
}
-static void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
+void cifs_open_info_to_fattr(struct cifs_fattr *fattr,
struct cifs_open_info_data *data,
struct super_block *sb)
{
@@ -1256,7 +1256,7 @@ out:
return rc;
}
-static int cifs_get_fattr(struct cifs_open_info_data *data,
+int cifs_get_fattr(struct cifs_open_info_data *data,
struct super_block *sb, int xid,
const struct cifs_fid *fid,
struct cifs_fattr *fattr,
@@ -2273,7 +2273,7 @@ struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode,
}
/* BB add setting the equivalent of mode via CreateX w/ACLs */
- rc = server->ops->mkdir(xid, inode, mode, tcon, full_path, cifs_sb);
+ rc = server->ops->mkdir(xid, inode, mode, tcon, full_path, cifs_sb, direntry);
if (rc) {
cifs_dbg(FYI, "cifs_mkdir returned 0x%x\n", rc);
d_drop(direntry);
@@ -2281,8 +2281,9 @@ struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode,
}
/* TODO: skip this for smb2/smb3 */
- rc = cifs_mkdir_qinfo(inode, direntry, mode, full_path, cifs_sb, tcon,
- xid);
+ if (server->vals->protocol_id < SMB20_PROT_ID)
+ rc = cifs_mkdir_qinfo(inode, direntry, mode, full_path, cifs_sb, tcon,
+ xid);
mkdir_out:
/*
* Force revalidate to get parent dir info when needed since cached
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c
index f5fd9fbcc096..cd3901e8649e 100644
--- a/fs/smb/client/smb2inode.c
+++ b/fs/smb/client/smb2inode.c
@@ -180,7 +180,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
struct smb2_query_info_rsp *qi_rsp = NULL;
struct smb2_compound_vars *vars = NULL;
__u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
- struct cifs_open_info_data *idata;
+ struct cifs_open_info_data *idata = NULL;
struct cifs_ses *ses = tcon->ses;
struct reparse_data_buffer *rbuf;
struct TCP_Server_Info *server;
@@ -198,6 +198,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
int tmp_rc, rc;
int flags = 0;
void *data[2];
+ struct cached_fid *parent_cfid = NULL;
replay_again:
/* reinitialize for possible replay */
@@ -205,6 +206,8 @@ replay_again:
oplock = SMB2_OPLOCK_LEVEL_NONE;
num_rqst = 0;
server = cifs_pick_channel(ses);
+ parent_cfid = NULL;
+ memset(&fid, 0, sizeof(fid));
vars = kzalloc(sizeof(*vars), GFP_ATOMIC);
if (vars == NULL)
@@ -243,14 +246,15 @@ replay_again:
*/
if (dentry) {
inode = d_inode(dentry);
- if (CIFS_I(inode)->lease_granted && server->ops->get_lease_key) {
+ if (inode && CIFS_I(inode)->lease_granted && server->ops->get_lease_key) {
oplock = SMB2_OPLOCK_LEVEL_LEASE;
server->ops->get_lease_key(inode, &fid);
}
}
vars->oparms = *oparms;
- vars->oparms.fid = &fid;
+ if (!vars->oparms.fid)
+ vars->oparms.fid = &fid;
rqst[num_rqst].rq_iov = &vars->open_iov[0];
rqst[num_rqst].rq_nvec = SMB2_CREATE_IOV_SIZE;
@@ -648,7 +652,7 @@ finished:
tmp_rc = rc;
- if (rc == 0 && num_cmds > 0 && cmds[0] == SMB2_OP_OPEN_QUERY) {
+ if (rc == 0 && num_cmds > 0 && (cmds[0] == SMB2_OP_OPEN_QUERY || cmds[0] == SMB2_OP_MKDIR)) {
create_rsp = rsp_iov[0].iov_base;
idata = in_iov[0].iov_base;
idata->fi.CreationTime = create_rsp->CreationTime;
@@ -663,11 +667,13 @@ finished:
idata->fi.DeletePending = 0;
idata->fi.Directory = !!(le32_to_cpu(create_rsp->FileAttributes) & ATTR_DIRECTORY);
- /* smb2_parse_contexts() fills idata->fi.IndexNumber */
- rc = smb2_parse_contexts(server, &rsp_iov[0], &oparms->fid->epoch,
- oparms->fid->lease_key, &oplock, &idata->fi, NULL);
- if (rc)
- cifs_dbg(VFS, "rc: %d parsing context of compound op\n", rc);
+ if (cmds[0] == SMB2_OP_OPEN_QUERY) {
+ /* smb2_parse_contexts() fills idata->fi.IndexNumber */
+ rc = smb2_parse_contexts(server, &rsp_iov[0], &oparms->fid->epoch,
+ oparms->fid->lease_key, &oplock, &idata->fi, NULL);
+ if (rc)
+ cifs_dbg(VFS, "rc: %d parsing context of compound op\n", rc);
+ }
}
for (i = 0; i < num_cmds; i++) {
@@ -850,9 +856,10 @@ finished:
num_cmds += 2;
if (out_iov && out_buftype) {
- memcpy(out_iov, rsp_iov, num_cmds * sizeof(*out_iov));
- memcpy(out_buftype, resp_buftype,
- num_cmds * sizeof(*out_buftype));
+ if (!cmds || cmds[0] != SMB2_OP_MKDIR) {
+ memcpy(out_iov, rsp_iov, num_cmds * sizeof(*out_iov));
+ memcpy(out_buftype, resp_buftype, num_cmds * sizeof(*out_buftype));
+ }
} else {
for (i = 0; i < num_cmds; i++)
free_rsp_buf(resp_buftype[i], rsp_iov[i].iov_base);
@@ -1108,16 +1115,120 @@ out:
int
smb2_mkdir(const unsigned int xid, struct inode *parent_inode, umode_t mode,
struct cifs_tcon *tcon, const char *name,
- struct cifs_sb_info *cifs_sb)
+ struct cifs_sb_info *cifs_sb, struct dentry *dentry)
{
+ struct cifs_open_info_data idata = {};
+ struct cached_fid *cfid, *parent_cfid = NULL;
struct cifs_open_parms oparms;
+ struct cifs_fid *fidp, fid = {};
+ struct dentry *parent;
+ struct kvec req_iov;
+ bool can_cache;
+ int ret;
+
+ oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES, FILE_CREATE, CREATE_NOT_FILE, mode);
+
+ parent = d_find_alias(parent_inode);
+ if (parent) {
+ parent_cfid = lookup_cached_dir(tcon->cfids, parent, CFID_LOOKUP_DENTRY);
+ dput(parent);
+ }
- oparms = CIFS_OPARMS(cifs_sb, tcon, name, FILE_WRITE_ATTRIBUTES,
- FILE_CREATE, CREATE_NOT_FILE, mode);
- return smb2_compound_op(xid, tcon, cifs_sb,
- name, &oparms, NULL,
- &(int){SMB2_OP_MKDIR}, 1,
- NULL, NULL, NULL, NULL);
+ can_cache = can_cache_dir(tcon);
+ if (can_cache) {
+ struct TCP_Server_Info *server = tcon->ses->server;
+ __le16 *utf16_path;
+ u8 oplock = SMB2_OPLOCK_LEVEL_LEASE;
+
+ if (!server->oplocks || tcon->no_lease || !(server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING))
+ goto mkdir_compound;
+
+ cfid = init_cached_dir(tcon->cfids, name);
+ if (!cfid) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ utf16_path = cifs_convert_path_to_utf16(name, cifs_sb);
+ if (!utf16_path) {
+ close_cached_dir(cfid);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ oparms.fid = fidp = &cfid->fid;
+
+ if (parent_cfid) {
+ memcpy(fidp->parent_lease_key, parent_cfid->fid.lease_key, SMB2_LEASE_KEY_SIZE);
+ oparms.lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+ }
+
+ ret = SMB2_open(xid, &oparms, utf16_path, &oplock, &cfid->file_all_info, NULL, NULL, NULL);
+ kfree(utf16_path);
+ } else {
+mkdir_compound:
+ req_iov.iov_base = &idata;
+ oparms.fid = fidp = &fid;
+
+ ret = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms, &req_iov, &(int){SMB2_OP_MKDIR}, 1,
+ NULL, NULL, NULL, NULL);
+ }
+
+ if (ret) {
+ if (can_cache) {
+ oparms.lease_flags = 0;
+ oparms.fid = NULL;
+ close_cached_dir(cfid);
+ cfid = NULL;
+ can_cache = false;
+ goto mkdir_compound;
+ }
+ goto out;
+ }
+
+ if (can_cache) {
+ struct cifs_fattr fattr = {};
+ struct inode *inode = NULL;
+
+ cfid->is_open = true;
+ idata.fi = cfid->file_all_info;
+
+ ret = cifs_get_fattr(&idata, parent_inode->i_sb, xid, &cfid->fid, &fattr, &inode, name);
+ if (ret) {
+ close_cached_dir(cfid);
+ goto out;
+ }
+
+ inode = cifs_iget(parent_inode->i_sb, &fattr);
+ if (!inode) {
+ close_cached_dir(cfid);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ d_drop(dentry);
+ d_add(dentry, inode);
+ cifs_set_time(dentry, jiffies);
+
+ CIFS_I(inode)->time = jiffies;
+ tcon->ses->server->ops->set_lease_key(inode, &fid);
+
+ cfid->file_all_info_is_valid = true;
+ cfid->dentry = dget(dentry);
+ cfid->time = jiffies;
+ ret = add_cached_dir(tcon, NULL, cfid);
+ if (ret)
+ close_cached_dir(cfid);
+ }
+out:
+ if (parent_cfid) {
+ mutex_lock(&parent_cfid->dirents.de_mutex);
+ parent_cfid->dirents.is_valid = false;
+ mutex_unlock(&parent_cfid->dirents.de_mutex);
+ close_cached_dir(parent_cfid);
+ }
+
+ return ret;
}
void
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index d478fc58ad70..1e5807415038 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2999,6 +2999,60 @@ err_free_path:
return rc;
}
+/*
+ * Set parent lease key for a Create op.
+ *
+ * If dir leases are supported, we can always set ParentLeaseKey -- it's only a hint to the server that we're
+ * not modifying parent itself, only the child.
+ *
+ * This avoids a leasebreak of the parent dir in most cases, but the server will still emit one when required
+ * (e.g. concurrent opens with different options/modes).
+ *
+ * Return: true if parent lease key was set, false otherwise.
+ */
+static bool smb2_set_parent_lease(struct cifs_tcon *tcon, struct cifs_open_parms *oparms)
+{
+ struct cached_fid *pcfid;
+ const char *path;
+ int i, len, sep;
+ char *tmp;
+
+ if (oparms->lease_flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE)
+ return true;
+
+ if (!oparms->fid)
+ return false;
+
+ memset(oparms->fid->parent_lease_key, 0, SMB2_LEASE_KEY_SIZE);
+
+ path = oparms->path;
+ if (!*path)
+ return false;
+
+ sep = oparms->cifs_sb ? CIFS_DIR_SEP(oparms->cifs_sb) : '\\';
+ tmp = strrchr(path, sep);
+ if (!tmp)
+ return false;
+
+ len = strlen(tmp);
+ tmp = kstrdup(path, GFP_ATOMIC);
+ if (!tmp)
+ return -ENOMEM;
+
+ i = strlen(path) - len;
+ memset(&tmp[i], 0, len);
+
+ pcfid = lookup_cached_dir(tcon->cfids, tmp, CFID_LOOKUP_PATH);
+ if (pcfid) {
+ memcpy(oparms->fid->parent_lease_key, pcfid->fid.lease_key, SMB2_LEASE_KEY_SIZE);
+ oparms->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
+ close_cached_dir(pcfid);
+ }
+
+ kfree(tmp);
+ return true;
+}
+
int
SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
struct smb_rqst *rqst, __u8 *oplock,
@@ -3075,48 +3129,24 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
iov[1].iov_len = uni_path_len;
iov[1].iov_base = path;
- if ((!server->oplocks) || (tcon->no_lease) || !(server->capabilities & SMB2_GLOBAL_CAP_LEASING) ||
- (!(server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) &&
- (oparms->create_options & CREATE_NOT_FILE)))
+ if (!server->oplocks || tcon->no_lease || !(server->capabilities & SMB2_GLOBAL_CAP_LEASING)) {
*oplock = SMB2_OPLOCK_LEVEL_NONE;
+ goto no_lease_ctx;
+ }
- if (*oplock != SMB2_OPLOCK_LEVEL_NONE) {
- /*
- * If server has dir leases support, we can always set ParentLeaseKey -- it's only a
- * hint to the server that we're not modifying parent itself, only the child.
- *
- * This avoids a leasebreak of the parent dir in most cases, but the server will
- * still emit one when required (e.g. concurrent opens with different options/modes).
- */
- memset(oparms->fid->parent_lease_key, 0, SMB2_LEASE_KEY_SIZE);
-
- if (*oparms->path) {
- char *tmp_path;
- int sep = oparms->cifs_sb ? CIFS_DIR_SEP(oparms->cifs_sb) : '\\';
-
- tmp_path = strrchr(oparms->path, sep);
- if (tmp_path) {
- struct cached_fid *pcfid;
- size_t i, len = strlen(tmp_path);
-
- tmp_path = kstrdup(oparms->path, GFP_ATOMIC);
- if (!tmp_path)
- return -ENOMEM;
-
- i = strlen(oparms->path) - len;
- memset(&tmp_path[i], 0, len);
-
- pcfid = lookup_cached_dir(tcon->cfids, tmp_path, CFID_LOOKUP_PATH);
- kfree(tmp_path);
- if (pcfid) {
- memcpy(oparms->fid->parent_lease_key, pcfid->fid.lease_key,
- SMB2_LEASE_KEY_SIZE);
- oparms->lease_flags |= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
- close_cached_dir(pcfid);
- }
- }
+ if (server->capabilities & SMB2_GLOBAL_CAP_DIRECTORY_LEASING) {
+ if (smb2_set_parent_lease(tcon, oparms)) {
+ if (*oplock == SMB2_OPLOCK_LEVEL_NONE &&
+ ((oparms->create_options & CREATE_NOT_FILE) ||
+ (oparms->disposition == FILE_OPEN && !(oparms->create_options & CREATE_NOT_DIR))))
+ *oplock = SMB2_OPLOCK_LEVEL_LEASE;
}
+ } else if (oparms->create_options & CREATE_NOT_FILE) {
+ *oplock = SMB2_OPLOCK_LEVEL_NONE;
+ goto no_lease_ctx;
+ }
+ if (*oplock != SMB2_OPLOCK_LEVEL_NONE) {
rc = add_lease_context(server, iov, &n_iov,
oparms->fid->lease_key, *oplock,
oparms->fid->parent_lease_key,
@@ -3124,7 +3154,7 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
if (rc)
return rc;
}
-
+no_lease_ctx:
req->RequestedOplockLevel = *oplock;
if (*oplock == SMB2_OPLOCK_LEVEL_BATCH) {
diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
index 035aa1624053..a208d7f78931 100644
--- a/fs/smb/client/smb2proto.h
+++ b/fs/smb/client/smb2proto.h
@@ -85,7 +85,7 @@ extern int smb311_posix_mkdir(const unsigned int xid, struct inode *inode,
struct cifs_sb_info *cifs_sb);
extern int smb2_mkdir(const unsigned int xid, struct inode *inode,
umode_t mode, struct cifs_tcon *tcon,
- const char *name, struct cifs_sb_info *cifs_sb);
+ const char *name, struct cifs_sb_info *cifs_sb, struct dentry *dentry);
extern void smb2_mkdir_setinfo(struct inode *inode, const char *full_path,
struct cifs_sb_info *cifs_sb,
struct cifs_tcon *tcon, const unsigned int xid);