#include "../librpc/gen_ndr/ndr_security.h"
#include "../librpc/gen_ndr/open_files.h"
#include "../librpc/gen_ndr/idmap.h"
+#include "../librpc/gen_ndr/ioctl.h"
#include "passdb/lookup_sid.h"
#include "auth.h"
#include "serverid.h"
#include "messages.h"
#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "locking/leases_db.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
extern const struct generic_mapping file_generic_mapping;
return NT_STATUS_OK;
}
- status = SMB_VFS_GET_NT_ACL(conn, smb_fname->base_name,
+ status = SMB_VFS_GET_NT_ACL(conn, smb_fname,
(SECINFO_OWNER |
SECINFO_GROUP |
SECINFO_DACL), talloc_tos(), &sd);
* Samba 3.6 and earlier granted execute access even
* if the ACL did not contain execute rights.
* Samba 4.0 is more correct and checks it.
- * The compatibilty mode allows to skip this check
+ * The compatibilty mode allows one to skip this check
* to smoothen upgrades.
*/
if (lp_acl_allow_execute_always(SNUM(conn))) {
char *parent_dir = NULL;
struct security_descriptor *parent_sd = NULL;
uint32_t access_granted = 0;
+ struct smb_filename *parent_smb_fname = NULL;
if (!parent_dirname(talloc_tos(),
smb_fname->base_name,
return NT_STATUS_NO_MEMORY;
}
+ parent_smb_fname = synthetic_smb_fname(talloc_tos(),
+ parent_dir,
+ NULL,
+ NULL);
+ if (parent_smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
if (get_current_uid(conn) == (uid_t)0) {
/* I'm sorry sir, I didn't know you were root... */
DEBUG(10,("check_parent_access: root override "
}
status = SMB_VFS_GET_NT_ACL(conn,
- parent_dir,
+ parent_smb_fname,
SECINFO_DACL,
talloc_tos(),
&parent_sd);
return NT_STATUS_OK;
}
+/****************************************************************************
+ Ensure when opening a base file for a stream open that we have permissions
+ to do so given the access mask on the base file.
+****************************************************************************/
+
+static NTSTATUS check_base_file_access(struct connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask)
+{
+ NTSTATUS status;
+
+ status = smbd_calculate_access_mask(conn, smb_fname,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("smbd_calculate_access_mask "
+ "on file %s returned %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) {
+ uint32_t dosattrs;
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ dosattrs = dos_mode(conn, smb_fname);
+ if (IS_DOS_READONLY(dosattrs)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ return smbd_check_access_rights(conn,
+ smb_fname,
+ false,
+ access_mask);
+}
+
/****************************************************************************
fd support routines - attempt to do a dos_open.
****************************************************************************/
* client should be doing this.
*/
- if (fsp->posix_open || !lp_symlinks(SNUM(conn))) {
+ if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) || !lp_follow_symlinks(SNUM(conn))) {
flags |= O_NOFOLLOW;
}
#endif
const char *parent_dir,
int flags,
mode_t unx_mode,
- uint32 access_mask, /* client requested access mask. */
- uint32 open_access_mask, /* what we're actually using in the open. */
+ uint32_t access_mask, /* client requested access mask. */
+ uint32_t open_access_mask, /* what we're actually using in the open. */
bool *p_file_created)
{
struct smb_filename *smb_fname = fsp->fsp_name;
wild = smb_fname->base_name;
}
if ((local_flags & O_CREAT) && !file_existed &&
+ !(fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) &&
ms_has_wild(wild)) {
return NT_STATUS_OBJECT_NAME_INVALID;
}
smb_fname,
false,
access_mask);
- } else if (local_flags & O_CREAT){
- status = check_parent_access(conn,
- smb_fname,
- SEC_DIR_ADD_FILE);
- } else {
- /* File didn't exist and no O_CREAT. */
- return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("open_file: "
+ "smbd_check_access_rights "
+ "on file %s returned %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ }
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ return status;
+ }
+
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ DEBUG(10, ("open_file: "
+ "file %s vanished since we "
+ "checked for existence.\n",
+ smb_fname_str_dbg(smb_fname)));
+ file_existed = false;
+ SET_STAT_INVALID(fsp->fsp_name->st);
+ }
}
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(10,("open_file: "
- "%s on file "
- "%s returned %s\n",
- file_existed ?
- "smbd_check_access_rights" :
- "check_parent_access",
- smb_fname_str_dbg(smb_fname),
- nt_errstr(status) ));
- return status;
+
+ if (!file_existed) {
+ if (!(local_flags & O_CREAT)) {
+ /* File didn't exist and no O_CREAT. */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = check_parent_access(conn,
+ smb_fname,
+ SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("open_file: "
+ "check_parent_access on "
+ "file %s returned %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status) ));
+ return status;
+ }
}
}
- /* Actually do the open */
- status = fd_open_atomic(conn, fsp, local_flags,
+ /*
+ * Actually do the open - if O_TRUNC is needed handle it
+ * below under the share mode lock.
+ */
+ status = fd_open_atomic(conn, fsp, local_flags & ~O_TRUNC,
unx_mode, p_file_created);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(3,("Error opening file %s (%s) (local_flags=%d) "
in the stat struct in fsp->fsp_name. */
/* Inherit the ACL if required */
- if (lp_inherit_perms(SNUM(conn))) {
+ if (lp_inherit_permissions(SNUM(conn))) {
inherit_access_posix_acl(conn, parent_dir,
smb_fname->base_name,
unx_mode);
access_mask);
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
- fsp->posix_open &&
+ (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) &&
S_ISLNK(smb_fname->st.st_ex_mode)) {
/* This is a POSIX stat open for delete
* or rename on a symlink that points
****************************************************************************/
static bool share_conflict(struct share_mode_entry *entry,
- uint32 access_mask,
- uint32 share_access)
+ uint32_t access_mask,
+ uint32_t share_access)
{
DEBUG(10,("share_conflict: entry->access_mask = 0x%x, "
"entry->share_access = 0x%x, "
return;
}
+ if (share_entry->op_mid == 0) {
+ /* INTERNAL_OPEN_ONLY */
+ return;
+ }
+
if (!is_valid_share_mode_entry(share_entry)) {
return;
}
"share entry with an open file\n");
}
- if (((uint16)fsp->oplock_type) != share_entry->op_type) {
+ if (((uint16_t)fsp->oplock_type) != share_entry->op_type) {
goto panic;
}
}
#endif
-bool is_stat_open(uint32 access_mask)
+bool is_stat_open(uint32_t access_mask)
{
const uint32_t stat_open_bits =
(SYNCHRONIZE_ACCESS|
/****************************************************************************
Deal with share modes
- Invarient: Share mode must be locked on entry and exit.
+ Invariant: Share mode must be locked on entry and exit.
Returns -1 on error, or number of share modes on success (may be zero).
****************************************************************************/
static NTSTATUS open_mode_check(connection_struct *conn,
struct share_mode_lock *lck,
- uint32 access_mask,
- uint32 share_access)
+ uint32_t access_mask,
+ uint32_t share_access)
{
int i;
* our client.
*/
-static NTSTATUS send_break_message(files_struct *fsp,
- struct share_mode_entry *exclusive,
- uint64_t mid,
- int oplock_request)
+NTSTATUS send_break_message(struct messaging_context *msg_ctx,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to)
{
NTSTATUS status;
char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+ struct server_id_buf tmp;
DEBUG(10, ("Sending break request to PID %s\n",
- procid_str_static(&exclusive->pid)));
- exclusive->op_mid = mid;
+ server_id_str_buf(exclusive->pid, &tmp)));
/* Create the message. */
share_mode_entry_to_message(msg, exclusive);
- status = messaging_send_buf(fsp->conn->sconn->msg_ctx, exclusive->pid,
+ /* Overload entry->op_type */
+ /*
+ * This is a cut from uint32_t to uint16_t, 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,
MSG_SMB_BREAK_REQUEST,
- (uint8 *)msg, sizeof(msg));
+ (uint8_t *)msg, sizeof(msg));
if (!NT_STATUS_IS_OK(status)) {
DEBUG(3, ("Could not send oplock break message: %s\n",
nt_errstr(status)));
}
/*
- * Return share_mode_entry pointers for :
- * 1). Batch oplock entry.
- * 2). Batch or exclusive oplock entry (may be identical to #1).
- * bool have_level2_oplock
- * bool have_no_oplock.
* Do internal consistency checks on the share mode for a file.
*/
-static bool validate_oplock_types(files_struct *fsp,
- int oplock_request,
- struct share_mode_lock *lck)
+static bool validate_oplock_types(struct share_mode_lock *lck)
{
struct share_mode_data *d = lck->data;
bool batch = false;
bool ex_or_batch = false;
bool level2 = false;
bool no_oplock = false;
+ uint32_t num_non_stat_opens = 0;
uint32_t i;
- /* Ignore stat or internal opens, as is done in
- delay_for_batch_oplocks() and
- delay_for_exclusive_oplocks().
- */
- if ((oplock_request & INTERNAL_OPEN_ONLY) || is_stat_open(fsp->access_mask)) {
- return true;
- }
-
for (i=0; i<d->num_share_modes; i++) {
struct share_mode_entry *e = &d->share_modes[i];
continue;
}
+ if (e->op_mid == 0) {
+ /* INTERNAL_OPEN_ONLY */
+ continue;
+ }
+
if (e->op_type == NO_OPLOCK && is_stat_open(e->access_mask)) {
/* We ignore stat opens in the table - they
always have NO_OPLOCK and never get or
continue;
}
+ num_non_stat_opens += 1;
+
if (BATCH_OPLOCK_TYPE(e->op_type)) {
/* batch - can only be one. */
if (share_mode_stale_pid(d, i)) {
remove_stale_share_mode_entries(d);
- if ((batch || ex_or_batch) && (d->num_share_modes != 1)) {
+ if ((batch || ex_or_batch) && (num_non_stat_opens != 1)) {
DEBUG(1, ("got batch (%d) or ex (%d) non-exclusively (%d)\n",
(int)batch, (int)ex_or_batch,
(int)d->num_share_modes));
}
static bool delay_for_oplock(files_struct *fsp,
- uint64_t mid,
int oplock_request,
+ const struct smb2_lease *lease,
struct share_mode_lock *lck,
- bool have_sharing_violation)
+ bool have_sharing_violation,
+ uint32_t create_disposition,
+ bool first_open_attempt)
{
struct share_mode_data *d = lck->data;
- struct share_mode_entry *entry;
+ uint32_t i;
+ bool delay = 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;
}
- if (lck->data->num_share_modes != 1) {
- /*
- * More than one. There can't be any exclusive or batch left.
- */
- return false;
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE:
+ case FILE_OVERWRITE_IF:
+ will_overwrite = true;
+ break;
+ default:
+ will_overwrite = false;
+ break;
}
- entry = &d->share_modes[0];
- 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.
- */
+ for (i=0; i<d->num_share_modes; i++) {
+ struct share_mode_entry *e = &d->share_modes[i];
+ struct share_mode_lease *l = NULL;
+ uint32_t e_lease_type = get_lease_type(d, e);
+ uint32_t break_to;
+ uint32_t delay_mask = 0;
+
+ if (e->op_type == LEASE_OPLOCK) {
+ l = &d->leases[e->lease_idx];
+ }
+
+ if (have_sharing_violation) {
+ delay_mask = SMB2_LEASE_HANDLE;
+ } else {
+ delay_mask = SMB2_LEASE_WRITE;
+ }
+
+ break_to = e_lease_type & ~delay_mask;
+
+ if (will_overwrite) {
+ /*
+ * we'll decide about SMB2_LEASE_READ later.
+ *
+ * Maybe the break will be defered
+ */
+ break_to &= ~SMB2_LEASE_HANDLE;
+ }
+
+ DEBUG(10, ("entry %u: e_lease_type %u, will_overwrite: %u\n",
+ (unsigned)i, (unsigned)e_lease_type,
+ (unsigned)will_overwrite));
+
+ if (lease != NULL && l != NULL) {
+ bool ign;
+
+ ign = smb2_lease_equal(fsp_client_guid(fsp),
+ &lease->lease_key,
+ &l->client_guid,
+ &l->lease_key);
+ if (ign) {
+ continue;
+ }
+ }
+
+ if ((e_lease_type & ~break_to) == 0) {
+ if (l != NULL && l->breaking) {
+ delay = true;
+ }
+ continue;
+ }
+
+ if (share_mode_stale_pid(d, i)) {
+ continue;
+ }
+
+ if (will_overwrite) {
+ /*
+ * If we break anyway break to NONE directly.
+ * Otherwise vfs_set_filelen() will trigger the
+ * break.
+ */
+ break_to &= ~(SMB2_LEASE_READ|SMB2_LEASE_WRITE);
+ }
+
+ if (e->op_type != LEASE_OPLOCK) {
+ /*
+ * Oplocks only support breaking to R or NONE.
+ */
+ break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
+ }
+
+ DEBUG(10, ("breaking from %d to %d\n",
+ (int)e_lease_type, (int)break_to));
+ send_break_message(fsp->conn->sconn->msg_ctx, e,
+ break_to);
+ if (e_lease_type & delay_mask) {
+ delay = true;
+ }
+ if (l != NULL && l->breaking && !first_open_attempt) {
+ delay = true;
+ }
+ continue;
+ }
+
+ return delay;
+}
+
+static bool file_has_brlocks(files_struct *fsp)
+{
+ struct byte_range_lock *br_lck;
+
+ br_lck = brl_get_locks_readonly(fsp);
+ if (!br_lck)
return false;
+
+ return (brl_num_locks(br_lck) > 0);
+}
+
+int find_share_mode_lease(struct share_mode_data *d,
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *key)
+{
+ uint32_t i;
+
+ for (i=0; i<d->num_leases; i++) {
+ struct share_mode_lease *l = &d->leases[i];
+
+ if (smb2_lease_equal(client_guid,
+ key,
+ &l->client_guid,
+ &l->lease_key)) {
+ return i;
+ }
}
- if (have_sharing_violation && (entry->op_type & BATCH_OPLOCK)) {
- if (share_mode_stale_pid(d, 0)) {
- return false;
+ return -1;
+}
+
+struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
+ const struct smb2_lease_key *key,
+ const struct share_mode_lease *l)
+{
+ struct 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;
}
- send_break_message(fsp, entry, mid, oplock_request);
- return true;
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ continue;
+ }
+ if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) {
+ fsp->lease->ref_count += 1;
+ return fsp->lease;
+ }
+ }
+
+ /* 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;
}
- if (have_sharing_violation) {
+ new_fsp->lease->ref_count = 1;
+ new_fsp->lease->sconn = new_fsp->conn->sconn;
+ new_fsp->lease->lease.lease_key = *key;
+ new_fsp->lease->lease.lease_state = l->current_state;
+ /*
+ * We internally treat all leases as V2 and update
+ * the epoch, but when sending breaks it matters if
+ * the requesting lease was v1 or v2.
+ */
+ new_fsp->lease->lease.lease_version = l->lease_version;
+ new_fsp->lease->lease.lease_epoch = l->epoch;
+ return new_fsp->lease;
+}
+
+static NTSTATUS grant_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct smb2_lease *lease,
+ uint32_t *p_lease_idx,
+ uint32_t granted)
+{
+ struct share_mode_data *d = lck->data;
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ struct share_mode_lease *tmp;
+ NTSTATUS status;
+ int idx;
+
+ idx = find_share_mode_lease(d, client_guid, &lease->lease_key);
+
+ if (idx != -1) {
+ struct share_mode_lease *l = &d->leases[idx];
+ bool do_upgrade;
+ uint32_t existing, requested;
+
+ fsp->lease = find_fsp_lease(fsp, &lease->lease_key, l);
+ if (fsp->lease == NULL) {
+ DEBUG(1, ("Did not find existing lease for file %s\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *p_lease_idx = idx;
+
/*
- * Non-batch exclusive is not broken if we have a sharing
- * violation
+ * Upgrade only if the requested lease is a strict upgrade.
*/
- return false;
- }
- if (!EXCLUSIVE_OPLOCK_TYPE(entry->op_type)) {
+ existing = l->current_state;
+ requested = lease->lease_state;
+
/*
- * No break for NO_OPLOCK or LEVEL2_OPLOCK oplocks
+ * Tricky: This test makes sure that "requested" is a
+ * strict bitwise superset of "existing".
*/
- return false;
+ do_upgrade = ((existing & requested) == existing);
+
+ /*
+ * Upgrade only if there's a change.
+ */
+ do_upgrade &= (granted != existing);
+
+ /*
+ * Upgrade only if other leases don't prevent what was asked
+ * for.
+ */
+ do_upgrade &= (granted == requested);
+
+ /*
+ * only upgrade if we are not in breaking state
+ */
+ do_upgrade &= !l->breaking;
+
+ DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", "
+ "granted=%"PRIu32", do_upgrade=%d\n",
+ existing, requested, granted, (int)do_upgrade));
+
+ if (do_upgrade) {
+ l->current_state = granted;
+ l->epoch += 1;
+ }
+
+ /* Ensure we're in sync with current lease state. */
+ fsp_lease_update(lck, fsp_client_guid(fsp), fsp->lease);
+ return NT_STATUS_OK;
}
- if (share_mode_stale_pid(d, 0)) {
- return false;
+
+ /*
+ * Create new lease
+ */
+
+ tmp = talloc_realloc(d, d->leases, struct share_mode_lease,
+ d->num_leases+1);
+ if (tmp == NULL) {
+ /*
+ * See [MS-SMB2]
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ d->leases = tmp;
+
+ fsp->lease = talloc_zero(fsp->conn->sconn, struct fsp_lease);
+ if (fsp->lease == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ fsp->lease->ref_count = 1;
+ fsp->lease->sconn = fsp->conn->sconn;
+ fsp->lease->lease.lease_version = lease->lease_version;
+ fsp->lease->lease.lease_key = lease->lease_key;
+ fsp->lease->lease.lease_state = granted;
+ fsp->lease->lease.lease_epoch = lease->lease_epoch + 1;
+
+ *p_lease_idx = d->num_leases;
+
+ d->leases[d->num_leases] = (struct share_mode_lease) {
+ .client_guid = *client_guid,
+ .lease_key = fsp->lease->lease.lease_key,
+ .current_state = fsp->lease->lease.lease_state,
+ .lease_version = fsp->lease->lease.lease_version,
+ .epoch = fsp->lease->lease.lease_epoch,
+ };
+
+ status = leases_db_add(client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ fsp->conn->connectpath,
+ 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;
}
- send_break_message(fsp, entry, mid, oplock_request);
- return true;
+ d->num_leases += 1;
+ d->modified = true;
+
+ return NT_STATUS_OK;
}
-static bool file_has_brlocks(files_struct *fsp)
+static bool is_same_lease(const files_struct *fsp,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ const struct smb2_lease *lease)
{
- struct byte_range_lock *br_lck;
-
- br_lck = brl_get_locks_readonly(fsp);
- if (!br_lck)
+ if (e->op_type != LEASE_OPLOCK) {
+ return false;
+ }
+ if (lease == NULL) {
return false;
+ }
- return (brl_num_locks(br_lck) > 0);
+ return smb2_lease_equal(fsp_client_guid(fsp),
+ &lease->lease_key,
+ &d->leases[e->lease_idx].client_guid,
+ &d->leases[e->lease_idx].lease_key);
}
-static void grant_fsp_oplock_type(files_struct *fsp,
- struct share_mode_lock *lck,
- int oplock_request)
+static NTSTATUS grant_fsp_oplock_type(struct smb_request *req,
+ struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ int oplock_request,
+ struct smb2_lease *lease)
{
- bool allow_level2 = (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
- lp_level2_oplocks(SNUM(fsp->conn));
- bool got_level2_oplock, got_a_none_oplock;
+ struct share_mode_data *d = lck->data;
+ bool got_handle_lease = false;
+ bool got_oplock = false;
uint32_t i;
-
- /* 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);
+ uint32_t granted;
+ uint32_t lease_idx = UINT32_MAX;
+ bool ok;
+ NTSTATUS status;
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) {
+ if (lease == NULL) {
+ /*
+ * The SMB2 layer should have checked this
+ */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ granted = lease->lease_state;
+
+ if (lp_kernel_oplocks(SNUM(fsp->conn))) {
+ DEBUG(10, ("No lease granted because kernel oplocks are enabled\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if ((granted & (SMB2_LEASE_READ|SMB2_LEASE_WRITE)) == 0) {
+ DEBUG(10, ("No read or write lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if (granted == SMB2_LEASE_WRITE) {
+ DEBUG(10, ("pure write lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if (granted == (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ DEBUG(10, ("write and handle lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ } else {
+ granted = map_oplock_to_lease_type(
+ oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
}
if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) {
DEBUG(10,("grant_fsp_oplock_type: file %s has byte range locks\n",
fsp_str_dbg(fsp)));
- fsp->oplock_type = NO_OPLOCK;
+ 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;
- }
+ for (i=0; i<d->num_share_modes; i++) {
+ struct share_mode_entry *e = &d->share_modes[i];
+ uint32_t e_lease_type;
- got_level2_oplock = false;
- got_a_none_oplock = false;
+ e_lease_type = get_lease_type(d, e);
- for (i=0; i<lck->data->num_share_modes; i++) {
- int op_type = lck->data->share_modes[i].op_type;
+ if ((granted & SMB2_LEASE_WRITE) &&
+ !is_same_lease(fsp, d, e, lease) &&
+ !share_mode_stale_pid(d, i)) {
+ /*
+ * Can grant only one writer
+ */
+ granted &= ~SMB2_LEASE_WRITE;
+ }
- if (LEVEL_II_OPLOCK_TYPE(op_type)) {
- got_level2_oplock = true;
+ if ((e_lease_type & SMB2_LEASE_HANDLE) && !got_handle_lease &&
+ !share_mode_stale_pid(d, i)) {
+ got_handle_lease = true;
}
- if (op_type == NO_OPLOCK) {
- got_a_none_oplock = true;
+
+ if ((e->op_type != LEASE_OPLOCK) && !got_oplock &&
+ !share_mode_stale_pid(d, i)) {
+ got_oplock = true;
}
}
- /*
- * Match what was requested (fsp->oplock_type) with
- * what was found in the existing share modes.
- */
+ if ((granted & SMB2_LEASE_READ) && !(granted & SMB2_LEASE_WRITE)) {
+ bool allow_level2 =
+ (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+ lp_level2_oplocks(SNUM(fsp->conn));
- if (got_level2_oplock || got_a_none_oplock) {
- if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
- fsp->oplock_type = LEVEL_II_OPLOCK;
+ if (!allow_level2) {
+ granted = SMB2_LEASE_NONE;
}
}
- /*
- * Don't grant level2 to clients that don't want them
- * or if we've turned them off.
- */
- if (fsp->oplock_type == LEVEL_II_OPLOCK && !allow_level2) {
- fsp->oplock_type = NO_OPLOCK;
- }
+ if (oplock_request == LEASE_OPLOCK) {
+ if (got_oplock) {
+ granted &= ~SMB2_LEASE_HANDLE;
+ }
- if (fsp->oplock_type == LEVEL_II_OPLOCK && !got_level2_oplock) {
- /*
- * We're the first level2 oplock. Indicate that in brlock.tdb.
- */
- struct byte_range_lock *brl;
+ fsp->oplock_type = LEASE_OPLOCK;
- brl = brl_get_locks(talloc_tos(), fsp);
- if (brl != NULL) {
- brl_set_have_read_oplocks(brl, true);
- TALLOC_FREE(brl);
+ status = grant_fsp_lease(fsp, lck, lease, &lease_idx,
+ granted);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+
+ }
+ *lease = fsp->lease->lease;
+ DEBUG(10, ("lease_state=%d\n", lease->lease_state));
+ } else {
+ if (got_handle_lease) {
+ granted = SMB2_LEASE_NONE;
}
+
+ switch (granted) {
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE:
+ fsp->oplock_type = BATCH_OPLOCK|EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE:
+ fsp->oplock_type = EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_HANDLE:
+ case SMB2_LEASE_READ:
+ fsp->oplock_type = LEVEL_II_OPLOCK;
+ break;
+ default:
+ fsp->oplock_type = NO_OPLOCK;
+ break;
+ }
+
+ status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Could not get the kernel oplock
+ */
+ fsp->oplock_type = NO_OPLOCK;
+ }
+ }
+
+ ok = set_share_mode(lck, fsp, get_current_uid(fsp->conn),
+ req ? req->mid : 0,
+ fsp->oplock_type,
+ lease_idx);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = update_num_read_oplocks(fsp, lck);
+ if (!ok) {
+ del_share_mode(lck, fsp);
+ return NT_STATUS_INTERNAL_ERROR;
}
DEBUG(10,("grant_fsp_oplock_type: oplock type 0x%x on file %s\n",
fsp->oplock_type, fsp_str_dbg(fsp)));
+
+ return NT_STATUS_OK;
}
static bool request_timed_out(struct timeval request_time,
}
struct defer_open_state {
- struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
uint64_t mid;
};
struct smb_request *req,
struct deferred_open_record *state)
{
+ struct deferred_open_record *open_rec;
+
DEBUG(10,("defer_open_sharing_error: time [%u.%06u] adding deferred "
"open entry for mid %llu\n",
(unsigned int)request_time.tv_sec,
(unsigned int)request_time.tv_usec,
(unsigned long long)req->mid));
- if (!push_deferred_open_message_smb(req, request_time, timeout,
- state->id, (char *)state, sizeof(*state))) {
+ open_rec = talloc(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
TALLOC_FREE(lck);
- exit_server("push_deferred_open_message_smb failed");
+ exit_server("talloc failed");
}
+
+ *open_rec = *state;
+
if (lck) {
struct defer_open_state *watch_state;
struct tevent_req *watch_req;
bool ret;
- watch_state = talloc(req->sconn, struct defer_open_state);
+ watch_state = talloc(open_rec, struct defer_open_state);
if (watch_state == NULL) {
exit_server("talloc failed");
}
- watch_state->sconn = req->sconn;
+ watch_state->xconn = req->xconn;
watch_state->mid = req->mid;
DEBUG(10, ("defering mid %llu\n",
timeval_sum(&request_time, &timeout));
SMB_ASSERT(ret);
}
+
+ if (!push_deferred_open_message_smb(req, request_time, timeout,
+ state->id, open_rec)) {
+ TALLOC_FREE(lck);
+ exit_server("push_deferred_open_message_smb failed");
+ }
}
static void defer_open_done(struct tevent_req *req)
DEBUG(10, ("scheduling mid %llu\n", (unsigned long long)state->mid));
- ret = schedule_deferred_open_message_smb(state->sconn, state->mid);
+ ret = schedule_deferred_open_message_smb(state->xconn, state->mid);
SMB_ASSERT(ret);
TALLOC_FREE(state);
}
****************************************************************************/
static bool open_match_attributes(connection_struct *conn,
- uint32 old_dos_attr,
- uint32 new_dos_attr,
+ uint32_t old_dos_attr,
+ uint32_t new_dos_attr,
mode_t existing_unx_mode,
mode_t new_unx_mode,
mode_t *returned_unx_mode)
{
- uint32 noarch_old_dos_attr, noarch_new_dos_attr;
+ uint32_t noarch_old_dos_attr, noarch_new_dos_attr;
noarch_old_dos_attr = (old_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
noarch_new_dos_attr = (new_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
files_struct *fsp_to_dup_into,
const struct smb_filename *smb_fname,
struct file_id id,
- uint16 file_pid,
+ uint16_t file_pid,
uint64_t vuid,
- uint32 access_mask,
- uint32 share_access,
- uint32 create_options)
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_options)
{
files_struct *fsp;
}
static void schedule_defer_open(struct share_mode_lock *lck,
+ struct file_id id,
struct timeval request_time,
struct smb_request *req)
{
state.delayed_for_oplocks = True;
state.async_open = false;
- state.id = lck->data->id;
+ state.id = id;
if (!request_timed_out(request_time, timeout)) {
defer_open(lck, request_time, timeout, req, &state);
return NT_STATUS_OK;
}
- status = SMB_VFS_GET_NT_ACL(conn, smb_fname->base_name,
+ status = SMB_VFS_GET_NT_ACL(conn, smb_fname,
(SECINFO_OWNER |
SECINFO_GROUP |
SECINFO_DACL),
Return true if this is a state pointer to an asynchronous create.
****************************************************************************/
-bool is_deferred_open_async(const void *ptr)
+bool is_deferred_open_async(const struct deferred_open_record *rec)
{
- const struct deferred_open_record *state = (const struct deferred_open_record *)ptr;
-
- return state->async_open;
+ return rec->async_open;
}
static bool clear_ads(uint32_t create_disposition)
}
static int calculate_open_access_flags(uint32_t access_mask,
- int oplock_request,
uint32_t private_flags)
{
bool need_write, need_read;
static NTSTATUS open_file_ntcreate(connection_struct *conn,
struct smb_request *req,
- uint32 access_mask, /* access bits (FILE_READ_DATA etc.) */
- uint32 share_access, /* share constants (FILE_SHARE_READ etc) */
- uint32 create_disposition, /* FILE_OPEN_IF etc. */
- uint32 create_options, /* options such as delete on close. */
- uint32 new_dos_attributes, /* attributes used for new file. */
+ uint32_t access_mask, /* access bits (FILE_READ_DATA etc.) */
+ uint32_t share_access, /* share constants (FILE_SHARE_READ etc) */
+ uint32_t create_disposition, /* FILE_OPEN_IF etc. */
+ uint32_t create_options, /* options such as delete on close. */
+ uint32_t new_dos_attributes, /* attributes used for new file. */
int oplock_request, /* internal Samba oplock codes. */
+ struct smb2_lease *lease,
/* Information (FILE_EXISTS etc.) */
uint32_t private_flags, /* Samba specific flags. */
int *pinfo,
mode_t new_unx_mode = (mode_t)0;
mode_t unx_mode = (mode_t)0;
int info;
- uint32 existing_dos_attributes = 0;
+ uint32_t existing_dos_attributes = 0;
struct timeval request_time = timeval_zero();
struct share_mode_lock *lck = NULL;
- uint32 open_access_mask = access_mask;
+ uint32_t open_access_mask = access_mask;
NTSTATUS status;
char *parent_dir;
SMB_STRUCT_STAT saved_stat = smb_fname->st;
create_options, (unsigned int)unx_mode, oplock_request,
(unsigned int)private_flags));
- if ((req == NULL) && ((oplock_request & INTERNAL_OPEN_ONLY) == 0)) {
- DEBUG(0, ("No smb request but not an internal only open!\n"));
- return NT_STATUS_INTERNAL_ERROR;
+ if (req == NULL) {
+ /* Ensure req == NULL means INTERNAL_OPEN_ONLY */
+ SMB_ASSERT(((oplock_request & INTERNAL_OPEN_ONLY) != 0));
+ } else {
+ /* And req != NULL means no INTERNAL_OPEN_ONLY */
+ SMB_ASSERT(((oplock_request & INTERNAL_OPEN_ONLY) == 0));
}
/*
*/
if (req) {
- void *ptr;
+ struct deferred_open_record *open_rec;
if (get_deferred_open_message_state(req,
&request_time,
- &ptr)) {
+ &open_rec)) {
/* Remember the absolute time of the original
request with this mid. We'll use it later to
see if this has timed out. */
/* If it was an async create retry, the file
didn't exist. */
- if (is_deferred_open_async(ptr)) {
+ if (is_deferred_open_async(open_rec)) {
SET_STAT_INVALID(smb_fname->st);
file_existed = false;
}
/* Ensure we don't reprocess this message. */
- remove_deferred_open_message_smb(req->sconn, req->mid);
+ remove_deferred_open_message_smb(req->xconn, req->mid);
first_open_attempt = false;
}
}
/* this is for OS/2 long file names - say we don't support them */
- if (!lp_posix_pathnames() && strstr(smb_fname->base_name,".+,;=[].")) {
+ if (req != NULL && !req->posix_pathnames &&
+ strstr(smb_fname->base_name,".+,;=[].")) {
/* OS/2 Workplace shell fix may be main code stream in a later
* release. */
DEBUG(5,("open_file_ntcreate: OS/2 long filenames are not "
* mean the same thing under DOS and Unix.
*/
- flags = calculate_open_access_flags(access_mask, oplock_request,
- private_flags);
+ flags = calculate_open_access_flags(access_mask, private_flags);
/*
* Currently we only look at FILE_WRITE_THROUGH for create options.
fsp->access_mask = open_access_mask; /* We change this to the
* requested access_mask after
* 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 (posix_open) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
if (timeval_is_zero(&request_time)) {
request_time = fsp->open_time;
return NT_STATUS_SHARING_VIOLATION;
}
- if (!validate_oplock_types(fsp, 0, lck)) {
+ if (!validate_oplock_types(lck)) {
smb_panic("validate_oplock_types failed");
}
- if (delay_for_oplock(fsp, req->mid, 0, lck, false)) {
- schedule_defer_open(lck, request_time, req);
+ if (delay_for_oplock(fsp, 0, lease, lck, false,
+ create_disposition, first_open_attempt)) {
+ schedule_defer_open(lck, fsp->file_id, request_time,
+ req);
TALLOC_FREE(lck);
DEBUG(10, ("Sent oplock break request to kernel "
"oplock holder\n"));
*/
state.delayed_for_oplocks = false;
state.async_open = false;
- state.id = lck->data->id;
+ state.id = fsp->file_id;
defer_open(lck, request_time, timeval_set(0, 0), req, &state);
TALLOC_FREE(lck);
DEBUG(10, ("No Samba oplock around after EWOULDBLOCK. "
return fsp_open;
}
+ if (new_file_created) {
+ /*
+ * As we atomically create using O_CREAT|O_EXCL,
+ * then if new_file_created is true, then
+ * file_existed *MUST* have been false (even
+ * if the file was previously detected as being
+ * there).
+ */
+ file_existed = false;
+ }
+
if (file_existed && !check_same_dev_ino(&saved_stat, &smb_fname->st)) {
/*
* The file did exist, but some other (local or NFS)
}
/* Get the types we need to examine. */
- if (!validate_oplock_types(fsp, oplock_request, lck)) {
+ if (!validate_oplock_types(lck)) {
smb_panic("validate_oplock_types failed");
}
if ((req != NULL) &&
delay_for_oplock(
- fsp, req->mid, oplock_request, lck,
- NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION))) {
- schedule_defer_open(lck, request_time, req);
+ fsp, oplock_request, lease, lck,
+ NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION),
+ create_disposition, first_open_attempt)) {
+ schedule_defer_open(lck, fsp->file_id, request_time, req);
TALLOC_FREE(lck);
fd_close(fsp);
return NT_STATUS_SHARING_VIOLATION;
}
if (!NT_STATUS_IS_OK(status)) {
- uint32 can_access_mask;
+ uint32_t can_access_mask;
bool can_access = True;
SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION));
return status;
}
- grant_fsp_oplock_type(fsp, lck, oplock_request);
+ /* Should we atomically (to the client at least) truncate ? */
+ if ((!new_file_created) &&
+ (flags2 & O_TRUNC) &&
+ (!S_ISFIFO(fsp->fsp_name->st.st_ex_mode))) {
+ int ret;
+
+ ret = vfs_set_filelen(fsp, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ return status;
+ }
+ }
/*
* We have the share entry *locked*.....
if (fsp->fh->fd != -1 && lp_kernel_share_modes(SNUM(conn))) {
int ret_flock;
+ /*
+ * Beware: streams implementing VFS modules may
+ * implement streams in a way that fsp will have the
+ * basefile open in the fsp fd, so lacking a distinct
+ * fd for the stream kernel_flock will apply on the
+ * basefile which is wrong. The actual check is
+ * deffered to the VFS module implementing the
+ * kernel_flock call.
+ */
ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, share_access, access_mask);
if(ret_flock == -1 ){
}
if (file_existed) {
- /* stat opens on existing files don't get oplocks. */
- if (is_stat_open(open_access_mask)) {
- fsp->oplock_type = NO_OPLOCK;
+ /*
+ * stat opens on existing files don't get oplocks.
+ * They can get leases.
+ *
+ * Note that we check for stat open on the *open_access_mask*,
+ * i.e. the access mask we actually used to do the open,
+ * not the one the client asked for (which is in
+ * fsp->access_mask). This is due to the fact that
+ * FILE_OVERWRITE and FILE_OVERWRITE_IF add in O_TRUNC,
+ * which adds FILE_WRITE_DATA to open_access_mask.
+ */
+ if (is_stat_open(open_access_mask) && lease == NULL) {
+ oplock_request = NO_OPLOCK;
}
}
* Setup the oplock info in both the shared memory and
* file structs.
*/
-
- status = set_file_oplock(fsp, fsp->oplock_type);
+ status = grant_fsp_oplock_type(req, fsp, lck, oplock_request, 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. */
}
if (info != FILE_WAS_OPENED) {
- /* Files should be initially set as archive */
- if (lp_map_archive(SNUM(conn)) ||
+ /* Overwritten files should be initially set as archive */
+ if ((info == FILE_WAS_OVERWRITTEN && lp_map_archive(SNUM(conn))) ||
lp_store_dos_attributes(SNUM(conn))) {
if (!posix_open) {
if (file_set_dosmode(conn, smb_fname,
(unsigned int)new_unx_mode));
}
- TALLOC_FREE(lck);
-
- return NT_STATUS_OK;
-}
-
-
-/****************************************************************************
- Open a file for for write to ensure that we can fchmod it.
-****************************************************************************/
+ {
+ /*
+ * Deal with other opens having a modified write time.
+ */
+ struct timespec write_time = get_share_mode_write_time(lck);
-NTSTATUS open_file_fchmod(connection_struct *conn,
- struct smb_filename *smb_fname,
- files_struct **result)
-{
- if (!VALID_STAT(smb_fname->st)) {
- return NT_STATUS_INVALID_PARAMETER;
+ if (!null_timespec(write_time)) {
+ update_stat_ex_mtime(&fsp->fsp_name->st, write_time);
+ }
}
- return SMB_VFS_CREATE_FILE(
- conn, /* conn */
- NULL, /* req */
- 0, /* root_dir_fid */
- smb_fname, /* fname */
- FILE_WRITE_DATA, /* access_mask */
- (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
- FILE_SHARE_DELETE),
- FILE_OPEN, /* create_disposition*/
- 0, /* create_options */
- 0, /* file_attributes */
- INTERNAL_OPEN_ONLY, /* oplock_request */
- 0, /* allocation_size */
- 0, /* private_flags */
- NULL, /* sd */
- NULL, /* ea_list */
- result, /* result */
- NULL); /* pinfo */
+ TALLOC_FREE(lck);
+
+ return NT_STATUS_OK;
}
static NTSTATUS mkdir_internal(connection_struct *conn,
struct smb_filename *smb_dname,
- uint32 file_attributes)
+ uint32_t file_attributes)
{
mode_t mode;
char *parent_dir = NULL;
}
}
- if (lp_inherit_perms(SNUM(conn))) {
+ if (lp_inherit_permissions(SNUM(conn))) {
inherit_access_posix_acl(conn, parent_dir,
smb_dname->base_name, mode);
need_re_stat = true;
static NTSTATUS open_directory(connection_struct *conn,
struct smb_request *req,
struct smb_filename *smb_dname,
- uint32 access_mask,
- uint32 share_access,
- uint32 create_disposition,
- uint32 create_options,
- uint32 file_attributes,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
int *pinfo,
files_struct **result)
{
NTSTATUS status;
struct timespec mtimespec;
int info = 0;
+ bool ok;
if (is_ntfs_stream_smb_fname(smb_dname)) {
DEBUG(2, ("open_directory: %s is a stream name!\n",
nt_errstr(status)));
return status;
}
+
+ /*
+ * If mkdir_internal() returned
+ * NT_STATUS_OBJECT_NAME_COLLISION
+ * we still must lstat the path.
+ */
+
+ if (SMB_VFS_LSTAT(conn, smb_dname)
+ == -1) {
+ DEBUG(2, ("Could not stat "
+ "directory '%s' just "
+ "opened: %s\n",
+ smb_fname_str_dbg(
+ smb_dname),
+ strerror(errno)));
+ return map_nt_error_from_unix(
+ errno);
+ }
+
info = FILE_WAS_OPENED;
}
}
fsp->oplock_type = NO_OPLOCK;
fsp->sent_oplock_break = NO_BREAK_SENT;
fsp->is_directory = True;
- fsp->posix_open = (file_attributes & FILE_FLAG_POSIX_SEMANTICS) ? True : False;
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
status = fsp_set_smb_fname(fsp, smb_dname);
if (!NT_STATUS_IS_OK(status)) {
file_free(req, fsp);
return status;
}
- mtimespec = smb_dname->st.st_ex_mtime;
-
+ /* Don't store old timestamps for directory
+ handles in the internal database. We don't
+ update them in there if new objects
+ are creaded in the directory. Currently
+ we only update timestamps on file writes.
+ See bug #9870.
+ */
+ ZERO_STRUCT(mtimespec);
+
+ if (access_mask & (FILE_LIST_DIRECTORY|
+ FILE_ADD_FILE|
+ FILE_ADD_SUBDIRECTORY|
+ FILE_TRAVERSE|
+ DELETE_ACCESS|
+ FILE_DELETE_CHILD)) {
#ifdef O_DIRECTORY
- status = fd_open(conn, fsp, O_RDONLY|O_DIRECTORY, 0);
+ status = fd_open(conn, fsp, O_RDONLY|O_DIRECTORY, 0);
#else
- /* POSIX allows us to open a directory with O_RDONLY. */
- status = fd_open(conn, fsp, O_RDONLY, 0);
+ /* POSIX allows us to open a directory with O_RDONLY. */
+ status = fd_open(conn, fsp, O_RDONLY, 0);
#endif
- if (!NT_STATUS_IS_OK(status)) {
- DEBUG(5, ("open_directory: Could not open fd for "
- "%s (%s)\n",
- smb_fname_str_dbg(smb_dname),
- nt_errstr(status)));
- file_free(req, fsp);
- return status;
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("open_directory: Could not open fd for "
+ "%s (%s)\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ file_free(req, fsp);
+ return status;
+ }
+ } else {
+ fsp->fh->fd = -1;
+ DEBUG(10, ("Not opening Directory %s\n",
+ smb_fname_str_dbg(smb_dname)));
}
status = vfs_stat_fsp(fsp);
return status;
}
- if (!set_share_mode(lck, fsp, get_current_uid(conn),
- req ? req->mid : 0, NO_OPLOCK)) {
+ ok = set_share_mode(lck, fsp, get_current_uid(conn),
+ req ? req->mid : 0, NO_OPLOCK,
+ UINT32_MAX);
+ if (!ok) {
TALLOC_FREE(lck);
fd_close(fsp);
file_free(req, fsp);
}
}
+ {
+ /*
+ * Deal with other opens having a modified write time. Is this
+ * possible for directories?
+ */
+ struct timespec write_time = get_share_mode_write_time(lck);
+
+ if (!null_timespec(write_time)) {
+ update_stat_ex_mtime(&fsp->fsp_name->st, write_time);
+ }
+ }
+
TALLOC_FREE(lck);
if (pinfo) {
FILE_DIRECTORY_FILE, /* create_options */
FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
0, /* oplock_request */
+ NULL, /* lease */
0, /* allocation_size */
0, /* private_flags */
NULL, /* sd */
NULL, /* ea_list */
&fsp, /* result */
- NULL); /* pinfo */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
if (NT_STATUS_IS_OK(status)) {
close_file(req, fsp, NORMAL_CLOSE);
0, /* create_options */
FILE_ATTRIBUTE_NORMAL, /* file_attributes */
0, /* oplock_request */
+ NULL, /* lease */
0, /* allocation_size */
NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE, /* private_flags */
NULL, /* sd */
NULL, /* ea_list */
&streams[i], /* result */
- NULL); /* pinfo */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
if (!NT_STATUS_IS_OK(status)) {
DEBUG(10, ("Could not open stream %s: %s\n",
const struct dom_sid *SY_U_sid = NULL;
const struct dom_sid *SY_G_sid = NULL;
size_t size = 0;
+ struct smb_filename *parent_smb_fname = NULL;
if (!parent_dirname(frame, fsp->fsp_name->base_name, &parent_name, NULL)) {
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
+ parent_smb_fname = synthetic_smb_fname(talloc_tos(),
+ parent_name,
+ NULL,
+ NULL);
+
+ if (parent_smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
status = SMB_VFS_GET_NT_ACL(fsp->conn,
- parent_name,
+ parent_smb_fname,
(SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL),
frame,
&parent_desc);
return status;
}
+/*
+ * 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_match_state {
+ /* Input parameters. */
+ TALLOC_CTX *mem_ctx;
+ const char *servicepath;
+ const struct smb_filename *fname;
+ bool file_existed;
+ struct file_id id;
+ /* Return parameters. */
+ uint32_t num_file_ids;
+ struct file_id *ids;
+ NTSTATUS match_status;
+};
+
+/*************************************************************
+ File doesn't exist but this lease key+guid is already in use.
+
+ This is only allowable in the dynamic share case where the
+ service path must be different.
+
+ There is a small race condition here in the multi-connection
+ case where a client sends two create calls on different connections,
+ where the file doesn't exist and one smbd creates the leases_db
+ entry first, but this will get fixed by the multichannel cleanup
+ when all identical client_guids get handled by a single smbd.
+**************************************************************/
+
+static void lease_match_parser_new_file(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ struct lease_match_state *state)
+{
+ uint32_t i;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+ if (strequal(state->servicepath, f->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ return;
+ }
+ }
+
+ /* Dynamic share case. Break leases on all other files. */
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+static void lease_match_parser(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data)
+{
+ struct lease_match_state *state =
+ (struct lease_match_state *)private_data;
+ uint32_t i;
+
+ if (!state->file_existed) {
+ /*
+ * Deal with name mismatch or
+ * possible dynamic share case separately
+ * to make code clearer.
+ */
+ lease_match_parser_new_file(num_files,
+ files,
+ state);
+ return;
+ }
+
+ /* File existed. */
+ state->match_status = NT_STATUS_OK;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+
+ /* Everything should be the same. */
+ if (!file_id_equal(&state->id, &f->id)) {
+ /* This should catch all dynamic share cases. */
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ break;
+ }
+ if (!strequal(f->servicepath, state->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->base_name, state->fname->base_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->stream_name, state->fname->stream_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(state->match_status)) {
+ /*
+ * Common case - just opening another handle on a
+ * file on a non-dynamic share.
+ */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->match_status, NT_STATUS_INVALID_PARAMETER)) {
+ /* Mismatched path. Error back to client. */
+ return;
+ }
+
+ /*
+ * File id mismatch. Dynamic share case NT_STATUS_OPLOCK_NOT_GRANTED.
+ * Don't allow leases.
+ */
+
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+static NTSTATUS lease_match(connection_struct *conn,
+ struct smb_request *req,
+ struct smb2_lease_key *lease_key,
+ const char *servicepath,
+ const struct smb_filename *fname,
+ uint16_t *p_version,
+ uint16_t *p_epoch)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ TALLOC_CTX *tos = talloc_tos();
+ struct lease_match_state state = {
+ .mem_ctx = tos,
+ .servicepath = servicepath,
+ .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);
+ } else {
+ memset(&state.id, '\0', sizeof(state.id));
+ }
+
+ status = leases_db_parse(&sconn->client->connections->smb2.client.guid,
+ lease_key, lease_match_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Not found or error means okay: We can make the lease pass
+ */
+ return NT_STATUS_OK;
+ }
+ 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; j<d->num_share_modes; j++) {
+ struct share_mode_entry *e = &d->share_modes[j];
+ uint32_t e_lease_type = get_lease_type(d, e);
+ struct share_mode_lease *l = NULL;
+
+ if (share_mode_stale_pid(d, j)) {
+ continue;
+ }
+
+ if (e->op_type == LEASE_OPLOCK) {
+ l = &lck->data->leases[e->lease_idx];
+ if (!smb2_lease_key_equal(&l->lease_key,
+ lease_key)) {
+ continue;
+ }
+ *p_epoch = l->epoch;
+ *p_version = l->lease_version;
+ }
+
+ if (e_lease_type == SMB2_LEASE_NONE) {
+ 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;
+}
+
/*
* Wrapper around open_file_ntcreate and open_directory
*/
uint32_t create_options,
uint32_t file_attributes,
uint32_t oplock_request,
+ struct smb2_lease *lease,
uint64_t allocation_size,
uint32_t private_flags,
struct security_descriptor *sd,
oplock_request |= INTERNAL_OPEN_ONLY;
}
+ if (lease != NULL) {
+ uint16_t epoch = lease->lease_epoch;
+ uint16_t version = lease->lease_version;
+ status = lease_match(conn,
+ req,
+ &lease->lease_key,
+ conn->connectpath,
+ smb_fname,
+ &version,
+ &epoch);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ /* Dynamic share file. No leases and update epoch... */
+ lease->lease_state = SMB2_LEASE_NONE;
+ lease->lease_epoch = epoch;
+ lease->lease_version = version;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
&& (access_mask & DELETE_ACCESS)
&& !is_ntfs_stream_smb_fname(smb_fname)) {
if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
&& is_ntfs_stream_smb_fname(smb_fname)
&& (!(private_flags & NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE))) {
- uint32 base_create_disposition;
+ uint32_t base_create_disposition;
struct smb_filename *smb_fname_base = NULL;
if (create_options & FILE_DIRECTORY_FILE) {
if (SMB_VFS_STAT(conn, smb_fname_base) == -1) {
DEBUG(10, ("Unable to stat stream: %s\n",
smb_fname_str_dbg(smb_fname_base)));
+ } else {
+ /*
+ * https://bugzilla.samba.org/show_bug.cgi?id=10229
+ * We need to check if the requested access mask
+ * could be used to open the underlying file (if
+ * it existed), as we're passing in zero for the
+ * access mask to the base filename.
+ */
+ status = check_base_file_access(conn,
+ smb_fname_base,
+ access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Permission check "
+ "for base %s failed: "
+ "%s\n", smb_fname->base_name,
+ nt_errstr(status)));
+ goto fail;
+ }
}
/* Open the base file. */
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE,
base_create_disposition,
- 0, 0, 0, 0, 0, NULL, NULL,
+ 0, 0, 0, NULL, 0, 0, NULL, NULL,
&base_fsp, NULL);
TALLOC_FREE(smb_fname_base);
nt_errstr(status)));
goto fail;
}
- /* we don't need to low level fd */
+ /* we don't need the low level fd */
fd_close(base_fsp);
}
create_options,
file_attributes,
oplock_request,
+ lease,
private_flags,
&info,
fsp);
/* Save the requested allocation size. */
if ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN)) {
- if (allocation_size
- && (allocation_size > fsp->fsp_name->st.st_ex_size)) {
+ if ((allocation_size > fsp->fsp_name->st.st_ex_size)
+ && !(fsp->is_directory))
+ {
fsp->initial_allocation_size = smb_roundup(
fsp->conn, allocation_size);
- if (fsp->is_directory) {
- /* Can't set allocation size on a directory. */
- status = NT_STATUS_ACCESS_DENIED;
- goto fail;
- }
if (vfs_allocate_file_space(
fsp, fsp->initial_allocation_size) == -1) {
status = NT_STATUS_DISK_FULL;
}
}
+ if ((conn->fs_capabilities & FILE_FILE_COMPRESSION)
+ && (create_options & FILE_NO_COMPRESSION)
+ && (info == FILE_WAS_CREATED)) {
+ status = SMB_VFS_SET_COMPRESSION(conn, fsp, fsp,
+ COMPRESSION_FORMAT_NONE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to disable compression: %s\n",
+ nt_errstr(status)));
+ }
+ }
+
DEBUG(10, ("create_file_unixpath: info=%d\n", info));
*result = fsp;
files_struct *dir_fsp;
char *parent_fname = NULL;
char *new_base_name = NULL;
+ uint32_t ucf_flags = ((req != NULL && req->posix_pathnames) ?
+ UCF_POSIX_PATHNAMES : 0);
NTSTATUS status;
if (root_dir_fid == 0 || !smb_fname) {
conn,
req->flags2 & FLAGS2_DFS_PATHNAMES,
new_base_name,
- 0,
+ ucf_flags,
NULL,
smb_fname_out);
if (!NT_STATUS_IS_OK(status)) {
uint32_t create_options,
uint32_t file_attributes,
uint32_t oplock_request,
+ struct smb2_lease *lease,
uint64_t allocation_size,
uint32_t private_flags,
struct security_descriptor *sd,
struct ea_list *ea_list,
files_struct **result,
- int *pinfo)
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
{
int info = FILE_WAS_OPENED;
files_struct *fsp = NULL;
status = NT_STATUS_NOT_A_DIRECTORY;
goto fail;
}
- if (lp_posix_pathnames()) {
+ if (req != NULL && req->posix_pathnames) {
ret = SMB_VFS_LSTAT(conn, smb_fname);
} else {
ret = SMB_VFS_STAT(conn, smb_fname);
status = create_file_unixpath(
conn, req, smb_fname, access_mask, share_access,
create_disposition, create_options, file_attributes,
- oplock_request, allocation_size, private_flags,
+ oplock_request, lease, allocation_size, private_flags,
sd, ea_list,
&fsp, &info);