1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-11 05:18:09 +03:00

s3:smbd: Implementation of SMB2.1 and SMB3.0 leases.

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>
This commit is contained in:
Volker Lendecke 2014-10-28 15:31:46 -07:00 committed by Jeremy Allison
parent 91b2488c9a
commit 02f2684dd8
6 changed files with 682 additions and 179 deletions

View 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;

View 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

View 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,

View 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;
bool delay = false;
bool will_overwrite;
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;
}
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;
}
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;
}
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;
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) {
delay_mask = SMB2_LEASE_HANDLE;
} else {
delay_mask = SMB2_LEASE_WRITE;
}
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;
}
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,76 +1710,138 @@ 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;
if (LEVEL_II_OPLOCK_TYPE(op_type)) {
got_level2_oplock = true;
}
if (op_type == NO_OPLOCK) {
got_a_none_oplock = true;
}
}
e_lease_type = get_lease_type(d, e);
if ((granted & SMB2_LEASE_WRITE) &&
!is_same_lease(fsp, d, e, lease) &&
!share_mode_stale_pid(d, i)) {
/*
* Match what was requested (fsp->oplock_type) with
* what was found in the existing share modes.
* Can grant only one writer
*/
granted &= ~SMB2_LEASE_WRITE;
}
if (got_level2_oplock || got_a_none_oplock) {
if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
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;
}
}
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 (!allow_level2) {
granted = SMB2_LEASE_NONE;
}
}
if (oplock_request == LEASE_OPLOCK) {
if (got_oplock) {
granted &= ~SMB2_LEASE_HANDLE;
}
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;
}
}
/*
* 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) {
break;
default:
fsp->oplock_type = NO_OPLOCK;
goto type_selected;
break;
}
type_selected:
status = set_file_oplock(fsp);
if (!NT_STATUS_IS_OK(status)) {
/*
@ -1773,11 +1849,12 @@ static NTSTATUS grant_fsp_oplock_type(struct smb_request *req,
*/
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,9 +3105,13 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
* which adds FILE_WRITE_DATA to open_access_mask.
*/
if (is_stat_open(open_access_mask)) {
if (lease) {
lease->lease_state = SMB2_LEASE_NONE;
} else {
oplock_request = NO_OPLOCK;
}
}
}
if (new_file_created) {
info = FILE_WAS_CREATED;
@ -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);

View 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;
}
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;
}
if (break_to == fsp->oplock_type) {
DEBUG(3, ("Already downgraded oplock on %s: %s\n",
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_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 ?
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.
*/
if (fsp->oplock_type != LEASE_OPLOCK) {
remove_oplock(fsp);
}
return;
}
fsp->sent_oplock_break = break_to_level2 ? LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
if (fsp->oplock_type == LEASE_OPLOCK) {
return;
}
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

View File

@ -24,6 +24,10 @@
#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 ));
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));