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;
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
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,
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,
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;
}
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)
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,
&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;
}
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"));
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);
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,
* 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;
+ }
}
}
* 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);
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;
}
}
{
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"));
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
}
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);
}
}
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);
}
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,
* 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;
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"));
}
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];
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
#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,
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);
}
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;
"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;
}
"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));