Fix bug #5979 - Level 2 oplocks being granted improperly,
authorJeremy Allison <jra@samba.org>
Thu, 18 Dec 2008 01:23:13 +0000 (17:23 -0800)
committerJeremy Allison <jra@samba.org>
Thu, 18 Dec 2008 01:23:13 +0000 (17:23 -0800)
Jeremy.

source3/locking/locking.c
source3/smbd/open.c
source3/smbd/oplock.c

index a70f9d20feb69d7921f1ec418e8bf8241aaaed07..b342fa9b1eaa0116440d8f3ad37ab06c089415e5 100644 (file)
@@ -1186,7 +1186,16 @@ bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp)
        }
 
        e->op_mid = 0;
-       e->op_type = NO_OPLOCK;
+       if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+               /*
+                * Going from exclusive or batch,
+                * we always go through FAKE_LEVEL_II
+                * first.
+                */
+               e->op_type = FAKE_LEVEL_II_OPLOCK;
+       } else {
+               e->op_type = NO_OPLOCK;
+       }
        lck->modified = True;
        return True;
 }
index d22eda2bb5490b8bd9afa141ebdbce40a22421f7..f00361979d5ffa20921053b40f8b53735f427307 100644 (file)
@@ -741,13 +741,52 @@ static bool is_delete_request(files_struct *fsp) {
                (fsp->oplock_type == NO_OPLOCK));
 }
 
+/*
+ * Send a break message to the oplock holder and delay the open for
+ * our client.
+ */
+
+static NTSTATUS send_break_message(files_struct *fsp,
+                                       struct share_mode_entry *exclusive,
+                                       uint16 mid,
+                                       int oplock_request)
+{
+       NTSTATUS status;
+       char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+
+       DEBUG(10, ("Sending break request to PID %s\n",
+                  procid_str_static(&exclusive->pid)));
+       exclusive->op_mid = mid;
+
+       /* Create the message. */
+       share_mode_entry_to_message(msg, exclusive);
+
+       /* Add in the FORCE_OPLOCK_BREAK_TO_NONE bit in the message if set. We
+          don't want this set in the share mode struct pointed to by lck. */
+
+       if (oplock_request & FORCE_OPLOCK_BREAK_TO_NONE) {
+               SSVAL(msg,6,exclusive->op_type | FORCE_OPLOCK_BREAK_TO_NONE);
+       }
+
+       status = messaging_send_buf(smbd_messaging_context(), exclusive->pid,
+                                   MSG_SMB_BREAK_REQUEST,
+                                   (uint8 *)msg,
+                                   MSG_SMB_SHARE_MODE_ENTRY_SIZE);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(3, ("Could not send oplock break message: %s\n",
+                         nt_errstr(status)));
+       }
+
+       return status;
+}
+
 /*
  * 1) No files open at all or internal open: Grant whatever the client wants.
  *
  * 2) Exclusive (or batch) oplock around: If the requested access is a delete
  *    request, break if the oplock around is a batch oplock. If it's another
  *    requested access type, break.
- * 
+ *
  * 3) Only level2 around: Grant level2 and do nothing else.
  */
 
@@ -757,20 +796,21 @@ static bool delay_for_oplocks(struct share_mode_lock *lck,
                              int pass_number,
                              int oplock_request)
 {
+       extern uint32 global_client_caps;
        int i;
        struct share_mode_entry *exclusive = NULL;
-       bool valid_entry = False;
-       bool delay_it = False;
-       bool have_level2 = False;
-       NTSTATUS status;
-       char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+       bool valid_entry = false;
+       bool have_level2 = false;
+       bool have_a_none_oplock = false;
+       bool allow_level2 = (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+                           lp_level2_oplocks(SNUM(fsp->conn));
 
        if (oplock_request & INTERNAL_OPEN_ONLY) {
                fsp->oplock_type = NO_OPLOCK;
        }
 
        if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) {
-               return False;
+               return false;
        }
 
        for (i=0; i<lck->num_share_modes; i++) {
@@ -780,86 +820,80 @@ static bool delay_for_oplocks(struct share_mode_lock *lck,
                }
 
                /* At least one entry is not an invalid or deferred entry. */
-               valid_entry = True;
+               valid_entry = true;
 
                if (pass_number == 1) {
                        if (BATCH_OPLOCK_TYPE(lck->share_modes[i].op_type)) {
-                               SMB_ASSERT(exclusive == NULL);                  
+                               SMB_ASSERT(exclusive == NULL);
                                exclusive = &lck->share_modes[i];
                        }
                } else {
                        if (EXCLUSIVE_OPLOCK_TYPE(lck->share_modes[i].op_type)) {
-                               SMB_ASSERT(exclusive == NULL);                  
+                               SMB_ASSERT(exclusive == NULL);
                                exclusive = &lck->share_modes[i];
                        }
                }
 
-               if (lck->share_modes[i].op_type == LEVEL_II_OPLOCK) {
-                       SMB_ASSERT(exclusive == NULL);                  
-                       have_level2 = True;
+               if (LEVEL_II_OPLOCK_TYPE(lck->share_modes[i].op_type)) {
+                       SMB_ASSERT(exclusive == NULL);
+                       have_level2 = true;
                }
-       }
 
-       if (!valid_entry) {
-               /* All entries are placeholders or deferred.
-                * Directly grant whatever the client wants. */
-               if (fsp->oplock_type == NO_OPLOCK) {
-                       /* Store a level2 oplock, but don't tell the client */
-                       fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
+               if (lck->share_modes[i].op_type == NO_OPLOCK) {
+                       have_a_none_oplock = true;
                }
-               return False;
        }
 
        if (exclusive != NULL) { /* Found an exclusive oplock */
+               bool delay_it = is_delete_request(fsp) ?
+                               BATCH_OPLOCK_TYPE(exclusive->op_type) : true;
                SMB_ASSERT(!have_level2);
-               delay_it = is_delete_request(fsp) ?
-                       BATCH_OPLOCK_TYPE(exclusive->op_type) : True;
-       }
-
-       if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
-               /* We can at most grant level2 as there are other
-                * level2 or NO_OPLOCK entries. */
-               fsp->oplock_type = LEVEL_II_OPLOCK;
+               if (delay_it) {
+                       send_break_message(fsp, exclusive, mid, oplock_request);
+                       return true;
+               }
        }
 
-       if ((fsp->oplock_type == NO_OPLOCK) && have_level2) {
-               /* Store a level2 oplock, but don't tell the client */
-               fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
-       }
+       /*
+        * Match what was requested (fsp->oplock_type) with
+        * what was found in the existing share modes.
+        */
 
-       if (!delay_it) {
-               return False;
+       if (!valid_entry) {
+               /* All entries are placeholders or deferred.
+                * Directly grant whatever the client wants. */
+               if (fsp->oplock_type == NO_OPLOCK) {
+                       /* Store a level2 oplock, but don't tell the client */
+                       fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
+               }
+       } else if (have_a_none_oplock) {
+               fsp->oplock_type = NO_OPLOCK;
+       } else if (have_level2) {
+               if (fsp->oplock_type == NO_OPLOCK ||
+                               fsp->oplock_type == FAKE_LEVEL_II_OPLOCK) {
+                       /* Store a level2 oplock, but don't tell the client */
+                       fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
+               } else {
+                       fsp->oplock_type = LEVEL_II_OPLOCK;
+               }
+       } else {
+               /* This case can never happen. */
+               SMB_ASSERT(1);
        }
 
        /*
-        * Send a break message to the oplock holder and delay the open for
-        * our client.
+        * Don't grant level2 to clients that don't want them
+        * or if we've turned them off.
         */
-
-       DEBUG(10, ("Sending break request to PID %s\n",
-                  procid_str_static(&exclusive->pid)));
-       exclusive->op_mid = mid;
-
-       /* Create the message. */
-       share_mode_entry_to_message(msg, exclusive);
-
-       /* Add in the FORCE_OPLOCK_BREAK_TO_NONE bit in the message if set. We
-          don't want this set in the share mode struct pointed to by lck. */
-
-       if (oplock_request & FORCE_OPLOCK_BREAK_TO_NONE) {
-               SSVAL(msg,6,exclusive->op_type | FORCE_OPLOCK_BREAK_TO_NONE);
+       if (fsp->oplock_type == LEVEL_II_OPLOCK && !allow_level2) {
+               fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
        }
 
-       status = messaging_send_buf(smbd_messaging_context(), exclusive->pid,
-                                   MSG_SMB_BREAK_REQUEST,
-                                   (uint8 *)msg,
-                                   MSG_SMB_SHARE_MODE_ENTRY_SIZE);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(3, ("Could not send oplock break message: %s\n",
-                         nt_errstr(status)));
-       }
+       DEBUG(10,("delay_for_oplocks: oplock type 0x%x on file %s\n",
+               fsp->oplock_type, fsp->fsp_name));
 
-       return True;
+       /* No delay. */
+       return false;
 }
 
 bool request_timed_out(struct timeval request_time,
@@ -1968,12 +2002,9 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
         * file structs.
         */
 
-       if ((fsp->oplock_type != NO_OPLOCK) &&
-           (fsp->oplock_type != FAKE_LEVEL_II_OPLOCK)) {
-               if (!set_file_oplock(fsp, fsp->oplock_type)) {
-                       /* Could not get the kernel oplock */
-                       fsp->oplock_type = NO_OPLOCK;
-               }
+       if (!set_file_oplock(fsp, fsp->oplock_type)) {
+               /* Could not get the kernel oplock */
+               fsp->oplock_type = NO_OPLOCK;
        }
 
        if (info == FILE_WAS_OVERWRITTEN || info == FILE_WAS_CREATED || info == FILE_WAS_SUPERSEDED) {
index 23411294df508b2bb4af2ae0db1e00c80560ebb0..c98d11426d49a63dcd08442df1846328ef12e480 100644 (file)
@@ -104,7 +104,10 @@ void process_kernel_oplocks(struct messaging_context *msg_ctx, fd_set *pfds)
 
 bool set_file_oplock(files_struct *fsp, int oplock_type)
 {
-       if (koplocks && !koplocks->set_oplock(fsp, oplock_type)) {
+       if ((fsp->oplock_type != NO_OPLOCK) &&
+           (fsp->oplock_type != FAKE_LEVEL_II_OPLOCK) &&
+           koplocks &&
+           !koplocks->set_oplock(fsp, oplock_type)) {
                return False;
        }
 
@@ -112,7 +115,7 @@ bool set_file_oplock(files_struct *fsp, int oplock_type)
        fsp->sent_oplock_break = NO_BREAK_SENT;
        if (oplock_type == LEVEL_II_OPLOCK) {
                level_II_oplocks_open++;
-       } else {
+       } else if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
                exclusive_oplocks_open++;
        }
 
@@ -145,10 +148,15 @@ void release_file_oplock(files_struct *fsp)
 
        SMB_ASSERT(exclusive_oplocks_open>=0);
        SMB_ASSERT(level_II_oplocks_open>=0);
-       
-       fsp->oplock_type = NO_OPLOCK;
+
+       if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+               /* This doesn't matter for close. */
+               fsp->oplock_type = FAKE_LEVEL_II_OPLOCK;
+       } else {
+               fsp->oplock_type = NO_OPLOCK;
+       }
        fsp->sent_oplock_break = NO_BREAK_SENT;
-       
+
        flush_write_cache(fsp, OPLOCK_RELEASE_FLUSH);
 
        TALLOC_FREE(fsp->oplock_timeout);
@@ -435,6 +443,11 @@ static void process_oplock_async_level2_break_message(struct messaging_context *
        /* Ensure we're really at level2 state. */
        SMB_ASSERT(fsp->oplock_type == LEVEL_II_OPLOCK);
 
+       DEBUG(10,("process_oplock_async_level2_break_message: sending break to "
+               "none message for fid %d, file %s\n",
+               fsp->fnum,
+               fsp->fsp_name));
+
        /* Now send a break to none message to our client. */
 
        break_msg = new_break_smb_message(NULL, fsp, OPLOCKLEVEL_NONE);