[PATCH] Kernel oplocks vs named streams

Ralph Böhme slow at samba.org
Wed Mar 8 12:51:13 UTC 2017


Hi!

Attached is a patch for bug 7537.

Commit "s3/smbd: fix deferred open with streams and kernel oplocks" contains the
actual fix, the rest is mainly restructuring and drive-by fixes.

I've also added two basic tests for kernel oplocks with some magic so they're
only run if the host supports Linux kernel oplocks.

Here's the description of the issue shamelessly copied from the mentioned
commit:

    I noticed smbd can get stuck in an open() call with kernel oplocks
    enabled and named streams (provided by vfs_streams_xattr):
    
    - client opens a file and with an exclusive oplock
    
    - client starts writing to the file
    
    - client opens an existing stream of the file
    
    - the smbd process gets stuck in an open()
    
    What happens is:
    
    we had setup a locking.tdb record watch in defer_open(), the watch was
    triggered, we reattempted the open and got stuck in a blocking open
    because the oplock holder (ourselves) hadn't given up the oplock yet.
    
    Cf e576bf5310bc9de9686a71539e9a1b60b4fba5cc for the commit that added
    the kernel oplock retry logic. tldr: with kernel oplocks the first open
    is non-blocking, but the second one is blocking.
    
    Detailed analysis follows.
    
    When opening a named stream of a file, Samba internally opens the
    underlying "base" file first. This internal open of the basefile suceeds
    and does *not* trigger an oplock break (because it is an internal open
    that doesn't call open() at all) but it is added as an entry to the
    locking.tdb record of the file.
    
    Next, the stream open ends up in streams_xattr where a non-blocking
    open() on the base file is called. This open fails with EWOULDBLOCK
    because we have another fd with a kernel oplock on the file.
    
    So we call defer_open() which sets up a watch on the locking.tdb record.
    
    In the subsequent error unwinding code in open_file_ntcreate() and
    callers we close the internal open file handle of the basefile which
    also removes the entry from the locking.tdb record and so *changes the
    record*.
    
    This fires the record watch and in the callback defer_open_done() we
    don't check whether the condition (oplock gone) we're interested in is
    actually met. The callback blindly reschedules the open request with
    schedule_deferred_open_message_smb().
    
    schedule_deferred_open_message_smb() schedules an immediate tevent event
    which has precedence over the IPC fd events in messaging, so the open is
    always (!) reattempted before processing the oplock break message.
    
    As explained above, this second open will be a blocking one so we get
    stuck in a blocking open.
    
    It doesn't help to make all opens non-blocking, that would just result
    in a busy loop failing the open, as we never process the oplock break
    message (remember, schedule_deferred_open_message_smb() used immediate
    tevent events).

    To fix this we must add some logic to the record watch callback to check
    whether the record watch was done for a kernel oplock file and if yes,
    check if the oplock state changed. If not, simply reschedule the
    deferred open and keep waiting.
    
    This logic is only needed for kernel oplocks, not for Samba-level
    oplocks, because there's no risk of deadlocking, the worst that can
    happen is a rescheduled open that fails again in the oplock checks and
    gets deferred again.

Please review carefully and push if happy. Thanks!

Cheerio!
-slow
-------------- next part --------------
From 59fadf60f96448bf358c7789e065cbb245a72b14 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 6 Mar 2017 12:09:53 +0100
Subject: [PATCH 01/13] s3/wscript: fix Linux kernel oplock detection

Fix a copy/paste error, the Linux kernel oplocks check was copied from
the change notify support check.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/wscript | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/source3/wscript b/source3/wscript
index 2ea1db2..7875368 100644
--- a/source3/wscript
+++ b/source3/wscript
@@ -160,11 +160,11 @@ main() {
 #include <sys/types.h>
 #include <fcntl.h>
 #include <signal.h>
-#ifndef F_NOTIFY
-#define F_NOTIFY 1026
+#ifndef F_GETLEASE
+#define F_GETLEASE 1025
 #endif
 main() {
-        exit(fcntl(open("/tmp", O_RDONLY), F_NOTIFY, 0) == -1 ?  1 : 0);
+        exit(fcntl(open("/tmp", O_RDONLY), F_GETLEASE, 0) == -1 ?  1 : 0);
 }''', 'HAVE_KERNEL_OPLOCKS_LINUX', addmain=False, execute=True,
         msg="Checking for Linux kernel oplocks")
 
-- 
2.9.3


From 5f38c9fd38052241eab6b90140339c27570d68b0 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Sat, 4 Mar 2017 13:55:55 +0100
Subject: [PATCH 02/13] s3/smbd: add const to get_lease_type() args

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/oplock.c | 3 ++-
 source3/smbd/proto.h  | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c
index 8e29f0d..e8a5a16 100644
--- a/source3/smbd/oplock.c
+++ b/source3/smbd/oplock.c
@@ -172,7 +172,8 @@ uint32_t map_oplock_to_lease_type(uint16_t op_type)
 	return ret;
 }
 
-uint32_t get_lease_type(struct share_mode_data *d, struct share_mode_entry *e)
+uint32_t get_lease_type(const struct share_mode_data *d,
+			const struct share_mode_entry *e)
 {
 	if (e->op_type == LEASE_OPLOCK) {
 		return d->leases[e->lease_idx].current_state;
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 7ccb12a..06ab56f 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -704,7 +704,8 @@ NTSTATUS get_relative_fid_filename(connection_struct *conn,
 /* The following definitions come from smbd/oplock.c  */
 
 uint32_t map_oplock_to_lease_type(uint16_t op_type);
-uint32_t get_lease_type(struct share_mode_data *d, struct share_mode_entry *e);
+uint32_t get_lease_type(const struct share_mode_data *d,
+			const struct share_mode_entry *e);
 bool update_num_read_oplocks(files_struct *fsp, struct share_mode_lock *lck);
 
 void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp);
-- 
2.9.3


From a1169df09528449e1926f2d9869bd7849fcae2a9 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Mon, 6 Mar 2017 11:43:08 +0100
Subject: [PATCH 03/13] s3/smbd: add comments and some reformatting to
 open_file_ntcreate()

No change in behaviour.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 45 +++++++++++++++++++++++++++++++++------------
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 7bd1086..660a5bb 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -2798,9 +2798,16 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 	if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) {
 		struct deferred_open_record state;
+		bool delay;
 
 		/*
-		 * EWOULDBLOCK/EAGAIN maps to NETWORK_BUSY.
+		 * This handles the kernel oplock case:
+		 *
+		 * the file has an active kernel oplock and the open() returned
+		 * EWOULDBLOCK/EAGAIN which maps to NETWORK_BUSY.
+		 *
+		 * "Samba locking.tdb oplocks" are handled below after acquiring
+		 * the sharemode lock with get_share_mode_lock().
 		 */
 		if (file_existed && S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
 			DEBUG(10, ("FIFO busy\n"));
@@ -2831,8 +2838,10 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 			smb_panic("validate_oplock_types failed");
 		}
 
-		if (delay_for_oplock(fsp, 0, lease, lck, false,
-				     create_disposition, first_open_attempt)) {
+		delay = delay_for_oplock(fsp, 0, lease, lck, false,
+					 create_disposition,
+					 first_open_attempt);
+		if (delay) {
 			schedule_defer_open(lck, fsp->file_id, request_time,
 					    req);
 			TALLOC_FREE(lck);
@@ -2953,15 +2962,27 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 		file_existed = true;
 	}
 
-	if ((req != NULL) &&
-	    delay_for_oplock(
-		    fsp, oplock_request, lease, lck,
-		    NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION),
-		    create_disposition, first_open_attempt)) {
-		schedule_defer_open(lck, fsp->file_id, request_time, req);
-		TALLOC_FREE(lck);
-		fd_close(fsp);
-		return NT_STATUS_SHARING_VIOLATION;
+	if (req != NULL) {
+		/*
+		 * Handle oplocks, deferring the request if delay_for_oplock()
+		 * triggered a break message and we have to wait for the break
+		 * response.
+		 */
+		bool delay;
+		bool sharing_violation = NT_STATUS_EQUAL(
+			status, NT_STATUS_SHARING_VIOLATION);
+
+		delay = delay_for_oplock(fsp, oplock_request, lease, lck,
+					 sharing_violation,
+					 create_disposition,
+					 first_open_attempt);
+		if (delay) {
+			schedule_defer_open(lck, fsp->file_id,
+					    request_time, req);
+			TALLOC_FREE(lck);
+			fd_close(fsp);
+			return NT_STATUS_SHARING_VIOLATION;
+		}
 	}
 
 	if (!NT_STATUS_IS_OK(status)) {
-- 
2.9.3


From e6ebf1e9f390da7cb8fdd39b69fa3a17ab94509e Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 14:10:39 +0100
Subject: [PATCH 04/13] s3/smbd: req is already validated at the beginning of
 open_file_ntcreate()

req can't be NULL because the if condition surrounding this code checks
!(oplock_request & INTERNAL_OPEN_ONLY).

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 660a5bb..70aa54f 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -3090,9 +3090,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 			state.async_open = false;
 			state.id = id;
 
-			if ((req != NULL)
-			    && !request_timed_out(request_time,
-						  timeout)) {
+			if (!request_timed_out(request_time, timeout)) {
 				defer_open(lck, request_time, timeout,
 					   req, &state);
 			}
-- 
2.9.3


From bb245c50a01addbab134a02df4ab897b187c419a Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 14:37:54 +0100
Subject: [PATCH 05/13] s3/smbd: simplify defer_open()

Add a helper function deferred_open_record_create() that creates a
deferred_open_record and let all callers pass all needed arguments
individually.

While we're at it, enhance the debug message in defer_open() to print
all variables.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 113 +++++++++++++++++++++++++++-------------------------
 1 file changed, 58 insertions(+), 55 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 70aa54f..10c1f98 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1932,6 +1932,27 @@ static bool request_timed_out(struct timeval request_time,
 	return (timeval_compare(&end_time, &now) < 0);
 }
 
+static struct deferred_open_record *deferred_open_record_create(
+	bool delayed_for_oplocks,
+	bool async_open,
+	struct file_id id)
+{
+	struct deferred_open_record *record = NULL;
+
+	record = talloc(NULL, struct deferred_open_record);
+	if (record == NULL) {
+		return NULL;
+	}
+
+	*record = (struct deferred_open_record) {
+		.delayed_for_oplocks = delayed_for_oplocks,
+		.async_open = async_open,
+		.id = id,
+	};
+
+	return record;
+}
+
 struct defer_open_state {
 	struct smbXsrv_connection *xconn;
 	uint64_t mid;
@@ -1947,24 +1968,32 @@ static void defer_open(struct share_mode_lock *lck,
 		       struct timeval request_time,
 		       struct timeval timeout,
 		       struct smb_request *req,
-		       struct deferred_open_record *state)
+		       bool delayed_for_oplocks,
+		       bool async_open,
+		       struct file_id id)
 {
-	struct deferred_open_record *open_rec;
-
-	DEBUG(10,("defer_open_sharing_error: time [%u.%06u] adding deferred "
-		  "open entry for mid %llu\n",
-		  (unsigned int)request_time.tv_sec,
-		  (unsigned int)request_time.tv_usec,
-		  (unsigned long long)req->mid));
-
-	open_rec = talloc(NULL, struct deferred_open_record);
+	struct deferred_open_record *open_rec = NULL;
+	struct timeval abs_timeout;
+
+	abs_timeout = timeval_sum(&request_time, &timeout);
+
+	DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
+		  "delayed_for_oplocks [%s] async_open [%s] file_id [%s]\n",
+		  timeval_string(talloc_tos(), &request_time, false),
+		  timeval_string(talloc_tos(), &abs_timeout, false),
+		  req->mid,
+		  delayed_for_oplocks ? "yes" : "no",
+		  async_open ? "yes" : "no",
+		  file_id_string_tos(&id));
+
+	open_rec = deferred_open_record_create(delayed_for_oplocks,
+					       async_open,
+					       id);
 	if (open_rec == NULL) {
 		TALLOC_FREE(lck);
 		exit_server("talloc failed");
 	}
 
-	*open_rec = *state;
-
 	if (lck) {
 		struct defer_open_state *watch_state;
 		struct tevent_req *watch_req;
@@ -1991,12 +2020,12 @@ static void defer_open(struct share_mode_lock *lck,
 
 		ret = tevent_req_set_endtime(
 			watch_req, req->sconn->ev_ctx,
-			timeval_sum(&request_time, &timeout));
+			abs_timeout);
 		SMB_ASSERT(ret);
 	}
 
 	if (!push_deferred_open_message_smb(req, request_time, timeout,
-					    state->id, open_rec)) {
+					    open_rec->id, open_rec)) {
 		TALLOC_FREE(lck);
 		exit_server("push_deferred_open_message_smb failed");
 	}
@@ -2144,8 +2173,6 @@ static void schedule_defer_open(struct share_mode_lock *lck,
 				struct timeval request_time,
 				struct smb_request *req)
 {
-	struct deferred_open_record state;
-
 	/* This is a relative time, added to the absolute
 	   request_time value to get the absolute timeout time.
 	   Note that if this is the second or greater time we enter
@@ -2164,18 +2191,11 @@ static void schedule_defer_open(struct share_mode_lock *lck,
 
 	timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
 
-	/* Nothing actually uses state.delayed_for_oplocks
-	   but it's handy to differentiate in debug messages
-	   between a 30 second delay due to oplock break, and
-	   a 1 second delay for share mode conflicts. */
-
-	state.delayed_for_oplocks = True;
-	state.async_open = false;
-	state.id = id;
-
-	if (!request_timed_out(request_time, timeout)) {
-		defer_open(lck, request_time, timeout, req, &state);
+	if (request_timed_out(request_time, timeout)) {
+		return;
 	}
+
+	defer_open(lck, request_time, timeout, req, true, false, id);
 }
 
 /****************************************************************************
@@ -2185,18 +2205,16 @@ static void schedule_defer_open(struct share_mode_lock *lck,
 static void schedule_async_open(struct timeval request_time,
 				struct smb_request *req)
 {
-	struct deferred_open_record state;
 	struct timeval timeout;
 
 	timeout = timeval_set(20, 0);
 
-	ZERO_STRUCT(state);
-	state.delayed_for_oplocks = false;
-	state.async_open = true;
-
-	if (!request_timed_out(request_time, timeout)) {
-		defer_open(NULL, request_time, timeout, req, &state);
+	if (request_timed_out(request_time, timeout)) {
+		return;
 	}
+
+	defer_open(NULL, request_time, timeout, req,
+		   false, true, (struct file_id){0});
 }
 
 /****************************************************************************
@@ -2797,7 +2815,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 			     open_access_mask, &new_file_created);
 
 	if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) {
-		struct deferred_open_record state;
 		bool delay;
 
 		/*
@@ -2824,11 +2841,8 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 		lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
 		if (lck == NULL) {
-			state.delayed_for_oplocks = false;
-			state.async_open = false;
-			state.id = fsp->file_id;
 			defer_open(NULL, request_time, timeval_set(0, 0),
-				   req, &state);
+				   req, false, false, fsp->file_id);
 			DEBUG(10, ("No share mode lock found after "
 				   "EWOULDBLOCK, retrying sync\n"));
 			return NT_STATUS_SHARING_VIOLATION;
@@ -2854,10 +2868,9 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 		 * No oplock from Samba around. Immediately retry with
 		 * a blocking open.
 		 */
-		state.delayed_for_oplocks = false;
-		state.async_open = false;
-		state.id = fsp->file_id;
-		defer_open(lck, request_time, timeval_set(0, 0), req, &state);
+		defer_open(lck, request_time, timeval_set(0, 0), req,
+			   false, false, fsp->file_id);
+
 		TALLOC_FREE(lck);
 		DEBUG(10, ("No Samba oplock around after EWOULDBLOCK. "
 			   "Retrying sync\n"));
@@ -3062,7 +3075,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 		    !conn->sconn->using_smb2 &&
 		    lp_defer_sharing_violations()) {
 			struct timeval timeout;
-			struct deferred_open_record state;
 			int timeout_usecs;
 
 			/* this is a hack to speed up torture tests
@@ -3081,18 +3093,9 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 			timeout = timeval_set(0, timeout_usecs);
 
-			/* Nothing actually uses state.delayed_for_oplocks
-			   but it's handy to differentiate in debug messages
-			   between a 30 second delay due to oplock break, and
-			   a 1 second delay for share mode conflicts. */
-
-			state.delayed_for_oplocks = False;
-			state.async_open = false;
-			state.id = id;
-
 			if (!request_timed_out(request_time, timeout)) {
-				defer_open(lck, request_time, timeout,
-					   req, &state);
+				defer_open(lck, request_time, timeout, req,
+					   false, false, id);
 			}
 		}
 
-- 
2.9.3


From 47887494d12a134e8debc127153085ffdd712ea1 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 15:03:12 +0100
Subject: [PATCH 06/13] s3/smbd: add and use retry_open() instead of
 defer_open() in two places

Add a new function that does an immediate open rescheduling.

The first deferred open this commit changes was never scheduled, as the
scheduling relies on a timeout of the watch on the sharemode lock.

This has been broken since the commits in

$ git log --reverse -p -10 8283fd0e0090ed12b0b12d5acb550642d621b026

That patchset added the dbwrap watch record logic to defer_open() and
removed the timers.

I'm doing this mainly to untangle the defer_open() logic which is
complicated by the lck arg.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 40 ++++++++++++++++++++++++++++++++++++----
 1 file changed, 36 insertions(+), 4 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 10c1f98..d845ec2 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -2057,6 +2057,40 @@ static void defer_open_done(struct tevent_req *req)
 	TALLOC_FREE(state);
 }
 
+/**
+ * Reschedule an open for immediate execution
+ **/
+static void retry_open(struct timeval request_time,
+		       struct smb_request *req,
+		       struct file_id id)
+{
+	struct deferred_open_record *open_rec = NULL;
+	bool ok;
+
+	DBG_DEBUG("request time [%s] mid [%" PRIu64 "] file_id [%s]\n",
+		  timeval_string(talloc_tos(), &request_time, false),
+		  req->mid,
+		  file_id_string_tos(&id));
+
+	open_rec = deferred_open_record_create(false, false, id);
+	if (open_rec == NULL) {
+		exit_server("talloc failed");
+	}
+
+	ok = push_deferred_open_message_smb(req,
+					    request_time,
+					    timeval_set(0, 0),
+					    id,
+					    open_rec);
+	if (!ok) {
+		exit_server("push_deferred_open_message_smb failed");
+	}
+
+	ok = schedule_deferred_open_message_smb(req->xconn, req->mid);
+	if (!ok) {
+		exit_server("schedule_deferred_open_message_smb failed");
+	}
+}
 
 /****************************************************************************
  On overwrite open ensure that the attributes match.
@@ -2841,8 +2875,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 		lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
 		if (lck == NULL) {
-			defer_open(NULL, request_time, timeval_set(0, 0),
-				   req, false, false, fsp->file_id);
+			retry_open(request_time, req, fsp->file_id);
 			DEBUG(10, ("No share mode lock found after "
 				   "EWOULDBLOCK, retrying sync\n"));
 			return NT_STATUS_SHARING_VIOLATION;
@@ -2868,8 +2901,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 		 * No oplock from Samba around. Immediately retry with
 		 * a blocking open.
 		 */
-		defer_open(lck, request_time, timeval_set(0, 0), req,
-			   false, false, fsp->file_id);
+		retry_open(request_time, req, fsp->file_id);
 
 		TALLOC_FREE(lck);
 		DEBUG(10, ("No Samba oplock around after EWOULDBLOCK. "
-- 
2.9.3


From 1a1b40efe6dc51aa162713b451d2f2f1677957c1 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 15:33:55 +0100
Subject: [PATCH 07/13] s3/smbd: fix schedule_async_open() timer

schedule_async_open() was calling defer_open with sharemode lock = NULL,
as a result there was never an active 20 s timeout.

This has been broken since the commits in

$ git log --reverse -p -10 8283fd0e0090ed12b0b12d5acb550642d621b026

Just roll our own deferred record instead of calling defer_open() and
also set up timer that, as a last resort, catches stuck opens and just
exits for now.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 42 +++++++++++++++++++++++++++++++++++++-----
 1 file changed, 37 insertions(+), 5 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index d845ec2..e90aebf 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -45,6 +45,13 @@ struct deferred_open_record {
         bool delayed_for_oplocks;
 	bool async_open;
         struct file_id id;
+
+	/*
+	 * Timer for async opens that one them because they don't use a watch on
+	 * a locking.tdb record. This is currently only used for real async
+	 * opens.
+	 */
+	struct tevent_timer *te;
 };
 
 /****************************************************************************
@@ -2236,19 +2243,44 @@ static void schedule_defer_open(struct share_mode_lock *lck,
  Reschedule an open call that went asynchronous.
 ****************************************************************************/
 
+static void schedule_async_open_timer(struct tevent_context *ev,
+				      struct tevent_timer *te,
+				      struct timeval current_time,
+				      void *private_data)
+{
+	exit_server("async open timeout");
+}
+
 static void schedule_async_open(struct timeval request_time,
 				struct smb_request *req)
 {
-	struct timeval timeout;
-
-	timeout = timeval_set(20, 0);
+	struct deferred_open_record *open_rec = NULL;
+	struct timeval timeout = timeval_set(20, 0);
+	bool ok;
 
 	if (request_timed_out(request_time, timeout)) {
 		return;
 	}
 
-	defer_open(NULL, request_time, timeout, req,
-		   false, true, (struct file_id){0});
+	open_rec = deferred_open_record_create(false, true, (struct file_id){0});
+	if (open_rec == NULL) {
+		exit_server("deferred_open_record_create failed");
+	}
+
+	ok = push_deferred_open_message_smb(req, request_time, timeout,
+					    (struct file_id){0}, open_rec);
+	if (!ok) {
+		exit_server("push_deferred_open_message_smb failed");
+	}
+
+	open_rec->te = tevent_add_timer(req->sconn->ev_ctx,
+					req,
+					timeval_current_ofs(20, 0),
+					schedule_async_open_timer,
+					open_rec);
+	if (open_rec->te == NULL) {
+		exit_server("tevent_add_timer failed");
+	}
 }
 
 /****************************************************************************
-- 
2.9.3


From a1bc9cae96c0d624b91da7e3af3e80a81bf14132 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 19:11:20 +0100
Subject: [PATCH 08/13] s3/smbd: remove async_open arg from defer_open()

All remaining callers pass false.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index e90aebf..0cda411 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1976,7 +1976,6 @@ static void defer_open(struct share_mode_lock *lck,
 		       struct timeval timeout,
 		       struct smb_request *req,
 		       bool delayed_for_oplocks,
-		       bool async_open,
 		       struct file_id id)
 {
 	struct deferred_open_record *open_rec = NULL;
@@ -1985,16 +1984,15 @@ static void defer_open(struct share_mode_lock *lck,
 	abs_timeout = timeval_sum(&request_time, &timeout);
 
 	DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
-		  "delayed_for_oplocks [%s] async_open [%s] file_id [%s]\n",
+		  "delayed_for_oplocks [%s] file_id [%s]\n",
 		  timeval_string(talloc_tos(), &request_time, false),
 		  timeval_string(talloc_tos(), &abs_timeout, false),
 		  req->mid,
 		  delayed_for_oplocks ? "yes" : "no",
-		  async_open ? "yes" : "no",
 		  file_id_string_tos(&id));
 
 	open_rec = deferred_open_record_create(delayed_for_oplocks,
-					       async_open,
+					       false,
 					       id);
 	if (open_rec == NULL) {
 		TALLOC_FREE(lck);
@@ -2236,7 +2234,7 @@ static void schedule_defer_open(struct share_mode_lock *lck,
 		return;
 	}
 
-	defer_open(lck, request_time, timeout, req, true, false, id);
+	defer_open(lck, request_time, timeout, req, true, id);
 }
 
 /****************************************************************************
@@ -3159,7 +3157,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 			if (!request_timed_out(request_time, timeout)) {
 				defer_open(lck, request_time, timeout, req,
-					   false, false, id);
+					   false, id);
 			}
 		}
 
-- 
2.9.3


From d6ef5de5db2e71e95376e0ac62deaa3b2e17084c Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 15:48:05 +0100
Subject: [PATCH 09/13] s3/smbd: all callers of defer_open() pass a lck

No change in behaviour. Update the function comment explaining how it
works and relies on lck for a record watch.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 64 +++++++++++++++++++++++++++--------------------------
 1 file changed, 33 insertions(+), 31 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 0cda411..56010b3 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1967,10 +1967,15 @@ struct defer_open_state {
 
 static void defer_open_done(struct tevent_req *req);
 
-/****************************************************************************
- Handle the 1 second delay in returning a SHARING_VIOLATION error.
-****************************************************************************/
-
+/**
+ * Defer an open and watch a locking.tdb record
+ *
+ * This defers an open that gets rescheduled once the locking.tdb record watch
+ * is triggered by a change to the record.
+ *
+ * It is used to defer opens that triggered an oplock break and for the SMB1
+ * sharing violation delay.
+ **/
 static void defer_open(struct share_mode_lock *lck,
 		       struct timeval request_time,
 		       struct timeval timeout,
@@ -1980,6 +1985,9 @@ static void defer_open(struct share_mode_lock *lck,
 {
 	struct deferred_open_record *open_rec = NULL;
 	struct timeval abs_timeout;
+	struct defer_open_state *watch_state;
+	struct tevent_req *watch_req;
+	bool ok;
 
 	abs_timeout = timeval_sum(&request_time, &timeout);
 
@@ -1999,38 +2007,32 @@ static void defer_open(struct share_mode_lock *lck,
 		exit_server("talloc failed");
 	}
 
-	if (lck) {
-		struct defer_open_state *watch_state;
-		struct tevent_req *watch_req;
-		bool ret;
-
-		watch_state = talloc(open_rec, struct defer_open_state);
-		if (watch_state == NULL) {
-			exit_server("talloc failed");
-		}
-		watch_state->xconn = req->xconn;
-		watch_state->mid = req->mid;
+	watch_state = talloc(open_rec, struct defer_open_state);
+	if (watch_state == NULL) {
+		exit_server("talloc failed");
+	}
+	watch_state->xconn = req->xconn;
+	watch_state->mid = req->mid;
 
-		DEBUG(10, ("defering mid %llu\n",
-			   (unsigned long long)req->mid));
+	DBG_DEBUG("defering mid %" PRIu64 "\n", req->mid);
 
-		watch_req = dbwrap_watched_watch_send(
-			watch_state, req->sconn->ev_ctx, lck->data->record,
-			(struct server_id){0});
-		if (watch_req == NULL) {
-			exit_server("Could not watch share mode record");
-		}
-		tevent_req_set_callback(watch_req, defer_open_done,
-					watch_state);
+	watch_req = dbwrap_watched_watch_send(watch_state,
+					      req->sconn->ev_ctx,
+					      lck->data->record,
+					      (struct server_id){0});
+	if (watch_req == NULL) {
+		exit_server("Could not watch share mode record");
+	}
+	tevent_req_set_callback(watch_req, defer_open_done, watch_state);
 
-		ret = tevent_req_set_endtime(
-			watch_req, req->sconn->ev_ctx,
-			abs_timeout);
-		SMB_ASSERT(ret);
+	ok = tevent_req_set_endtime(watch_req, req->sconn->ev_ctx, abs_timeout);
+	if (!ok) {
+		exit_server("tevent_req_set_endtime failed");
 	}
 
-	if (!push_deferred_open_message_smb(req, request_time, timeout,
-					    open_rec->id, open_rec)) {
+	ok = push_deferred_open_message_smb(req, request_time, timeout,
+					    open_rec->id, open_rec);
+	if (!ok) {
 		TALLOC_FREE(lck);
 		exit_server("push_deferred_open_message_smb failed");
 	}
-- 
2.9.3


From 1dda9ea650c84c59a5f41010ea43dcfa940c5236 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 16:27:39 +0100
Subject: [PATCH 10/13] s3/smbd: fix deferred open with streams and kernel
 oplocks

I noticed smbd can get stuck in an open() call with kernel oplocks
enabled and named streams (provided by vfs_streams_xattr):

- client opens a file and with an exclusive oplock

- client starts writing to the file

- client opens an existing stream of the file

- the smbd process gets stuck in an open()

What happens is:

we had setup a locking.tdb record watch in defer_open(), the watch was
triggered, we reattempted the open and got stuck in a blocking open
because the oplock holder (ourselves) hadn't given up the oplock yet.

Cf e576bf5310bc9de9686a71539e9a1b60b4fba5cc for the commit that added
the kernel oplock retry logic. tldr: with kernel oplocks the first open
is non-blocking, but the second one is blocking.

Detailed analysis follows.

When opening a named stream of a file, Samba internally opens the
underlying "base" file first. This internal open of the basefile suceeds
and does *not* trigger an oplock break (because it is an internal open
that doesn't call open() at all) but it is added as an entry to the
locking.tdb record of the file.

Next, the stream open ends up in streams_xattr where a non-blocking
open() on the base file is called. This open fails with EWOULDBLOCK
because we have another fd with a kernel oplock on the file.

So we call defer_open() which sets up a watch on the locking.tdb record.

In the subsequent error unwinding code in open_file_ntcreate() and
callers we close the internal open file handle of the basefile which
also removes the entry from the locking.tdb record and so *changes the
record*.

This fires the record watch and in the callback defer_open_done() we
don't check whether the condition (oplock gone) we're interested in is
actually met. The callback blindly reschedules the open request with
schedule_deferred_open_message_smb().

schedule_deferred_open_message_smb() schedules an immediate tevent event
which has precedence over the IPC fd events in messaging, so the open is
always (!) reattempted before processing the oplock break message.

As explained above, this second open will be a blocking one so we get
stuck in a blocking open.

It doesn't help to make all opens non-blocking, that would just result
in a busy loop failing the open, as we never process the oplock break
message (remember, schedule_deferred_open_message_smb() used immediate
tevent events).

To fix this we must add some logic to the record watch callback to check
whether the record watch was done for a kernel oplock file and if yes,
check if the oplock state changed. If not, simply reschedule the
deferred open and keep waiting.

This logic is only needed for kernel oplocks, not for Samba-level
oplocks, because there's no risk of deadlocking, the worst that can
happen is a rescheduled open that fails again in the oplock checks and
gets deferred again.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 104 insertions(+), 11 deletions(-)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 56010b3..655fe91 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1551,6 +1551,23 @@ static bool delay_for_oplock(files_struct *fsp,
 	return delay;
 }
 
+/**
+ * Return lease or oplock state from a share mode
+ **/
+static uint32_t get_lease_type_from_share_mode(const struct share_mode_data *d)
+{
+	uint32_t e_lease_type = 0;
+	uint32_t i;
+
+	for (i=0; i < d->num_share_modes; i++) {
+		struct share_mode_entry *e = &d->share_modes[i];
+
+		e_lease_type |= get_lease_type(d, e);
+	}
+
+	return e_lease_type;
+}
+
 static bool file_has_brlocks(files_struct *fsp)
 {
 	struct byte_range_lock *br_lck;
@@ -1963,6 +1980,11 @@ static struct deferred_open_record *deferred_open_record_create(
 struct defer_open_state {
 	struct smbXsrv_connection *xconn;
 	uint64_t mid;
+	struct file_id file_id;
+	struct timeval request_time;
+	struct timeval timeout;
+	bool kernel_oplock;
+	uint32_t lease_type;
 };
 
 static void defer_open_done(struct tevent_req *req);
@@ -1981,6 +2003,7 @@ static void defer_open(struct share_mode_lock *lck,
 		       struct timeval timeout,
 		       struct smb_request *req,
 		       bool delayed_for_oplocks,
+		       bool kernel_oplock,
 		       struct file_id id)
 {
 	struct deferred_open_record *open_rec = NULL;
@@ -1992,11 +2015,12 @@ static void defer_open(struct share_mode_lock *lck,
 	abs_timeout = timeval_sum(&request_time, &timeout);
 
 	DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
-		  "delayed_for_oplocks [%s] file_id [%s]\n",
+		  "delayed_for_oplocks [%s] kernel_oplock [%s] file_id [%s]\n",
 		  timeval_string(talloc_tos(), &request_time, false),
 		  timeval_string(talloc_tos(), &abs_timeout, false),
 		  req->mid,
 		  delayed_for_oplocks ? "yes" : "no",
+		  kernel_oplock ? "yes" : "no",
 		  file_id_string_tos(&id));
 
 	open_rec = deferred_open_record_create(delayed_for_oplocks,
@@ -2013,6 +2037,11 @@ static void defer_open(struct share_mode_lock *lck,
 	}
 	watch_state->xconn = req->xconn;
 	watch_state->mid = req->mid;
+	watch_state->file_id = lck->data->id;
+	watch_state->request_time = request_time;
+	watch_state->timeout = timeout;
+	watch_state->kernel_oplock = kernel_oplock;
+	watch_state->lease_type = get_lease_type_from_share_mode(lck->data);
 
 	DBG_DEBUG("defering mid %" PRIu64 "\n", req->mid);
 
@@ -2042,8 +2071,12 @@ static void defer_open_done(struct tevent_req *req)
 {
 	struct defer_open_state *state = tevent_req_callback_data(
 		req, struct defer_open_state);
+	struct tevent_req *watch_req = NULL;
+	struct share_mode_lock *lck = NULL;
+	bool shedule_req = true;
+	struct timeval timeout;
 	NTSTATUS status;
-	bool ret;
+	bool ok;
 
 	status = dbwrap_watched_watch_recv(req, talloc_tos(), NULL, NULL,
 					  NULL);
@@ -2055,13 +2088,72 @@ static void defer_open_done(struct tevent_req *req)
 		 * Even if it failed, retry anyway. TODO: We need a way to
 		 * tell a re-scheduled open about that error.
 		 */
+		if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
+		    state->kernel_oplock)
+		{
+			/*
+			 * If we reschedule but the kernel oplock is still hold
+			 * we would block in the second open as that will be a
+			 * blocking open attempt.
+			 */
+			exit_server("Kernel oplock holder didn't "
+				    "respond to break message");
+		}
 	}
 
-	DEBUG(10, ("scheduling mid %llu\n", (unsigned long long)state->mid));
+	if (state->kernel_oplock) {
+		lck = get_existing_share_mode_lock(talloc_tos(), state->file_id);
+		if (lck != NULL) {
+			uint32_t lease_type;
+
+			lease_type = get_lease_type_from_share_mode(lck->data);
 
-	ret = schedule_deferred_open_message_smb(state->xconn, state->mid);
-	SMB_ASSERT(ret);
-	TALLOC_FREE(state);
+			if ((lease_type != 0) &&
+			    (lease_type == state->lease_type))
+			{
+				DBG_DEBUG("Unchanged lease: %" PRIu32 "\n",
+					  lease_type);
+				shedule_req = false;
+			}
+		}
+	}
+
+	if (shedule_req) {
+		DBG_DEBUG("scheduling mid %" PRIu64 "\n", state->mid);
+
+		ok = schedule_deferred_open_message_smb(state->xconn,
+							state->mid);
+		if (!ok) {
+			exit_server("schedule_deferred_open_message_smb failed");
+		}
+		TALLOC_FREE(lck);
+		TALLOC_FREE(state);
+		return;
+	}
+
+	DBG_DEBUG("Keep waiting for oplock release for [%s/%s%s] "
+		  "mid: %" PRIu64 "\n",
+		  lck->data->servicepath,
+		  lck->data->base_name,
+		  lck->data->stream_name ? lck->data->stream_name : "",
+		  state->mid);
+
+	watch_req = dbwrap_watched_watch_send(state,
+					      state->xconn->ev_ctx,
+					      lck->data->record,
+					      (struct server_id){0});
+	if (watch_req == NULL) {
+		exit_server("Could not watch share mode record");
+	}
+	tevent_req_set_callback(watch_req, defer_open_done, state);
+
+	timeout = timeval_sum(&state->request_time, &state->timeout);
+	ok = tevent_req_set_endtime(watch_req, state->xconn->ev_ctx, timeout);
+	if (!ok) {
+		exit_server("tevent_req_set_endtime failed");
+	}
+
+	TALLOC_FREE(lck);
 }
 
 /**
@@ -2212,7 +2304,8 @@ static NTSTATUS fcb_or_dos_open(struct smb_request *req,
 static void schedule_defer_open(struct share_mode_lock *lck,
 				struct file_id id,
 				struct timeval request_time,
-				struct smb_request *req)
+				struct smb_request *req,
+				bool kernel_oplock)
 {
 	/* This is a relative time, added to the absolute
 	   request_time value to get the absolute timeout time.
@@ -2236,7 +2329,7 @@ static void schedule_defer_open(struct share_mode_lock *lck,
 		return;
 	}
 
-	defer_open(lck, request_time, timeout, req, true, id);
+	defer_open(lck, request_time, timeout, req, true, kernel_oplock, id);
 }
 
 /****************************************************************************
@@ -2922,7 +3015,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 					 first_open_attempt);
 		if (delay) {
 			schedule_defer_open(lck, fsp->file_id, request_time,
-					    req);
+					    req, true);
 			TALLOC_FREE(lck);
 			DEBUG(10, ("Sent oplock break request to kernel "
 				   "oplock holder\n"));
@@ -3055,7 +3148,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 					 first_open_attempt);
 		if (delay) {
 			schedule_defer_open(lck, fsp->file_id,
-					    request_time, req);
+					    request_time, req, false);
 			TALLOC_FREE(lck);
 			fd_close(fsp);
 			return NT_STATUS_SHARING_VIOLATION;
@@ -3159,7 +3252,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
 			if (!request_timed_out(request_time, timeout)) {
 				defer_open(lck, request_time, timeout, req,
-					   false, id);
+					   false, false, id);
 			}
 		}
 
-- 
2.9.3


From c18256d99f757a34a23936f007b85358d76cbffb Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 8 Mar 2017 07:18:36 +0100
Subject: [PATCH 11/13] s3/selftest: adopt config.h check from source4

No change in behaviour.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/selftest/tests.py | 33 +++++++++++++++++++--------------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index f180710..e673bde 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -36,6 +36,25 @@ def plansmbtorture4testsuite(name, env, options, description=''):
     selftesthelpers.plansmbtorture4testsuite(
         name, env, options, target='samba3', modname=modname)
 
+# find config.h
+try:
+    config_h = os.environ["CONFIG_H"]
+except KeyError:
+    samba4bindir = bindir()
+    config_h = os.path.join(samba4bindir, "default/include/config.h")
+
+# check available features
+config_hash = dict()
+f = open(config_h, 'r')
+try:
+    lines = f.readlines()
+    config_hash = dict((x[0], ' '.join(x[1:]))
+            for x in map(lambda line: line.strip().split(' ')[1:],
+                         filter(lambda line: (line[0:7] == '#define') and (len(line.split(' ')) > 2), lines)))
+finally:
+    f.close()
+
+have_libarchive = ("HAVE_LIBARCHIVE" in config_hash)
 
 plantestsuite("samba3.blackbox.success", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_success.sh")])
 plantestsuite("samba3.blackbox.failure", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_failure.sh")])
@@ -213,20 +232,6 @@ for env in ["fileserver"]:
     # tar command tests
     #
 
-    # find config.h
-    try:
-        config_h = os.environ["CONFIG_H"]
-    except KeyError:
-        samba4bindir = bindir()
-        config_h = os.path.join(samba4bindir, "default/include/config.h")
-
-    # see if libarchive is supported
-    f = open(config_h, 'r')
-    try:
-        have_libarchive = ("HAVE_LIBARCHIVE 1" in f.read())
-    finally:
-        f.close()
-
     # tar command enabled only if built with libarchive
     if have_libarchive:
         # Test smbclient/tarmode
-- 
2.9.3


From afd5bf5d7b9cf25ae1cf35f382389a9d30cd57cc Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Wed, 1 Mar 2017 18:13:35 +0100
Subject: [PATCH 12/13] s4/torture: some tests for kernel oplocks

Bug: https://bugzilla.samba.org/show_bug.cgi?id=7537

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 selftest/target/Samba3.pm     |   5 ++
 source3/selftest/tests.py     |   4 ++
 source4/selftest/tests.py     |   2 +-
 source4/torture/smb2/oplock.c | 140 ++++++++++++++++++++++++++++++++++++++++++
 source4/torture/smb2/smb2.c   |   1 +
 5 files changed, 151 insertions(+), 1 deletion(-)

diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm
index 775dc16..d754b5f 100755
--- a/selftest/target/Samba3.pm
+++ b/selftest/target/Samba3.pm
@@ -1860,6 +1860,11 @@ sub provision($$$$$$$$)
 [mangle_illegal]
 	copy = tmp
         mangled names = illegal
+
+[kernel_oplocks]
+	copy = tmp
+	kernel oplocks = yes
+	vfs objects = streams_xattr xattr_tdb
 	";
 	close(CONF);
 
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
index e673bde..e386b1f 100755
--- a/source3/selftest/tests.py
+++ b/source3/selftest/tests.py
@@ -55,6 +55,7 @@ finally:
     f.close()
 
 have_libarchive = ("HAVE_LIBARCHIVE" in config_hash)
+have_linux_kernel_oplocks = ("HAVE_KERNEL_OPLOCKS_LINUX" in config_hash)
 
 plantestsuite("samba3.blackbox.success", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_success.sh")])
 plantestsuite("samba3.blackbox.failure", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_failure.sh")])
@@ -441,6 +442,9 @@ for t in tests:
         plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD --signing=required')
     elif t == "smb2.dosmode":
         plansmbtorture4testsuite(t, "simpleserver", '//$SERVER/dosmode -U$USERNAME%$PASSWORD')
+    elif t == "smb2.kernel-oplocks":
+        if have_linux_kernel_oplocks:
+            plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER/kernel_oplocks -U$USERNAME%$PASSWORD')
     elif t == "vfs.acl_xattr":
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
     else:
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index ac601f5..3786f73 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -303,7 +303,7 @@ for t in nbt_tests:
 ntvfsargs = ["--option=torture:sharedelay=100000", "--option=torture:oplocktimeout=3", "--option=torture:writetimeupdatedelay=500000"]
 
 # Filter smb2 tests that should not run against ad_dc_ntvfs
-smb2_s3only = ["smb2.change_notify_disabled", "smb2.dosmode", "smb2.credits"]
+smb2_s3only = ["smb2.change_notify_disabled", "smb2.dosmode", "smb2.credits", "smb2.kernel-oplocks"]
 smb2 = [x for x in smbtorture4_testsuites("smb2.") if x not in smb2_s3only]
 
 #The QFILEINFO-IPC test needs to be on ipc$
diff --git a/source4/torture/smb2/oplock.c b/source4/torture/smb2/oplock.c
index ead341d..53a6c18 100644
--- a/source4/torture/smb2/oplock.c
+++ b/source4/torture/smb2/oplock.c
@@ -4204,3 +4204,143 @@ bool test_smb2_hold_oplock(struct torture_context *tctx,
 	talloc_free(mem_ctx);
 	return true;
 }
+
+
+static bool test_smb2_kernel_oplocks1(struct torture_context *tctx,
+				      struct smb2_tree *tree)
+{
+	const char *fname = "test_kernel_oplock1.dat";
+	NTSTATUS status;
+	bool ret = true;
+	struct smb2_create create;
+	struct smb2_handle h1 = {{0}}, h2 = {{0}};
+
+	smb2_util_unlink(tree, fname);
+
+	tree->session->transport->oplock.handler = torture_oplock_handler;
+	tree->session->transport->oplock.private_data = tree;
+	ZERO_STRUCT(break_info);
+
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_RIGHTS_FILE_ALL;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_NONE;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+	create.in.fname = fname;
+	create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n");
+	h1 = create.out.file.handle;
+
+	torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE, ret, done,
+			    "Oplock level is not SMB2_OPLOCK_LEVEL_EXCLUSIVE\n");
+
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_RIGHTS_FILE_ALL;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+	create.in.fname = fname;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_equal_goto(tctx, status, NT_STATUS_SHARING_VIOLATION, ret, done,
+					   "Open didn't return NT_STATUS_SHARING_VIOLATION\n");
+	h2 = create.out.file.handle;
+
+	torture_wait_for_oplock_break(tctx);
+	if (break_info.count != 0) {
+		torture_warning(tctx, "Open caused oplock break\n");
+	}
+
+	smb2_util_close(tree, h1);
+	smb2_util_close(tree, h2);
+
+done:
+	if (!smb2_util_handle_empty(h1)) {
+		smb2_util_close(tree, h1);
+	}
+	if (!smb2_util_handle_empty(h2)) {
+		smb2_util_close(tree, h2);
+	}
+	smb2_util_unlink(tree, fname);
+	return ret;
+}
+
+static bool test_smb2_kernel_oplocks2(struct torture_context *tctx,
+				      struct smb2_tree *tree)
+{
+	const char *fname = "test_kernel_oplock2.dat";
+	const char *sname = "test_kernel_oplock2.dat:foo";
+	NTSTATUS status;
+	bool ret = true;
+	struct smb2_create create;
+	struct smb2_handle h1 = {{0}}, h2 = {{0}};
+
+	smb2_util_unlink(tree, fname);
+
+	tree->session->transport->oplock.handler = torture_oplock_handler;
+	tree->session->transport->oplock.private_data = tree;
+	ZERO_STRUCT(break_info);
+
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_RIGHTS_FILE_ALL;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_NONE;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+	create.in.fname = fname;
+	create.in.oplock_level = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n");
+	h1 = create.out.file.handle;
+
+	torture_assert_goto(tctx, create.out.oplock_level == SMB2_OPLOCK_LEVEL_EXCLUSIVE, ret, done,
+			    "Oplock level is not SMB2_OPLOCK_LEVEL_EXCLUSIVE\n");
+
+	ZERO_STRUCT(create);
+	create.in.desired_access = SEC_RIGHTS_FILE_ALL;
+	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
+	create.in.share_access = NTCREATEX_SHARE_ACCESS_MASK;
+	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
+	create.in.fname = sname;
+
+	status = smb2_create(tree, tctx, &create);
+	torture_assert_ntstatus_ok_goto(tctx, status, ret, done, "Error opening the file\n");
+	h2 = create.out.file.handle;
+
+	torture_wait_for_oplock_break(tctx);
+	if (break_info.count != 0) {
+		torture_warning(tctx, "Stream open caused oplock break\n");
+	}
+
+	smb2_util_close(tree, h1);
+	smb2_util_close(tree, h2);
+
+done:
+	if (!smb2_util_handle_empty(h1)) {
+		smb2_util_close(tree, h1);
+	}
+	if (!smb2_util_handle_empty(h2)) {
+		smb2_util_close(tree, h2);
+	}
+	smb2_util_unlink(tree, fname);
+	return ret;
+}
+
+struct torture_suite *torture_smb2_kernel_oplocks_init(void)
+{
+	struct torture_suite *suite =
+	    torture_suite_create(talloc_autofree_context(), "kernel-oplocks");
+
+	torture_suite_add_1smb2_test(suite, "kernel_oplocks1", test_smb2_kernel_oplocks1);
+	torture_suite_add_1smb2_test(suite, "kernel_oplocks2", test_smb2_kernel_oplocks2);
+
+	suite->description = talloc_strdup(suite, "SMB2-KERNEL-OPLOCK tests");
+
+	return suite;
+}
diff --git a/source4/torture/smb2/smb2.c b/source4/torture/smb2/smb2.c
index 9f0f6b3..9f8cbe7 100644
--- a/source4/torture/smb2/smb2.c
+++ b/source4/torture/smb2/smb2.c
@@ -163,6 +163,7 @@ NTSTATUS torture_smb2_init(void)
 	torture_suite_add_suite(suite, torture_smb2_lease_init());
 	torture_suite_add_suite(suite, torture_smb2_compound_init());
 	torture_suite_add_suite(suite, torture_smb2_oplocks_init());
+	torture_suite_add_suite(suite, torture_smb2_kernel_oplocks_init());
 	torture_suite_add_suite(suite, torture_smb2_streams_init());
 	torture_suite_add_suite(suite, torture_smb2_ioctl_init());
 	torture_suite_add_suite(suite, torture_smb2_rename_init());
-- 
2.9.3


From 8db65c15c6779c5f32e4ff573cc72559b2c3ebd8 Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow at samba.org>
Date: Tue, 7 Mar 2017 19:24:45 +0100
Subject: [PATCH 13/13] s3/smbd: add my copyright to open.c

Signed-off-by: Ralph Boehme <slow at samba.org>
---
 source3/smbd/open.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index 655fe91..3905329 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -4,6 +4,7 @@
    Copyright (C) Andrew Tridgell 1992-1998
    Copyright (C) Jeremy Allison 2001-2004
    Copyright (C) Volker Lendecke 2005
+   Copyright (C) Ralph Boehme 2017
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-- 
2.9.3



More information about the samba-technical mailing list