s3:smbd: Implementation of SMB2.1 and SMB3.0 leases.
authorVolker Lendecke <vl@samba.org>
Tue, 28 Oct 2014 22:31:46 +0000 (15:31 -0700)
committerJeremy Allison <jra@samba.org>
Thu, 4 Dec 2014 04:45:10 +0000 (05:45 +0100)
Pair-Programmed-With: Jeremy Allison <jra@samba.org>
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>

Signed-off-by: Volker Lendecke <vl@samba.org>
Signed-off-by: Jeremy Allison <jra@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
source3/smbd/durable.c
source3/smbd/files.c
source3/smbd/globals.h
source3/smbd/open.c
source3/smbd/oplock.c
source3/smbd/smb2_break.c

index c0d1883204985bb28df0b9b8a135273c3adf7867..d9b88a859a426aac4e77583cc6ffa453f7dcb2d1 100644 (file)
@@ -724,6 +724,32 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
        fsp->aio_write_behind = false;
        fsp->oplock_type = e->op_type;
 
+       if (fsp->oplock_type == LEASE_OPLOCK) {
+               struct share_mode_lease *l = &lck->data->leases[e->lease_idx];
+               struct smb2_lease_key key;
+
+               key.data[0] = l->lease_key.data[0];
+               key.data[1] = l->lease_key.data[1];
+
+               fsp->lease = find_fsp_lease(fsp, &key, l);
+               if (fsp->lease == NULL) {
+                       TALLOC_FREE(lck);
+                       fsp_free(fsp);
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               /*
+                * Ensure the existing client guid matches the
+                * stored one in the share_mode_lease.
+                */
+               if (!GUID_equal(fsp_client_guid(fsp),
+                               &l->client_guid)) {
+                       TALLOC_FREE(lck);
+                       fsp_free(fsp);
+                       return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+               }
+       }
+
        fsp->initial_allocation_size = cookie.initial_allocation_size;
        fsp->fh->position_information = cookie.position_information;
        fsp->update_write_time_triggered = cookie.update_write_time_triggered;
index 91eddb879b2a8aa7adf488570926bb8915c550c3..1ad601abcdbf158d589c010f95da183f8cb634d5 100644 (file)
@@ -490,6 +490,14 @@ void fsp_free(files_struct *fsp)
                fsp->fh->ref_count--;
        }
 
+       if (fsp->lease != NULL) {
+               if (fsp->lease->ref_count == 1) {
+                       TALLOC_FREE(fsp->lease);
+               } else {
+                       fsp->lease->ref_count--;
+               }
+       }
+
        fsp->conn->num_files_open--;
 
        /* this is paranoia, just in case someone tries to reuse the
index d85e68629a6957ef1f885b07718071ef18ba27ce..7726c24b63737738623175cb19f6a6c0cd00bb0f 100644 (file)
@@ -305,7 +305,9 @@ void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
 struct deferred_open_record;
 
 /* SMB1 -> SMB2 glue. */
-void send_break_message_smb2(files_struct *fsp, int level);
+void send_break_message_smb2(files_struct *fsp,
+                            uint32_t break_from,
+                            uint32_t break_to);
 struct blocking_lock_record *get_pending_smb2req_blr(struct smbd_smb2_request *smb2req);
 bool push_blocking_lock_request_smb2( struct byte_range_lock *br_lck,
                                struct smb_request *req,
index 28f740848d3dbfab95919daa4b507158043b4a24..f5ad900f749a892015e8dd625de4c02861179131 100644 (file)
@@ -1258,6 +1258,10 @@ static NTSTATUS send_break_message(struct messaging_context *msg_ctx,
        share_mode_entry_to_message(msg, exclusive);
 
        /* Overload entry->op_type */
+       /*
+        * This is a cut from uint32 to uint16, but so far only the lower 3
+        * bits (LEASE_WRITE/HANDLE/READ are used anyway.
+        */
        SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET, break_to);
 
        status = messaging_send_buf(msg_ctx, exclusive->pid,
@@ -1377,56 +1381,19 @@ static bool validate_oplock_types(struct share_mode_lock *lck)
 
 static bool delay_for_oplock(files_struct *fsp,
                             int oplock_request,
+                            const struct smb2_lease *lease,
                             struct share_mode_lock *lck,
                             bool have_sharing_violation,
-                            uint32_t create_disposition)
+                            uint32_t create_disposition,
+                            bool first_open_attempt)
 {
        struct share_mode_data *d = lck->data;
-       struct share_mode_entry *entry;
-       uint32_t num_non_stat_opens = 0;
        uint32_t i;
-       uint16_t break_to;
-
-       if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) {
-               return false;
-       }
-       for (i=0; i<d->num_share_modes; i++) {
-               struct share_mode_entry *e = &d->share_modes[i];
-               if (e->op_type == NO_OPLOCK && is_stat_open(e->access_mask)) {
-                       continue;
-               }
-               num_non_stat_opens += 1;
-
-               /*
-                * We found the a non-stat open, which in the exclusive/batch
-                * case will be inspected further down.
-                */
-               entry = e;
-       }
-       if (num_non_stat_opens == 0) {
-               /*
-                * Nothing to wait for around
-                */
-               return false;
-       }
-       if (num_non_stat_opens != 1) {
-               /*
-                * More than one open around. There can't be any exclusive or
-                * batch left, this is all level2.
-                */
-               return false;
-       }
+       bool delay = false;
+       bool will_overwrite;
 
-       if (server_id_is_disconnected(&entry->pid)) {
-               /*
-                * TODO: clean up.
-                * This could be achieved by sending a break message
-                * to ourselves. Special considerations for files
-                * with delete_on_close flag set!
-                *
-                * For now we keep it simple and do not
-                * allow delete on close for durable handles.
-                */
+       if ((oplock_request & INTERNAL_OPEN_ONLY) ||
+           is_stat_open(fsp->access_mask)) {
                return false;
        }
 
@@ -1434,50 +1401,98 @@ static bool delay_for_oplock(files_struct *fsp,
        case FILE_SUPERSEDE:
        case FILE_OVERWRITE:
        case FILE_OVERWRITE_IF:
-               break_to = NO_OPLOCK;
+               will_overwrite = true;
                break;
        default:
-               break_to = LEVEL_II_OPLOCK;
+               will_overwrite = false;
                break;
        }
 
-       if (have_sharing_violation && (entry->op_type & BATCH_OPLOCK)) {
-               if (share_mode_stale_pid(d, 0)) {
-                       return false;
+       for (i=0; i<d->num_share_modes; i++) {
+               struct share_mode_entry *e = &d->share_modes[i];
+               struct share_mode_lease *l = NULL;
+               uint32_t e_lease_type = get_lease_type(d, e);
+               uint32_t break_to;
+               uint32_t delay_mask = 0;
+
+               if (e->op_type == LEASE_OPLOCK) {
+                       l = &d->leases[e->lease_idx];
                }
-               send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to);
-               return true;
-       }
-       if (have_sharing_violation) {
-               /*
-                * Non-batch exclusive is not broken if we have a sharing
-                * violation
-                */
-               return false;
-       }
-       if (LEVEL_II_OPLOCK_TYPE(entry->op_type) &&
-           (break_to == NO_OPLOCK)) {
-               if (share_mode_stale_pid(d, 0)) {
-                       return false;
+
+               if (have_sharing_violation) {
+                       delay_mask = SMB2_LEASE_HANDLE;
+               } else {
+                       delay_mask = SMB2_LEASE_WRITE;
                }
-               DEBUG(10, ("Asynchronously breaking level2 oplock for "
-                          "create_disposition=%u\n",
-                          (unsigned)create_disposition));
-               send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to);
-               return false;
-       }
-       if (!EXCLUSIVE_OPLOCK_TYPE(entry->op_type)) {
-               /*
-                * No break for NO_OPLOCK or LEVEL2_OPLOCK oplocks
-                */
-               return false;
-       }
-       if (share_mode_stale_pid(d, 0)) {
-               return false;
+
+               break_to = e_lease_type & ~delay_mask;
+
+               if (will_overwrite) {
+                       /*
+                        * we'll decide about SMB2_LEASE_READ later.
+                        *
+                        * Maybe the break will be defered
+                        */
+                       break_to &= ~SMB2_LEASE_HANDLE;
+               }
+
+               DEBUG(10, ("entry %u: e_lease_type %u, will_overwrite: %u\n",
+                          (unsigned)i, (unsigned)e_lease_type,
+                          (unsigned)will_overwrite));
+
+               if (lease != NULL && l != NULL) {
+                       bool ign;
+
+                       ign = smb2_lease_equal(fsp_client_guid(fsp),
+                                              &lease->lease_key,
+                                              &l->client_guid,
+                                              &l->lease_key);
+                       if (ign) {
+                               continue;
+                       }
+               }
+
+               if ((e_lease_type & ~break_to) == 0) {
+                       if (l != NULL && l->breaking) {
+                               delay = true;
+                       }
+                       continue;
+               }
+
+               if (share_mode_stale_pid(d, i)) {
+                       continue;
+               }
+
+               if (will_overwrite) {
+                       /*
+                        * If we break anyway break to NONE directly.
+                        * Otherwise vfs_set_filelen() will trigger the
+                        * break.
+                        */
+                       break_to &= ~(SMB2_LEASE_READ|SMB2_LEASE_WRITE);
+               }
+
+               if (e->op_type != LEASE_OPLOCK) {
+                       /*
+                        * Oplocks only support breaking to R or NONE.
+                        */
+                       break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
+               }
+
+               DEBUG(10, ("breaking from %d to %d\n",
+                          (int)e_lease_type, (int)break_to));
+               send_break_message(fsp->conn->sconn->msg_ctx, e,
+                                  break_to);
+               if (e_lease_type & delay_mask) {
+                       delay = true;
+               }
+               if (l != NULL && l->breaking && !first_open_attempt) {
+                       delay = true;
+               }
+               continue;
        }
 
-       send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to);
-       return true;
+       return delay;
 }
 
 static bool file_has_brlocks(files_struct *fsp)
@@ -1557,7 +1572,6 @@ struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
        return new_fsp->lease;
 }
 
-#if 0
 static NTSTATUS grant_fsp_lease(struct files_struct *fsp,
                                struct share_mode_lock *lck,
                                const struct smb2_lease *lease,
@@ -1696,88 +1710,151 @@ static bool is_same_lease(const files_struct *fsp,
                                &d->leases[e->lease_idx].client_guid,
                                &d->leases[e->lease_idx].lease_key);
 }
-#endif
 
 static NTSTATUS grant_fsp_oplock_type(struct smb_request *req,
                                      struct files_struct *fsp,
                                      struct share_mode_lock *lck,
-                                     int oplock_request)
+                                     int oplock_request,
+                                     struct smb2_lease *lease)
 {
-       bool allow_level2 = (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
-                           lp_level2_oplocks(SNUM(fsp->conn));
-       bool got_level2_oplock, got_a_none_oplock;
+       struct share_mode_data *d = lck->data;
+       bool got_handle_lease = false;
+       bool got_oplock = false;
        uint32_t i;
+       uint32_t granted;
+       uint32_t lease_idx = UINT32_MAX;
        bool ok;
        NTSTATUS status;
 
-       /* Start by granting what the client asked for,
-          but ensure no SAMBA_PRIVATE bits can be set. */
-       fsp->oplock_type = (oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
-
-       if (fsp->oplock_type == NO_OPLOCK) {
-               goto type_selected;
-       }
-
        if (oplock_request & INTERNAL_OPEN_ONLY) {
                /* No oplocks on internal open. */
-               fsp->oplock_type = NO_OPLOCK;
-               goto type_selected;
+               oplock_request = NO_OPLOCK;
+               DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n",
+                       fsp->oplock_type, fsp_str_dbg(fsp)));
+       }
+
+       if (oplock_request == LEASE_OPLOCK) {
+               if (lease == NULL) {
+                       /*
+                        * The SMB2 layer should have checked this
+                        */
+                       return NT_STATUS_INTERNAL_ERROR;
+               }
+
+               granted = lease->lease_state;
+
+               if (lp_kernel_oplocks(SNUM(fsp->conn))) {
+                       DEBUG(10, ("No lease granted because kernel oplocks are enabled\n"));
+                       granted = SMB2_LEASE_NONE;
+               }
+               if ((granted & (SMB2_LEASE_READ|SMB2_LEASE_WRITE)) == 0) {
+                       DEBUG(10, ("No read or write lease requested\n"));
+                       granted = SMB2_LEASE_NONE;
+               }
+               if (granted == SMB2_LEASE_WRITE) {
+                       DEBUG(10, ("pure write lease requested\n"));
+                       granted = SMB2_LEASE_NONE;
+               }
+               if (granted == (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+                       DEBUG(10, ("write and handle lease requested\n"));
+                       granted = SMB2_LEASE_NONE;
+               }
+       } else {
+               granted = map_oplock_to_lease_type(
+                       oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
        }
 
        if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) {
                DEBUG(10,("grant_fsp_oplock_type: file %s has byte range locks\n",
                        fsp_str_dbg(fsp)));
-               fsp->oplock_type = NO_OPLOCK;
-               goto type_selected;
+               granted &= ~SMB2_LEASE_READ;
        }
 
-       got_level2_oplock = false;
-       got_a_none_oplock = false;
+       for (i=0; i<d->num_share_modes; i++) {
+               struct share_mode_entry *e = &d->share_modes[i];
+               uint32_t e_lease_type;
 
-       for (i=0; i<lck->data->num_share_modes; i++) {
-               int op_type = lck->data->share_modes[i].op_type;
+               e_lease_type = get_lease_type(d, e);
 
-               if (LEVEL_II_OPLOCK_TYPE(op_type)) {
-                       got_level2_oplock = true;
+               if ((granted & SMB2_LEASE_WRITE) &&
+                   !is_same_lease(fsp, d, e, lease) &&
+                   !share_mode_stale_pid(d, i)) {
+                       /*
+                        * Can grant only one writer
+                        */
+                       granted &= ~SMB2_LEASE_WRITE;
                }
-               if (op_type == NO_OPLOCK) {
-                       got_a_none_oplock = true;
+
+               if ((e_lease_type & SMB2_LEASE_HANDLE) && !got_handle_lease &&
+                   !share_mode_stale_pid(d, i)) {
+                       got_handle_lease = true;
+               }
+
+               if ((e->op_type != LEASE_OPLOCK) && !got_oplock &&
+                   !share_mode_stale_pid(d, i)) {
+                       got_oplock = true;
                }
        }
 
-       /*
-        * Match what was requested (fsp->oplock_type) with
-        * what was found in the existing share modes.
-        */
+       if ((granted & SMB2_LEASE_READ) && !(granted & SMB2_LEASE_WRITE)) {
+               bool allow_level2 =
+                       (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+                       lp_level2_oplocks(SNUM(fsp->conn));
 
-       if (got_level2_oplock || got_a_none_oplock) {
-               if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
-                       fsp->oplock_type = LEVEL_II_OPLOCK;
+               if (!allow_level2) {
+                       granted = SMB2_LEASE_NONE;
                }
        }
 
-       /*
-        * Don't grant level2 to clients that don't want them
-        * or if we've turned them off.
-        */
-       if (fsp->oplock_type == LEVEL_II_OPLOCK && !allow_level2) {
-               fsp->oplock_type = NO_OPLOCK;
-               goto type_selected;
-       }
+       if (oplock_request == LEASE_OPLOCK) {
+               if (got_oplock) {
+                       granted &= ~SMB2_LEASE_HANDLE;
+               }
 
- type_selected:
-       status = set_file_oplock(fsp);
-       if (!NT_STATUS_IS_OK(status)) {
-               /*
-                * Could not get the kernel oplock
-                */
-               fsp->oplock_type = NO_OPLOCK;
+               fsp->oplock_type = LEASE_OPLOCK;
+
+               status = grant_fsp_lease(fsp, lck, lease, &lease_idx,
+                                        granted);
+               if (!NT_STATUS_IS_OK(status)) {
+                       return status;
+
+               }
+               *lease = fsp->lease->lease;
+               DEBUG(10, ("lease_state=%d\n", lease->lease_state));
+       } else {
+               if (got_handle_lease) {
+                       granted = SMB2_LEASE_NONE;
+               }
+
+               switch (granted) {
+               case SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE:
+                       fsp->oplock_type = BATCH_OPLOCK|EXCLUSIVE_OPLOCK;
+                       break;
+               case SMB2_LEASE_READ|SMB2_LEASE_WRITE:
+                       fsp->oplock_type = EXCLUSIVE_OPLOCK;
+                       break;
+               case SMB2_LEASE_READ|SMB2_LEASE_HANDLE:
+               case SMB2_LEASE_READ:
+                       fsp->oplock_type = LEVEL_II_OPLOCK;
+                       break;
+               default:
+                       fsp->oplock_type = NO_OPLOCK;
+                       break;
+               }
+
+               status = set_file_oplock(fsp);
+               if (!NT_STATUS_IS_OK(status)) {
+                       /*
+                        * Could not get the kernel oplock
+                        */
+                       fsp->oplock_type = NO_OPLOCK;
+               }
        }
 
        ok = set_share_mode(lck, fsp, get_current_uid(fsp->conn),
                            req ? req->mid : 0,
                            fsp->oplock_type,
-                           UINT32_MAX);
+                           lease_idx);
        if (!ok) {
                return NT_STATUS_NO_MEMORY;
        }
@@ -2683,8 +2760,10 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
                        smb_panic("validate_oplock_types failed");
                }
 
-               if (delay_for_oplock(fsp, 0, lck, false, create_disposition)) {
-                       schedule_defer_open(lck, fsp->file_id, request_time, req);
+               if (delay_for_oplock(fsp, 0, lease, lck, false,
+                                    create_disposition, first_open_attempt)) {
+                       schedule_defer_open(lck, fsp->file_id, request_time,
+                                           req);
                        TALLOC_FREE(lck);
                        DEBUG(10, ("Sent oplock break request to kernel "
                                   "oplock holder\n"));
@@ -2805,9 +2884,9 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
        if ((req != NULL) &&
            delay_for_oplock(
-                   fsp, oplock_request, lck,
+                   fsp, oplock_request, lease, lck,
                    NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION),
-                   create_disposition)) {
+                   create_disposition, first_open_attempt)) {
                schedule_defer_open(lck, fsp->file_id, request_time, req);
                TALLOC_FREE(lck);
                fd_close(fsp);
@@ -3016,7 +3095,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 
        if (file_existed) {
                /*
-                * stat opens on existing files don't get oplocks.
+                * stat opens on existing files don't get oplocks or leases.
                 *
                 * Note that we check for stat open on the *open_access_mask*,
                 * i.e. the access mask we actually used to do the open,
@@ -3026,7 +3105,11 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
                 * which adds FILE_WRITE_DATA to open_access_mask.
                 */
                if (is_stat_open(open_access_mask)) {
-                       oplock_request = NO_OPLOCK;
+                       if (lease) {
+                               lease->lease_state = SMB2_LEASE_NONE;
+                       } else {
+                               oplock_request = NO_OPLOCK;
+                       }
                }
        }
 
@@ -3048,7 +3131,7 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
         * Setup the oplock info in both the shared memory and
         * file structs.
         */
-       status = grant_fsp_oplock_type(req, fsp, lck, oplock_request);
+       status = grant_fsp_oplock_type(req, fsp, lck, oplock_request, lease);
        if (!NT_STATUS_IS_OK(status)) {
                TALLOC_FREE(lck);
                fd_close(fsp);
index 5ad881dbdfcf5336e0893ffb64785ef6632c015d..8f318f58485f3371adea5a759d60b76b2819daf7 100644 (file)
@@ -196,15 +196,10 @@ bool update_num_read_oplocks(files_struct *fsp, struct share_mode_lock *lck)
 
        for (i=0; i<d->num_share_modes; i++) {
                struct share_mode_entry *e = &d->share_modes[i];
+               uint32_t e_lease_type = get_lease_type(d, e);
 
-               if (EXCLUSIVE_OPLOCK_TYPE(e->op_type)) {
+               if (e_lease_type & SMB2_LEASE_READ) {
                        num_read_oplocks += 1;
-                       continue;
-               }
-
-               if (LEVEL_II_OPLOCK_TYPE(e->op_type)) {
-                       num_read_oplocks += 1;
-                       continue;
                }
        }
 
@@ -772,14 +767,15 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
 {
        struct share_mode_entry msg;
        files_struct *fsp;
-       bool break_to_level2 = False;
        bool use_kernel;
        struct smbd_server_connection *sconn =
                talloc_get_type_abort(private_data,
                struct smbd_server_connection);
        struct server_id self = messaging_server_id(sconn->msg_ctx);
        struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+       uint16_t break_from;
        uint16_t break_to;
+       bool break_needed = true;
 
        if (data->data == NULL) {
                DEBUG(0, ("Got NULL buffer\n"));
@@ -809,27 +805,118 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
                return;
        }
 
-       if (fsp->sent_oplock_break != NO_BREAK_SENT) {
-               /*
-                * Nothing to do anymore
-                */
+       break_from = fsp_lease_type(fsp);
+
+       if (fsp->oplock_type != LEASE_OPLOCK) {
+               if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+                       /*
+                        * Nothing to do anymore
+                        */
+                       DEBUG(10, ("fsp->sent_oplock_break = %d\n",
+                                  fsp->sent_oplock_break));
+                       return;
+               }
+       }
+
+       if (!(global_client_caps & CAP_LEVEL_II_OPLOCKS)) {
+               DEBUG(10, ("client_caps without level2 oplocks\n"));
+               break_to &= ~SMB2_LEASE_READ;
+       }
+
+       use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) && koplocks;
+       if (use_kernel && !(koplocks->flags & KOPLOCKS_LEVEL2_SUPPORTED)) {
+               DEBUG(10, ("Kernel oplocks don't allow level2\n"));
+               break_to &= ~SMB2_LEASE_READ;
+       }
+
+       if (!lp_level2_oplocks(SNUM(fsp->conn))) {
+               DEBUG(10, ("no level2 oplocks by config\n"));
+               break_to &= ~SMB2_LEASE_READ;
+       }
+
+       if (fsp->oplock_type == LEASE_OPLOCK) {
+               struct share_mode_lock *lck;
+               int idx;
+
+               lck = get_existing_share_mode_lock(
+                       talloc_tos(), fsp->file_id);
+               if (lck == NULL) {
+                       /*
+                        * We hit a race here. Break messages are sent, and
+                        * before we get to process this message, we have closed
+                        * the file.
+                        */
+                       DEBUG(3, ("Did not find share_mode\n"));
+                       return;
+               }
+
+               idx = find_share_mode_lease(
+                       lck->data,
+                       fsp_client_guid(fsp),
+                       &fsp->lease->lease.lease_key);
+               if (idx != -1) {
+                       struct share_mode_lease *l;
+                       l = &lck->data->leases[idx];
+
+                       break_from = l->current_state;
+                       break_to &= l->current_state;
+
+                       if (l->breaking) {
+                               break_to &= l->breaking_to_required;
+                               if (l->breaking_to_required != break_to) {
+                                       /*
+                                        * Note we don't increment the epoch
+                                        * here, which might be a bug in
+                                        * Windows too...
+                                        */
+                                       l->breaking_to_required = break_to;
+                                       lck->data->modified = true;
+                               }
+                               break_needed = false;
+                       } else if (l->current_state == break_to) {
+                               break_needed = false;
+                       } else if (l->current_state == SMB2_LEASE_READ) {
+                               l->current_state = SMB2_LEASE_NONE;
+                               /* Need to increment the epoch */
+                               l->epoch += 1;
+                               lck->data->modified = true;
+                       } else {
+                               l->breaking = true;
+                               l->breaking_to_required = break_to;
+                               l->breaking_to_requested = break_to;
+                               /* Need to increment the epoch */
+                               l->epoch += 1;
+                               lck->data->modified = true;
+                       }
+
+                       /* Ensure we're in sync with current lease state. */
+                       fsp_lease_update(lck, fsp_client_guid(fsp), fsp->lease);
+               }
+
+               TALLOC_FREE(lck);
+       }
+
+       if (!break_needed) {
+               DEBUG(10,("%s: skip break\n", __func__));
                return;
        }
 
-       if (break_to == fsp->oplock_type) {
-               DEBUG(3, ("Already downgraded oplock on %s: %s\n",
+       if ((break_from == SMB2_LEASE_NONE) && !break_needed) {
+               DEBUG(3, ("Already downgraded oplock to none on %s: %s\n",
                          file_id_string_tos(&fsp->file_id),
                          fsp_str_dbg(fsp)));
                return;
        }
 
-       use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) && koplocks;
+       DEBUG(10, ("break_from=%u, break_to=%u\n",
+                  (unsigned)break_from, (unsigned)break_to));
 
-       if ((global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
-           (break_to != NO_OPLOCK) &&
-           !(use_kernel && !(koplocks->flags & KOPLOCKS_LEVEL2_SUPPORTED)) &&
-           lp_level2_oplocks(SNUM(fsp->conn))) {
-               break_to_level2 = True;
+       if ((break_from == break_to) && !break_needed) {
+               DEBUG(3, ("Already downgraded oplock to %u on %s: %s\n",
+                         (unsigned)break_to,
+                         file_id_string_tos(&fsp->file_id),
+                         fsp_str_dbg(fsp)));
+               return;
        }
 
        /* Need to wait before sending a break
@@ -839,21 +926,31 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx,
        }
 
        if (sconn->using_smb2) {
-               send_break_message_smb2(fsp, break_to_level2 ?
-                       OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+               send_break_message_smb2(fsp, break_from, break_to);
        } else {
-               send_break_message_smb1(fsp, break_to_level2 ?
-                       OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+               send_break_message_smb1(fsp, (break_to & SMB2_LEASE_READ) ?
+                                       OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
        }
 
-       if ((fsp->oplock_type == LEVEL_II_OPLOCK) && (break_to == NO_OPLOCK)) {
+       if ((break_from == SMB2_LEASE_READ) &&
+           (break_to == SMB2_LEASE_NONE)) {
                /*
                 * This is an async break without a reply and thus no timeout
+                *
+                * leases are handled above.
                 */
-               remove_oplock(fsp);
+               if (fsp->oplock_type != LEASE_OPLOCK) {
+                       remove_oplock(fsp);
+               }
+               return;
+       }
+       if (fsp->oplock_type == LEASE_OPLOCK) {
                return;
        }
-       fsp->sent_oplock_break = break_to_level2 ? LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+
+       fsp->sent_oplock_break = (break_to & SMB2_LEASE_READ) ?
+               LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+
        add_oplock_timeout_handler(fsp);
 }
 
@@ -908,7 +1005,7 @@ static void process_kernel_oplock_break(struct messaging_context *msg_ctx,
        }
 
        if (sconn->using_smb2) {
-               send_break_message_smb2(fsp, OPLOCKLEVEL_NONE);
+               send_break_message_smb2(fsp, 0, OPLOCKLEVEL_NONE);
        } else {
                send_break_message_smb1(fsp, OPLOCKLEVEL_NONE);
        }
@@ -921,6 +1018,8 @@ static void process_kernel_oplock_break(struct messaging_context *msg_ctx,
 struct break_to_none_state {
        struct smbd_server_connection *sconn;
        struct file_id id;
+       struct smb2_lease_key lease_key;
+       struct GUID client_guid;
 };
 static void do_break_to_none(struct tevent_context *ctx,
                             struct tevent_immediate *im,
@@ -975,7 +1074,7 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
         * anyway, so we postpone this into an immediate event.
         */
 
-       state = talloc(sconn, struct break_to_none_state);
+       state = talloc_zero(sconn, struct break_to_none_state);
        if (state == NULL) {
                DEBUG(1, ("talloc failed\n"));
                return;
@@ -983,6 +1082,14 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp,
        state->sconn = sconn;
        state->id = fsp->file_id;
 
+       if (fsp->oplock_type == LEASE_OPLOCK) {
+               state->client_guid = *fsp_client_guid(fsp);
+               state->lease_key = fsp->lease->lease.lease_key;
+               DEBUG(10, ("Breaking through lease key %"PRIu64"/%"PRIu64"\n",
+                          state->lease_key.data[0],
+                          state->lease_key.data[1]));
+       }
+
        im = tevent_create_immediate(state);
        if (im == NULL) {
                DEBUG(1, ("tevent_create_immediate failed\n"));
@@ -1023,8 +1130,50 @@ static void do_break_to_none(struct tevent_context *ctx,
        }
        d = lck->data;
 
-       DEBUG(10,("%s: num_share_modes = %d\n", __func__,
-                 lck->data->num_share_modes ));
+       /*
+        * Walk leases and oplocks separately: We have to send one break per
+        * lease. If we have multiple share_mode_entry having a common lease,
+        * we would break the lease twice if we don't walk the leases list
+        * separately.
+        */
+
+       for (i=0; i<d->num_leases; i++) {
+               struct share_mode_lease *l = &d->leases[i];
+               struct share_mode_entry *e;
+               uint32_t j;
+
+               if ((l->current_state & SMB2_LEASE_READ) == 0) {
+                       continue;
+               }
+               if (smb2_lease_equal(&state->client_guid,
+                                    &state->lease_key,
+                                    &l->client_guid,
+                                    &l->lease_key)) {
+                       DEBUG(10, ("Don't break our own lease\n"));
+                       continue;
+               }
+
+               for (j=0; j<d->num_share_modes; j++) {
+                       e = &d->share_modes[j];
+
+                       if (!is_valid_share_mode_entry(e)) {
+                               continue;
+                       }
+                       if (e->lease_idx == i) {
+                               break;
+                       }
+               }
+               if (j == d->num_share_modes) {
+                       DEBUG(0, ("leases[%"PRIu32"] has no share mode\n",
+                                 i));
+                       continue;
+               }
+
+               DEBUG(10, ("Breaking lease# %"PRIu32" with share_entry# "
+                          "%"PRIu32"\n", i, j));
+
+               send_break_to_none(state->sconn->msg_ctx, e);
+       }
 
        for(i = 0; i < d->num_share_modes; i++) {
                struct share_mode_entry *e = &d->share_modes[i];
@@ -1032,6 +1181,12 @@ static void do_break_to_none(struct tevent_context *ctx,
                if (!is_valid_share_mode_entry(e)) {
                        continue;
                }
+               if (e->op_type == LEASE_OPLOCK) {
+                       /*
+                        * Took care of those in the loop above
+                        */
+                       continue;
+               }
 
                /*
                 * As there could have been multiple writes waiting at the
index 449245645d9a0cc0fc25a4181cb25f749af94ecb..126bf7898f2409d9bd1ed1559c9c47acecc7adfa 100644 (file)
 #include "smbd/globals.h"
 #include "../libcli/smb/smb_common.h"
 #include "../lib/util/tevent_ntstatus.h"
+#include "locking/leases_db.h"
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+       struct smbd_smb2_request *req);
 
 static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
                                                      struct tevent_context *ev,
@@ -45,6 +49,12 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
        struct tevent_req *subreq;
 
        status = smbd_smb2_request_verify_sizes(req, 0x18);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+               /*
+                * Retry as a lease break
+                */
+               return smbd_smb2_request_process_lease_break(req);
+       }
        if (!NT_STATUS_IS_OK(status)) {
                return smbd_smb2_request_error(req, status);
        }
@@ -222,16 +232,213 @@ static NTSTATUS smbd_smb2_oplock_break_recv(struct tevent_req *req,
        return NT_STATUS_OK;
 }
 
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+       TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+       struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+       uint32_t in_lease_state);
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+                                          uint32_t *out_lease_state);
+
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+       struct smbd_smb2_request *req)
+{
+       NTSTATUS status;
+       const uint8_t *inbody;
+       struct smb2_lease_key in_lease_key;
+       uint32_t in_lease_state;
+       struct tevent_req *subreq;
+
+       status = smbd_smb2_request_verify_sizes(req, 0x24);
+       if (!NT_STATUS_IS_OK(status)) {
+               return smbd_smb2_request_error(req, status);
+       }
+
+       inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+       in_lease_key.data[0] = BVAL(inbody, 8);
+       in_lease_key.data[1] = BVAL(inbody, 16);
+       in_lease_state = IVAL(inbody, 24);
+
+       subreq = smbd_smb2_lease_break_send(req, req->sconn->ev_ctx, req,
+                                           in_lease_key, in_lease_state);
+       if (subreq == NULL) {
+               return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+       }
+       tevent_req_set_callback(subreq, smbd_smb2_request_lease_break_done, req);
+
+       return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq)
+{
+       struct smbd_smb2_request *req = tevent_req_callback_data(
+               subreq, struct smbd_smb2_request);
+       const uint8_t *inbody;
+       struct smb2_lease_key in_lease_key;
+       uint32_t out_lease_state = 0;
+       DATA_BLOB outbody;
+       NTSTATUS status;
+       NTSTATUS error; /* transport error */
+
+       status = smbd_smb2_lease_break_recv(subreq, &out_lease_state);
+       TALLOC_FREE(subreq);
+       if (!NT_STATUS_IS_OK(status)) {
+               error = smbd_smb2_request_error(req, status);
+               if (!NT_STATUS_IS_OK(error)) {
+                       smbd_server_connection_terminate(req->xconn,
+                                                        nt_errstr(error));
+                       return;
+               }
+               return;
+       }
+
+       inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+       in_lease_key.data[0] = BVAL(inbody, 8);
+       in_lease_key.data[1] = BVAL(inbody, 16);
+
+       outbody = smbd_smb2_generate_outbody(req, 0x24);
+       if (outbody.data == NULL) {
+               error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+               if (!NT_STATUS_IS_OK(error)) {
+                       smbd_server_connection_terminate(req->xconn,
+                                                        nt_errstr(error));
+                       return;
+               }
+               return;
+       }
+
+       SSVAL(outbody.data, 0x00, 0x24);        /* struct size */
+       SSVAL(outbody.data, 0x02, 0);           /* reserved */
+       SIVAL(outbody.data, 0x04, 0);           /* flags, must be 0 */
+       SBVAL(outbody.data, 0x08, in_lease_key.data[0]);
+       SBVAL(outbody.data, 0x10, in_lease_key.data[1]);
+       SIVAL(outbody.data, 0x18, out_lease_state);
+       SBVAL(outbody.data, 0x1c, 0);           /* leaseduration, must be 0 */
+
+       error = smbd_smb2_request_done(req, outbody, NULL);
+       if (!NT_STATUS_IS_OK(error)) {
+               smbd_server_connection_terminate(req->xconn,
+                                                nt_errstr(error));
+               return;
+       }
+}
+
+struct smbd_smb2_lease_break_state {
+       uint32_t lease_state;
+};
+
+struct lease_lookup_state {
+       TALLOC_CTX *mem_ctx;
+       /* Return parameters. */
+       uint32_t num_file_ids;
+       struct file_id *ids;
+       NTSTATUS status;
+};
+
+static void lease_parser(
+       uint32_t num_file_ids,
+       struct file_id *ids, const char *filename, const char *stream_name,
+       void *private_data)
+{
+       struct lease_lookup_state *lls =
+               (struct lease_lookup_state *)private_data;
+
+       lls->status = NT_STATUS_OK;
+       lls->num_file_ids = num_file_ids;
+       lls->ids = talloc_memdup(lls->mem_ctx,
+                               ids,
+                               num_file_ids * sizeof(struct file_id));
+       if (lls->ids == NULL) {
+               lls->status = NT_STATUS_NO_MEMORY;
+       }
+}
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+       TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+       struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+       uint32_t in_lease_state)
+{
+       struct tevent_req *req;
+       struct smbd_smb2_lease_break_state *state;
+       struct lease_lookup_state lls = {.mem_ctx = mem_ctx};
+       NTSTATUS status;
+
+       req = tevent_req_create(mem_ctx, &state,
+                               struct smbd_smb2_lease_break_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       state->lease_state = in_lease_state;
+
+       /* Find any file ids with this lease key. */
+       status = leases_db_parse(&smb2_req->xconn->smb2.client.guid,
+                                &in_lease_key,
+                                lease_parser,
+                                &lls);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+                       status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+                       DEBUG(10, ("No record for lease key found\n"));
+               }
+       } else if (!NT_STATUS_IS_OK(lls.status)) {
+               status = lls.status;
+       } else if (lls.num_file_ids == 0) {
+               status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+       }
+
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return tevent_req_post(req, ev);
+       }
+
+       status = downgrade_lease(smb2_req->xconn,
+                               lls.num_file_ids,
+                               lls.ids,
+                               &in_lease_key,
+                               in_lease_state);
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)) {
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       if (tevent_req_nterror(req, status)) {
+               DEBUG(10, ("downgrade_lease returned %s\n",
+                          nt_errstr(status)));
+               return tevent_req_post(req, ev);
+       }
+
+       tevent_req_done(req);
+       return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+                                          uint32_t *out_lease_state)
+{
+       struct smbd_smb2_lease_break_state *state = tevent_req_data(
+               req, struct smbd_smb2_lease_break_state);
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               return status;
+       }
+       *out_lease_state = state->lease_state;
+       return NT_STATUS_OK;
+}
+
 /*********************************************************
  Create and send an asynchronous
  SMB2 OPLOCK_BREAK_NOTIFICATION.
 *********************************************************/
 
-void send_break_message_smb2(files_struct *fsp, int level)
+void send_break_message_smb2(files_struct *fsp,
+                            uint32_t break_from,
+                            uint32_t break_to)
 {
-       uint8_t smb2_oplock_level = (level == OPLOCKLEVEL_II) ?
-                               SMB2_OPLOCK_LEVEL_II :
-                               SMB2_OPLOCK_LEVEL_NONE;
        NTSTATUS status;
        struct smbXsrv_connection *xconn = NULL;
        struct smbXsrv_session *session = NULL;
@@ -257,7 +464,7 @@ void send_break_message_smb2(files_struct *fsp, int level)
                        "for file %s, %s, smb2 level %u session %llu not found\n",
                        fsp_str_dbg(fsp),
                        fsp_fnum_dbg(fsp),
-                       (unsigned int)smb2_oplock_level,
+                       (unsigned int)break_to,
                        (unsigned long long)fsp->vuid));
                return;
        }
@@ -266,13 +473,35 @@ void send_break_message_smb2(files_struct *fsp, int level)
                "for file %s, %s, smb2 level %u\n",
                fsp_str_dbg(fsp),
                fsp_fnum_dbg(fsp),
-               (unsigned int)smb2_oplock_level ));
+               (unsigned int)break_to ));
 
-       status = smbd_smb2_send_oplock_break(xconn,
-                                            session,
-                                            fsp->conn->tcon,
-                                            fsp->op,
-                                            smb2_oplock_level);
+       if (fsp->oplock_type == LEASE_OPLOCK) {
+               uint32_t break_flags = 0;
+               uint16_t new_epoch;
+
+               if (fsp->lease->lease.lease_state != SMB2_LEASE_NONE) {
+                       break_flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
+               }
+
+               if (fsp->lease->lease.lease_version > 1) {
+                       new_epoch = fsp->lease->lease.lease_epoch;
+               } else {
+                       new_epoch = 0;
+               }
+
+               status = smbd_smb2_send_lease_break(xconn, new_epoch, break_flags,
+                                                   &fsp->lease->lease.lease_key,
+                                                   break_from, break_to);
+       } else {
+               uint8_t smb2_oplock_level;
+               smb2_oplock_level = (break_to & SMB2_LEASE_READ) ?
+                       SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE;
+               status = smbd_smb2_send_oplock_break(xconn,
+                                                    session,
+                                                    fsp->conn->tcon,
+                                                    fsp->op,
+                                                    smb2_oplock_level);
+       }
        if (!NT_STATUS_IS_OK(status)) {
                smbd_server_connection_terminate(xconn,
                                                 nt_errstr(status));