Discussion:
[PATCH 0/2] Fix data corruption at oplock break handler
Sachin Prabhu
2014-03-07 13:29:18 UTC
Permalink
Fix data corruption at oplock break handler.

To avoid data corruption, we have to avoid changing the oplock
value for a particular file until existing writer threads have quit. For that
reason, we move the set_oplock_value to the oplock_break handler. Since the
values used for the oplock break vary according to the protocol versions, we
need to have protocol specific oplock_break handlers.

I have successfully tested this against a reproducer we have for both SMB1 and
SMB2.

Sachin Prabhu (2):
Add oplock_break to smb_version_operations
cifs: Wait for writebacks to complete before attempting write.

fs/cifs/cifsfs.c | 14 +++++++++-
fs/cifs/cifsglob.h | 9 +++++--
fs/cifs/cifsproto.h | 5 ++++
fs/cifs/file.c | 69 ++++++++++++-------------------------------------
fs/cifs/misc.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++--
fs/cifs/smb1ops.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2misc.c | 18 ++++++++++---
fs/cifs/smb2ops.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 264 insertions(+), 61 deletions(-)
--
1.8.4.2
Sachin Prabhu
2014-03-07 13:29:19 UTC
Permalink
We need to add protocol specific calls to the oplock break thread.

Signed-off-by: Sachin Prabhu <sprabhu-H+wXaHxf7aLQT0dZR+***@public.gmane.org>
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53 +++--------------------------------------------------
fs/cifs/smb1ops.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 106 insertions(+), 52 deletions(-)

diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *,
int);
+ void (*oplock_break)(struct work_struct *);
};

struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */

-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;

diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock);
extern int cifs_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int cifs_push_mandatory_locks(struct cifsFileInfo *cfile);
+extern int cifs_push_locks(struct cifsFileInfo *cfile);
+bool cifs_has_mand_locks(struct cifsInodeInfo *cinode);

extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
@@ -251,7 +251,7 @@ out:
return rc;
}

-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break, server->ops->oplock_break);
mutex_init(&cfile->fh_mutex);

cifs_sb_active(inode->i_sb);
@@ -1204,7 +1204,7 @@ err_out:
goto out;
}

-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->dentry->d_sb);
@@ -3656,53 +3656,6 @@ static int cifs_launder_page(struct page *page)
return rc;
}

-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
- cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity issue but do
- * not bother sending an oplock release if session to server still is
- * disconnected since oplock already released by the server
- */
- if (!cfile->oplock_break_cancelled) {
- rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
- cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"

/*
* An NT cancel request header looks just like the original request except:
@@ -999,6 +1000,53 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ;
}

+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations smb1_operations = {
.query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"

static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}

+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations smb20_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};

struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations smb21_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};

struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations smb30_operations = {
.parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};

struct smb_version_values smb20_values = {
--
1.8.4.2
Jeff Layton
2014-03-10 15:06:40 UTC
Permalink
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53 +++--------------------------------------------------
fs/cifs/smb1ops.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *,
int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock);
extern int cifs_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int cifs_push_mandatory_locks(struct cifsFileInfo *cfile);
+extern int cifs_push_locks(struct cifsFileInfo *cfile);
+bool cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break, server->ops->oplock_break);
mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->dentry->d_sb);
@@ -3656,53 +3656,6 @@ static int cifs_launder_page(struct page *page)
return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
- cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity issue but do
- * not bother sending an oplock release if session to server still is
- * disconnected since oplock already released by the server
- */
- if (!cfile->oplock_break_cancelled) {
- rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
- cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
@@ -999,6 +1000,53 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations smb1_operations = {
.query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations smb20_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations smb21_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations smb30_operations = {
.parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
--
Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+***@public.gmane.org>
Sachin Prabhu
2014-03-10 16:21:10 UTC
Permalink
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix to
work, we need to downgrade the oplock only once we are sure that no
writers are operating.

We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out the
functions required to downgrade the oplocks. For this we need version
specific functions. This could either be the oplock_break function as a
whole or we could define a new function called downgrade_oplock() in
smb_version_operations. I have chosen to use version specific
oplock_break here. downgrade_oplock() could instead be used here which
would require fewer changes.

Should I implement this using a downgrade_oplock() function instead?

Sachin Prabhu
Post by Jeff Layton
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53 +++--------------------------------------------------
fs/cifs/smb1ops.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *,
int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock);
extern int cifs_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int cifs_push_mandatory_locks(struct cifsFileInfo *cfile);
+extern int cifs_push_locks(struct cifsFileInfo *cfile);
+bool cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break, server->ops->oplock_break);
mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->dentry->d_sb);
@@ -3656,53 +3656,6 @@ static int cifs_launder_page(struct page *page)
return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
- cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity issue but do
- * not bother sending an oplock release if session to server still is
- * disconnected since oplock already released by the server
- */
- if (!cfile->oplock_break_cancelled) {
- rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
- cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
@@ -999,6 +1000,53 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations smb1_operations = {
.query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations smb20_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations smb21_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations smb30_operations = {
.parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
Steve French
2014-03-10 16:28:46 UTC
Permalink
Since this might have to go to stable (or be backported) isn't the smaller
fix size a big consideration?
Post by Sachin Prabhu
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix to
work, we need to downgrade the oplock only once we are sure that no
writers are operating.
We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out the
functions required to downgrade the oplocks. For this we need version
specific functions. This could either be the oplock_break function as a
whole or we could define a new function called downgrade_oplock() in
smb_version_operations. I have chosen to use version specific
oplock_break here. downgrade_oplock() could instead be used here which
would require fewer changes.
Should I implement this using a downgrade_oplock() function instead?
Sachin Prabhu
Post by Jeff Layton
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53 +++--------------------------------------------------
fs/cifs/smb1ops.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *,
int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock);
extern int cifs_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int cifs_push_mandatory_locks(struct cifsFileInfo *cfile);
+extern int cifs_push_locks(struct cifsFileInfo *cfile);
+bool cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break, server->ops->oplock_break);
mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->dentry->d_sb);
@@ -3656,53 +3656,6 @@ static int cifs_launder_page(struct page *page)
return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
- cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity issue but do
- * not bother sending an oplock release if session to server still is
- * disconnected since oplock already released by the server
- */
- if (!cfile->oplock_break_cancelled) {
- rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
- cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
@@ -999,6 +1000,53 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations smb1_operations = {
.query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations smb20_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations smb21_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations smb30_operations = {
.parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Thanks,

Steve
Sachin Prabhu
2014-03-10 16:47:49 UTC
Permalink
Post by Steve French
Since this might have to go to stable (or be backported) isn't the smaller
fix size a big consideration?
Yes. It may be better. The only reason I split oplock_break into version
specific options was because I thought we may need to do it any how for
other reasons in the future.

I'll post a new version using a version specific downgrade_oplock()
instead.

Sachin Prabhu
Post by Steve French
Post by Sachin Prabhu
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix to
work, we need to downgrade the oplock only once we are sure that no
writers are operating.
We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out the
functions required to downgrade the oplocks. For this we need version
specific functions. This could either be the oplock_break function as a
whole or we could define a new function called downgrade_oplock() in
smb_version_operations. I have chosen to use version specific
oplock_break here. downgrade_oplock() could instead be used here which
would require fewer changes.
Should I implement this using a downgrade_oplock() function instead?
Sachin Prabhu
Post by Jeff Layton
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53 +++--------------------------------------------------
fs/cifs/smb1ops.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode *, const char *,
int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock);
extern int cifs_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int cifs_push_mandatory_locks(struct cifsFileInfo *cfile);
+extern int cifs_push_locks(struct cifsFileInfo *cfile);
+bool cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break, server->ops->oplock_break);
mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->dentry->d_sb);
@@ -3656,53 +3656,6 @@ static int cifs_launder_page(struct page *page)
return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
- cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity issue but do
- * not bother sending an oplock release if session to server still is
- * disconnected since oplock already released by the server
- */
- if (!cfile->oplock_break_cancelled) {
- rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
- cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed otherwise.
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
@@ -999,6 +1000,53 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations smb1_operations = {
.query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+ cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc = tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+ cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations smb20_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations smb21_operations = {
.create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations smb30_operations = {
.parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jeffrey Layton
2014-03-10 18:16:32 UTC
Permalink
On Mon, 10 Mar 2014 16:21:10 +0000
Post by Sachin Prabhu
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix
to work, we need to downgrade the oplock only once we are sure that no
writers are operating.
We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out
the functions required to downgrade the oplocks. For this we need
version specific functions. This could either be the oplock_break
function as a whole or we could define a new function called
downgrade_oplock() in smb_version_operations. I have chosen to use
version specific oplock_break here. downgrade_oplock() could instead
be used here which would require fewer changes.
Should I implement this using a downgrade_oplock() function instead?
I guess I'm unclear on why the set_oplock_level operation (possibly
combined with some new struct smb_version_values fields) wouldn't be a
better solution.

I'll grant however that the oplock handing code is a bit of a mess. It
seems like it could benefit from some rethink and redesign...
Post by Sachin Prabhu
Sachin Prabhu
Post by Jeff Layton
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53
+++--------------------------------------------------
fs/cifs/smb1ops.c | 49
+++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51
+++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files
changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode
*, const char *, int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct
cifsInodeInfo *cinode, __u32 oplock); extern int
cifs_unlock_range(struct cifsFileInfo *cfile, struct file_lock
*flock, const unsigned int xid); extern int
cifs_push_mandatory_locks(struct cifsFileInfo *cfile); +extern
int cifs_push_locks(struct cifsFileInfo *cfile); +bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid
*fid, struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file, cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break,
server->ops->oplock_break); mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb =
cifs_launder_page(struct page *page) return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct
cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode)
&&
-
cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb
session using
- * a now incorrect file handle is not a data integrity
issue but do
- * not bother sending an oplock release if session to
server still is
- * disconnected since oplock already released by the
server
- */
- if (!cfile->oplock_break_cancelled) {
- rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
-
cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed
otherwise. diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
* An NT cancel request header looks just like the original
oplock) return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct
cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode)
&&
+
cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb
session using
+ * a now incorrect file handle is not a data integrity
issue but do
+ * not bother sending an oplock release if session to
server still is
+ * disconnected since oplock already released by the
server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+
cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations
smb1_operations = { .query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid,
struct cifs_tcon *tcon, return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct
cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode)
&&
+
cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb
session using
+ * a now incorrect file handle is not a data integrity
issue but do
+ * not bother sending an oplock release if session to
server still is
+ * disconnected since oplock already released by the
server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+
cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations
smb20_operations = { .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations
smb21_operations = { .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations
smb30_operations = { .parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs"
More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+***@public.gmane.org>
Sachin Prabhu
2014-03-11 10:25:49 UTC
Permalink
Post by Jeffrey Layton
On Mon, 10 Mar 2014 16:21:10 +0000
Post by Sachin Prabhu
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix
to work, we need to downgrade the oplock only once we are sure that no
writers are operating.
We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out
the functions required to downgrade the oplocks. For this we need
version specific functions. This could either be the oplock_break
function as a whole or we could define a new function called
downgrade_oplock() in smb_version_operations. I have chosen to use
version specific oplock_break here. downgrade_oplock() could instead
be used here which would require fewer changes.
Should I implement this using a downgrade_oplock() function instead?
I guess I'm unclear on why the set_oplock_level operation (possibly
combined with some new struct smb_version_values fields) wouldn't be a
better solution.
I'll grant however that the oplock handing code is a bit of a mess. It
seems like it could benefit from some rethink and redesign...
The constants used to denote oplock values are not consistent across the
versions

In this case, a level 2 oplock for smb1 is
#define OPLOCK_READ 3 /* level 2 oplock */
for smb2, it is
#define SMB2_OPLOCK_LEVEL_II 0x01

similarly the values for other states are different too.

We could abstract these but it sounded like overkill in this situation.

Sachin Prabhu
Post by Jeffrey Layton
Post by Sachin Prabhu
Sachin Prabhu
Post by Jeff Layton
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53
+++--------------------------------------------------
fs/cifs/smb1ops.c | 49
+++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51
+++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files
changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode
*, const char *, int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct
cifsInodeInfo *cinode, __u32 oplock); extern int
cifs_unlock_range(struct cifsFileInfo *cfile, struct file_lock
*flock, const unsigned int xid); extern int
cifs_push_mandatory_locks(struct cifsFileInfo *cfile); +extern
int cifs_push_locks(struct cifsFileInfo *cfile); +bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid
*fid, struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file, cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break,
server->ops->oplock_break); mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb =
cifs_launder_page(struct page *page) return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct
cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
-
cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity
issue but do
- * not bother sending an oplock release if session to
server still is
- * disconnected since oplock already released by the
server
- */
- if (!cfile->oplock_break_cancelled) {
- rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
-
cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed
otherwise. diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
* An NT cancel request header looks just like the original
oplock) return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct
cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+
cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity
issue but do
+ * not bother sending an oplock release if session to
server still is
+ * disconnected since oplock already released by the
server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+
cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations
smb1_operations = { .query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid,
struct cifs_tcon *tcon, return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct
cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+
cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity
issue but do
+ * not bother sending an oplock release if session to
server still is
+ * disconnected since oplock already released by the
server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+
cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations
smb20_operations = { .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations
smb21_operations = { .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations
smb30_operations = { .parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs"
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jeff Layton
2014-03-11 11:08:58 UTC
Permalink
On Tue, 11 Mar 2014 10:25:49 +0000
Post by Sachin Prabhu
Post by Jeffrey Layton
On Mon, 10 Mar 2014 16:21:10 +0000
Post by Sachin Prabhu
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix
to work, we need to downgrade the oplock only once we are sure that no
writers are operating.
We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out
the functions required to downgrade the oplocks. For this we need
version specific functions. This could either be the oplock_break
function as a whole or we could define a new function called
downgrade_oplock() in smb_version_operations. I have chosen to use
version specific oplock_break here. downgrade_oplock() could instead
be used here which would require fewer changes.
Should I implement this using a downgrade_oplock() function instead?
I guess I'm unclear on why the set_oplock_level operation (possibly
combined with some new struct smb_version_values fields) wouldn't be a
better solution.
I'll grant however that the oplock handing code is a bit of a mess. It
seems like it could benefit from some rethink and redesign...
The constants used to denote oplock values are not consistent across the
versions
In this case, a level 2 oplock for smb1 is
#define OPLOCK_READ 3 /* level 2 oplock */
for smb2, it is
#define SMB2_OPLOCK_LEVEL_II 0x01
similarly the values for other states are different too.
We could abstract these but it sounded like overkill in this situation.
Sachin Prabhu
Yeah, that's the part that's a mess. We have a generic set_oplock_level
operation but that requires you to pass down oplock levels that are
version specific. Typically when you have a generic operation, you want
it to take a set of generic values as arguments.
Post by Sachin Prabhu
Post by Jeffrey Layton
Post by Sachin Prabhu
Sachin Prabhu
Post by Jeff Layton
Post by Sachin Prabhu
---
fs/cifs/cifsglob.h | 3 +--
fs/cifs/cifsproto.h | 2 ++
fs/cifs/file.c | 53
+++--------------------------------------------------
fs/cifs/smb1ops.c | 49
+++++++++++++++++++++++++++++++++++++++++++++++++
fs/cifs/smb2ops.c | 51
+++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files
changed, 106 insertions(+), 52 deletions(-)
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index cf32f03..93a8762 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -402,6 +402,7 @@ struct smb_version_operations {
const struct cifs_fid *, u32 *);
int (*set_acl)(struct cifs_ntsd *, __u32, struct inode
*, const char *, int);
+ void (*oplock_break)(struct work_struct *);
};
struct smb_version_values {
@@ -1557,8 +1558,6 @@ GLOBAL_EXTERN spinlock_t uidsidlock;
GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
-void cifs_oplock_break(struct work_struct *work);
-
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index acc4ee8..e4d0add 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -130,6 +130,8 @@ extern void cifs_set_oplock_level(struct
cifsInodeInfo *cinode, __u32 oplock); extern int
cifs_unlock_range(struct cifsFileInfo *cfile, struct file_lock
*flock, const unsigned int xid); extern int
cifs_push_mandatory_locks(struct cifsFileInfo *cfile); +extern
int cifs_push_locks(struct cifsFileInfo *cfile); +bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode);
extern struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid
*fid, struct file *file,
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 53c1507..998dec7 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
return rc;
}
-static bool
+bool
cifs_has_mand_locks(struct cifsInodeInfo *cinode)
{
struct cifs_fid_locks *cur;
@@ -304,7 +304,7 @@ cifs_new_fileinfo(struct cifs_fid *fid,
struct file *file, cfile->f_flags = file->f_flags;
cfile->invalidHandle = false;
cfile->tlink = cifs_get_tlink(tlink);
- INIT_WORK(&cfile->oplock_break, cifs_oplock_break);
+ INIT_WORK(&cfile->oplock_break,
server->ops->oplock_break); mutex_init(&cfile->fh_mutex);
cifs_sb_active(inode->i_sb);
goto out;
}
-static int
+int
cifs_push_locks(struct cifsFileInfo *cfile)
{
struct cifs_sb_info *cifs_sb =
cifs_launder_page(struct page *page) return rc;
}
-void cifs_oplock_break(struct work_struct *work)
-{
- struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
- oplock_break);
- struct inode *inode = cfile->dentry->d_inode;
- struct cifsInodeInfo *cinode = CIFS_I(inode);
- struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
- int rc = 0;
-
- if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
-
cifs_has_mand_locks(cinode)) {
- cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
- inode);
- cinode->oplock = 0;
- }
-
- if (inode && S_ISREG(inode->i_mode)) {
- if (CIFS_CACHE_READ(cinode))
- break_lease(inode, O_RDONLY);
- else
- break_lease(inode, O_WRONLY);
- rc = filemap_fdatawrite(inode->i_mapping);
- if (!CIFS_CACHE_READ(cinode)) {
- rc = filemap_fdatawait(inode->i_mapping);
- mapping_set_error(inode->i_mapping, rc);
- cifs_invalidate_mapping(inode);
- }
- cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
- }
-
- rc = cifs_push_locks(cfile);
- if (rc)
- cifs_dbg(VFS, "Push locks rc = %d\n", rc);
-
- /*
- * releasing stale oplock after recent reconnect of smb session using
- * a now incorrect file handle is not a data integrity issue but do
- * not bother sending an oplock release if session to server still is
- * disconnected since oplock already released by the
server
- */
- if (!cfile->oplock_break_cancelled) {
- rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
-
cinode);
- cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
- }
-}
-
/*
* The presence of cifs_direct_io() in the address space ops vector
* allowes open() O_DIRECT flags which would have failed
otherwise. diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 526fb89..346ee2a 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -23,6 +23,7 @@
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifspdu.h"
+#include "cifsfs.h"
/*
* An NT cancel request header looks just like the original
oplock) return oplock == OPLOCK_READ;
}
+static void cifs_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+
cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the
server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+
cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
struct smb_version_operations smb1_operations = {
.send_cancel = send_nt_cancel,
.compare_fids = cifs_compare_fids,
@@ -1067,6 +1115,7 @@ struct smb_version_operations
smb1_operations = { .query_mf_symlink = cifs_query_mf_symlink,
.create_mf_symlink = cifs_create_mf_symlink,
.is_read_op = cifs_is_read_op,
+ .oplock_break = cifs_oplock_break,
#ifdef CONFIG_CIFS_XATTR
.query_all_EAs = CIFSSMBQAllEAs,
.set_EA = CIFSSMBSetEA,
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 192f51a..e7081cd 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -27,6 +27,7 @@
#include "cifs_unicode.h"
#include "smb2status.h"
#include "smb2glob.h"
+#include "cifsfs.h"
static int
change_conf(struct TCP_Server_Info *server)
@@ -904,6 +905,53 @@ smb2_query_symlink(const unsigned int xid,
struct cifs_tcon *tcon, return rc;
}
+static void smb2_oplock_break(struct work_struct *work)
+{
+ struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+ oplock_break);
+ struct inode *inode = cfile->dentry->d_inode;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+ struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ int rc = 0;
+
+ if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
+
cifs_has_mand_locks(cinode)) {
+ cifs_dbg(FYI, "Reset oplock to None for inode=%p
due to mand locks\n",
+ inode);
+ cinode->oplock = 0;
+ }
+
+ if (inode && S_ISREG(inode->i_mode)) {
+ if (CIFS_CACHE_READ(cinode))
+ break_lease(inode, O_RDONLY);
+ else
+ break_lease(inode, O_WRONLY);
+ rc = filemap_fdatawrite(inode->i_mapping);
+ if (!CIFS_CACHE_READ(cinode)) {
+ rc = filemap_fdatawait(inode->i_mapping);
+ mapping_set_error(inode->i_mapping, rc);
+ cifs_invalidate_mapping(inode);
+ }
+ cifs_dbg(FYI, "Oplock flush inode %p rc %d\n",
inode, rc);
+ }
+
+ rc = cifs_push_locks(cfile);
+ if (rc)
+ cifs_dbg(VFS, "Push locks rc = %d\n", rc);
+
+ /*
+ * releasing stale oplock after recent reconnect of smb session using
+ * a now incorrect file handle is not a data integrity issue but do
+ * not bother sending an oplock release if session to server still is
+ * disconnected since oplock already released by the
server
+ */
+ if (!cfile->oplock_break_cancelled) {
+ rc =
tcon->ses->server->ops->oplock_response(tcon, &cfile->fid,
+
cinode);
+ cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
+ }
+}
+
static void
smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache)
@@ -1163,6 +1211,7 @@ struct smb_version_operations
smb20_operations = { .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb21_operations = {
@@ -1237,6 +1286,7 @@ struct smb_version_operations
smb21_operations = { .create_lease_buf = smb2_create_lease_buf,
.parse_lease_buf = smb2_parse_lease_buf,
.clone_range = smb2_clone_range,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_operations smb30_operations = {
@@ -1314,6 +1364,7 @@ struct smb_version_operations
smb30_operations = { .parse_lease_buf = smb3_parse_lease_buf,
.clone_range = smb2_clone_range,
.validate_negotiate = smb3_validate_negotiate,
+ .oplock_break = smb2_oplock_break,
};
struct smb_version_values smb20_values = {
--
To unsubscribe from this list: send the line "unsubscribe linux-cifs"
More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+***@public.gmane.org>
Sachin Prabhu
2014-03-11 15:05:20 UTC
Permalink
Post by Jeff Layton
On Tue, 11 Mar 2014 10:25:49 +0000
Post by Sachin Prabhu
Post by Jeffrey Layton
On Mon, 10 Mar 2014 16:21:10 +0000
Post by Sachin Prabhu
Post by Jeff Layton
On Fri, 7 Mar 2014 13:29:19 +0000
Post by Sachin Prabhu
We need to add protocol specific calls to the oplock break thread.
I'm not necessarily doubting this, but why? AFAICT, the oplock_break
calls look more or less identical...
The oplock values for level 2 oplocks are different. For the 2nd fix
to work, we need to downgrade the oplock only once we are sure that no
writers are operating.
We need to downgrade oplocks. Since the constants used to describe
oplock states varies between cifs and smb2, we need to abstract out
the functions required to downgrade the oplocks. For this we need
version specific functions. This could either be the oplock_break
function as a whole or we could define a new function called
downgrade_oplock() in smb_version_operations. I have chosen to use
version specific oplock_break here. downgrade_oplock() could instead
be used here which would require fewer changes.
Should I implement this using a downgrade_oplock() function instead?
I guess I'm unclear on why the set_oplock_level operation (possibly
combined with some new struct smb_version_values fields) wouldn't be a
better solution.
I'll grant however that the oplock handing code is a bit of a mess. It
seems like it could benefit from some rethink and redesign...
The constants used to denote oplock values are not consistent across the
versions
In this case, a level 2 oplock for smb1 is
#define OPLOCK_READ 3 /* level 2 oplock */
for smb2, it is
#define SMB2_OPLOCK_LEVEL_II 0x01
similarly the values for other states are different too.
We could abstract these but it sounded like overkill in this situation.
Sachin Prabhu
Yeah, that's the part that's a mess. We have a generic set_oplock_level
operation but that requires you to pass down oplock levels that are
version specific. Typically when you have a generic operation, you want
it to take a set of generic values as arguments.
I have a patch ready with the downgrade_oplock version specific
function. Since this problem leads to data corruption and needs to be
included in stable, I propose we go ahead with this version for now. The
other option to fix the set_oplock_levels() call will require a lot more
changes and may not be suitable for the stable kernels.
Once this issue has been fixed, we can work to fix set_oplock_level and
remove downgrade_oplock().

Sachin Prabhu

Sachin Prabhu
2014-03-07 13:29:20 UTC
Permalink
Problem reported in Red Hat bz 1040329 for strict writes where we cache
only when we hold oplock and write direct to the server when we don't.

When we receive an oplock break, we first change the oplock value for
the inode in cifsInodeInfo->oplock to indicate that we no longer hold
the oplock before we enqueue a task to flush changes to the backing
device. Once we have completed flushing the changes, we return the
oplock to the server.

There are 2 ways here where we can have data corruption
1) While we flush changes to the backing device as part of the oplock
break, we can have processes write to the file. These writes check for
the oplock, find none and attempt to write directly to the server.
These direct writes made while we are flushing from cache could be
overwritten by data being flushed from the cache causing data
corruption.
2) While a thread runs in cifs_strict_writev, the machine could receive
and process an oplock break after the thread has checked the oplock and
found that it allows us to cache and before we have made changes to the
cache. In that case, we end up with a dirty page in cache when we
shouldn't have any. This will be flushed later and will overwrite all
subsequent writes to the part of the file represented by this page.

Before making any writes to the server, we need to confirm that we are
not in the process of flushing data to the server and if we are, we
should wait until the process is complete before we attempt the write.
We should also wait for existing writes to complete before we process
an oplock break request which changes oplock values.

Cc: stable-***@public.gmane.org
Signed-off-by: Sachin Prabhu <sprabhu-H+wXaHxf7aLQT0dZR+***@public.gmane.org>
---
fs/cifs/cifsfs.c | 14 +++++++++-
fs/cifs/cifsglob.h | 6 +++++
fs/cifs/cifsproto.h | 3 +++
fs/cifs/file.c | 16 +++++++++---
fs/cifs/misc.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++--
fs/cifs/smb1ops.c | 16 ++++++++++++
fs/cifs/smb2misc.c | 18 ++++++++++---
fs/cifs/smb2ops.c | 20 +++++++++++++++
8 files changed, 158 insertions(+), 9 deletions(-)

diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index 849f613..7c6b73c 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -253,6 +253,11 @@ cifs_alloc_inode(struct super_block *sb)
cifs_set_oplock_level(cifs_inode, 0);
cifs_inode->delete_pending = false;
cifs_inode->invalid_mapping = false;
+ clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cifs_inode->flags);
+ clear_bit(CIFS_INODE_PENDING_WRITERS, &cifs_inode->flags);
+ clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cifs_inode->flags);
+ spin_lock_init(&cifs_inode->writers_lock);
+ cifs_inode->writers = 0;
cifs_inode->vfs_inode.i_blkbits = 14; /* 2**14 = CIFS_MAX_MSGSIZE */
cifs_inode->server_eof = 0;
cifs_inode->uniqueid = 0;
@@ -731,19 +736,26 @@ static ssize_t cifs_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct inode *inode = file_inode(iocb->ki_filp);
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
ssize_t written;
int rc;

+ written = cifs_get_writer(cinode);
+ if (written)
+ return written;
+
written = generic_file_aio_write(iocb, iov, nr_segs, pos);

if (CIFS_CACHE_WRITE(CIFS_I(inode)))
- return written;
+ goto out;

rc = filemap_fdatawrite(inode->i_mapping);
if (rc)
cifs_dbg(FYI, "cifs_file_aio_write: %d rc on %p inode\n",
rc, inode);

+out:
+ cifs_put_writer(cinode);
return written;
}

diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 93a8762..1e134c2 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -1114,6 +1114,12 @@ struct cifsInodeInfo {
unsigned int epoch; /* used to track lease state changes */
bool delete_pending; /* DELETE_ON_CLOSE is set */
bool invalid_mapping; /* pagecache is invalid */
+ unsigned long flags;
+#define CIFS_INODE_PENDING_OPLOCK_BREAK (0) /* oplock break in progress */
+#define CIFS_INODE_PENDING_WRITERS (1) /* Writes in progress */
+#define CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2 (2) /* Downgrade oplock to L2 */
+ spinlock_t writers_lock;
+ unsigned int writers; /* Number of writers on this inode */
unsigned long time; /* jiffies of last update of inode */
u64 server_eof; /* current file size on server -- protected by i_lock */
u64 uniqueid; /* server inode number */
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index e4d0add..23c5164 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -127,6 +127,9 @@ extern u64 cifs_UnixTimeToNT(struct timespec);
extern struct timespec cnvrtDosUnixTm(__le16 le_date, __le16 le_time,
int offset);
extern void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock);
+extern int cifs_get_writer(struct cifsInodeInfo *cinode);
+extern void cifs_put_writer(struct cifsInodeInfo *cinode);
+extern void cifs_done_oplock_break(struct cifsInodeInfo *cinode);
extern int cifs_unlock_range(struct cifsFileInfo *cfile,
struct file_lock *flock, const unsigned int xid);
extern int cifs_push_mandatory_locks(struct cifsFileInfo *cfile);
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 998dec7..81ef820 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
@@ -2620,12 +2620,20 @@ cifs_strict_writev(struct kiocb *iocb, const struct iovec *iov,
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
ssize_t written;

+ written = cifs_get_writer(cinode);
+ if (written)
+ return written;
+
if (CIFS_CACHE_WRITE(cinode)) {
if (cap_unix(tcon->ses) &&
(CIFS_UNIX_FCNTL_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability))
- && ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOPOSIXBRL) == 0))
- return generic_file_aio_write(iocb, iov, nr_segs, pos);
- return cifs_writev(iocb, iov, nr_segs, pos);
+ && ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOPOSIXBRL) == 0)) {
+ written = generic_file_aio_write(
+ iocb, iov, nr_segs, pos);
+ goto out;
+ }
+ written = cifs_writev(iocb, iov, nr_segs, pos);
+ goto out;
}
/*
* For non-oplocked files in strict cache mode we need to write the data
@@ -2645,6 +2653,8 @@ cifs_strict_writev(struct kiocb *iocb, const struct iovec *iov,
inode);
cinode->oplock = 0;
}
+out:
+ cifs_put_writer(cinode);
return written;
}

diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index 2f9f379..3b0c62e 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -466,8 +466,22 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
cifs_dbg(FYI, "file id match, oplock break\n");
pCifsInode = CIFS_I(netfile->dentry->d_inode);

- cifs_set_oplock_level(pCifsInode,
- pSMB->OplockLevel ? OPLOCK_READ : 0);
+ set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
+ &pCifsInode->flags);
+
+ /*
+ * Set flag if the server downgrades the oplock
+ * to L2 else clear.
+ */
+ if (pSMB->OplockLevel)
+ set_bit(
+ CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
+ &pCifsInode->flags);
+ else
+ clear_bit(
+ CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
+ &pCifsInode->flags);
+
queue_work(cifsiod_wq,
&netfile->oplock_break);
netfile->oplock_break_cancelled = false;
@@ -551,6 +565,62 @@ void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock)
cinode->oplock = 0;
}

+static int
+cifs_oplock_break_wait(void *unused)
+{
+ schedule();
+ return signal_pending(current) ? -ERESTARTSYS : 0;
+}
+
+/*
+ * We wait for oplock breaks to be processed before we attempt to perform
+ * writes.
+ */
+int cifs_get_writer(struct cifsInodeInfo *cinode)
+{
+ int rc;
+
+start:
+ rc = wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_OPLOCK_BREAK,
+ cifs_oplock_break_wait, TASK_KILLABLE);
+ if (rc)
+ return rc;
+
+ spin_lock(&cinode->writers_lock);
+ if (!cinode->writers)
+ set_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
+ cinode->writers++;
+ /* Check to see if we have started servicing an oplock break */
+ if (test_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags)) {
+ cinode->writers--;
+ if (cinode->writers == 0) {
+ clear_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
+ wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS);
+ }
+ spin_unlock(&cinode->writers_lock);
+ goto start;
+ }
+ spin_unlock(&cinode->writers_lock);
+ return 0;
+}
+
+void cifs_put_writer(struct cifsInodeInfo *cinode)
+{
+ spin_lock(&cinode->writers_lock);
+ cinode->writers--;
+ if (cinode->writers == 0) {
+ clear_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
+ wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS);
+ }
+ spin_unlock(&cinode->writers_lock);
+}
+
+void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
+{
+ clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
+ wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_OPLOCK_BREAK);
+}
+
bool
backup_cred(struct cifs_sb_info *cifs_sb)
{
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index 346ee2a..44572ae 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -1000,6 +1000,13 @@ cifs_is_read_op(__u32 oplock)
return oplock == OPLOCK_READ;
}

+static int
+cifs_pending_writers_wait(void *unused)
+{
+ schedule();
+ return 0;
+}
+
static void cifs_oplock_break(struct work_struct *work)
{
struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
@@ -1009,6 +1016,14 @@ static void cifs_oplock_break(struct work_struct *work)
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
int rc = 0;

+ wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
+ cifs_pending_writers_wait, TASK_UNINTERRUPTIBLE);
+
+ if (test_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags))
+ cifs_set_oplock_level(cinode, OPLOCK_READ);
+ else
+ cifs_set_oplock_level(cinode, 0);
+
if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
cifs_has_mand_locks(cinode)) {
cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
@@ -1045,6 +1060,7 @@ static void cifs_oplock_break(struct work_struct *work)
cinode);
cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
}
+ cifs_done_oplock_break(cinode);
}

struct smb_version_operations smb1_operations = {
diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c
index fb39662..b8021fd 100644
--- a/fs/cifs/smb2misc.c
+++ b/fs/cifs/smb2misc.c
@@ -575,9 +575,21 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
else
cfile->oplock_break_cancelled = false;

- server->ops->set_oplock_level(cinode,
- rsp->OplockLevel ? SMB2_OPLOCK_LEVEL_II : 0,
- 0, NULL);
+ set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
+ &cinode->flags);
+
+ /*
+ * Set flag if the server downgrades the oplock
+ * to L2 else clear.
+ */
+ if (rsp->OplockLevel)
+ set_bit(
+ CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
+ &cinode->flags);
+ else
+ clear_bit(
+ CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
+ &cinode->flags);

queue_work(cifsiod_wq, &cfile->oplock_break);

diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index e7081cd..4898ca3 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -905,6 +905,13 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,
return rc;
}

+static int
+smb2_pending_writers_wait(void *unused)
+{
+ schedule();
+ return 0;
+}
+
static void smb2_oplock_break(struct work_struct *work)
{
struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
@@ -912,8 +919,20 @@ static void smb2_oplock_break(struct work_struct *work)
struct inode *inode = cfile->dentry->d_inode;
struct cifsInodeInfo *cinode = CIFS_I(inode);
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
+ struct TCP_Server_Info *server = tcon->ses->server;
int rc = 0;

+
+ wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
+ smb2_pending_writers_wait, TASK_UNINTERRUPTIBLE);
+
+ if (test_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags))
+ server->ops->set_oplock_level(cinode, SMB2_OPLOCK_LEVEL_II,
+ 0, NULL);
+ else
+ server->ops->set_oplock_level(cinode, 0,
+ 0, NULL);
+
if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
cifs_has_mand_locks(cinode)) {
cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
@@ -950,6 +969,7 @@ static void smb2_oplock_break(struct work_struct *work)
cinode);
cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
}
+ cifs_done_oplock_break(cinode);
}

static void
--
1.8.4.2
Loading...