From 9b0d7e981354c330e246c9ace2cbef3777f8c3fd Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Mon, 22 Sep 2014 21:21:36 +0200 Subject: [PATCH 01/15] s3: leases: mask off SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET. Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- libcli/smb/smb2_lease.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c index f97f096..6fc26f2 100644 --- a/libcli/smb/smb2_lease.c +++ b/libcli/smb/smb2_lease.c @@ -47,6 +47,7 @@ ssize_t smb2_lease_pull(const uint8_t *buf, size_t len, switch (version) { case 1: ZERO_STRUCT(lease->parent_lease_key); + lease->lease_flags &= ~SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET; lease->lease_epoch = 0; break; case 2: -- 2.1.0.rc2.206.gedb03e5 From 94e8a7d6cc7dbbdbaf9a2edc40aecb290a9ff0ce Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 28 Oct 2014 15:27:09 -0700 Subject: [PATCH 02/15] s3: leases - convert have_read field to num_read. Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- source3/locking/brlock.c | 123 +++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 67 deletions(-) diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c index 0bed9f9..6c73c72 100644 --- a/source3/locking/brlock.c +++ b/source3/locking/brlock.c @@ -47,7 +47,7 @@ struct byte_range_lock { struct files_struct *fsp; unsigned int num_locks; bool modified; - bool have_read_oplocks; + uint32_t num_read_oplocks; struct lock_struct *lock_data; struct db_record *record; }; @@ -82,18 +82,18 @@ struct files_struct *brl_fsp(struct byte_range_lock *brl) return brl->fsp; } -bool brl_have_read_oplocks(const struct byte_range_lock *brl) +uint32_t brl_num_read_oplocks(const struct byte_range_lock *brl) { - return brl->have_read_oplocks; + return brl->num_read_oplocks; } -void brl_set_have_read_oplocks(struct byte_range_lock *brl, - bool have_read_oplocks) +void brl_set_num_read_oplocks(struct byte_range_lock *brl, + uint32_t num_read_oplocks) { - DEBUG(10, ("Setting have_read_oplocks to %s\n", - have_read_oplocks ? "true" : "false")); + DEBUG(10, ("Setting num_read_oplocks to %"PRIu32"\n", + num_read_oplocks)); SMB_ASSERT(brl->record != NULL); /* otherwise we're readonly */ - brl->have_read_oplocks = have_read_oplocks; + brl->num_read_oplocks = num_read_oplocks; brl->modified = true; } @@ -1850,7 +1850,6 @@ int brl_forall(void (*fn)(struct file_id id, struct server_id pid, static void byte_range_lock_flush(struct byte_range_lock *br_lck) { - size_t data_len; unsigned i; struct lock_struct *locks = br_lck->lock_data; @@ -1874,15 +1873,7 @@ static void byte_range_lock_flush(struct byte_range_lock *br_lck) } } - data_len = br_lck->num_locks * sizeof(struct lock_struct); - - if (br_lck->have_read_oplocks) { - data_len += 1; - } - - DEBUG(10, ("data_len=%d\n", (int)data_len)); - - if (data_len == 0) { + if ((br_lck->num_locks == 0) && (br_lck->num_read_oplocks == 0)) { /* No locks - delete this entry. */ NTSTATUS status = dbwrap_record_delete(br_lck->record); if (!NT_STATUS_IS_OK(status)) { @@ -1891,19 +1882,20 @@ static void byte_range_lock_flush(struct byte_range_lock *br_lck) smb_panic("Could not delete byte range lock entry"); } } else { + size_t lock_len, data_len; TDB_DATA data; NTSTATUS status; + lock_len = br_lck->num_locks * sizeof(struct lock_struct); + data_len = lock_len + sizeof(br_lck->num_read_oplocks); + data.dsize = data_len; data.dptr = talloc_array(talloc_tos(), uint8_t, data_len); SMB_ASSERT(data.dptr != NULL); - memcpy(data.dptr, br_lck->lock_data, - br_lck->num_locks * sizeof(struct lock_struct)); - - if (br_lck->have_read_oplocks) { - data.dptr[data_len-1] = 1; - } + memcpy(data.dptr, br_lck->lock_data, lock_len); + memcpy(data.dptr + lock_len, &br_lck->num_read_oplocks, + sizeof(br_lck->num_read_oplocks)); status = dbwrap_record_store(br_lck->record, data, TDB_REPLACE); TALLOC_FREE(data.dptr); @@ -1926,6 +1918,32 @@ static int byte_range_lock_destructor(struct byte_range_lock *br_lck) return 0; } +static bool brl_parse_data(struct byte_range_lock *br_lck, TDB_DATA data) +{ + size_t data_len; + + if (data.dsize == 0) { + return true; + } + if (data.dsize % sizeof(struct lock_struct) != + sizeof(br_lck->num_read_oplocks)) { + DEBUG(1, ("Invalid data size: %u\n", (unsigned)data.dsize)); + return false; + } + + br_lck->num_locks = data.dsize / sizeof(struct lock_struct); + data_len = br_lck->num_locks * sizeof(struct lock_struct); + + br_lck->lock_data = talloc_memdup(br_lck, data.dptr, data_len); + if (br_lck->lock_data == NULL) { + DEBUG(1, ("talloc_memdup failed\n")); + return false; + } + memcpy(&br_lck->num_read_oplocks, data.dptr + data_len, + sizeof(br_lck->num_read_oplocks)); + return true; +} + /******************************************************************* Fetch a set of byte range lock data from the database. Leave the record locked. @@ -1935,16 +1953,14 @@ static int byte_range_lock_destructor(struct byte_range_lock *br_lck) struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp) { TDB_DATA key, data; - struct byte_range_lock *br_lck = talloc(mem_ctx, struct byte_range_lock); + struct byte_range_lock *br_lck; + br_lck = talloc_zero(mem_ctx, struct byte_range_lock); if (br_lck == NULL) { return NULL; } br_lck->fsp = fsp; - br_lck->num_locks = 0; - br_lck->have_read_oplocks = false; - br_lck->modified = False; key.dptr = (uint8 *)&fsp->file_id; key.dsize = sizeof(struct file_id); @@ -1959,30 +1975,12 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp) data = dbwrap_record_get_value(br_lck->record); - br_lck->lock_data = NULL; - - talloc_set_destructor(br_lck, byte_range_lock_destructor); - - br_lck->num_locks = data.dsize / sizeof(struct lock_struct); - - if (br_lck->num_locks != 0) { - br_lck->lock_data = talloc_array( - br_lck, struct lock_struct, br_lck->num_locks); - if (br_lck->lock_data == NULL) { - DEBUG(0, ("malloc failed\n")); - TALLOC_FREE(br_lck); - return NULL; - } - - memcpy(br_lck->lock_data, data.dptr, - talloc_get_size(br_lck->lock_data)); + if (!brl_parse_data(br_lck, data)) { + TALLOC_FREE(br_lck); + return NULL; } - DEBUG(10, ("data.dsize=%d\n", (int)data.dsize)); - - if ((data.dsize % sizeof(struct lock_struct)) == 1) { - br_lck->have_read_oplocks = (data.dptr[data.dsize-1] == 1); - } + talloc_set_destructor(br_lck, byte_range_lock_destructor); if (DEBUGLEVEL >= 10) { unsigned int i; @@ -2008,28 +2006,19 @@ static void brl_get_locks_readonly_parser(TDB_DATA key, TDB_DATA data, { struct brl_get_locks_readonly_state *state = (struct brl_get_locks_readonly_state *)private_data; - struct byte_range_lock *br_lock; + struct byte_range_lock *br_lck; - br_lock = talloc_pooled_object( + br_lck = talloc_pooled_object( state->mem_ctx, struct byte_range_lock, 1, data.dsize); - if (br_lock == NULL) { + if (br_lck == NULL) { *state->br_lock = NULL; return; } - br_lock->lock_data = (struct lock_struct *)talloc_memdup( - br_lock, data.dptr, data.dsize); - br_lock->num_locks = data.dsize / sizeof(struct lock_struct); - - if ((data.dsize % sizeof(struct lock_struct)) == 1) { - br_lock->have_read_oplocks = (data.dptr[data.dsize-1] == 1); - } else { - br_lock->have_read_oplocks = false; + if (!brl_parse_data(br_lck, data)) { + *state->br_lock = NULL; + return; } - - DEBUG(10, ("Got %d bytes, have_read_oplocks: %s\n", (int)data.dsize, - br_lock->have_read_oplocks ? "true" : "false")); - - *state->br_lock = br_lock; + *state->br_lock = br_lck; } struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp) @@ -2072,7 +2061,7 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp) return NULL; } - br_lock->have_read_oplocks = false; + br_lock->num_read_oplocks = 0; br_lock->num_locks = 0; br_lock->lock_data = NULL; -- 2.1.0.rc2.206.gedb03e5 From 22c9d6db30604274369dbad847ef086d8d68c5e5 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 28 Oct 2014 15:31:46 -0700 Subject: [PATCH 03/15] s3: smbd: Implementation of SMB2.1 leases. Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- libcli/smb/smb2_lease.c | 6 + libcli/smb/smb2_lease.h | 2 + selftest/knownfail | 12 - source3/include/smb.h | 1 + source3/librpc/idl/open_files.idl | 30 ++ source3/librpc/idl/wscript_build | 1 + source3/librpc/wscript_build | 7 +- source3/locking/locking.c | 141 ++++++++- source3/locking/proto.h | 17 +- source3/smbd/durable.c | 28 +- source3/smbd/files.c | 34 +++ source3/smbd/globals.h | 10 +- source3/smbd/open.c | 576 +++++++++++++++++++++++++++--------- source3/smbd/oplock.c | 364 +++++++++++++++++------ source3/smbd/proto.h | 14 + source3/smbd/server.c | 5 + source3/smbd/smb2_break.c | 194 +++++++++++- source3/smbd/smb2_create.c | 151 +++++++++- source3/smbd/smb2_negprot.c | 4 + source3/smbd/smb2_server.c | 25 ++ source3/utils/status.c | 2 + source3/wscript_build | 6 + source4/torture/smb2/durable_open.c | 5 +- source4/torture/smb2/lease.c | 5 +- 24 files changed, 1381 insertions(+), 259 deletions(-) diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c index 6fc26f2..70dd3d4 100644 --- a/libcli/smb/smb2_lease.c +++ b/libcli/smb/smb2_lease.c @@ -86,3 +86,9 @@ bool smb2_lease_push(const struct smb2_lease *lease, uint8_t *buf, size_t len) return true; } + +bool smb2_lease_key_equal(const struct smb2_lease_key *k1, + const struct smb2_lease_key *k2) +{ + return ((k1->data[0] == k2->data[0]) && (k1->data[1] == k2->data[1])); +} diff --git a/libcli/smb/smb2_lease.h b/libcli/smb/smb2_lease.h index ba8178d..9db239d 100644 --- a/libcli/smb/smb2_lease.h +++ b/libcli/smb/smb2_lease.h @@ -32,5 +32,7 @@ ssize_t smb2_lease_pull(const uint8_t *buf, size_t len, struct smb2_lease *lease); bool smb2_lease_push(const struct smb2_lease *lease, uint8_t *buf, size_t len); +bool smb2_lease_key_equal(const struct smb2_lease_key *k1, + const struct smb2_lease_key *k2); #endif /* _LIBCLI_SMB_SMB2_LEASE_H_ */ diff --git a/selftest/knownfail b/selftest/knownfail index 6cca3dd..6b720f0 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -188,25 +188,13 @@ ^samba3.smb2.notify.valid-req ^samba3.smb2.notify.dir ^samba3.smb2.notify.rec -^samba3.smb2.durable-open.lock-lease ^samba3.smb2.durable-open.delete_on_close2 -^samba3.smb2.durable-v2-open.open-lease -^samba3.smb2.durable-v2-open.persistent-open-lease ^samba3.smb2.durable-v2-open.app-instance ^samba4.smb2.ioctl.req_resume_key\(dc\) # not supported by s4 ntvfs server ^samba4.smb2.ioctl.copy_chunk_\w*\(dc\) # not supported by s4 ntvfs server ^samba3.smb2.dir.one ^samba3.smb2.dir.modify -^samba3.smb2.lease.request -^samba3.smb2.lease.upgrade -^samba3.smb2.lease.break -^samba3.smb2.lease.oplock -^samba3.smb2.lease.multibreak ^samba3.smb2.lease.v2_request -^samba3.smb2.lease.v2_request_parent -^samba3.smb2.lease.break_twice -^samba3.smb2.lease.nobreakself -^samba3.smb2.lease.v2_epoch1 ^samba3.smb2.oplock.batch20 ^samba3.smb2.oplock.stream1 ^samba3.smb2.streams.rename diff --git a/source3/include/smb.h b/source3/include/smb.h index aab4ff5..7bace88 100644 --- a/source3/include/smb.h +++ b/source3/include/smb.h @@ -577,6 +577,7 @@ enum remote_arch_types {RA_UNKNOWN, RA_WFWG, RA_OS2, RA_WIN95, RA_WINNT, #define EXCLUSIVE_OPLOCK OPLOCK_EXCLUSIVE #define BATCH_OPLOCK OPLOCK_BATCH #define LEVEL_II_OPLOCK OPLOCK_LEVEL_II +#define LEASE_OPLOCK 0x100 /* The following are Samba-private. */ #define INTERNAL_OPEN_ONLY 0x8 diff --git a/source3/librpc/idl/open_files.idl b/source3/librpc/idl/open_files.idl index 4278301..48e6bbe 100644 --- a/source3/librpc/idl/open_files.idl +++ b/source3/librpc/idl/open_files.idl @@ -3,6 +3,8 @@ import "server_id.idl"; import "security.idl"; import "file_id.idl"; +import "smb2_lease_struct.idl"; +import "misc.idl"; [ pointer_default(unique) @@ -14,6 +16,7 @@ interface open_files server_id pid; hyper op_mid; uint16 op_type; + uint32 lease_idx; uint32 access_mask; uint32 share_access; uint32 private_options; @@ -31,6 +34,31 @@ interface open_files [skip] boolean8 stale; } share_mode_entry; + typedef [public,bitmap8bit] bitmap { + SHARE_MODE_NO_CACHING = 0x00, + SHARE_MODE_READ_CACHING = 0x01, + SHARE_MODE_HANDLE_CACHING = 0x02, + SHARE_MODE_WRITE_CACHING = 0x04 + } share_mode_caching; + + typedef [public,flag(NDR_PAHEX)] struct { + GUID client_guid; + smb2_lease_key lease_key; + share_mode_caching current_state; + /* + * breaking_to_state indicates to which level + * the current state is broken when a conflicting + * request is processed. The calculation is as follows: + * + * breaking_to_state = current_state; + * breaking_to_state &= ~(remove_state) + * breaking_to_state &= allowed_shared_state + */ + share_mode_caching breaking_to_state; + boolean8 breaking; + uint16 epoch; + } share_mode_oplock; + typedef [public] struct { uint32 name_hash; security_token *delete_nt_token; @@ -43,6 +71,8 @@ interface open_files [string,charset(UTF8)] char *stream_name; uint32 num_share_modes; [size_is(num_share_modes)] share_mode_entry share_modes[]; + uint32 num_leases; + [size_is(num_leases)] share_mode_oplock leases[]; uint32 num_delete_tokens; [size_is(num_delete_tokens)] delete_token delete_tokens[]; timespec old_write_time; diff --git a/source3/librpc/idl/wscript_build b/source3/librpc/idl/wscript_build index c38fe7b..f9b1bd7 100644 --- a/source3/librpc/idl/wscript_build +++ b/source3/librpc/idl/wscript_build @@ -8,6 +8,7 @@ bld.SAMBA_PIDL_LIST('PIDL', '''messaging.idl libnetapi.idl open_files.idl perfcount.idl secrets.idl libnet_join.idl smbXsrv.idl + leases_db.idl ''', options='--includedir=%s --header --ndr-parser' % topinclude, output_dir='../gen_ndr') diff --git a/source3/librpc/wscript_build b/source3/librpc/wscript_build index 77ae048..5c83cf2 100644 --- a/source3/librpc/wscript_build +++ b/source3/librpc/wscript_build @@ -17,7 +17,7 @@ bld.SAMBA3_SUBSYSTEM('NDR_MESSAGING', bld.SAMBA3_SUBSYSTEM('NDR_OPEN_FILES', source='gen_ndr/ndr_open_files.c', - public_deps='ndr NDR_SERVER_ID NDR_FILE_ID NDR_SECURITY' + public_deps='ndr NDR_SERVER_ID NDR_FILE_ID NDR_SECURITY NDR_SMB2_LEASE_STRUCT' ) bld.SAMBA3_SUBSYSTEM('NDR_SMBXSRV', @@ -25,6 +25,11 @@ bld.SAMBA3_SUBSYSTEM('NDR_SMBXSRV', public_deps='ndr NDR_SERVER_ID NDR_SECURITY NDR_AUTH' ) +bld.SAMBA3_SUBSYSTEM('NDR_LEASES_DB', + source='gen_ndr/ndr_leases_db.c', + public_deps='ndr' + ) + bld.SAMBA3_SUBSYSTEM('NDR_SECRETS', source='gen_ndr/ndr_secrets.c', public_deps='ndr' diff --git a/source3/locking/locking.c b/source3/locking/locking.c index a320068..64bd043 100644 --- a/source3/locking/locking.c +++ b/source3/locking/locking.c @@ -46,6 +46,7 @@ #include "messages.h" #include "util_tdb.h" #include "../librpc/gen_ndr/ndr_open_files.h" +#include "locking/leases_db.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_LOCKING @@ -608,6 +609,7 @@ bool is_valid_share_mode_entry(const struct share_mode_entry *e) num_props += ((e->op_type == NO_OPLOCK) ? 1 : 0); num_props += (EXCLUSIVE_OPLOCK_TYPE(e->op_type) ? 1 : 0); num_props += (LEVEL_II_OPLOCK_TYPE(e->op_type) ? 1 : 0); + num_props += (e->op_type == LEASE_OPLOCK); if ((num_props > 1) && serverid_exists(&e->pid)) { smb_panic("Invalid share mode entry"); @@ -694,7 +696,8 @@ void remove_stale_share_mode_entries(struct share_mode_data *d) } bool set_share_mode(struct share_mode_lock *lck, files_struct *fsp, - uid_t uid, uint64_t mid, uint16 op_type) + uid_t uid, uint64_t mid, uint16 op_type, + uint32_t lease_idx) { struct share_mode_data *d = lck->data; struct share_mode_entry *tmp, *e; @@ -716,6 +719,7 @@ bool set_share_mode(struct share_mode_lock *lck, files_struct *fsp, e->access_mask = fsp->access_mask; e->op_mid = mid; e->op_type = op_type; + e->lease_idx = lease_idx; e->time.tv_sec = fsp->open_time.tv_sec; e->time.tv_usec = fsp->open_time.tv_usec; e->id = fsp->file_id; @@ -816,16 +820,70 @@ bool mark_share_mode_disconnected(struct share_mode_lock *lck, bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp) { + struct share_mode_data *d = lck->data; struct share_mode_entry *e; + uint16_t op_type; + uint32_t lease_idx; + uint32_t i; e = find_share_mode_entry(lck, fsp); if (e == NULL) { return False; } + op_type = e->op_type; e->op_type = NO_OPLOCK; - lck->data->modified = True; - return True; + + d->modified = True; + + if (op_type != LEASE_OPLOCK) { + return true; + } + + /* + * This used to reference a lease. If there's no other one referencing + * it, remove it. + */ + + lease_idx = e->lease_idx; + e->lease_idx = UINT32_MAX; + + for (i=0; inum_share_modes; i++) { + if (d->share_modes[i].lease_idx == lease_idx) { + break; + } + } + if (i < d->num_share_modes) { + /* + * Found another one + */ + return true; + } + + d->num_leases -= 1; + d->leases[lease_idx] = d->leases[d->num_leases]; + + /* + * We changed the lease array. Fix all references to it. + */ + for (i=0; inum_share_modes; i++) { + if (d->share_modes[i].lease_idx == d->num_leases) { + d->share_modes[i].lease_idx = lease_idx; + } + } + + { + NTSTATUS status; + + status = leases_db_del( + &fsp->conn->sconn->client->connections->smb2.client.guid, + &fsp->lease->lease.lease_key); + + DEBUG(10, ("%s: leases_db_del returned %s\n", __func__, + nt_errstr(status))); + } + + return true; } /******************************************************************* @@ -846,6 +904,83 @@ bool downgrade_share_oplock(struct share_mode_lock *lck, files_struct *fsp) return True; } +/* + * "key" has been broken, it is referenced somewhere in "lck". If "fsp" + * applies to it, reset fsp->sent_oplock_break. + */ + +bool fsp_lease_broken(struct share_mode_lock *lck, + struct file_id lck_id, + const struct smb2_lease_key *key, + files_struct *fsp, uint32_t new_lease_state) +{ + struct share_mode_data *d = lck->data; + struct share_mode_entry *e; + + if (!file_id_equal(&fsp->file_id, &lck_id)) { + return false; + } + if (fsp->oplock_type != LEASE_OPLOCK) { + return false; + } + + e = find_share_mode_entry(lck, fsp); + if (e == NULL) { + DEBUG(1, ("downgrade_lease_fsps: Could not find share mode " + "entry\n")); + return false; + } + + if (!smb2_lease_key_equal(key, &d->leases[e->lease_idx].lease_key)) { + return false; + } + fsp->sent_oplock_break = NO_BREAK_SENT; + TALLOC_FREE(fsp->oplock_timeout); + fsp->lease->lease.lease_state = new_lease_state; + return true; +} + +NTSTATUS downgrade_share_lease(struct smbd_server_connection *sconn, + struct share_mode_lock *lck, + const struct smb2_lease_key *key, + uint32_t new_lease_state) +{ + struct share_mode_data *d = lck->data; + struct share_mode_oplock *l; + uint32_t i; + + for (i=0; inum_leases; i++) { + if (smb2_lease_key_equal(key, &d->leases[i].lease_key)) { + break; + } + } + if (i == d->num_leases) { + DEBUG(10, ("lease not found\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + l = &d->leases[i]; + + /* + * Can't upgrade anything: l->current_state must be a strict bitwise + * superset of new_lease_state + */ + + if ((new_lease_state & l->current_state) != new_lease_state) { + DEBUG(10, ("Attempt to upgrade from %d to %d\n", + (int)l->current_state, (int)new_lease_state)); + return NT_STATUS_INVALID_PARAMETER; + } + + l->current_state = new_lease_state; + l->breaking_to_state = 0; + l->breaking = 0; + + d->modified = true; + + return NT_STATUS_OK; +} + /**************************************************************************** Adds a delete on close token. ****************************************************************************/ diff --git a/source3/locking/proto.h b/source3/locking/proto.h index 44f3ba1..94e9b8f 100644 --- a/source3/locking/proto.h +++ b/source3/locking/proto.h @@ -30,9 +30,9 @@ void brl_shutdown(void); unsigned int brl_num_locks(const struct byte_range_lock *brl); struct files_struct *brl_fsp(struct byte_range_lock *brl); -bool brl_have_read_oplocks(const struct byte_range_lock *brl); -void brl_set_have_read_oplocks(struct byte_range_lock *brl, - bool have_read_oplocks); +uint32_t brl_num_read_oplocks(const struct byte_range_lock *brl); +void brl_set_num_read_oplocks(struct byte_range_lock *brl, + uint32_t num_read_oplocks); NTSTATUS brl_lock_windows_default(struct byte_range_lock *br_lck, struct lock_struct *plock, @@ -167,13 +167,22 @@ void get_file_infos(struct file_id id, bool is_valid_share_mode_entry(const struct share_mode_entry *e); bool share_mode_stale_pid(struct share_mode_data *d, uint32_t idx); bool set_share_mode(struct share_mode_lock *lck, files_struct *fsp, - uid_t uid, uint64_t mid, uint16 op_type); + uid_t uid, uint64_t mid, uint16 op_type, + uint32_t lease_idx); void remove_stale_share_mode_entries(struct share_mode_data *d); bool del_share_mode(struct share_mode_lock *lck, files_struct *fsp); bool mark_share_mode_disconnected(struct share_mode_lock *lck, struct files_struct *fsp); bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp); bool downgrade_share_oplock(struct share_mode_lock *lck, files_struct *fsp); +bool fsp_lease_broken(struct share_mode_lock *lck, + struct file_id lck_id, + const struct smb2_lease_key *key, + files_struct *fsp, uint32_t new_lease_state); +NTSTATUS downgrade_share_lease(struct smbd_server_connection *sconn, + struct share_mode_lock *lck, + const struct smb2_lease_key *key, + uint32_t new_lease_state); bool get_delete_on_close_token(struct share_mode_lock *lck, uint32_t name_hash, const struct security_token **pp_nt_tok, diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c index 9489cf1..660865d 100644 --- a/source3/smbd/durable.c +++ b/source3/smbd/durable.c @@ -168,7 +168,7 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp, return NT_STATUS_INVALID_PARAMETER; } - if (!BATCH_OPLOCK_TYPE(fsp->oplock_type)) { + if ((fsp_lease_type(fsp) & SMB2_LEASE_HANDLE) == 0) { return NT_STATUS_NOT_SUPPORTED; } @@ -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_oplock *o = &lck->data->leases[e->lease_idx]; + struct smb2_lease_key key; + + key.data[0] = o->lease_key.data[0]; + key.data[1] = o->lease_key.data[1]; + + fsp->lease = find_fsp_lease(fsp, &key); + + if (fsp->lease != NULL) { + fsp->lease->ref_count += 1; + } else { + fsp->lease = talloc_zero(fsp->conn->sconn, + struct fsp_lease); + if (fsp->lease == NULL) { + TALLOC_FREE(lck); + fsp_free(fsp); + return NT_STATUS_NO_MEMORY; + } + fsp->lease->ref_count = 1; + fsp->lease->lease.lease_key = key; + fsp->lease->lease.lease_state = o->current_state; + fsp->lease->lease.lease_epoch = o->epoch; + } + } + 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; diff --git a/source3/smbd/files.c b/source3/smbd/files.c index a9e8357..13d1138 100644 --- a/source3/smbd/files.c +++ b/source3/smbd/files.c @@ -387,6 +387,24 @@ files_struct *file_find_di_next(files_struct *start_fsp) return NULL; } +struct files_struct *file_find_one_fsp_from_lease_key( + struct smbd_server_connection *sconn, + const struct smb2_lease_key *lease_key) +{ + struct files_struct *fsp; + + for (fsp = sconn->files; fsp; fsp=fsp->next) { + if ((fsp->lease != NULL) && + (fsp->lease->lease.lease_key.data[0] == + lease_key->data[0]) && + (fsp->lease->lease.lease_key.data[1] == + lease_key->data[1])) { + return fsp; + } + } + return NULL; +} + /**************************************************************************** Find any fsp open with a pathname below that of an already open path. ****************************************************************************/ @@ -472,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 @@ -740,3 +766,11 @@ NTSTATUS fsp_set_smb_fname(struct files_struct *fsp, smb_fname_str_dbg(fsp->fsp_name), &fsp->name_hash); } + +uint32_t fsp_lease_type(struct files_struct *fsp) +{ + if (fsp->oplock_type == LEASE_OPLOCK) { + return fsp->lease->lease.lease_state; + } + return map_oplock_to_lease_type(fsp->oplock_type); +} diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 36e7f0f..39c537c 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -250,6 +250,14 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn, struct smbXsrv_tcon *tcon, struct smbXsrv_open *op, uint8_t oplock_level); +NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn, + struct smbXsrv_session *session, + struct smbXsrv_tcon *tcon, + uint16_t new_epoch, + uint32_t lease_flags, + struct smb2_lease_key *lease_key, + uint32_t current_lease_state, + uint32_t new_lease_state); NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req, struct tevent_req *subreq, @@ -298,7 +306,7 @@ 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_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, diff --git a/source3/smbd/open.c b/source3/smbd/open.c index ccea1e9..c9196d2 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -35,6 +35,12 @@ #include "serverid.h" #include "messages.h" #include "source3/lib/dbwrap/dbwrap_watch.h" +#include "locking/leases_db.h" + +static bool is_same_lease(const struct share_mode_data *d, + const struct share_mode_entry *e, + const struct smb2_lease *lease); + extern const struct generic_mapping file_generic_mapping; @@ -1257,6 +1263,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, @@ -1376,31 +1386,28 @@ 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) { 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 have_broken = false; + bool will_overwrite; - if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) { + if ((oplock_request & INTERNAL_OPEN_ONLY) || + is_stat_open(fsp->access_mask)) { return false; } + for (i=0; inum_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) { /* @@ -1408,74 +1415,98 @@ static bool delay_for_oplock(files_struct *fsp, */ 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 (have_sharing_violation) { + for (i=0; inum_share_modes; i++) { + struct share_mode_entry *e = &d->share_modes[i]; + uint32_t e_lease_type = get_lease_type(d, e); + + if (!(e_lease_type & SMB2_LEASE_HANDLE)) { + continue; + } + if (is_same_lease(d, e, lease)) { + continue; + } + if (share_mode_stale_pid(d, i)) { + continue; + } + send_break_message(fsp->conn->sconn->msg_ctx, e, + e_lease_type & ~SMB2_LEASE_HANDLE); + have_broken = true; + } } - 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 (have_broken) { + return true; + } + if (have_sharing_violation) { return false; } switch (create_disposition) { case FILE_SUPERSEDE: 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; inum_share_modes; i++) { + struct share_mode_entry *e = &d->share_modes[i]; + uint32_t e_lease_type = get_lease_type(d, e); + + DEBUG(10, ("entry %u: e_lease_type %u, will_overwrite: %u\n", + (unsigned)i, (unsigned)e_lease_type, + (unsigned)will_overwrite)); + + if (e_lease_type & SMB2_LEASE_WRITE) { + uint32_t break_to; + + if (share_mode_stale_pid(d, i)) { + return false; + } + if ((e->op_type == LEASE_OPLOCK) && + (lease != NULL) && + smb2_lease_key_equal( + &lease->lease_key, + &d->leases[e->lease_idx].lease_key)) { + return false; + } + + break_to = e_lease_type & ~SMB2_LEASE_WRITE; + if (will_overwrite) { + /* + * There's no H only lease that we could break + * to + */ + break_to = SMB2_LEASE_NONE; + } + + DEBUG(10, ("breaking SMB2_LEASE_WRITE to %d\n", + (int)break_to)); + send_break_message(fsp->conn->sconn->msg_ctx, e, + break_to); + return true; } - 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 (will_overwrite && (e_lease_type & SMB2_LEASE_READ)) { + if (share_mode_stale_pid(d, i)) { + continue; + } + DEBUG(10, ("breaking SMB2_LEASE_READ\n")); + send_break_message(fsp->conn->sconn->msg_ctx, e, + SMB2_LEASE_NONE); + /* + * This is an async break. No need to wait for a + * response. + */ + continue; } - 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; } - send_break_message(fsp->conn->sconn->msg_ctx, entry, break_to); - return true; + return have_broken; } static bool file_has_brlocks(files_struct *fsp) @@ -1489,88 +1520,330 @@ static bool file_has_brlocks(files_struct *fsp) return (brl_num_locks(br_lck) > 0); } -static void grant_fsp_oplock_type(files_struct *fsp, - struct share_mode_lock *lck, - int oplock_request) +static int find_share_mode_oplock(struct share_mode_data *d, + const struct GUID *client_guid, + const struct smb2_lease_key *key) { - bool allow_level2 = (global_client_caps & CAP_LEVEL_II_OPLOCKS) && - lp_level2_oplocks(SNUM(fsp->conn)); - bool got_level2_oplock, got_a_none_oplock; - uint32_t i; + uint16_t i; - /* 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); + for (i=0; inum_leases; i++) { + struct share_mode_oplock *l = &d->leases[i]; + if (GUID_equal(client_guid, &l->client_guid) && + smb2_lease_key_equal(key, &l->lease_key)) { + return i; + } + } + return -1; +} + +static bool is_same_lease(const struct share_mode_data *d, + const struct share_mode_entry *e, + const struct smb2_lease *lease) +{ + if (e->op_type != LEASE_OPLOCK) { + return false; + } + if (lease == NULL) { + return false; + } + return smb2_lease_key_equal(&d->leases[e->lease_idx].lease_key, + &lease->lease_key); +} + +struct fsp_lease *find_fsp_lease(files_struct *new_fsp, + const struct smb2_lease_key *key) +{ + files_struct *fsp; + + /* + * TODO: Measure how expensive this loop is with thousands of open + * handles... + */ + + for (fsp = file_find_di_first(new_fsp->conn->sconn, new_fsp->file_id); + fsp != NULL; + fsp = file_find_di_next(fsp)) { + + if (fsp == new_fsp) { + continue; + } + if (fsp->oplock_type != LEASE_OPLOCK) { + continue; + } + if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) { + return fsp->lease; + } + } + + return NULL; +} + +static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d, + const struct smb2_lease *lease, + uint32_t *p_lease_idx, + uint32_t granted) +{ + const struct GUID *client_guid; + struct share_mode_oplock *o; + struct share_mode_oplock *tmp; + NTSTATUS status; + int idx; + + + /* + * TODO: in future we can have multiple connections... + */ + client_guid = &fsp->conn->sconn->client->connections->smb2.client.guid; + + idx = find_share_mode_oplock(d, client_guid, &lease->lease_key); + + if (idx != -1) { + + bool do_upgrade; + uint32_t existing, requested; + + fsp->lease = find_fsp_lease(fsp, &lease->lease_key); + if (fsp->lease == NULL) { + DEBUG(1, ("Did not find existing lease for file %s\n", + fsp_str_dbg(fsp))); + return NT_STATUS_INTERNAL_ERROR; + } + fsp->lease->ref_count += 1; + + *p_lease_idx = idx; + o = &d->leases[idx]; + + /* + * Upgrade only if the requested lease is a strict upgrade. + */ + existing = o->current_state; + requested = lease->lease_state; + + /* + * Tricky: This test makes sure that "requested" is a + * strict bitwise superset of "existing". + */ + do_upgrade = ((existing & requested) == existing); + + /* + * Upgrade only if other leases don't prevent what was asked + * for. + */ + do_upgrade &= (granted == requested); + + DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", " + "granted=%"PRIu32", do_upgrade=%d\n", + existing, requested, granted, (int)do_upgrade)); + + if (do_upgrade) { + o->current_state = granted; + } + fsp->lease->lease.lease_state = o->current_state; + return NT_STATUS_OK; + } + + /* + * Create new lease + */ + + tmp = talloc_realloc(d, d->leases, struct share_mode_oplock, + d->num_leases+1); + if (tmp == NULL) { + /* + * See [MS-SMB2] + */ + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + d->leases = tmp; + + fsp->lease = talloc(fsp->conn->sconn, struct fsp_lease); + if (fsp->lease == NULL) { + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + fsp->lease->ref_count = 1; + fsp->lease->lease = *lease; + fsp->lease->lease.lease_state = granted; + fsp->lease->lease.lease_epoch += 1; + + *p_lease_idx = d->num_leases; + + d->leases[d->num_leases] = (struct share_mode_oplock) { + .client_guid = *client_guid, + .lease_key = lease->lease_key, + .epoch = lease->lease_epoch, + .current_state = granted, + }; + + status = leases_db_add(client_guid, &lease->lease_key, + &fsp->file_id, fsp->fsp_name->base_name, + fsp->fsp_name->stream_name); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("%s: leases_db_add failed: %s\n", __func__, + nt_errstr(status))); + TALLOC_FREE(fsp->lease); + return NT_STATUS_INSUFFICIENT_RESOURCES; + } + + d->num_leases += 1; + d->modified = true; + + return NT_STATUS_OK; + +} + +static NTSTATUS grant_fsp_oplock_type(struct smb_request *req, files_struct *fsp, + struct share_mode_lock *lck, + int oplock_request, + uint32_t create_disposition, + struct smb2_lease *lease) +{ + struct share_mode_data *d = lck->data; + bool got_handle_lease, got_oplock; + uint32_t i; + uint32_t granted; + uint32_t lease_idx = UINT32_MAX; + NTSTATUS status; + bool ret; if (oplock_request & INTERNAL_OPEN_ONLY) { /* No oplocks on internal open. */ - fsp->oplock_type = NO_OPLOCK; + 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))); - return; + } + + if (oplock_request == LEASE_OPLOCK) { + granted = lease->lease_state; + + 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; + granted &= ~SMB2_LEASE_READ; } - if (is_stat_open(fsp->access_mask)) { - /* Leave the value already set. */ - DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n", - fsp->oplock_type, fsp_str_dbg(fsp))); - return; - } + got_handle_lease = false; + got_oplock = false; - got_level2_oplock = false; - got_a_none_oplock = false; + for (i=0; inum_share_modes; i++) { + struct share_mode_entry *e = &d->share_modes[i]; + uint32_t e_lease_type; - for (i=0; idata->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 (op_type == NO_OPLOCK) { - got_a_none_oplock = true; + if ((granted & SMB2_LEASE_WRITE) && + !is_same_lease(d, e, lease) && + !share_mode_stale_pid(d, i)) { + /* + * Can grant only one writer + */ + granted &= ~SMB2_LEASE_WRITE; } - } - - /* - * Match what was requested (fsp->oplock_type) with - * what was found in the existing share modes. - */ - if (got_level2_oplock || got_a_none_oplock) { - if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) { - fsp->oplock_type = LEVEL_II_OPLOCK; + if ((e_lease_type & SMB2_LEASE_HANDLE) && !got_handle_lease && + !share_mode_stale_pid(d, i)) { + got_handle_lease = true; } - } - /* - * 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; + if ((e->op_type != LEASE_OPLOCK) && !got_oplock && + !share_mode_stale_pid(d, i)) { + got_oplock = true; + } } - if (fsp->oplock_type == LEVEL_II_OPLOCK && !got_level2_oplock) { + if (is_stat_open(fsp->access_mask) && + ((create_disposition == FILE_OPEN) || + (create_disposition == FILE_OPEN_IF))) { /* - * We're the first level2 oplock. Indicate that in brlock.tdb. + * No-overwrite stat open doesn't get oplocks */ - struct byte_range_lock *brl; + granted = SMB2_LEASE_NONE; + } + + if (oplock_request == LEASE_OPLOCK) { + + fsp->oplock_type = LEASE_OPLOCK; + + if (got_oplock) { + granted &= SMB2_LEASE_READ; + } + + status = grant_fsp_lease(fsp, lck->data, lease, &lease_idx, + granted); + if (!NT_STATUS_IS_OK(status)) { + return status; + + } + lease->lease_state = d->leases[lease_idx].current_state; + DEBUG(10, ("lease_state=%d\n", lease->lease_state)); + } else { + 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; + } + if (fsp->oplock_type == LEVEL_II_OPLOCK) { + bool allow_level2 = + (global_client_caps & CAP_LEVEL_II_OPLOCKS) && + lp_level2_oplocks(SNUM(fsp->conn)); - brl = brl_get_locks(talloc_tos(), fsp); - if (brl != NULL) { - brl_set_have_read_oplocks(brl, true); - TALLOC_FREE(brl); + if (!allow_level2) { + fsp->oplock_type = NO_OPLOCK; + } + } + if (got_handle_lease) { + fsp->oplock_type = NO_OPLOCK; } } DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n", fsp->oplock_type, fsp_str_dbg(fsp))); + + status = set_file_oplock(fsp); + if (!NT_STATUS_IS_OK(status)) { + /* + * Could not get the kernel oplock + */ + fsp->oplock_type = NO_OPLOCK; + } + + if (!set_share_mode(lck, fsp, get_current_uid(fsp->conn), + req ? req->mid : 0, + fsp->oplock_type, lease_idx)) { + return NT_STATUS_NO_MEMORY; + } + ret = update_num_read_oplocks(fsp, lck); + if (!ret) { + del_share_mode(lck, fsp); + return NT_STATUS_INTERNAL_ERROR; + } + return NT_STATUS_OK; } static bool request_timed_out(struct timeval request_time, @@ -2465,8 +2738,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)) { + schedule_defer_open(lck, fsp->file_id, request_time, + req); TALLOC_FREE(lck); DEBUG(10, ("Sent oplock break request to kernel " "oplock holder\n")); @@ -2587,7 +2862,7 @@ 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)) { schedule_defer_open(lck, fsp->file_id, request_time, req); @@ -2738,8 +3013,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, } } - grant_fsp_oplock_type(fsp, lck, oplock_request); - /* * We have the share entry *locked*..... */ @@ -2798,13 +3071,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, fsp->access_mask = access_mask | FILE_READ_ATTRIBUTES; } - if (file_existed) { - /* stat opens on existing files don't get oplocks. */ - if (is_stat_open(open_access_mask)) { - fsp->oplock_type = NO_OPLOCK; - } - } - if (new_file_created) { info = FILE_WAS_CREATED; } else { @@ -2824,20 +3090,12 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, * file structs. */ - status = set_file_oplock(fsp); + status = grant_fsp_oplock_type(req, fsp, lck, oplock_request, + create_disposition, lease); if (!NT_STATUS_IS_OK(status)) { - /* - * Could not get the kernel oplock - */ - fsp->oplock_type = NO_OPLOCK; - } - - if (!set_share_mode(lck, fsp, get_current_uid(conn), - req ? req->mid : 0, - fsp->oplock_type)) { TALLOC_FREE(lck); fd_close(fsp); - return NT_STATUS_NO_MEMORY; + return status; } /* Handle strange delete on close create semantics. */ @@ -3331,7 +3589,7 @@ static NTSTATUS open_directory(connection_struct *conn, } if (!set_share_mode(lck, fsp, get_current_uid(conn), - req ? req->mid : 0, NO_OPLOCK)) { + req ? req->mid : 0, NO_OPLOCK, UINT32_MAX)) { TALLOC_FREE(lck); fd_close(fsp); file_free(req, fsp); @@ -3816,6 +4074,48 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) } /* + * If we already have a lease, it must match the new file id. [MS-SMB2] + * 3.3.5.9.8 speaks about INVALID_PARAMETER if an already used lease key is + * used for a different file name. + */ + +struct lease_fname_match_state { + const struct smb_filename *fname; + bool match; +}; + +static void lease_fname_match_parser( + struct file_id id, const char *filename, const char *stream_name, + void *private_data) +{ + struct lease_fname_match_state *state = + (struct lease_fname_match_state *)private_data; + + state->match = + strequal(filename, state->fname->base_name) && + strequal(stream_name, state->fname->stream_name); +} + +static bool lease_fname_match(struct smbd_server_connection *sconn, + struct smb2_lease_key *lease_key, + const struct smb_filename *fname) +{ + struct lease_fname_match_state state = + { .fname = fname, .match = true }; + NTSTATUS status; + + status = leases_db_parse(&sconn->client->connections->smb2.client.guid, + lease_key, lease_fname_match_parser, &state); + if (!NT_STATUS_IS_OK(status)) { + /* + * Not found or error means okay: We can make the lease pass + */ + return true; + } + return state.match; +} + +/* * Wrapper around open_file_ntcreate and open_directory */ @@ -3871,6 +4171,12 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, oplock_request |= INTERNAL_OPEN_ONLY; } + if ((lease != NULL) && + !lease_fname_match(req->sconn, &lease->lease_key, smb_fname)) { + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + if ((conn->fs_capabilities & FILE_NAMED_STREAMS) && (access_mask & DELETE_ACCESS) && !is_ntfs_stream_smb_fname(smb_fname)) { diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c index 17cb22e..c55fb3b 100644 --- a/source3/smbd/oplock.c +++ b/source3/smbd/oplock.c @@ -116,8 +116,6 @@ static void release_file_oplock(files_struct *fsp) flush_write_cache(fsp, SAMBA_OPLOCK_RELEASE_FLUSH); delete_write_cache(fsp); - - TALLOC_FREE(fsp->oplock_timeout); } /**************************************************************************** @@ -141,8 +139,69 @@ static void downgrade_file_oplock(files_struct *fsp) sconn->oplocks.exclusive_open--; sconn->oplocks.level_II_open++; fsp->sent_oplock_break = NO_BREAK_SENT; +} - TALLOC_FREE(fsp->oplock_timeout); +uint32_t map_oplock_to_lease_type(uint16_t op_type) +{ + uint32_t ret; + + switch(op_type) { + case BATCH_OPLOCK: + case BATCH_OPLOCK|EXCLUSIVE_OPLOCK: + ret = SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE; + break; + case EXCLUSIVE_OPLOCK: + ret = SMB2_LEASE_READ|SMB2_LEASE_WRITE; + break; + case LEVEL_II_OPLOCK: + ret = SMB2_LEASE_READ; + break; + default: + ret = SMB2_LEASE_NONE; + break; + } + return ret; +} + +uint32_t get_lease_type(struct share_mode_data *d, struct share_mode_entry *e) +{ + if (e->op_type == LEASE_OPLOCK) { + return d->leases[e->lease_idx].current_state; + } + return map_oplock_to_lease_type(e->op_type); +} + +bool update_num_read_oplocks(files_struct *fsp, struct share_mode_lock *lck) +{ + struct share_mode_data *d = lck->data; + struct byte_range_lock *br_lck; + uint32_t num_read_oplocks = 0; + uint32_t i; + + for (i=0; inum_share_modes; i++) { + struct share_mode_entry *e = &d->share_modes[i]; + uint32_t e_lease_type = get_lease_type(d, e); + + if (e_lease_type & SMB2_LEASE_READ) { + num_read_oplocks += 1; + } + } + + br_lck = brl_get_locks_readonly(fsp); + if (br_lck == NULL) { + return false; + } + if (brl_num_read_oplocks(br_lck) == num_read_oplocks) { + return true; + } + + br_lck = brl_get_locks(talloc_tos(), fsp); + if (br_lck == NULL) { + return false; + } + brl_set_num_read_oplocks(br_lck, num_read_oplocks); + TALLOC_FREE(br_lck); + return true; } /**************************************************************************** @@ -167,44 +226,6 @@ bool remove_oplock(files_struct *fsp) return False; } - if (fsp->oplock_type == LEVEL_II_OPLOCK) { - - /* - * If we're the only LEVEL_II holder, we have to remove the - * have_read_oplocks from the brlock entry - */ - - struct share_mode_data *data = lck->data; - uint32_t i, num_level2; - - num_level2 = 0; - for (i=0; inum_share_modes; i++) { - if (data->share_modes[i].op_type == LEVEL_II_OPLOCK) { - num_level2 += 1; - } - if (num_level2 > 1) { - /* - * No need to count them all... - */ - break; - } - } - - if (num_level2 == 1) { - /* - * That's only us. We are dropping that level2 oplock, - * so remove the brlock flag. - */ - struct byte_range_lock *brl; - - brl = brl_get_locks(talloc_tos(), fsp); - if (brl) { - brl_set_have_read_oplocks(brl, false); - TALLOC_FREE(brl); - } - } - } - ret = remove_share_oplock(lck, fsp); if (!ret) { DEBUG(0,("remove_oplock: failed to remove share oplock for " @@ -213,6 +234,16 @@ bool remove_oplock(files_struct *fsp) file_id_string_tos(&fsp->file_id))); } release_file_oplock(fsp); + TALLOC_FREE(fsp->oplock_timeout); + + ret = update_num_read_oplocks(fsp, lck); + if (!ret) { + DEBUG(0, ("%s: update_num_read_oplocks failed for " + "file %s, %s, %s\n", + __func__, fsp_str_dbg(fsp), fsp_fnum_dbg(fsp), + file_id_string_tos(&fsp->file_id))); + } + TALLOC_FREE(lck); return ret; } @@ -224,7 +255,6 @@ bool downgrade_oplock(files_struct *fsp) { bool ret; struct share_mode_lock *lck; - struct byte_range_lock *brl; DEBUG(10, ("downgrade_oplock called for %s\n", fsp_str_dbg(fsp))); @@ -244,17 +274,78 @@ bool downgrade_oplock(files_struct *fsp) } downgrade_file_oplock(fsp); + TALLOC_FREE(fsp->oplock_timeout); - brl = brl_get_locks(talloc_tos(), fsp); - if (brl != NULL) { - brl_set_have_read_oplocks(brl, true); - TALLOC_FREE(brl); + ret = update_num_read_oplocks(fsp, lck); + if (!ret) { + DEBUG(0,("%s: failed to downgrade share oplock " + "for file %s, %s, file_id %s\n", + __func__, fsp_str_dbg(fsp), fsp_fnum_dbg(fsp), + file_id_string_tos(&fsp->file_id))); } TALLOC_FREE(lck); return ret; } +struct downgrade_lease_fsps_state { + struct file_id id; + struct share_mode_lock *lck; + const struct smb2_lease_key *key; + uint32_t new_lease_state; + unsigned count; +}; + +static struct files_struct *downgrade_lease_fsps(struct files_struct *fsp, + void *private_data) +{ + struct downgrade_lease_fsps_state *state = + (struct downgrade_lease_fsps_state *)private_data; + bool downgraded; + + downgraded = fsp_lease_broken(state->lck, state->id, state->key, fsp, + state->new_lease_state); + state->count += downgraded ? 1 : 0; + return NULL; +} + +NTSTATUS downgrade_lease(struct smbd_server_connection *sconn, + const struct file_id id, + const struct smb2_lease_key *key, + uint32_t lease_state) +{ + struct share_mode_lock *lck; + NTSTATUS status; + + DEBUG(10, ("%s: Downgrading %s to %x\n", __func__, + file_id_string_tos(&id), (unsigned)lease_state)); + + lck = get_existing_share_mode_lock(talloc_tos(), id); + if (lck == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + status = downgrade_share_lease(sconn, lck, key, lease_state); + + /* + * This sucks. We have to reset fsp->sent_oplock_break on all fsps + * that reference this lease. + */ + { + struct downgrade_lease_fsps_state state = { + .id = id, .lck = lck, .key = key, + .count = 0, .new_lease_state = lease_state + }; + + files_forall(sconn, downgrade_lease_fsps, &state); + + /* Paranoia */ + SMB_ASSERT(state.count > 0); + } + + TALLOC_FREE(lck); + return status; +} + /**************************************************************************** Set up an oplock break message. ****************************************************************************/ @@ -426,7 +517,6 @@ 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, @@ -467,23 +557,42 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx, /* * Nothing to do anymore */ + DEBUG(10, ("fsp->sent_oplock_break = %d\n", + fsp->sent_oplock_break)); return; } - if (break_to == fsp->oplock_type) { - DEBUG(3, ("Already downgraded oplock on %s: %s\n", - file_id_string_tos(&fsp->file_id), - fsp_str_dbg(fsp))); - 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 ((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; + DEBUG(10, ("msg.op_type=%u, break_to=%u\n", + (unsigned)msg.op_type, (unsigned)break_to)); + + if (fsp->oplock_type == NO_OPLOCK) { + DEBUG(3, ("Already downgraded oplock to none on %s: %s\n", + file_id_string_tos(&fsp->file_id), + fsp_str_dbg(fsp))); + return; + } + if ((break_to & SMB2_LEASE_READ) && + (fsp->oplock_type == LEVEL_II_OPLOCK)) { + DEBUG(3, ("Already downgraded oplock to level2 on %s: %s\n", + file_id_string_tos(&fsp->file_id), + fsp_str_dbg(fsp))); + return; } /* Need to wait before sending a break @@ -493,21 +602,33 @@ 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_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 ((fsp_lease_type(fsp) == SMB2_LEASE_READ) && + (break_to == SMB2_LEASE_NONE)) { /* * This is an async break without a reply and thus no timeout */ - remove_oplock(fsp); + if (fsp->oplock_type == LEASE_OPLOCK) { + /* + * We must leave the lease around, it might be + * upgraded later + */ + downgrade_lease(fsp->conn->sconn, fsp->file_id, + &fsp->lease->lease.lease_key, + break_to); + } else { + remove_oplock(fsp); + } 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); } @@ -575,6 +696,7 @@ 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; }; static void do_break_to_none(struct tevent_context *ctx, struct tevent_immediate *im, @@ -593,6 +715,7 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp, struct tevent_immediate *im; struct break_to_none_state *state; struct byte_range_lock *brl; + uint32_t num_read_oplocks; /* * If this file is level II oplocked then we need @@ -609,11 +732,23 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp, return; } + num_read_oplocks = 0; + brl = brl_get_locks_readonly(fsp); - if ((brl != NULL) && !brl_have_read_oplocks(brl)) { + if (brl != NULL) { + num_read_oplocks = brl_num_read_oplocks(brl); + } + + DEBUG(10, ("num_read_oplocks = %"PRIu32"\n", num_read_oplocks)); + + if (num_read_oplocks == 0) { DEBUG(10, ("No read oplocks around\n")); return; } + if ((num_read_oplocks == 1) && (fsp->oplock_type == LEASE_OPLOCK)) { + DEBUG(10, ("We're the only reader, don't break\n")); + return; + } /* * When we get here we might have a brlock entry locked. Also @@ -622,7 +757,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; @@ -630,6 +765,13 @@ 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->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")); @@ -639,14 +781,28 @@ static void contend_level2_oplocks_begin_default(files_struct *fsp, tevent_schedule_immediate(im, sconn->ev_ctx, do_break_to_none, state); } +static void send_break_to_none(struct messaging_context *msg_ctx, + const struct share_mode_entry *e) +{ + char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE]; + + share_mode_entry_to_message(msg, e); + /* Overload entry->op_type */ + SSVAL(msg, OP_BREAK_MSG_OP_TYPE_OFFSET, NO_OPLOCK); + + messaging_send_buf(msg_ctx, e->pid, MSG_SMB_BREAK_REQUEST, + (uint8 *)msg, sizeof(msg)); +} + static void do_break_to_none(struct tevent_context *ctx, struct tevent_immediate *im, void *private_data) { struct break_to_none_state *state = talloc_get_type_abort( private_data, struct break_to_none_state); - int i; + uint32_t i; struct share_mode_lock *lck; + struct share_mode_data *d; lck = get_existing_share_mode_lock(talloc_tos(), state->id); if (lck == NULL) { @@ -654,15 +810,61 @@ static void do_break_to_none(struct tevent_context *ctx, __func__, file_id_string_tos(&state->id))); goto done; } + 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 < lck->data->num_share_modes; i++) { - struct share_mode_entry *share_entry = &lck->data->share_modes[i]; - char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE]; + for (i=0; inum_leases; i++) { + struct share_mode_oplock *l = &d->leases[i]; + struct share_mode_entry *e; + uint32_t j; - if (!is_valid_share_mode_entry(share_entry)) { + if ((l->current_state & SMB2_LEASE_READ) == 0) { + continue; + } + if ((l->lease_key.data[0] == state->lease_key.data[0]) && + (l->lease_key.data[1] == state->lease_key.data[1])) { + DEBUG(10, ("Don't break our own lease\n")); + continue; + } + + for (j=0; jnum_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; } @@ -677,15 +879,15 @@ static void do_break_to_none(struct tevent_context *ctx, * NO_OPLOCK states. JRA. */ - DEBUG(10,("%s: share_entry[%i]->op_type == %d\n", __func__, - i, share_entry->op_type )); + DEBUG(10, ("%s: share_entry[%i]->op_type == %d\n", __func__, + i, e->op_type )); - if (share_entry->op_type == NO_OPLOCK) { + if (e->op_type == NO_OPLOCK) { continue; } /* Paranoia .... */ - if (EXCLUSIVE_OPLOCK_TYPE(share_entry->op_type)) { + if (EXCLUSIVE_OPLOCK_TYPE(e->op_type)) { DEBUG(0,("%s: PANIC. " "share mode entry %d is an exlusive " "oplock !\n", __func__, i )); @@ -693,13 +895,7 @@ static void do_break_to_none(struct tevent_context *ctx, abort(); } - share_mode_entry_to_message(msg, share_entry); - /* Overload entry->op_type */ - SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET, NO_OPLOCK); - - messaging_send_buf(state->sconn->msg_ctx, share_entry->pid, - MSG_SMB_BREAK_REQUEST, - (uint8 *)msg, sizeof(msg)); + send_break_to_none(state->sconn->msg_ctx, e); } /* We let the message receivers handle removing the oplock state diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index 68c2da2..613af8a 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -369,6 +369,9 @@ files_struct *file_find_dif(struct smbd_server_connection *sconn, files_struct *file_find_di_first(struct smbd_server_connection *sconn, struct file_id id); files_struct *file_find_di_next(files_struct *start_fsp); +struct files_struct *file_find_one_fsp_from_lease_key( + struct smbd_server_connection *sconn, + const struct smb2_lease_key *lease_key); bool file_find_subpath(files_struct *dir_fsp); void file_sync_all(connection_struct *conn); void fsp_free(files_struct *fsp); @@ -387,6 +390,7 @@ NTSTATUS file_name_hash(connection_struct *conn, const char *name, uint32_t *p_name_hash); NTSTATUS fsp_set_smb_fname(struct files_struct *fsp, const struct smb_filename *smb_fname_in); +uint32_t fsp_lease_type(struct files_struct *fsp); /* The following definitions come from smbd/ipc.c */ @@ -609,6 +613,8 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, const char *inherit_from_dir, const char *fname, SMB_STRUCT_STAT *psbuf); +struct fsp_lease *find_fsp_lease(files_struct *new_fsp, + const struct smb2_lease_key *key); bool is_stat_open(uint32 access_mask); struct deferred_open_record; bool is_deferred_open_async(const struct deferred_open_record *rec); @@ -647,10 +653,18 @@ NTSTATUS get_relative_fid_filename(connection_struct *conn, /* The following definitions come from smbd/oplock.c */ +uint32_t map_oplock_to_lease_type(uint16_t op_type); +uint32_t get_lease_type(struct share_mode_data *d, struct share_mode_entry *e); +bool update_num_read_oplocks(files_struct *fsp, struct share_mode_lock *lck); + void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp); NTSTATUS set_file_oplock(files_struct *fsp); bool remove_oplock(files_struct *fsp); bool downgrade_oplock(files_struct *fsp); +NTSTATUS downgrade_lease(struct smbd_server_connection *sconn, + const struct file_id id, + const struct smb2_lease_key *key, + uint32_t lease_state); void contend_level2_oplocks_begin(files_struct *fsp, enum level2_contention_type type); void contend_level2_oplocks_end(files_struct *fsp, diff --git a/source3/smbd/server.c b/source3/smbd/server.c index 0d649e1..60394dd 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -47,6 +47,7 @@ #include "../lib/util/pidfile.h" #include "lib/smbd_shim.h" #include "scavenger.h" +#include "locking/leases_db.h" struct smbd_open_socket; struct smbd_child_pid; @@ -1450,6 +1451,10 @@ extern void build_options(bool screen); if (!locking_init()) exit_daemon("Samba cannot init locking", EACCES); + if (!leases_db_init(false)) { + exit_daemon("Samba cannot init leases", EACCES); + } + if (!smbd_parent_notify_init(NULL, msg_ctx, ev_ctx)) { exit_daemon("Samba cannot init notification", EACCES); } diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c index 5c079ec..a11cb5f 100644 --- a/source3/smbd/smb2_break.c +++ b/source3/smbd/smb2_break.c @@ -25,6 +25,9 @@ #include "../libcli/smb/smb_common.h" #include "../lib/util/tevent_ntstatus.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 smbd_smb2_request *smb2req, @@ -45,6 +48,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 +231,163 @@ 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; +}; + +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 files_struct *fsp; + 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; + + fsp = file_find_one_fsp_from_lease_key(smb2_req->sconn, &in_lease_key); + if (fsp == NULL) { + DEBUG(10, ("No fsp for lease key found\n")); + tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return tevent_req_post(req, ev); + } + + status = downgrade_lease(smb2_req->sconn, fsp->file_id, &in_lease_key, + in_lease_state); + if (tevent_req_nterror(req, status)) { + DEBUG(10, ("downgrade_lease returned %s\n", + nt_errstr(status))); + return tevent_req_post(req, ev); + } + TALLOC_FREE(fsp->oplock_timeout); + + 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_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 +413,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 +422,29 @@ 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) { + bool no_ack; - status = smbd_smb2_send_oplock_break(xconn, - session, - fsp->conn->tcon, - fsp->op, - smb2_oplock_level); + no_ack = ((fsp->lease->lease.lease_state == SMB2_LEASE_READ) && + (break_to == SMB2_LEASE_NONE)); + + status = smbd_smb2_send_lease_break( + xconn, session, fsp->conn->tcon, 0, + no_ack ? 0 : SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED, + &fsp->lease->lease.lease_key, + fsp->lease->lease.lease_state, 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)); diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c index 48bc486..67d4fc0 100644 --- a/source3/smbd/smb2_create.c +++ b/source3/smbd/smb2_create.c @@ -25,6 +25,7 @@ #include "smbd/globals.h" #include "../libcli/smb/smb_common.h" #include "../librpc/gen_ndr/ndr_security.h" +#include "../librpc/gen_ndr/ndr_smb2_lease_struct.h" #include "../lib/util/tevent_ntstatus.h" #include "messages.h" @@ -40,9 +41,7 @@ int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level) case SMB2_OPLOCK_LEVEL_BATCH: return BATCH_OPLOCK; case SMB2_OPLOCK_LEVEL_LEASE: - DEBUG(2,("map_smb2_oplock_levels_to_samba: " - "LEASE_OPLOCK_REQUESTED\n")); - return NO_OPLOCK; + return LEASE_OPLOCK; default: DEBUG(2,("map_smb2_oplock_levels_to_samba: " "unknown level %u\n", @@ -59,6 +58,8 @@ static uint8_t map_samba_oplock_levels_to_smb2(int oplock_type) return SMB2_OPLOCK_LEVEL_EXCLUSIVE; } else if (oplock_type == LEVEL_II_OPLOCK) { return SMB2_OPLOCK_LEVEL_II; + } else if (oplock_type == LEASE_OPLOCK) { + return SMB2_OPLOCK_LEVEL_LEASE; } else { return SMB2_OPLOCK_LEVEL_NONE; } @@ -374,6 +375,62 @@ static void smbd_smb2_request_create_done(struct tevent_req *tsubreq) } } +static bool smb2_lease_key_valid(const struct smb2_lease_key *key) +{ + return ((key->data[0] != 0) || (key->data[1] != 0)); +} + +static NTSTATUS smbd_smb2_create_durable_lease_check( + const char *requested_filename, const struct files_struct *fsp, + const struct smb2_lease *lease_ptr) +{ + struct smb_filename *smb_fname = NULL; + NTSTATUS status; + + if (lease_ptr == NULL) { + if (fsp->oplock_type != LEASE_OPLOCK) { + return NT_STATUS_OK; + } + DEBUG(10, ("Reopened file has lease, but no lease " + "requested\n")); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (fsp->oplock_type != LEASE_OPLOCK) { + DEBUG(10, ("Lease requested, but reopened file has no " + "lease\n")); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (!smb2_lease_key_equal(&lease_ptr->lease_key, + &fsp->lease->lease.lease_key)) { + DEBUG(10, ("Different lease key requested than found " + "in reopened file\n")); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + status = filename_convert(talloc_tos(), fsp->conn, false, + requested_filename, UCF_PREP_CREATEFILE, + NULL, &smb_fname); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("filename_convert returned %s\n", + nt_errstr(status))); + return status; + } + + if (!strequal(fsp->fsp_name->base_name, smb_fname->base_name)) { + DEBUG(10, ("Lease requested for file %s, reopened file " + "is named %s\n", smb_fname->base_name, + fsp->fsp_name->base_name)); + TALLOC_FREE(smb_fname); + return NT_STATUS_INVALID_PARAMETER; + } + + TALLOC_FREE(smb_fname); + + return NT_STATUS_OK; +} + struct smbd_smb2_create_state { struct smbd_smb2_request *smb2req; struct smb_request *smb1req; @@ -507,6 +564,11 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, num_blobs_allowed = 1; } + if (smb2_create_blob_find(&in_context_blobs, + SMB2_CREATE_TAG_RQLS) != NULL) { + num_blobs_allowed += 1; + } + if (in_context_blobs.num_blobs != num_blobs_allowed) { tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); return tevent_req_post(req, ev); @@ -539,6 +601,11 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, num_blobs_allowed = 1; + if (smb2_create_blob_find(&in_context_blobs, + SMB2_CREATE_TAG_RQLS) != NULL) { + num_blobs_allowed += 1; + } + if (in_context_blobs.num_blobs != num_blobs_allowed) { tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND); return tevent_req_post(req, ev); @@ -600,11 +667,15 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, struct smb2_create_blob *qfid = NULL; struct GUID _create_guid = GUID_zero(); struct GUID *create_guid = NULL; + struct smb2_create_blob *rqls = NULL; bool update_open = false; bool durable_requested = false; uint32_t durable_timeout_msec = 0; bool do_durable_reconnect = false; uint64_t persistent_id = 0; + struct smb2_lease lease; + struct smb2_lease *lease_ptr; + ssize_t lease_len = -1; exta = smb2_create_blob_find(&in_context_blobs, SMB2_CREATE_TAG_EXTA); @@ -618,6 +689,8 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, SMB2_CREATE_TAG_TWRP); qfid = smb2_create_blob_find(&in_context_blobs, SMB2_CREATE_TAG_QFID); + rqls = smb2_create_blob_find(&in_context_blobs, + SMB2_CREATE_TAG_RQLS); fname = talloc_strdup(state, in_name); if (tevent_req_nomem(fname, req)) { @@ -804,6 +877,39 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, } } + lease_ptr = NULL; + + if (rqls) { + lease_len = smb2_lease_pull( + rqls->data.data, rqls->data.length, &lease); + if (lease_len == -1) { + tevent_req_nterror( + req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + lease_ptr = &lease; + + if (DEBUGLEVEL >= 10) { + DEBUG(10, ("Got lease request size %d\n", + (int)lease_len)); + NDR_PRINT_DEBUG(smb2_lease, lease_ptr); + } + + if (!smb2_lease_key_valid(&lease.lease_key)) { + lease_ptr = NULL; + requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; + } + + /* TODO client->connections ... */ + if ((smb2req->sconn->client->connections->protocol < + PROTOCOL_SMB3_00) + && (lease_len == 52)) { + DEBUG(10, ("v2 lease key only for SMB3\n")); + lease_ptr = NULL; + requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; + } + } + /* these are ignored for SMB2 */ in_create_options &= ~(0x10);/* NTCREATEX_OPTIONS_SYNC_ALERT */ in_create_options &= ~(0x20);/* NTCREATEX_OPTIONS_ASYNC_ALERT */ @@ -863,6 +969,18 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, return tevent_req_post(req, ev); } + DEBUG(10, ("result->oplock_type=%u, lease_ptr==%p\n", + (unsigned)result->oplock_type, lease_ptr)); + + status = smbd_smb2_create_durable_lease_check( + fname, result, lease_ptr); + + if (!NT_STATUS_IS_OK(status)) { + close_file(smb1req, result, SHUTDOWN_CLOSE); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + data_blob_free(&op->global->backend_cookie); op->global->backend_cookie = new_cookie; @@ -948,7 +1066,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, in_create_options, in_file_attributes, map_smb2_oplock_levels_to_samba(requested_oplock_level), - NULL, + lease_ptr, allocation_size, 0, /* private_flags */ sec_desc, @@ -1003,7 +1121,7 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, } if (durable_requested && - BATCH_OPLOCK_TYPE(result->oplock_type)) + (fsp_lease_type(result) & SMB2_LEASE_HANDLE)) { status = SMB_VFS_DURABLE_COOKIE(result, op, @@ -1086,6 +1204,29 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, return tevent_req_post(req, ev); } } + + if ((rqls != NULL) && (result->oplock_type == LEASE_OPLOCK)) { + uint8_t buf[52]; + + lease = result->lease->lease; + lease.lease_flags &= + SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET; + + if (!smb2_lease_push(&lease, buf, lease_len)) { + tevent_req_nterror( + req, NT_STATUS_INTERNAL_ERROR); + return tevent_req_post(req, ev); + } + + status = smb2_create_blob_add( + state, &out_context_blobs, + SMB2_CREATE_TAG_RQLS, + data_blob_const(buf, lease_len)); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + } } smb2req->compat_chain_fsp = smb1req->chain_fsp; diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c index 6904972..a27ab0c 100644 --- a/source3/smbd/smb2_negprot.c +++ b/source3/smbd/smb2_negprot.c @@ -230,6 +230,10 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) capabilities |= SMB2_CAP_DFS; } + if (protocol >= PROTOCOL_SMB2_10) { + capabilities |= SMB2_CAP_LEASING; + } + if ((protocol >= PROTOCOL_SMB2_24) && (lp_smb_encrypt(-1) != SMB_SIGNING_OFF) && (in_capabilities & SMB2_CAP_ENCRYPTION)) { diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index 689bfd7..cff10d8 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -2888,6 +2888,31 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn, return smbd_smb2_send_break(xconn, session, tcon, body, sizeof(body)); } +NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn, + struct smbXsrv_session *session, + struct smbXsrv_tcon *tcon, + uint16_t new_epoch, + uint32_t lease_flags, + struct smb2_lease_key *lease_key, + uint32_t current_lease_state, + uint32_t new_lease_state) +{ + uint8_t body[0x2c]; + + SSVAL(body, 0x00, sizeof(body)); + SSVAL(body, 0x02, new_epoch); + SIVAL(body, 0x04, lease_flags); + SBVAL(body, 0x08, lease_key->data[0]); + SBVAL(body, 0x10, lease_key->data[1]); + SIVAL(body, 0x18, current_lease_state); + SIVAL(body, 0x1c, new_lease_state); + SIVAL(body, 0x20, 0); /* BreakReason, MUST be 0 */ + SIVAL(body, 0x24, 0); /* AccessMaskHint, MUST be 0 */ + SIVAL(body, 0x28, 0); /* ShareMaskHint, MUST be 0 */ + + return smbd_smb2_send_break(sconn, session, tcon, body, sizeof(body)); +} + static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state) { NTSTATUS status; diff --git a/source3/utils/status.c b/source3/utils/status.c index 936e87b7..bde21e4 100644 --- a/source3/utils/status.c +++ b/source3/utils/status.c @@ -177,6 +177,8 @@ static int print_share_mode(const struct share_mode_entry *e, d_printf("BATCH "); } else if (e->op_type & LEVEL_II_OPLOCK) { d_printf("LEVEL_II "); + } else if (e->op_type == LEASE_OPLOCK) { + d_printf("LEASE "); } else { d_printf("NONE "); } diff --git a/source3/wscript_build b/source3/wscript_build index 54ba3a7..4328500 100755 --- a/source3/wscript_build +++ b/source3/wscript_build @@ -618,6 +618,7 @@ bld.SAMBA3_LIBRARY('smbd_base', LIBAFS RPC_SERVICE NDR_SMBXSRV + LEASES_DB LIBASYS sysquotas ccan-hash @@ -635,9 +636,14 @@ bld.SAMBA3_SUBSYSTEM('LOCKING', deps=''' tdb_compat talloc + LEASES_DB NDR_OPEN_FILES FNAME_UTIL''') +bld.SAMBA3_SUBSYSTEM('LEASES_DB', + source='locking/leases_db.c', + deps='NDR_LEASES_DB') + if bld.CONFIG_GET("WITH_PROFILE"): bld.SAMBA3_SUBSYSTEM('PROFILE', source='profile/profile.c', diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index c3d63d1..7e1e6a2 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -25,6 +25,7 @@ #include "libcli/smb2/smb2_calls.h" #include "../libcli/smb/smbXcli_base.h" #include "torture/torture.h" +#include "torture/util.h" #include "torture/smb2/proto.h" #include "../libcli/smb/smbXcli_base.h" @@ -53,7 +54,9 @@ #define CHECK_CREATED(__io, __created, __attribute) \ do { \ CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ - CHECK_VAL((__io)->out.alloc_size, 0); \ + if (!TARGET_IS_SAMBA3(tctx)) { \ + CHECK_VAL((__io)->out.alloc_size, 0); \ + } \ CHECK_VAL((__io)->out.size, 0); \ CHECK_VAL((__io)->out.file_attr, (__attribute)); \ CHECK_VAL((__io)->out.reserved2, 0); \ diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c index 4326958..7ea2eab 100644 --- a/source4/torture/smb2/lease.c +++ b/source4/torture/smb2/lease.c @@ -25,6 +25,7 @@ #include "libcli/smb2/smb2_calls.h" #include "torture/torture.h" #include "torture/smb2/proto.h" +#include "torture/util.h" #include "libcli/smb/smbXcli_base.h" #define CHECK_VAL(v, correct) do { \ @@ -45,7 +46,9 @@ #define CHECK_CREATED(__io, __created, __attribute) \ do { \ CHECK_VAL((__io)->out.create_action, NTCREATEX_ACTION_ ## __created); \ - CHECK_VAL((__io)->out.alloc_size, 0); \ + if (!TARGET_IS_SAMBA3(tctx)) { \ + CHECK_VAL((__io)->out.alloc_size, 0); \ + } \ CHECK_VAL((__io)->out.size, 0); \ CHECK_VAL((__io)->out.file_attr, (__attribute)); \ CHECK_VAL((__io)->out.reserved2, 0); \ -- 2.1.0.rc2.206.gedb03e5 From ac86a6c3e2c2bf2ac6cb2e43254708195554b46d Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Sun, 21 Sep 2014 09:32:25 +0200 Subject: [PATCH 04/15] smbd: use smbXsrv_connection as argument to smbd_smb2_send_lease_break() Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- source3/smbd/globals.h | 2 +- source3/smbd/smb2_server.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 39c537c..f297ff7 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -250,7 +250,7 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn, struct smbXsrv_tcon *tcon, struct smbXsrv_open *op, uint8_t oplock_level); -NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn, +NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_connection *xconn, struct smbXsrv_session *session, struct smbXsrv_tcon *tcon, uint16_t new_epoch, diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index cff10d8..36ac0d7 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -2888,7 +2888,7 @@ NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_connection *xconn, return smbd_smb2_send_break(xconn, session, tcon, body, sizeof(body)); } -NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn, +NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_connection *xconn, struct smbXsrv_session *session, struct smbXsrv_tcon *tcon, uint16_t new_epoch, @@ -2910,7 +2910,7 @@ NTSTATUS smbd_smb2_send_lease_break(struct smbd_server_connection *sconn, SIVAL(body, 0x24, 0); /* AccessMaskHint, MUST be 0 */ SIVAL(body, 0x28, 0); /* ShareMaskHint, MUST be 0 */ - return smbd_smb2_send_break(sconn, session, tcon, body, sizeof(body)); + return smbd_smb2_send_break(xconn, session, tcon, body, sizeof(body)); } static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state) -- 2.1.0.rc2.206.gedb03e5 From c4d2ec9f84e2b468c0fa93f1fedadad1c2db18e1 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Sun, 21 Sep 2014 09:46:30 +0200 Subject: [PATCH 05/15] selftest: samba3.smb2.leases.v2_request_parent succeeds Signed-off-by: Michael Adam Reviewed-by: Jeremy Allison --- selftest/knownfail | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selftest/knownfail b/selftest/knownfail index 6b720f0..3f13ee0 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -194,7 +194,7 @@ ^samba4.smb2.ioctl.copy_chunk_\w*\(dc\) # not supported by s4 ntvfs server ^samba3.smb2.dir.one ^samba3.smb2.dir.modify -^samba3.smb2.lease.v2_request +^samba3.smb2.lease.v2_request\(.*\)$ ^samba3.smb2.oplock.batch20 ^samba3.smb2.oplock.stream1 ^samba3.smb2.streams.rename -- 2.1.0.rc2.206.gedb03e5 From 9ae6202753b2d9bf72660b6ee8ce886e4b98d75b Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 23 Sep 2014 02:28:38 +0200 Subject: [PATCH 06/15] s3: leases - epoch handling Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- source3/smbd/open.c | 6 +++--- source3/smbd/oplock.c | 23 +++++++++++++++++++++++ source3/smbd/proto.h | 3 +++ source3/smbd/smb2_break.c | 9 ++++++++- source3/smbd/smb2_create.c | 6 ++++++ 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/source3/smbd/open.c b/source3/smbd/open.c index c9196d2..3cc89f0 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -1520,7 +1520,7 @@ static bool file_has_brlocks(files_struct *fsp) return (brl_num_locks(br_lck) > 0); } -static int find_share_mode_oplock(struct share_mode_data *d, +int find_share_mode_oplock(struct share_mode_data *d, const struct GUID *client_guid, const struct smb2_lease_key *key) { @@ -1669,8 +1669,8 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d, d->leases[d->num_leases] = (struct share_mode_oplock) { .client_guid = *client_guid, - .lease_key = lease->lease_key, - .epoch = lease->lease_epoch, + .lease_key = fsp->lease->lease.lease_key, + .epoch = fsp->lease->lease.lease_epoch, .current_state = granted, }; diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c index c55fb3b..f80a3999 100644 --- a/source3/smbd/oplock.c +++ b/source3/smbd/oplock.c @@ -595,6 +595,29 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx, return; } + if (fsp->oplock_type == LEASE_OPLOCK) { + /* Need to increment the epoch */ + struct share_mode_lock *lck; + int idx; + + lck = get_existing_share_mode_lock( + talloc_tos(), fsp->file_id); + + idx = find_share_mode_oplock( + lck->data, + &fsp->conn->sconn->client->connections-> + smb2.client.guid, + &fsp->lease->lease.lease_key); + if (idx != -1) { + struct share_mode_oplock *o; + o = &lck->data->leases[idx]; + o->epoch += 1; + fsp->lease->lease.lease_epoch = o->epoch; + } + + TALLOC_FREE(lck); + } + /* Need to wait before sending a break message if we sent ourselves this message. */ if (serverid_equal(&self, &src)) { diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index 613af8a..08ad413 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -627,6 +627,9 @@ void msg_file_was_renamed(struct messaging_context *msg, DATA_BLOB *data); NTSTATUS open_streams_for_delete(connection_struct *conn, const char *fname); +int find_share_mode_oplock(struct share_mode_data *d, + const struct GUID *client_guid, + const struct smb2_lease_key *key); NTSTATUS create_file_default(connection_struct *conn, struct smb_request *req, uint16_t root_dir_fid, diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c index a11cb5f..178dba9 100644 --- a/source3/smbd/smb2_break.c +++ b/source3/smbd/smb2_break.c @@ -426,12 +426,19 @@ void send_break_message_smb2(files_struct *fsp, uint32_t break_to) if (fsp->oplock_type == LEASE_OPLOCK) { bool no_ack; + uint16_t new_epoch; no_ack = ((fsp->lease->lease.lease_state == SMB2_LEASE_READ) && (break_to == SMB2_LEASE_NONE)); + if (xconn->protocol >= PROTOCOL_SMB3_00) { + new_epoch = fsp->lease->lease.lease_epoch; + } else { + new_epoch = 0; + } + status = smbd_smb2_send_lease_break( - xconn, session, fsp->conn->tcon, 0, + xconn, session, fsp->conn->tcon, new_epoch, no_ack ? 0 : SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED, &fsp->lease->lease.lease_key, fsp->lease->lease.lease_state, break_to); diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c index 67d4fc0..abcfa1a 100644 --- a/source3/smbd/smb2_create.c +++ b/source3/smbd/smb2_create.c @@ -900,6 +900,12 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; } + if (smb2req->sconn->client->connections->protocol < + PROTOCOL_SMB2_10) { + lease_ptr = NULL; + requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; + } + /* TODO client->connections ... */ if ((smb2req->sconn->client->connections->protocol < PROTOCOL_SMB3_00) -- 2.1.0.rc2.206.gedb03e5 From 589d9444113fb1f9a131ebd4595d076e90dd2a80 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 23 Sep 2014 18:49:46 +0200 Subject: [PATCH 07/15] First test for NT_STATUS_INVALID_OPLOCK_PROTOCOL, then for in_oplock_level being reasonable Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- source3/smbd/smb2_break.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c index 178dba9..abe6709 100644 --- a/source3/smbd/smb2_break.c +++ b/source3/smbd/smb2_break.c @@ -61,11 +61,6 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req) in_oplock_level = CVAL(inbody, 0x02); - if (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE && - in_oplock_level != SMB2_OPLOCK_LEVEL_II) { - return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); - } - /* 0x03 1 bytes reserved */ /* 0x04 4 bytes reserved */ in_file_id_persistent = BVAL(inbody, 0x08); @@ -76,6 +71,17 @@ NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req) return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED); } + /* Are we awaiting a break message ? */ + if (in_fsp->oplock_timeout == NULL) { + return smbd_smb2_request_error( + req, NT_STATUS_INVALID_OPLOCK_PROTOCOL); + } + + if (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE && + in_oplock_level != SMB2_OPLOCK_LEVEL_II) { + return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); + } + subreq = smbd_smb2_oplock_break_send(req, req->sconn->ev_ctx, req, in_fsp, in_oplock_level); if (subreq == NULL) { @@ -186,12 +192,6 @@ static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx, fsp_str_dbg(fsp), fsp_fnum_dbg(fsp))); - /* Are we awaiting a break message ? */ - if (fsp->oplock_timeout == NULL) { - tevent_req_nterror(req, NT_STATUS_INVALID_OPLOCK_PROTOCOL); - return tevent_req_post(req, ev); - } - if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) || (break_to_none)) { result = remove_oplock(fsp); -- 2.1.0.rc2.206.gedb03e5 From aa462aaf9107e13bdea21c015a648aaeb838ac27 Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 23 Sep 2014 22:56:41 +0200 Subject: [PATCH 08/15] s3: leases - Only increment epoch for V2 leases Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- libcli/smb/smb2_lease.c | 1 + librpc/idl/smb2_lease_struct.idl | 1 + source3/smbd/open.c | 4 +++- source3/smbd/oplock.c | 3 ++- source3/smbd/smb2_create.c | 4 ++-- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libcli/smb/smb2_lease.c b/libcli/smb/smb2_lease.c index 70dd3d4..41eafc9 100644 --- a/libcli/smb/smb2_lease.c +++ b/libcli/smb/smb2_lease.c @@ -43,6 +43,7 @@ ssize_t smb2_lease_pull(const uint8_t *buf, size_t len, lease->lease_state = IVAL(buf, 16); lease->lease_flags = IVAL(buf, 20); lease->lease_duration = BVAL(buf, 24); + lease->lease_version = version; switch (version) { case 1: diff --git a/librpc/idl/smb2_lease_struct.idl b/librpc/idl/smb2_lease_struct.idl index be80d14..5ccd8a3 100644 --- a/librpc/idl/smb2_lease_struct.idl +++ b/librpc/idl/smb2_lease_struct.idl @@ -28,6 +28,7 @@ interface smb2_lease_struct uint32 lease_flags; hyper lease_duration; /* should be 0 */ smb2_lease_key parent_lease_key; + uint16 lease_version; uint16 lease_epoch; } smb2_lease; }; \ No newline at end of file diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 3cc89f0..2a4440a 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -1663,7 +1663,9 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d, fsp->lease->ref_count = 1; fsp->lease->lease = *lease; fsp->lease->lease.lease_state = granted; - fsp->lease->lease.lease_epoch += 1; + if (fsp->lease->lease.lease_version > 1) { + fsp->lease->lease.lease_epoch += 1; + } *p_lease_idx = d->num_leases; diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c index f80a3999..d42058c 100644 --- a/source3/smbd/oplock.c +++ b/source3/smbd/oplock.c @@ -595,7 +595,8 @@ static void process_oplock_break_message(struct messaging_context *msg_ctx, return; } - if (fsp->oplock_type == LEASE_OPLOCK) { + if ((fsp->oplock_type == LEASE_OPLOCK) && + (fsp->lease->lease.lease_version != 1)) { /* Need to increment the epoch */ struct share_mode_lock *lck; int idx; diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c index abcfa1a..38ef04c 100644 --- a/source3/smbd/smb2_create.c +++ b/source3/smbd/smb2_create.c @@ -908,8 +908,8 @@ static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx, /* TODO client->connections ... */ if ((smb2req->sconn->client->connections->protocol < - PROTOCOL_SMB3_00) - && (lease_len == 52)) { + PROTOCOL_SMB3_00) && + (lease.lease_version != 1)) { DEBUG(10, ("v2 lease key only for SMB3\n")); lease_ptr = NULL; requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE; -- 2.1.0.rc2.206.gedb03e5 From 075d2140b7db1450d533acd2fe98cbc2b68b749e Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Tue, 23 Sep 2014 23:34:14 +0200 Subject: [PATCH 09/15] s3: leases - break to none with FILE_OVERWRITE Signed-off-by: Volker Lendecke Reviewed-by: Jeremy Allison --- source3/smbd/open.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 2a4440a..8f20d99 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -1446,6 +1446,7 @@ static bool delay_for_oplock(files_struct *fsp, switch (create_disposition) { case FILE_SUPERSEDE: case FILE_OVERWRITE_IF: + case FILE_OVERWRITE: will_overwrite = true; break; default: -- 2.1.0.rc2.206.gedb03e5 From d45f1f58159190989611a69482a33efed3ad9095 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 10 Oct 2014 14:32:19 -0700 Subject: [PATCH 10/15] s3: smbd: Leases - ensure all share mode removal functions go through a common lease refcount manager. Signed-off-by: Jeremy Allison --- source3/locking/locking.c | 136 ++++++++++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 54 deletions(-) diff --git a/source3/locking/locking.c b/source3/locking/locking.c index 64bd043..7ccd8d4 100644 --- a/source3/locking/locking.c +++ b/source3/locking/locking.c @@ -618,6 +618,84 @@ bool is_valid_share_mode_entry(const struct share_mode_entry *e) } /* + * See if we need to remove a lease being referred to by a + * share mode that is being marked stale or deleted. + */ + +static void remove_share_mode_lease(struct share_mode_data *d, + struct share_mode_entry *e) +{ + struct GUID client_guid; + struct smb2_lease_key lease_key; + uint16_t op_type; + uint32_t lease_idx; + uint32_t i; + + op_type = e->op_type; + e->op_type = NO_OPLOCK; + + d->modified = true; + + if (op_type != LEASE_OPLOCK) { + return; + } + + /* + * This used to reference a lease. If there's no other one referencing + * it, remove it. + */ + + lease_idx = e->lease_idx; + e->lease_idx = UINT32_MAX; + + for (i=0; inum_share_modes; i++) { + if (d->share_modes[i].stale) { + continue; + } + if (e == &d->share_modes[i]) { + /* Not ourselves. */ + continue; + } + if (d->share_modes[i].lease_idx == lease_idx) { + break; + } + } + if (i < d->num_share_modes) { + /* + * Found another one + */ + return; + } + + memcpy(&client_guid, + &d->leases[lease_idx].client_guid, + sizeof(client_guid)); + lease_key = d->leases[lease_idx].lease_key; + + d->num_leases -= 1; + d->leases[lease_idx] = d->leases[d->num_leases]; + + /* + * We changed the lease array. Fix all references to it. + */ + for (i=0; inum_share_modes; i++) { + if (d->share_modes[i].lease_idx == d->num_leases) { + d->share_modes[i].lease_idx = lease_idx; + } + } + + { + NTSTATUS status; + + status = leases_db_del(&client_guid, + &lease_key); + + DEBUG(10, ("%s: leases_db_del returned %s\n", __func__, + nt_errstr(status))); + } +} + +/* * In case d->share_modes[i] conflicts with something or otherwise is * being used, we need to make sure the corresponding process still * exists. @@ -675,6 +753,8 @@ bool share_mode_stale_pid(struct share_mode_data *d, uint32_t idx) } } + remove_share_mode_lease(d, e); + d->modified = true; return true; } @@ -773,6 +853,7 @@ bool del_share_mode(struct share_mode_lock *lck, files_struct *fsp) if (e == NULL) { return False; } + remove_share_mode_lease(lck->data, e); *e = lck->data->share_modes[lck->data->num_share_modes-1]; lck->data->num_share_modes -= 1; lck->data->modified = True; @@ -822,67 +903,14 @@ bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp) { struct share_mode_data *d = lck->data; struct share_mode_entry *e; - uint16_t op_type; - uint32_t lease_idx; - uint32_t i; e = find_share_mode_entry(lck, fsp); if (e == NULL) { return False; } - op_type = e->op_type; - e->op_type = NO_OPLOCK; - + remove_share_mode_lease(d, e); d->modified = True; - - if (op_type != LEASE_OPLOCK) { - return true; - } - - /* - * This used to reference a lease. If there's no other one referencing - * it, remove it. - */ - - lease_idx = e->lease_idx; - e->lease_idx = UINT32_MAX; - - for (i=0; inum_share_modes; i++) { - if (d->share_modes[i].lease_idx == lease_idx) { - break; - } - } - if (i < d->num_share_modes) { - /* - * Found another one - */ - return true; - } - - d->num_leases -= 1; - d->leases[lease_idx] = d->leases[d->num_leases]; - - /* - * We changed the lease array. Fix all references to it. - */ - for (i=0; inum_share_modes; i++) { - if (d->share_modes[i].lease_idx == d->num_leases) { - d->share_modes[i].lease_idx = lease_idx; - } - } - - { - NTSTATUS status; - - status = leases_db_del( - &fsp->conn->sconn->client->connections->smb2.client.guid, - &fsp->lease->lease.lease_key); - - DEBUG(10, ("%s: leases_db_del returned %s\n", __func__, - nt_errstr(status))); - } - return true; } -- 2.1.0.rc2.206.gedb03e5 From 71114d17acd4850d7f7137967300ac2763a0023a Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 10 Oct 2014 16:36:54 -0700 Subject: [PATCH 11/15] s3: smbd: leases - expand the leases db to hold a list of file_ids (dev,ino) with this lease. Will enable us to solve the dynamic share path problem. Signed-off-by: Jeremy Allison --- source3/librpc/idl/leases_db.idl | 24 +++ source3/locking/leases_db.c | 368 +++++++++++++++++++++++++++++++++++++++ source3/locking/leases_db.h | 46 +++++ source3/locking/locking.c | 3 +- 4 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 source3/librpc/idl/leases_db.idl create mode 100644 source3/locking/leases_db.c create mode 100644 source3/locking/leases_db.h diff --git a/source3/librpc/idl/leases_db.idl b/source3/librpc/idl/leases_db.idl new file mode 100644 index 0000000..28ec479 --- /dev/null +++ b/source3/librpc/idl/leases_db.idl @@ -0,0 +1,24 @@ +#include "idl_types.h" + +import "misc.idl"; +import "smb2_lease_struct.idl"; +import "file_id.idl"; + +[ + pointer_default(unique) +] + +interface leases_db +{ + typedef [public] struct { + GUID client_guid; + smb2_lease_key lease_key; + } leases_db_key; + + typedef [public] struct { + uint32 num_file_ids; + [size_is(num_file_ids)] file_id ids[]; + [string,charset(UTF8)] char *filename; + [string,charset(UTF8)] char *stream_name; + } leases_db_value; +} diff --git a/source3/locking/leases_db.c b/source3/locking/leases_db.c new file mode 100644 index 0000000..29b8e35 --- /dev/null +++ b/source3/locking/leases_db.c @@ -0,0 +1,368 @@ +/* + Unix SMB/CIFS implementation. + Map lease keys to file ids + Copyright (C) Volker Lendecke 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "locking/leases_db.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "util_tdb.h" +#include "ndr.h" +#include "librpc/gen_ndr/ndr_leases_db.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_LOCKING + +/* the leases database handle */ +static struct db_context *leases_db; + +bool leases_db_init(bool read_only) +{ + if (leases_db) { + return true; + } + + leases_db = db_open(NULL, lock_path("leases.tdb"), 0, + TDB_DEFAULT|TDB_VOLATILE|TDB_CLEAR_IF_FIRST| + TDB_INCOMPATIBLE_HASH, + read_only ? O_RDONLY : O_RDWR|O_CREAT, 0644, + DBWRAP_LOCK_ORDER_2, DBWRAP_FLAG_NONE); + + if (leases_db == NULL) { + DEBUG(1, ("ERROR: Failed to initialise leases database\n")); + return false; + } + + return true; +} + +static bool leases_db_key(TALLOC_CTX *mem_ctx, + const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + TDB_DATA *key) +{ + struct leases_db_key db_key = { + .client_guid = *client_guid, + .lease_key = *lease_key }; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + if (DEBUGLEVEL >= 10) { + DEBUG(10, ("%s:\n", __func__)); + NDR_PRINT_DEBUG(leases_db_key, &db_key); + } + + ndr_err = ndr_push_struct_blob( + &blob, mem_ctx, &db_key, + (ndr_push_flags_fn_t)ndr_push_leases_db_key); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_push_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + return false; + } + + *key = make_tdb_data(blob.data, blob.length); + return true; +} + +NTSTATUS leases_db_add(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id, + const char *filename, + const char *stream_name) +{ + TDB_DATA db_key, db_value; + DATA_BLOB blob; + struct db_record *rec; + NTSTATUS status; + bool ok; + struct leases_db_value new_value; + struct leases_db_value *value = NULL; + enum ndr_err_code ndr_err; + + if (!leases_db_init(false)) { + return NT_STATUS_INTERNAL_ERROR; + } + + ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key); + if (!ok) { + DEBUG(10, ("%s: leases_db_key failed\n", __func__)); + return NT_STATUS_NO_MEMORY; + } + + rec = dbwrap_fetch_locked(leases_db, talloc_tos(), db_key); + TALLOC_FREE(db_key.dptr); + if (rec == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + db_value = dbwrap_record_get_value(rec); + if (db_value.dsize != 0) { + uint32_t i; + + DEBUG(10, ("%s: record exists\n", __func__)); + + value = talloc(talloc_tos(), struct leases_db_value); + if (value == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + blob.data = db_value.dptr; + blob.length = db_value.dsize; + + ndr_err = ndr_pull_struct_blob_all( + &blob, value, value, + (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + status = ndr_map_error2ntstatus(ndr_err); + goto out; + } + + /* id must be unique. */ + for (i = 0; i < value->num_file_ids; i++) { + if (file_id_equal(id, &value->ids[i])) { + status = NT_STATUS_OBJECT_NAME_COLLISION; + goto out; + } + } + + value->ids = talloc_realloc(value, value->ids, struct file_id, + value->num_file_ids + 1); + if (value->ids == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + value->ids[value->num_file_ids] = *id; + value->num_file_ids += 1; + + } else { + new_value = (struct leases_db_value) { + .num_file_ids = 1, + .ids = id, + .filename = filename, + .stream_name = stream_name, + }; + value = &new_value; + } + + ndr_err = ndr_push_struct_blob( + &blob, talloc_tos(), value, + (ndr_push_flags_fn_t)ndr_push_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_push_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + status = ndr_map_error2ntstatus(ndr_err); + goto out; + } + + db_value = make_tdb_data(blob.data, blob.length); + + status = dbwrap_record_store(rec, db_value, 0); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("%s: dbwrap_record_store returned %s\n", + __func__, nt_errstr(status))); + } + + out: + + if (value != &new_value) { + TALLOC_FREE(value); + } + TALLOC_FREE(rec); + return status; +} + +NTSTATUS leases_db_del(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id) +{ + TDB_DATA db_key, db_value; + struct db_record *rec; + NTSTATUS status; + struct leases_db_value *value; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + uint32_t i; + bool ok; + + if (!leases_db_init(false)) { + return NT_STATUS_INTERNAL_ERROR; + } + + ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + rec = dbwrap_fetch_locked(leases_db, talloc_tos(), db_key); + TALLOC_FREE(db_key.dptr); + if (rec == NULL) { + return NT_STATUS_NOT_FOUND; + } + db_value = dbwrap_record_get_value(rec); + if (db_value.dsize == 0) { + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + value = talloc(talloc_tos(), struct leases_db_value); + if (value == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + blob.data = db_value.dptr; + blob.length = db_value.dsize; + + ndr_err = ndr_pull_struct_blob_all( + &blob, value, value, + (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + status = ndr_map_error2ntstatus(ndr_err); + goto out; + } + + /* id must exist. */ + for (i = 0; i < value->num_file_ids; i++) { + if (file_id_equal(id, &value->ids[i])) { + break; + } + } + + if (i == value->num_file_ids) { + status = NT_STATUS_NOT_FOUND; + goto out; + } + + value->ids[i] = value->ids[value->num_file_ids-1]; + value->num_file_ids -= 1; + + if (value->num_file_ids == 0) { + status = dbwrap_record_delete(rec); + } else { + ndr_err = ndr_push_struct_blob( + &blob, talloc_tos(), value, + (ndr_push_flags_fn_t)ndr_push_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_push_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + status = ndr_map_error2ntstatus(ndr_err); + goto out; + } + + db_value = make_tdb_data(blob.data, blob.length); + + status = dbwrap_record_store(rec, db_value, 0); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("%s: dbwrap_record_store returned %s\n", + __func__, nt_errstr(status))); + } + } + + out: + + TALLOC_FREE(value); + TALLOC_FREE(rec); + return status; +} + +struct leases_db_fetch_state { + void (*parser)(uint32_t num_file_ids, + struct file_id *ids, const char *filename, + const char *stream_name, void *private_data); + void *private_data; + NTSTATUS status; +}; + +static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data) +{ + struct leases_db_fetch_state *state = + (struct leases_db_fetch_state *)private_data; + DATA_BLOB blob = { .data = data.dptr, .length = data.dsize }; + enum ndr_err_code ndr_err; + struct leases_db_value *value; + + value = talloc(talloc_tos(), struct leases_db_value); + if (value == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + + ndr_err = ndr_pull_struct_blob_all( + &blob, value, value, + (ndr_pull_flags_fn_t)ndr_pull_leases_db_value); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n", + __func__, ndr_errstr(ndr_err))); + TALLOC_FREE(value); + state->status = ndr_map_error2ntstatus(ndr_err); + return; + } + + state->parser(value->num_file_ids, + value->ids, value->filename, value->stream_name, + state->private_data); + + TALLOC_FREE(value); + state->status = NT_STATUS_OK; +} + +NTSTATUS leases_db_parse(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + void (*parser)(uint32_t num_file_ids, + struct file_id *ids, + const char *filename, + const char *stream_name, + void *private_data), + void *private_data) +{ + TDB_DATA db_key; + struct leases_db_fetch_state state; + NTSTATUS status; + bool ok; + + if (!leases_db_init(true)) { + return NT_STATUS_INTERNAL_ERROR; + } + + ok = leases_db_key(talloc_tos(), client_guid, lease_key, &db_key); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + state = (struct leases_db_fetch_state) { + .parser = parser, + .private_data = private_data, + .status = NT_STATUS_OK + }; + + status = dbwrap_parse_record(leases_db, db_key, leases_db_parser, + &state); + TALLOC_FREE(db_key.dptr); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return state.status; +} diff --git a/source3/locking/leases_db.h b/source3/locking/leases_db.h new file mode 100644 index 0000000..f570356 --- /dev/null +++ b/source3/locking/leases_db.h @@ -0,0 +1,46 @@ +/* + * Unix SMB/CIFS implementation. + * leases.tdb functions + * + * Copyright (C) Volker Lendecke 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _LEASES_DB_H_ +#define _LEASES_DB_H_ + +struct GUID; +struct smb2_lease_key; +struct file_id; + +bool leases_db_init(bool read_only); +NTSTATUS leases_db_add(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id, + const char *filename, + const char *stream_name); +NTSTATUS leases_db_del(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id); +NTSTATUS leases_db_parse(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + void (*parser)(uint32_t num_file_ids, + struct file_id *ids, + const char *filename, + const char *stream_name, + void *private_data), + void *private_data); + +#endif /* _LEASES_DB_H_ */ diff --git a/source3/locking/locking.c b/source3/locking/locking.c index 7ccd8d4..1c6707e 100644 --- a/source3/locking/locking.c +++ b/source3/locking/locking.c @@ -688,7 +688,8 @@ static void remove_share_mode_lease(struct share_mode_data *d, NTSTATUS status; status = leases_db_del(&client_guid, - &lease_key); + &lease_key, + &e->id); DEBUG(10, ("%s: leases_db_del returned %s\n", __func__, nt_errstr(status))); -- 2.1.0.rc2.206.gedb03e5 From 431134029d57c5149c654860bf38524abea04511 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Fri, 10 Oct 2014 17:05:46 -0700 Subject: [PATCH 12/15] s3: smbd: leases - Fix the dynamic share file case [homes]. Signed-off-by: Jeremy Allison --- source3/smbd/open.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 135 insertions(+), 16 deletions(-) diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 8f20d99..976dd16 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -4083,39 +4083,148 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) */ struct lease_fname_match_state { + /* Input parameters. */ const struct smb_filename *fname; - bool match; + bool file_existed; + struct file_id id; + /* Return parameters. */ + uint32_t num_file_ids; + struct file_id *ids; + NTSTATUS match_status; }; static void lease_fname_match_parser( - struct file_id id, const char *filename, const char *stream_name, + uint32_t num_file_ids, + struct file_id *ids, const char *filename, const char *stream_name, void *private_data) { struct lease_fname_match_state *state = (struct lease_fname_match_state *)private_data; - state->match = - strequal(filename, state->fname->base_name) && - strequal(stream_name, state->fname->stream_name); + if (!strequal(filename, state->fname->base_name) || + !strequal(stream_name, state->fname->stream_name)) { + /* Names don't match lease key. */ + state->match_status = NT_STATUS_INVALID_PARAMETER; + return; + } + + if (!state->file_existed) { + /* New file. */ + state->match_status = NT_STATUS_OK; + return; + } + + if (num_file_ids == 1 && file_id_equal(&ids[0],&state->id)) { + /* Common case - non-dynamic share. We're ok.. */ + state->match_status = NT_STATUS_OK; + return; + } + + /* More than one file id, or not equal. Don't allow leases. */ + state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED; + state->num_file_ids = num_file_ids; + state->ids = talloc_memdup(talloc_tos(), + ids, + num_file_ids * sizeof(struct file_id)); + if (state->ids == NULL) { + state->match_status = NT_STATUS_NO_MEMORY; + } } -static bool lease_fname_match(struct smbd_server_connection *sconn, - struct smb2_lease_key *lease_key, - const struct smb_filename *fname) +static NTSTATUS lease_match(connection_struct *conn, + struct smb_request *req, + struct smb2_lease_key *lease_key, + const struct smb_filename *fname) { - struct lease_fname_match_state state = - { .fname = fname, .match = true }; + struct smbd_server_connection *sconn = req->sconn; + struct lease_fname_match_state state = { + .fname = fname, + .match_status = NT_STATUS_OK + }; + uint32_t i; NTSTATUS status; + state.file_existed = VALID_STAT(fname->st); + if (state.file_existed) { + state.id = vfs_file_id_from_sbuf(conn, &fname->st); + } + status = leases_db_parse(&sconn->client->connections->smb2.client.guid, lease_key, lease_fname_match_parser, &state); if (!NT_STATUS_IS_OK(status)) { /* * Not found or error means okay: We can make the lease pass */ - return true; + return NT_STATUS_OK; } - return state.match; + if (!NT_STATUS_EQUAL(state.match_status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + /* + * Anything but NT_STATUS_OPLOCK_NOT_GRANTED, let the caller + * deal with it. + */ + return state.match_status; + } + + /* We have to break all existing leases. */ + for (i = 0; i < state.num_file_ids; i++) { + struct share_mode_lock *lck; + struct share_mode_data *d; + uint32_t j; + + if (file_id_equal(&state.ids[i], &state.id)) { + /* Don't need to break our own file. */ + continue; + } + lck = get_existing_share_mode_lock(talloc_tos(), state.ids[i]); + if (lck == NULL) { + /* Race condition - file already closed. */ + continue; + } + d = lck->data; + for (j=0; jnum_share_modes; j++) { + struct share_mode_entry *e = &d->share_modes[j]; + uint32_t e_lease_type = get_lease_type(d, e); + + if (e_lease_type == SMB2_LEASE_NONE) { + continue; + } + if (share_mode_stale_pid(d, j)) { + continue; + } + + send_break_message(conn->sconn->msg_ctx, e, + SMB2_LEASE_NONE); + + /* + * Windows 7 and 8 lease clients + * are broken in that they will not + * respond to lease break requests + * whilst waiting for an outstanding + * open request on that lease handle + * on the same TCP connection, due + * to holding an internal inode lock. + * + * This means we can't reschedule + * ourselves here, but must return + * from the create. + * + * Work around: + * + * Send the breaks and then return + * SMB2_LEASE_NONE in the lease handle + * to cause them to acknowledge the + * lease break. Consulatation with + * Microsoft engineering confirmed + * this approach is safe. + */ + } + TALLOC_FREE(lck); + } + /* + * Ensure we don't grant anything more so we + * never upgrade. + */ + return NT_STATUS_OPLOCK_NOT_GRANTED; } /* @@ -4174,10 +4283,20 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, oplock_request |= INTERNAL_OPEN_ONLY; } - if ((lease != NULL) && - !lease_fname_match(req->sconn, &lease->lease_key, smb_fname)) { - status = NT_STATUS_INVALID_PARAMETER; - goto fail; + if (lease != NULL) { + status = lease_match(conn, + req, + &lease->lease_key, + smb_fname); + if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + /* Dynamic share file. No leases and update epoch... */ + lease->lease_state = SMB2_LEASE_NONE; + if (lease->lease_version > 1) { + lease->lease_epoch += 1; + } + } else if (!NT_STATUS_IS_OK(status)) { + goto fail; + } } if ((conn->fs_capabilities & FILE_NAMED_STREAMS) -- 2.1.0.rc2.206.gedb03e5 From 82f4c9124f4162ce145afe7dddbd04acbc251e13 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 14 Oct 2014 10:24:31 -0700 Subject: [PATCH 13/15] s3: smbd: leases - If find_fsp_lease() doesn't find a lease this isn't an error. As the lease exists in the oplock db, then it just must be leased in another smbd (multi-connection case). This unifies code that previously existed only inside the durable reconnect case. Signed-off-by: Jeremy Allison --- source3/smbd/durable.c | 21 +++++---------------- source3/smbd/open.c | 24 ++++++++++++++++-------- source3/smbd/proto.h | 4 +++- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c index 660865d..73bcd58 100644 --- a/source3/smbd/durable.c +++ b/source3/smbd/durable.c @@ -731,22 +731,11 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, key.data[0] = o->lease_key.data[0]; key.data[1] = o->lease_key.data[1]; - fsp->lease = find_fsp_lease(fsp, &key); - - if (fsp->lease != NULL) { - fsp->lease->ref_count += 1; - } else { - fsp->lease = talloc_zero(fsp->conn->sconn, - struct fsp_lease); - if (fsp->lease == NULL) { - TALLOC_FREE(lck); - fsp_free(fsp); - return NT_STATUS_NO_MEMORY; - } - fsp->lease->ref_count = 1; - fsp->lease->lease.lease_key = key; - fsp->lease->lease.lease_state = o->current_state; - fsp->lease->lease.lease_epoch = o->epoch; + fsp->lease = find_fsp_lease(fsp, &key, o); + if (fsp->lease == NULL) { + TALLOC_FREE(lck); + fsp_free(fsp); + return NT_STATUS_NO_MEMORY; } } diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 976dd16..8217a9a 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -1552,7 +1552,8 @@ static bool is_same_lease(const struct share_mode_data *d, } struct fsp_lease *find_fsp_lease(files_struct *new_fsp, - const struct smb2_lease_key *key) + const struct smb2_lease_key *key, + const struct share_mode_oplock *o) { files_struct *fsp; @@ -1572,11 +1573,21 @@ struct fsp_lease *find_fsp_lease(files_struct *new_fsp, continue; } if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) { + fsp->lease->ref_count += 1; return fsp->lease; } } - return NULL; + /* Not found - must be leased in another smbd. */ + new_fsp->lease = talloc_zero(new_fsp->conn->sconn, struct fsp_lease); + if (new_fsp->lease == NULL) { + return NULL; + } + new_fsp->lease->ref_count = 1; + new_fsp->lease->lease.lease_key = *key; + new_fsp->lease->lease.lease_state = o->current_state; + new_fsp->lease->lease.lease_epoch = o->epoch; + return new_fsp->lease; } static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d, @@ -1585,7 +1596,6 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d, uint32_t granted) { const struct GUID *client_guid; - struct share_mode_oplock *o; struct share_mode_oplock *tmp; NTSTATUS status; int idx; @@ -1599,20 +1609,18 @@ static NTSTATUS grant_fsp_lease(files_struct *fsp, struct share_mode_data *d, idx = find_share_mode_oplock(d, client_guid, &lease->lease_key); if (idx != -1) { - + struct share_mode_oplock *o = &d->leases[idx]; bool do_upgrade; uint32_t existing, requested; - fsp->lease = find_fsp_lease(fsp, &lease->lease_key); + fsp->lease = find_fsp_lease(fsp, &lease->lease_key, o); if (fsp->lease == NULL) { DEBUG(1, ("Did not find existing lease for file %s\n", fsp_str_dbg(fsp))); - return NT_STATUS_INTERNAL_ERROR; + return NT_STATUS_NO_MEMORY; } - fsp->lease->ref_count += 1; *p_lease_idx = idx; - o = &d->leases[idx]; /* * Upgrade only if the requested lease is a strict upgrade. diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index 08ad413..22b1a82 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -613,8 +613,10 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, const char *inherit_from_dir, const char *fname, SMB_STRUCT_STAT *psbuf); +struct share_mode_oplock; struct fsp_lease *find_fsp_lease(files_struct *new_fsp, - const struct smb2_lease_key *key); + const struct smb2_lease_key *key, + const struct share_mode_oplock *o); bool is_stat_open(uint32 access_mask); struct deferred_open_record; bool is_deferred_open_async(const struct deferred_open_record *rec); -- 2.1.0.rc2.206.gedb03e5 From 386904aeb7c3fb41702be893026d0cd3d80e3406 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Wed, 22 Oct 2014 17:53:01 -0700 Subject: [PATCH 14/15] s3: leases: Don't set fsp->oplock_type before we've granted any oplocks. It's not needed, and when we have leases it causes a crash in the call stack when truncate is requested as follows: open_file_ntcreate()->vfs_set_filelen()->smbd_contend_level2_oplocks_begin()-> contend_level2_oplocks_begin_default(). Inside which we check if fsp->oplock_type == LEASE_TYPE. In this case we have not yet added the entry in the share mode database so fsp->lease == NULL. Signed-off-by: Jeremy Allison --- source3/smbd/open.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 8217a9a..754de89 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -2688,9 +2688,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, * the open is done. */ fsp->posix_open = posix_open; - /* Ensure no SAMBA_PRIVATE bits can be set. */ - fsp->oplock_type = (oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK); - if (timeval_is_zero(&request_time)) { request_time = fsp->open_time; } -- 2.1.0.rc2.206.gedb03e5 From 60848f50f813e9e3fa0def69991e4937f2505975 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 14 Oct 2014 10:34:53 -0700 Subject: [PATCH 15/15] s3: smbd: Add "smb2 leases" parameter. Default "false". Signed-off-by: Jeremy Allison --- docs-xml/smbdotconf/locking/smb2leases.xml | 19 +++++++++++++++++++ lib/param/param_table.c | 9 +++++++++ selftest/target/Samba3.pm | 1 + source3/param/loadparm.c | 1 + source3/smbd/smb2_negprot.c | 2 +- 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 docs-xml/smbdotconf/locking/smb2leases.xml diff --git a/docs-xml/smbdotconf/locking/smb2leases.xml b/docs-xml/smbdotconf/locking/smb2leases.xml new file mode 100644 index 0000000..2927e56 --- /dev/null +++ b/docs-xml/smbdotconf/locking/smb2leases.xml @@ -0,0 +1,19 @@ + + + + This boolean option tells smbd whether to + globally negotiate SMB2 leases on file open requests. Leasing is an SMB2-only + feature which allows clients to aggressively cache files locally above and + beyond the caching allowed by SMB1 oplocks. This (experimental) parameter is + set to off by default until the SMB2 leasing code is declared fully stable. + + + +oplocks +kernel oplocks +level2 oplocks +no + diff --git a/lib/param/param_table.c b/lib/param/param_table.c index 7a13e6f..b6ff571 100644 --- a/lib/param/param_table.c +++ b/lib/param/param_table.c @@ -3010,6 +3010,15 @@ struct parm_struct parm_table[] = { .flags = FLAG_ADVANCED | FLAG_SHARE | FLAG_GLOBAL, }, { + .label = "smb2 leases", + .type = P_BOOL, + .p_class = P_GLOBAL, + .offset = GLOBAL_VAR(smb2_leases), + .special = NULL, + .enum_list = NULL, + .flags = FLAG_ADVANCED | FLAG_SHARE, + }, + { .label = "locking", .type = P_BOOL, .p_class = P_LOCAL, diff --git a/selftest/target/Samba3.pm b/selftest/target/Samba3.pm index 48b8164..ae0a3a6 100755 --- a/selftest/target/Samba3.pm +++ b/selftest/target/Samba3.pm @@ -1051,6 +1051,7 @@ sub provision($$$$$$) kernel oplocks = no kernel change notify = no + smb2 leases = yes syslog = no printing = bsd diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index d6ba8fb..4a85045 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -854,6 +854,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.smb2_max_write = DEFAULT_SMB2_MAX_WRITE; Globals.smb2_max_trans = DEFAULT_SMB2_MAX_TRANSACT; Globals.ismb2_max_credits = DEFAULT_SMB2_MAX_CREDITS; + Globals.smb2_leases = false; string_set(Globals.ctx, &Globals.ncalrpc_dir, get_dyn_NCALRPCDIR()); diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c index a27ab0c..e88165b 100644 --- a/source3/smbd/smb2_negprot.c +++ b/source3/smbd/smb2_negprot.c @@ -230,7 +230,7 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) capabilities |= SMB2_CAP_DFS; } - if (protocol >= PROTOCOL_SMB2_10) { + if (protocol >= PROTOCOL_SMB2_10 && lp_smb2_leases()) { capabilities |= SMB2_CAP_LEASING; } -- 2.1.0.rc2.206.gedb03e5