Copyright (C) Andrew Tridgell 1992-1998
Copyright (C) Jeremy Allison 2001-2004
Copyright (C) Volker Lendecke 2005
+ Copyright (C) Ralph Boehme 2017
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
bool delayed_for_oplocks;
bool async_open;
struct file_id id;
+
+ /*
+ * Timer for async opens, needed because they don't use a watch on
+ * a locking.tdb record. This is currently only used for real async
+ * opens and just terminates smbd if the async open times out.
+ */
+ struct tevent_timer *te;
};
/****************************************************************************
struct security_descriptor *parent_sd = NULL;
uint32_t access_granted = 0;
struct smb_filename *parent_smb_fname = NULL;
+ struct share_mode_lock *lck = NULL;
+ struct file_id id = {0};
+ uint32_t name_hash;
+ bool delete_on_close_set;
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
- if (!parent_dirname(talloc_tos(),
+ if (!parent_dirname(frame,
smb_fname->base_name,
&parent_dir,
NULL)) {
- return NT_STATUS_NO_MEMORY;
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
}
- parent_smb_fname = synthetic_smb_fname(talloc_tos(),
+ parent_smb_fname = synthetic_smb_fname(frame,
parent_dir,
NULL,
NULL,
smb_fname->flags);
if (parent_smb_fname == NULL) {
- return NT_STATUS_NO_MEMORY;
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
}
if (get_current_uid(conn) == (uid_t)0) {
"on %s. Granting 0x%x\n",
smb_fname_str_dbg(smb_fname),
(unsigned int)access_mask ));
- return NT_STATUS_OK;
+ status = NT_STATUS_OK;
+ goto out;
}
status = SMB_VFS_GET_NT_ACL(conn,
parent_smb_fname,
SECINFO_DACL,
- talloc_tos(),
+ frame,
&parent_sd);
if (!NT_STATUS_IS_OK(status)) {
"%s with error %s\n",
parent_dir,
nt_errstr(status)));
- return status;
+ goto out;
}
/*
access_mask,
access_granted,
nt_errstr(status) ));
- return status;
+ goto out;
}
- return NT_STATUS_OK;
+ if (!(access_mask & (SEC_DIR_ADD_FILE | SEC_DIR_ADD_SUBDIR))) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+ if (!lp_check_parent_directory_delete_on_close(SNUM(conn))) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ /* Check if the directory has delete-on-close set */
+ ret = SMB_VFS_STAT(conn, parent_smb_fname);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ id = SMB_VFS_FILE_ID_CREATE(conn, &parent_smb_fname->st);
+
+ status = file_name_hash(conn, parent_smb_fname->base_name, &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ lck = get_existing_share_mode_lock(frame, id);
+ if (lck == NULL) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ delete_on_close_set = is_delete_on_close_set(lck, name_hash);
+ if (delete_on_close_set) {
+ status = NT_STATUS_DELETE_PENDING;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+out:
+ TALLOC_FREE(frame);
+ return status;
}
/****************************************************************************
access_mask);
}
+/****************************************************************************
+ Handle differing symlink errno's
+****************************************************************************/
+
+static int link_errno_convert(int err)
+{
+#if defined(ENOTSUP) && defined(OSF1)
+ /* handle special Tru64 errno */
+ if (err == ENOTSUP) {
+ err = ELOOP;
+ }
+#endif /* ENOTSUP */
+#ifdef EFTYPE
+ /* fix broken NetBSD errno */
+ if (err == EFTYPE) {
+ err = ELOOP;
+ }
+#endif /* EFTYPE */
+ /* fix broken FreeBSD errno */
+ if (err == EMLINK) {
+ err = ELOOP;
+ }
+ return err;
+}
+
+static int non_widelink_open(struct connection_struct *conn,
+ const struct smb_filename *conn_rootdir_fname,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode,
+ unsigned int link_depth);
+
+/****************************************************************************
+ Follow a symlink in userspace.
+****************************************************************************/
+
+static int process_symlink_open(struct connection_struct *conn,
+ const struct smb_filename *conn_rootdir_fname,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode,
+ unsigned int link_depth)
+{
+ int fd = -1;
+ char *link_target = NULL;
+ struct smb_filename target_fname = {0};
+ int link_len = -1;
+ struct smb_filename *oldwd_fname = NULL;
+ size_t rootdir_len = 0;
+ struct smb_filename *resolved_fname = NULL;
+ char *resolved_name = NULL;
+ bool matched = false;
+ int saved_errno = 0;
+
+ /*
+ * Ensure we don't get stuck in a symlink loop.
+ */
+ link_depth++;
+ if (link_depth >= 20) {
+ errno = ELOOP;
+ goto out;
+ }
+
+ /* Allocate space for the link target. */
+ link_target = talloc_array(talloc_tos(), char, PATH_MAX);
+ if (link_target == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ /* Read the link target. */
+ link_len = SMB_VFS_READLINK(conn,
+ smb_fname,
+ link_target,
+ PATH_MAX - 1);
+ if (link_len == -1) {
+ goto out;
+ }
+
+ /* Ensure it's at least null terminated. */
+ link_target[link_len] = '\0';
+ target_fname = (struct smb_filename){ .base_name = link_target };
+
+ /* Convert to an absolute path. */
+ resolved_fname = SMB_VFS_REALPATH(conn, talloc_tos(), &target_fname);
+ if (resolved_fname == NULL) {
+ goto out;
+ }
+ resolved_name = resolved_fname->base_name;
+
+ /*
+ * We know conn_rootdir starts with '/' and
+ * does not end in '/'. FIXME ! Should we
+ * smb_assert this ?
+ */
+ rootdir_len = strlen(conn_rootdir_fname->base_name);
+
+ matched = (strncmp(conn_rootdir_fname->base_name,
+ resolved_name,
+ rootdir_len) == 0);
+ if (!matched) {
+ errno = EACCES;
+ goto out;
+ }
+
+ /*
+ * Turn into a path relative to the share root.
+ */
+ if (resolved_name[rootdir_len] == '\0') {
+ /* Link to the root of the share. */
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = talloc_strdup(smb_fname, ".");
+ } else if (resolved_name[rootdir_len] == '/') {
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = talloc_strdup(smb_fname,
+ &resolved_name[rootdir_len+1]);
+ } else {
+ errno = EACCES;
+ goto out;
+ }
+
+ if (smb_fname->base_name == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ oldwd_fname = vfs_GetWd(talloc_tos(), conn);
+ if (oldwd_fname == NULL) {
+ goto out;
+ }
+
+ /* Ensure we operate from the root of the share. */
+ if (vfs_ChDir(conn, conn_rootdir_fname) == -1) {
+ goto out;
+ }
+
+ /* And do it all again.. */
+ fd = non_widelink_open(conn,
+ conn_rootdir_fname,
+ fsp,
+ smb_fname,
+ flags,
+ mode,
+ link_depth);
+ if (fd == -1) {
+ saved_errno = errno;
+ }
+
+ out:
+
+ TALLOC_FREE(resolved_fname);
+ TALLOC_FREE(link_target);
+ if (oldwd_fname != NULL) {
+ int ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return fd;
+}
+
+/****************************************************************************
+ Non-widelink open.
+****************************************************************************/
+
+static int non_widelink_open(struct connection_struct *conn,
+ const struct smb_filename *conn_rootdir_fname,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode,
+ unsigned int link_depth)
+{
+ NTSTATUS status;
+ int fd = -1;
+ struct smb_filename *smb_fname_rel = NULL;
+ int saved_errno = 0;
+ struct smb_filename *oldwd_fname = NULL;
+ char *parent_dir = NULL;
+ struct smb_filename parent_dir_fname = {0};
+ const char *final_component = NULL;
+ bool is_directory = false;
+ bool ok;
+
+#ifdef O_DIRECTORY
+ if (flags & O_DIRECTORY) {
+ is_directory = true;
+ }
+#endif
+
+ if (is_directory) {
+ parent_dir = talloc_strdup(talloc_tos(), smb_fname->base_name);
+ if (parent_dir == NULL) {
+ saved_errno = errno;
+ goto out;
+ }
+
+ final_component = ".";
+ } else {
+ ok = parent_dirname(talloc_tos(),
+ smb_fname->base_name,
+ &parent_dir,
+ &final_component);
+ if (!ok) {
+ saved_errno = errno;
+ goto out;
+ }
+ }
+
+ parent_dir_fname = (struct smb_filename) { .base_name = parent_dir };
+
+ oldwd_fname = vfs_GetWd(talloc_tos(), conn);
+ if (oldwd_fname == NULL) {
+ goto out;
+ }
+
+ /* Pin parent directory in place. */
+ if (vfs_ChDir(conn, &parent_dir_fname) == -1) {
+ goto out;
+ }
+
+ smb_fname_rel = synthetic_smb_fname(talloc_tos(),
+ final_component,
+ smb_fname->stream_name,
+ &smb_fname->st,
+ smb_fname->flags);
+ if (smb_fname_rel == NULL) {
+ saved_errno = ENOMEM;
+ goto out;
+ }
+
+ /* Ensure the relative path is below the share. */
+ status = check_reduced_name(conn, &parent_dir_fname, smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ saved_errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ flags |= O_NOFOLLOW;
+
+ {
+ struct smb_filename *tmp_name = fsp->fsp_name;
+ fsp->fsp_name = smb_fname_rel;
+ fd = SMB_VFS_OPEN(conn, smb_fname_rel, fsp, flags, mode);
+ fsp->fsp_name = tmp_name;
+ }
+
+ if (fd == -1) {
+ saved_errno = link_errno_convert(errno);
+ /*
+ * Trying to open a symlink to a directory with O_NOFOLLOW and
+ * O_DIRECTORY can return either of ELOOP and ENOTDIR. So
+ * ENOTDIR really means: might be a symlink, but we're not sure.
+ * In this case, we just assume there's a symlink. If we were
+ * wrong, process_symlink_open() will return EINVAL. We check
+ * this below, and fall back to returning the initial
+ * saved_errno.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=12860
+ */
+ if (saved_errno == ELOOP || saved_errno == ENOTDIR) {
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ /* Never follow symlinks on posix open. */
+ goto out;
+ }
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ /* Explicitly no symlinks. */
+ goto out;
+ }
+ /*
+ * We may have a symlink. Follow in userspace
+ * to ensure it's under the share definition.
+ */
+ fd = process_symlink_open(conn,
+ conn_rootdir_fname,
+ fsp,
+ smb_fname_rel,
+ flags,
+ mode,
+ link_depth);
+ if (fd == -1) {
+ if (saved_errno == ENOTDIR &&
+ errno == EINVAL) {
+ /*
+ * O_DIRECTORY on neither a directory,
+ * nor a symlink. Just return
+ * saved_errno from initial open()
+ */
+ goto out;
+ }
+ saved_errno =
+ link_errno_convert(errno);
+ }
+ }
+ }
+
+ out:
+
+ TALLOC_FREE(parent_dir);
+ TALLOC_FREE(smb_fname_rel);
+
+ if (oldwd_fname != NULL) {
+ int ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return fd;
+}
+
/****************************************************************************
fd support routines - attempt to do a dos_open.
****************************************************************************/
struct smb_filename *smb_fname = fsp->fsp_name;
NTSTATUS status = NT_STATUS_OK;
-#ifdef O_NOFOLLOW
- /*
+ /*
* Never follow symlinks on a POSIX client. The
* client should be doing this.
*/
if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) || !lp_follow_symlinks(SNUM(conn))) {
flags |= O_NOFOLLOW;
}
-#endif
- fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, mode);
- if (fsp->fh->fd == -1) {
- int posix_errno = errno;
-#ifdef O_NOFOLLOW
-#if defined(ENOTSUP) && defined(OSF1)
- /* handle special Tru64 errno */
- if (errno == ENOTSUP) {
- posix_errno = ELOOP;
+ /* Ensure path is below share definition. */
+ if (!lp_widelinks(SNUM(conn))) {
+ struct smb_filename *conn_rootdir_fname = NULL;
+ const char *conn_rootdir = SMB_VFS_CONNECTPATH(conn,
+ smb_fname);
+ int saved_errno = 0;
+
+ if (conn_rootdir == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
-#endif /* ENOTSUP */
-#ifdef EFTYPE
- /* fix broken NetBSD errno */
- if (errno == EFTYPE) {
- posix_errno = ELOOP;
+ conn_rootdir_fname = synthetic_smb_fname(talloc_tos(),
+ conn_rootdir,
+ NULL,
+ NULL,
+ 0);
+ if (conn_rootdir_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
}
-#endif /* EFTYPE */
- /* fix broken FreeBSD errno */
- if (errno == EMLINK) {
- posix_errno = ELOOP;
+
+ /*
+ * Only follow symlinks within a share
+ * definition.
+ */
+ fsp->fh->fd = non_widelink_open(conn,
+ conn_rootdir_fname,
+ fsp,
+ smb_fname,
+ flags,
+ mode,
+ 0);
+ if (fsp->fh->fd == -1) {
+ saved_errno = errno;
}
-#endif /* O_NOFOLLOW */
+ TALLOC_FREE(conn_rootdir_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ } else {
+ fsp->fh->fd = SMB_VFS_OPEN(conn, smb_fname, fsp, flags, mode);
+ }
+
+ if (fsp->fh->fd == -1) {
+ int posix_errno = link_errno_convert(errno);
status = map_nt_error_from_unix(posix_errno);
if (errno == EMFILE) {
static time_t last_warned = 0L;
TALLOC_FREE(smb_fname_parent);
}
-NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
- const char *inherit_from_dir,
- const char *fname,
- SMB_STRUCT_STAT *psbuf)
+static NTSTATUS change_dir_owner_to_parent(connection_struct *conn,
+ const char *inherit_from_dir,
+ struct smb_filename *smb_dname,
+ SMB_STRUCT_STAT *psbuf)
{
struct smb_filename *smb_fname_parent;
struct smb_filename *smb_fname_cwd = NULL;
- char *saved_dir = NULL;
+ struct smb_filename *saved_dir_fname = NULL;
TALLOC_CTX *ctx = talloc_tos();
NTSTATUS status = NT_STATUS_OK;
int ret;
should work on any UNIX (thanks tridge :-). JRA.
*/
- saved_dir = vfs_GetWd(ctx,conn);
- if (!saved_dir) {
+ saved_dir_fname = vfs_GetWd(ctx,conn);
+ if (!saved_dir_fname) {
status = map_nt_error_from_unix(errno);
DEBUG(0,("change_dir_owner_to_parent: failed to get "
"current working directory. Error was %s\n",
}
/* Chdir into the new path. */
- if (vfs_ChDir(conn, fname) == -1) {
+ if (vfs_ChDir(conn, smb_dname) == -1) {
status = map_nt_error_from_unix(errno);
DEBUG(0,("change_dir_owner_to_parent: failed to change "
"current working directory to %s. Error "
- "was %s\n", fname, strerror(errno) ));
+ "was %s\n", smb_dname->base_name, strerror(errno) ));
goto chdir;
}
status = map_nt_error_from_unix(errno);
DEBUG(0,("change_dir_owner_to_parent: failed to stat "
"directory '.' (%s) Error was %s\n",
- fname, strerror(errno)));
+ smb_dname->base_name, strerror(errno)));
goto chdir;
}
smb_fname_cwd->st.st_ex_ino != psbuf->st_ex_ino) {
DEBUG(0,("change_dir_owner_to_parent: "
"device/inode on directory %s changed. "
- "Refusing to chown !\n", fname ));
+ "Refusing to chown !\n",
+ smb_dname->base_name ));
status = NT_STATUS_ACCESS_DENIED;
goto chdir;
}
/* Already this uid - no need to change. */
DEBUG(10,("change_dir_owner_to_parent: directory %s "
"is already owned by uid %d\n",
- fname,
+ smb_dname->base_name,
(int)smb_fname_cwd->st.st_ex_uid ));
status = NT_STATUS_OK;
goto chdir;
status = map_nt_error_from_unix(errno);
DEBUG(10,("change_dir_owner_to_parent: failed to chown "
"directory %s to parent directory uid %u. "
- "Error was %s\n", fname,
+ "Error was %s\n",
+ smb_dname->base_name,
(unsigned int)smb_fname_parent->st.st_ex_uid,
strerror(errno) ));
} else {
DEBUG(10,("change_dir_owner_to_parent: changed ownership of new "
"directory %s to parent directory uid %u.\n",
- fname, (unsigned int)smb_fname_parent->st.st_ex_uid ));
+ smb_dname->base_name,
+ (unsigned int)smb_fname_parent->st.st_ex_uid ));
/* Ensure the uid entry is updated. */
psbuf->st_ex_uid = smb_fname_parent->st.st_ex_uid;
}
chdir:
- vfs_ChDir(conn,saved_dir);
+ vfs_ChDir(conn, saved_dir_fname);
out:
+ TALLOC_FREE(saved_dir_fname);
TALLOC_FREE(smb_fname_parent);
TALLOC_FREE(smb_fname_cwd);
return status;
bool *file_created)
{
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS retry_status;
bool file_existed = VALID_STAT(fsp->fsp_name->st);
+ int curr_flags;
*file_created = false;
* we can never call O_CREAT without O_EXCL. So if
* we think the file existed, try without O_CREAT|O_EXCL.
* If we think the file didn't exist, try with
- * O_CREAT|O_EXCL. Keep bouncing between these two
- * requests until either the file is created, or
- * opened. Either way, we keep going until we get
- * a returnable result (error, or open/create).
+ * O_CREAT|O_EXCL.
+ *
+ * The big problem here is dangling symlinks. Opening
+ * without O_NOFOLLOW means both bad symlink
+ * and missing path return -1, ENOENT from open(). As POSIX
+ * is pathname based it's not possible to tell
+ * the difference between these two cases in a
+ * non-racy way, so change to try only two attempts before
+ * giving up.
+ *
+ * We don't have this problem for the O_NOFOLLOW
+ * case as it just returns NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * mapped from the ELOOP POSIX error.
*/
- while(1) {
- int curr_flags = flags;
+ curr_flags = flags;
- if (file_existed) {
- /* Just try open, do not create. */
- curr_flags &= ~(O_CREAT);
- status = fd_open(conn, fsp, curr_flags, mode);
- if (NT_STATUS_EQUAL(status,
- NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
- /*
- * Someone deleted it in the meantime.
- * Retry with O_EXCL.
- */
- file_existed = false;
- DEBUG(10,("fd_open_atomic: file %s existed. "
- "Retry.\n",
- smb_fname_str_dbg(fsp->fsp_name)));
- continue;
- }
- } else {
- /* Try create exclusively, fail if it exists. */
- curr_flags |= O_EXCL;
- status = fd_open(conn, fsp, curr_flags, mode);
- if (NT_STATUS_EQUAL(status,
- NT_STATUS_OBJECT_NAME_COLLISION)) {
- /*
- * Someone created it in the meantime.
- * Retry without O_CREAT.
- */
- file_existed = true;
- DEBUG(10,("fd_open_atomic: file %s "
- "did not exist. Retry.\n",
- smb_fname_str_dbg(fsp->fsp_name)));
- continue;
- }
- if (NT_STATUS_IS_OK(status)) {
- /*
- * Here we've opened with O_CREAT|O_EXCL
- * and got success. We *know* we created
- * this file.
- */
- *file_created = true;
- }
+ if (file_existed) {
+ curr_flags &= ~(O_CREAT);
+ retry_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ } else {
+ curr_flags |= O_EXCL;
+ retry_status = NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = fd_open(conn, fsp, curr_flags, mode);
+ if (NT_STATUS_IS_OK(status)) {
+ if (!file_existed) {
+ *file_created = true;
}
- /* Create is done, or failed. */
- break;
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(status, retry_status)) {
+ return status;
+ }
+
+ curr_flags = flags;
+
+ /*
+ * Keep file_existed up to date for clarity.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ file_existed = false;
+ curr_flags |= O_EXCL;
+ DBG_DEBUG("file %s did not exist. Retry.\n",
+ smb_fname_str_dbg(fsp->fsp_name));
+ } else {
+ file_existed = true;
+ curr_flags &= ~(O_CREAT);
+ DBG_DEBUG("file %s existed. Retry.\n",
+ smb_fname_str_dbg(fsp->fsp_name));
+ }
+
+ status = fd_open(conn, fsp, curr_flags, mode);
+
+ if (NT_STATUS_IS_OK(status) && (!file_existed)) {
+ *file_created = true;
}
+
return status;
}
/* Inherit the ACL if required */
if (lp_inherit_permissions(SNUM(conn))) {
inherit_access_posix_acl(conn, parent_dir,
- smb_fname->base_name,
+ smb_fname,
unx_mode);
need_re_stat = true;
}
#if defined(DEVELOPER)
static void validate_my_share_entries(struct smbd_server_connection *sconn,
+ const struct file_id id,
int num,
struct share_mode_entry *share_entry)
{
return;
}
- fsp = file_find_dif(sconn, share_entry->id,
- share_entry->share_file_id);
+ fsp = file_find_dif(sconn, id, share_entry->share_file_id);
if (!fsp) {
- DEBUG(0,("validate_my_share_entries: PANIC : %s\n",
- share_mode_str(talloc_tos(), num, share_entry) ));
+ DBG_ERR("PANIC : %s\n",
+ share_mode_str(talloc_tos(), num, &id,
+ share_entry));
smb_panic("validate_my_share_entries: Cannot match a "
"share entry with an open file\n");
}
panic:
{
char *str;
- DEBUG(0,("validate_my_share_entries: PANIC : %s\n",
- share_mode_str(talloc_tos(), num, share_entry) ));
+ DBG_ERR("validate_my_share_entries: PANIC : %s\n",
+ share_mode_str(talloc_tos(), num, &id,
+ share_entry));
str = talloc_asprintf(talloc_tos(),
"validate_my_share_entries: "
"file %s, oplock_type = 0x%x, op_type = 0x%x\n",
#if defined(DEVELOPER)
for(i = 0; i < lck->data->num_share_modes; i++) {
- validate_my_share_entries(conn->sconn, i,
+ validate_my_share_entries(conn->sconn, lck->data->id, i,
&lck->data->share_modes[i]);
}
#endif
*/
NTSTATUS send_break_message(struct messaging_context *msg_ctx,
- const struct share_mode_entry *exclusive,
- uint16_t break_to)
+ const struct file_id *id,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to)
{
NTSTATUS status;
char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
server_id_str_buf(exclusive->pid, &tmp)));
/* Create the message. */
- share_mode_entry_to_message(msg, exclusive);
+ share_mode_entry_to_message(msg, id, exclusive);
/* Overload entry->op_type */
/*
for (i=0; i<d->num_share_modes; i++) {
struct share_mode_entry *e = &d->share_modes[i];
- struct share_mode_lease *l = NULL;
+ bool e_is_lease = (e->op_type == LEASE_OPLOCK);
uint32_t e_lease_type = get_lease_type(d, e);
uint32_t break_to;
uint32_t delay_mask = 0;
+ bool lease_is_breaking = false;
+
+ if (e_is_lease) {
+ NTSTATUS status;
- if (e->op_type == LEASE_OPLOCK) {
- l = &d->leases[e->lease_idx];
+ status = leases_db_get(
+ &e->client_guid,
+ &e->lease_key,
+ &fsp->file_id,
+ NULL, /* current_state */
+ &lease_is_breaking,
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
}
if (have_sharing_violation) {
/*
* we'll decide about SMB2_LEASE_READ later.
*
- * Maybe the break will be defered
+ * Maybe the break will be deferred
*/
break_to &= ~SMB2_LEASE_HANDLE;
}
(unsigned)i, (unsigned)e_lease_type,
(unsigned)will_overwrite));
- if (lease != NULL && l != NULL) {
+ if (e_is_lease && lease != NULL) {
bool ign;
ign = smb2_lease_equal(fsp_client_guid(fsp),
&lease->lease_key,
- &l->client_guid,
- &l->lease_key);
+ &e->client_guid,
+ &e->lease_key);
if (ign) {
continue;
}
}
if ((e_lease_type & ~break_to) == 0) {
- if (l != NULL && l->breaking) {
+ if (lease_is_breaking) {
delay = true;
}
continue;
break_to &= ~(SMB2_LEASE_READ|SMB2_LEASE_WRITE);
}
- if (e->op_type != LEASE_OPLOCK) {
+ if (!e_is_lease) {
/*
* Oplocks only support breaking to R or NONE.
*/
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);
+ send_break_message(fsp->conn->sconn->msg_ctx, &fsp->file_id,
+ e, break_to);
if (e_lease_type & delay_mask) {
delay = true;
}
- if (l != NULL && l->breaking && !first_open_attempt) {
+ if (lease_is_breaking && !first_open_attempt) {
delay = true;
}
continue;
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)
+static int find_share_mode_lease(struct share_mode_data *d,
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *key)
{
uint32_t i;
return -1;
}
+NTSTATUS update_share_mode_lease_from_db(
+ struct share_mode_data *d,
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key)
+{
+ int idx;
+ struct share_mode_lease *l;
+ uint32_t current_state, breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+
+ idx = find_share_mode_lease(d, client_guid, lease_key);
+ if (idx == -1) {
+ DBG_WARNING("find_share_mode_lease failed\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+ l = &d->leases[idx];
+
+ status = leases_db_get(client_guid,
+ lease_key,
+ &d->id,
+ ¤t_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get returned %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if ((l->current_state == current_state) &&
+ (l->breaking == breaking) &&
+ (l->breaking_to_requested == breaking_to_requested) &&
+ (l->breaking_to_required == breaking_to_required) &&
+ (l->lease_version == lease_version) &&
+ (l->epoch == epoch)) {
+ return NT_STATUS_OK;
+ }
+
+ l->current_state = current_state;
+ l->breaking = breaking;
+ l->breaking_to_requested = breaking_to_requested;
+ l->breaking_to_required = breaking_to_required;
+ l->lease_version = lease_version;
+ l->epoch = epoch;
+
+ d->modified = true;
+
+ return NT_STATUS_OK;
+}
+
struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
const struct smb2_lease_key *key,
- const struct share_mode_lease *l)
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t lease_epoch)
{
struct files_struct *fsp;
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;
+ new_fsp->lease->lease.lease_state = 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;
+ new_fsp->lease->lease.lease_version = lease_version;
+ new_fsp->lease->lease.lease_epoch = lease_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)
+static NTSTATUS try_lease_upgrade(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease *lease,
+ uint32_t granted)
{
struct share_mode_data *d = lck->data;
- const struct GUID *client_guid = fsp_client_guid(fsp);
- struct share_mode_lease *tmp;
+ bool do_upgrade;
+ uint32_t current_state, breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ uint32_t existing, requested;
NTSTATUS status;
- int idx;
- idx = find_share_mode_lease(d, client_guid, &lease->lease_key);
+ status = leases_db_get(
+ client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ ¤t_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->lease = find_fsp_lease(
+ fsp,
+ &lease->lease_key,
+ current_state,
+ lease_version,
+ epoch);
+ if (fsp->lease == NULL) {
+ DEBUG(1, ("Did not find existing lease for file %s\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_NO_MEMORY;
+ }
- if (idx != -1) {
- struct share_mode_lease *l = &d->leases[idx];
- bool do_upgrade;
- uint32_t existing, requested;
+ /*
+ * Upgrade only if the requested lease is a strict upgrade.
+ */
+ existing = current_state;
+ requested = lease->lease_state;
- 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;
- }
+ /*
+ * Tricky: This test makes sure that "requested" is a
+ * strict bitwise superset of "existing".
+ */
+ do_upgrade = ((existing & requested) == existing);
- *p_lease_idx = idx;
+ /*
+ * Upgrade only if there's a change.
+ */
+ do_upgrade &= (granted != existing);
- /*
- * Upgrade only if the requested lease is a strict upgrade.
- */
- existing = l->current_state;
- requested = lease->lease_state;
+ /*
+ * Upgrade only if other leases don't prevent what was asked
+ * for.
+ */
+ do_upgrade &= (granted == requested);
- /*
- * Tricky: This test makes sure that "requested" is a
- * strict bitwise superset of "existing".
- */
- do_upgrade = ((existing & requested) == existing);
+ /*
+ * only upgrade if we are not in breaking state
+ */
+ do_upgrade &= !breaking;
- /*
- * Upgrade only if there's a change.
- */
- do_upgrade &= (granted != existing);
+ DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", "
+ "granted=%"PRIu32", do_upgrade=%d\n",
+ existing, requested, granted, (int)do_upgrade));
- /*
- * Upgrade only if other leases don't prevent what was asked
- * for.
- */
- do_upgrade &= (granted == requested);
+ if (do_upgrade) {
+ NTSTATUS set_status;
- /*
- * only upgrade if we are not in breaking state
- */
- do_upgrade &= !l->breaking;
+ current_state = granted;
+ epoch += 1;
- DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", "
- "granted=%"PRIu32", do_upgrade=%d\n",
- existing, requested, granted, (int)do_upgrade));
+ set_status = leases_db_set(
+ client_guid,
+ &lease->lease_key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
- if (do_upgrade) {
- l->current_state = granted;
- l->epoch += 1;
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return set_status;
}
+ }
- /* Ensure we're in sync with current lease state. */
- fsp_lease_update(lck, fsp_client_guid(fsp), fsp->lease);
- return NT_STATUS_OK;
+ status = update_share_mode_lease_from_db(
+ d, client_guid, &lease->lease_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("update_share_mode_lease_from_db failed: %s\n",
+ nt_errstr(status));
+ return status;
}
- /*
- * Create new lease
- */
+ fsp_lease_update(lck, fsp_client_guid(fsp), fsp->lease);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS grant_new_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ struct share_mode_data *d = lck->data;
+ struct share_mode_lease *tmp;
+ NTSTATUS status;
tmp = talloc_realloc(d, d->leases, struct share_mode_lease,
d->num_leases+1);
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,
status = leases_db_add(client_guid,
&lease->lease_key,
&fsp->file_id,
+ fsp->lease->lease.lease_state,
+ fsp->lease->lease.lease_version,
+ fsp->lease->lease.lease_epoch,
fsp->conn->connectpath,
fsp->fsp_name->base_name,
fsp->fsp_name->stream_name);
return NT_STATUS_OK;
}
+static NTSTATUS grant_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ NTSTATUS status;
+
+ status = try_lease_upgrade(fsp, lck, client_guid, lease, granted);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = grant_new_fsp_lease(
+ fsp, lck, client_guid, lease, granted);
+ }
+
+ return status;
+}
+
static bool is_same_lease(const files_struct *fsp,
const struct share_mode_data *d,
const struct share_mode_entry *e,
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);
+ &e->client_guid,
+ &e->lease_key);
+}
+
+static int map_lease_type_to_oplock(uint32_t lease_type)
+{
+ int result = NO_OPLOCK;
+
+ switch (lease_type) {
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE:
+ result = BATCH_OPLOCK|EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE:
+ result = EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_HANDLE:
+ case SMB2_LEASE_READ:
+ result = LEVEL_II_OPLOCK;
+ break;
+ }
+
+ return result;
}
static NTSTATUS grant_fsp_oplock_type(struct smb_request *req,
bool got_oplock = false;
uint32_t i;
uint32_t granted;
- uint32_t lease_idx = UINT32_MAX;
+ const struct GUID *client_guid = NULL;
+ const struct smb2_lease_key *lease_key = NULL;
bool ok;
NTSTATUS status;
fsp->oplock_type = LEASE_OPLOCK;
- status = grant_fsp_lease(fsp, lck, lease, &lease_idx,
- granted);
+ status = grant_fsp_lease(fsp, lck, lease, granted);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
*lease = fsp->lease->lease;
+
+ lease_key = &fsp->lease->lease.lease_key;
+ client_guid = fsp_client_guid(fsp);
+
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;
- }
+ fsp->oplock_type = map_lease_type_to_oplock(granted);
status = set_file_oplock(fsp);
if (!NT_STATUS_IS_OK(status)) {
}
}
- ok = set_share_mode(lck, fsp, get_current_uid(fsp->conn),
- req ? req->mid : 0,
- fsp->oplock_type,
- lease_idx);
+ ok = set_share_mode(
+ lck,
+ fsp,
+ get_current_uid(fsp->conn),
+ req ? req->mid : 0,
+ fsp->oplock_type,
+ client_guid,
+ lease_key);
if (!ok) {
return NT_STATUS_NO_MEMORY;
}
return (timeval_compare(&end_time, &now) < 0);
}
+static struct deferred_open_record *deferred_open_record_create(
+ bool delayed_for_oplocks,
+ bool async_open,
+ struct file_id id)
+{
+ struct deferred_open_record *record = NULL;
+
+ record = talloc(NULL, struct deferred_open_record);
+ if (record == NULL) {
+ return NULL;
+ }
+
+ *record = (struct deferred_open_record) {
+ .delayed_for_oplocks = delayed_for_oplocks,
+ .async_open = async_open,
+ .id = id,
+ };
+
+ return record;
+}
+
struct defer_open_state {
struct smbXsrv_connection *xconn;
uint64_t mid;
static void defer_open_done(struct tevent_req *req);
-/****************************************************************************
- Handle the 1 second delay in returning a SHARING_VIOLATION error.
-****************************************************************************/
-
+/**
+ * Defer an open and watch a locking.tdb record
+ *
+ * This defers an open that gets rescheduled once the locking.tdb record watch
+ * is triggered by a change to the record.
+ *
+ * It is used to defer opens that triggered an oplock break and for the SMB1
+ * sharing violation delay.
+ **/
static void defer_open(struct share_mode_lock *lck,
struct timeval request_time,
struct timeval timeout,
struct smb_request *req,
- struct deferred_open_record *state)
+ bool delayed_for_oplocks,
+ struct file_id id)
{
- struct deferred_open_record *open_rec;
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval abs_timeout;
+ struct defer_open_state *watch_state;
+ struct tevent_req *watch_req;
+ bool ok;
+
+ abs_timeout = timeval_sum(&request_time, &timeout);
- 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));
+ DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
+ "delayed_for_oplocks [%s] file_id [%s]\n",
+ timeval_string(talloc_tos(), &request_time, false),
+ timeval_string(talloc_tos(), &abs_timeout, false),
+ req->mid,
+ delayed_for_oplocks ? "yes" : "no",
+ file_id_string_tos(&id));
- open_rec = talloc(NULL, struct deferred_open_record);
+ open_rec = deferred_open_record_create(delayed_for_oplocks,
+ false,
+ id);
if (open_rec == NULL) {
TALLOC_FREE(lck);
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(open_rec, struct defer_open_state);
- if (watch_state == NULL) {
- exit_server("talloc failed");
- }
- watch_state->xconn = req->xconn;
- watch_state->mid = req->mid;
+ watch_state = talloc(open_rec, struct defer_open_state);
+ if (watch_state == NULL) {
+ exit_server("talloc failed");
+ }
+ watch_state->xconn = req->xconn;
+ watch_state->mid = req->mid;
- DEBUG(10, ("defering mid %llu\n",
- (unsigned long long)req->mid));
+ DBG_DEBUG("defering mid %" PRIu64 "\n", req->mid);
- watch_req = dbwrap_watched_watch_send(
- watch_state, req->sconn->ev_ctx, lck->data->record,
- (struct server_id){0});
- if (watch_req == NULL) {
- exit_server("Could not watch share mode record");
- }
- tevent_req_set_callback(watch_req, defer_open_done,
- watch_state);
+ watch_req = dbwrap_watched_watch_send(watch_state,
+ req->sconn->ev_ctx,
+ lck->data->record,
+ (struct server_id){0});
+ if (watch_req == NULL) {
+ exit_server("Could not watch share mode record");
+ }
+ tevent_req_set_callback(watch_req, defer_open_done, watch_state);
- ret = tevent_req_set_endtime(
- watch_req, req->sconn->ev_ctx,
- timeval_sum(&request_time, &timeout));
- SMB_ASSERT(ret);
+ ok = tevent_req_set_endtime(watch_req, req->sconn->ev_ctx, abs_timeout);
+ if (!ok) {
+ exit_server("tevent_req_set_endtime failed");
}
- if (!push_deferred_open_message_smb(req, request_time, timeout,
- state->id, open_rec)) {
+ ok = push_deferred_open_message_smb(req, request_time, timeout,
+ open_rec->id, open_rec);
+ if (!ok) {
TALLOC_FREE(lck);
exit_server("push_deferred_open_message_smb failed");
}
NTSTATUS status;
bool ret;
- status = dbwrap_watched_watch_recv(req, talloc_tos(), NULL, NULL,
- NULL);
+ status = dbwrap_watched_watch_recv(req, NULL, NULL);
TALLOC_FREE(req);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(5, ("dbwrap_watched_watch_recv returned %s\n",
TALLOC_FREE(state);
}
+/**
+ * Actually attempt the kernel oplock polling open.
+ */
+
+static void kernel_oplock_poll_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ bool ok;
+ struct smb_request *req = (struct smb_request *)private_data;
+
+ ok = schedule_deferred_open_message_smb(req->xconn, req->mid);
+ if (!ok) {
+ exit_server("schedule_deferred_open_message_smb failed");
+ }
+ DBG_DEBUG("kernel_oplock_poll_open_timer fired. Retying open !\n");
+}
+
+/**
+ * Reschedule an open for 1 second from now, if not timed out.
+ **/
+static void setup_kernel_oplock_poll_open(struct timeval request_time,
+ struct smb_request *req,
+ struct file_id id)
+{
+
+ bool ok;
+ struct deferred_open_record *open_rec = NULL;
+ /* Maximum wait time. */
+ struct timeval timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+
+ if (request_timed_out(request_time, timeout)) {
+ return;
+ }
+
+ open_rec = deferred_open_record_create(false, false, id);
+ if (open_rec == NULL) {
+ exit_server("talloc failed");
+ }
+
+ ok = push_deferred_open_message_smb(req,
+ request_time,
+ timeout,
+ id,
+ open_rec);
+ if (!ok) {
+ exit_server("push_deferred_open_message_smb failed");
+ }
+
+ /*
+ * As this timer event is owned by req, it will
+ * disappear if req it talloc_freed.
+ */
+ open_rec->te = tevent_add_timer(req->sconn->ev_ctx,
+ req,
+ timeval_current_ofs(1, 0),
+ kernel_oplock_poll_open_timer,
+ req);
+ if (open_rec->te == NULL) {
+ exit_server("tevent_add_timer failed");
+ }
+
+ DBG_DEBUG("poll request time [%s] mid [%" PRIu64 "] file_id [%s]\n",
+ timeval_string(talloc_tos(), &request_time, false),
+ req->mid,
+ file_id_string_tos(&id));
+}
/****************************************************************************
On overwrite open ensure that the attributes match.
static bool open_match_attributes(connection_struct *conn,
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)
{
}
DEBUG(10,("open_match_attributes: old_dos_attr = 0x%x, "
- "existing_unx_mode = 0%o, new_dos_attr = 0x%x "
+ "new_dos_attr = 0x%x "
"returned_unx_mode = 0%o\n",
(unsigned int)old_dos_attr,
- (unsigned int)existing_unx_mode,
(unsigned int)new_dos_attr,
(unsigned int)*returned_unx_mode ));
struct timeval request_time,
struct smb_request *req)
{
- struct deferred_open_record state;
-
/* This is a relative time, added to the absolute
request_time value to get the absolute timeout time.
Note that if this is the second or greater time we enter
timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
- /* Nothing actually uses state.delayed_for_oplocks
- but it's handy to differentiate in debug messages
- between a 30 second delay due to oplock break, and
- a 1 second delay for share mode conflicts. */
-
- state.delayed_for_oplocks = True;
- state.async_open = false;
- state.id = id;
-
- if (!request_timed_out(request_time, timeout)) {
- defer_open(lck, request_time, timeout, req, &state);
+ if (request_timed_out(request_time, timeout)) {
+ return;
}
+
+ defer_open(lck, request_time, timeout, req, true, id);
}
/****************************************************************************
Reschedule an open call that went asynchronous.
****************************************************************************/
+static void schedule_async_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ exit_server("async open timeout");
+}
+
static void schedule_async_open(struct timeval request_time,
struct smb_request *req)
{
- struct deferred_open_record state;
- struct timeval timeout;
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval timeout = timeval_set(20, 0);
+ bool ok;
- timeout = timeval_set(20, 0);
+ if (request_timed_out(request_time, timeout)) {
+ return;
+ }
+
+ open_rec = deferred_open_record_create(false, true, (struct file_id){0});
+ if (open_rec == NULL) {
+ exit_server("deferred_open_record_create failed");
+ }
- ZERO_STRUCT(state);
- state.delayed_for_oplocks = false;
- state.async_open = true;
+ ok = push_deferred_open_message_smb(req, request_time, timeout,
+ (struct file_id){0}, open_rec);
+ if (!ok) {
+ exit_server("push_deferred_open_message_smb failed");
+ }
- if (!request_timed_out(request_time, timeout)) {
- defer_open(NULL, request_time, timeout, req, &state);
+ open_rec->te = tevent_add_timer(req->sconn->ev_ctx,
+ req,
+ timeval_current_ofs(20, 0),
+ schedule_async_open_timer,
+ open_rec);
+ if (open_rec->te == NULL) {
+ exit_server("tevent_add_timer failed");
}
}
(create_disposition == FILE_OVERWRITE_IF))) {
if (!open_match_attributes(conn, existing_dos_attributes,
new_dos_attributes,
- smb_fname->st.st_ex_mode,
unx_mode, &new_unx_mode)) {
- DEBUG(5,("open_file_ntcreate: attributes missmatch "
+ DEBUG(5,("open_file_ntcreate: attributes mismatch "
"for file %s (%x %x) (0%o, 0%o)\n",
smb_fname_str_dbg(smb_fname),
existing_dos_attributes,
flags2 &= ~(O_CREAT|O_TRUNC);
}
- if (first_open_attempt && lp_kernel_oplocks(SNUM(conn))) {
+ if (lp_kernel_oplocks(SNUM(conn))) {
/*
* With kernel oplocks the open breaking an oplock
* blocks until the oplock holder has given up the
- * oplock or closed the file. We prevent this by first
+ * oplock or closed the file. We prevent this by always
* trying to open the file with O_NONBLOCK (see "man
- * fcntl" on Linux). For the second try, triggered by
- * an oplock break response, we do not need this
- * anymore.
+ * fcntl" on Linux).
*
- * This is true under the assumption that only Samba
- * requests kernel oplocks. Once someone else like
- * NFSv4 starts to use that API, we will have to
- * modify this by communicating with the NFSv4 server.
+ * If a process that doesn't use the smbd open files
+ * database or communication methods holds a kernel
+ * oplock we must periodically poll for available open
+ * using O_NONBLOCK.
*/
flags2 |= O_NONBLOCK;
}
request_time = fsp->open_time;
}
+ if ((create_options & FILE_DELETE_ON_CLOSE) &&
+ (flags2 & O_CREAT) &&
+ !file_existed) {
+ /* Delete on close semantics for new files. */
+ status = can_set_delete_on_close(fsp,
+ new_dos_attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+ }
+
/*
* Ensure we pay attention to default ACLs on directories if required.
*/
open_access_mask, &new_file_created);
if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) {
- struct deferred_open_record state;
+ bool delay;
/*
- * EWOULDBLOCK/EAGAIN maps to NETWORK_BUSY.
+ * This handles the kernel oplock case:
+ *
+ * the file has an active kernel oplock and the open() returned
+ * EWOULDBLOCK/EAGAIN which maps to NETWORK_BUSY.
+ *
+ * "Samba locking.tdb oplocks" are handled below after acquiring
+ * the sharemode lock with get_share_mode_lock().
*/
if (file_existed && S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
DEBUG(10, ("FIFO busy\n"));
lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
if (lck == NULL) {
- state.delayed_for_oplocks = false;
- state.async_open = false;
- state.id = fsp->file_id;
- defer_open(NULL, request_time, timeval_set(0, 0),
- req, &state);
- DEBUG(10, ("No share mode lock found after "
- "EWOULDBLOCK, retrying sync\n"));
+ /*
+ * No oplock from Samba around. Set up a poll every 1
+ * second to retry a non-blocking open until the time
+ * expires.
+ */
+ setup_kernel_oplock_poll_open(request_time,
+ req,
+ fsp->file_id);
+ DBG_DEBUG("No Samba oplock around after EWOULDBLOCK. "
+ "Retrying with poll\n");
return NT_STATUS_SHARING_VIOLATION;
}
smb_panic("validate_oplock_types failed");
}
- if (delay_for_oplock(fsp, 0, lease, lck, false,
- create_disposition, first_open_attempt)) {
+ delay = delay_for_oplock(fsp, 0, lease, lck, false,
+ create_disposition,
+ first_open_attempt);
+ if (delay) {
schedule_defer_open(lck, fsp->file_id, request_time,
req);
TALLOC_FREE(lck);
}
/*
- * No oplock from Samba around. Immediately retry with
- * a blocking open.
+ * No oplock from Samba around. Set up a poll every 1
+ * second to retry a non-blocking open until the time
+ * expires.
*/
- state.delayed_for_oplocks = false;
- state.async_open = false;
- state.id = fsp->file_id;
- defer_open(lck, request_time, timeval_set(0, 0), req, &state);
+ setup_kernel_oplock_poll_open(request_time, req, fsp->file_id);
+
TALLOC_FREE(lck);
- DEBUG(10, ("No Samba oplock around after EWOULDBLOCK. "
- "Retrying sync\n"));
+ DBG_DEBUG("No Samba oplock around after EWOULDBLOCK. "
+ "Retrying with poll\n");
return NT_STATUS_SHARING_VIOLATION;
}
* in the open file db having the wrong dev/ino key.
*/
fd_close(fsp);
- DEBUG(1,("open_file_ntcreate: file %s - dev/ino mismatch. "
- "Old (dev=0x%llu, ino =0x%llu). "
- "New (dev=0x%llu, ino=0x%llu). Failing open "
- " with NT_STATUS_ACCESS_DENIED.\n",
- smb_fname_str_dbg(smb_fname),
- (unsigned long long)saved_stat.st_ex_dev,
- (unsigned long long)saved_stat.st_ex_ino,
- (unsigned long long)smb_fname->st.st_ex_dev,
- (unsigned long long)smb_fname->st.st_ex_ino));
+ DBG_WARNING("file %s - dev/ino mismatch. "
+ "Old (dev=%ju, ino=%ju). "
+ "New (dev=%ju, ino=%ju). Failing open "
+ "with NT_STATUS_ACCESS_DENIED.\n",
+ smb_fname_str_dbg(smb_fname),
+ (uintmax_t)saved_stat.st_ex_dev,
+ (uintmax_t)saved_stat.st_ex_ino,
+ (uintmax_t)smb_fname->st.st_ex_dev,
+ (uintmax_t)smb_fname->st.st_ex_ino);
return NT_STATUS_ACCESS_DENIED;
}
file_existed = true;
}
- if ((req != NULL) &&
- delay_for_oplock(
- 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 (req != NULL) {
+ /*
+ * Handle oplocks, deferring the request if delay_for_oplock()
+ * triggered a break message and we have to wait for the break
+ * response.
+ */
+ bool delay;
+ bool sharing_violation = NT_STATUS_EQUAL(
+ status, NT_STATUS_SHARING_VIOLATION);
+
+ delay = delay_for_oplock(fsp, oplock_request, lease, lck,
+ sharing_violation,
+ create_disposition,
+ first_open_attempt);
+ if (delay) {
+ 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)) {
!conn->sconn->using_smb2 &&
lp_defer_sharing_violations()) {
struct timeval timeout;
- struct deferred_open_record state;
int timeout_usecs;
/* this is a hack to speed up torture tests
timeout = timeval_set(0, timeout_usecs);
- /* Nothing actually uses state.delayed_for_oplocks
- but it's handy to differentiate in debug messages
- between a 30 second delay due to oplock break, and
- a 1 second delay for share mode conflicts. */
-
- state.delayed_for_oplocks = False;
- state.async_open = false;
- state.id = id;
-
- if ((req != NULL)
- && !request_timed_out(request_time,
- timeout)) {
- defer_open(lck, request_time, timeout,
- req, &state);
+ if (!request_timed_out(request_time, timeout)) {
+ defer_open(lck, request_time, timeout, req,
+ false, id);
}
}
/* Handle strange delete on close create semantics. */
if (create_options & FILE_DELETE_ON_CLOSE) {
+ if (!new_file_created) {
+ status = can_set_delete_on_close(fsp,
+ existing_dos_attributes);
- status = can_set_delete_on_close(fsp, new_dos_attributes);
-
- if (!NT_STATUS_IS_OK(status)) {
- /* Remember to delete the mode we just added. */
- del_share_mode(lck, fsp);
- TALLOC_FREE(lck);
- fd_close(fsp);
- return status;
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Remember to delete the mode we just added. */
+ del_share_mode(lck, fsp);
+ TALLOC_FREE(lck);
+ fd_close(fsp);
+ return status;
+ }
}
- /* Note that here we set the *inital* delete on close flag,
+ /* Note that here we set the *initial* delete on close flag,
not the regular one. The magic gets handled in close. */
fsp->initial_delete_on_close = True;
}
*/
if (!posix_open && new_file_created && !def_acl) {
-
- int saved_errno = errno; /* We might get ENOSYS in the next
- * call.. */
-
- if (SMB_VFS_FCHMOD_ACL(fsp, unx_mode) == -1 &&
- errno == ENOSYS) {
- errno = saved_errno; /* Ignore ENOSYS */
+ if (unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
+ "attributes of file %s to 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)unx_mode);
+ }
}
} else if (new_unx_mode) {
+ /*
+ * We only get here in the case of:
+ *
+ * a). Not a POSIX open.
+ * b). File already existed.
+ * c). File was overwritten.
+ * d). Requested DOS attributes didn't match
+ * the DOS attributes on the existing file.
+ *
+ * In that case new_unx_mode has been set
+ * equal to the calculated mode (including
+ * possible inheritance of the mode from the
+ * containing directory).
+ *
+ * Note this mode was calculated with the
+ * DOS attribute FILE_ATTRIBUTE_ARCHIVE added,
+ * so the mode change here is suitable for
+ * an overwritten file.
+ */
- int ret = -1;
-
- /* Attributes need changing. File already existed. */
-
- {
- int saved_errno = errno; /* We might get ENOSYS in the
- * next call.. */
- ret = SMB_VFS_FCHMOD_ACL(fsp, new_unx_mode);
-
- if (ret == -1 && errno == ENOSYS) {
- errno = saved_errno; /* Ignore ENOSYS */
- } else {
- DEBUG(5, ("open_file_ntcreate: reset "
- "attributes of file %s to 0%o\n",
- smb_fname_str_dbg(smb_fname),
- (unsigned int)new_unx_mode));
- ret = 0; /* Don't do the fchmod below. */
- }
- }
-
- if ((ret == -1) &&
- (SMB_VFS_FCHMOD(fsp, new_unx_mode) == -1))
- DEBUG(5, ("open_file_ntcreate: failed to reset "
+ if (new_unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, new_unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
"attributes of file %s to 0%o\n",
smb_fname_str_dbg(smb_fname),
- (unsigned int)new_unx_mode));
+ (unsigned int)new_unx_mode);
+ }
+ }
}
{
if (lp_inherit_permissions(SNUM(conn))) {
inherit_access_posix_acl(conn, parent_dir,
- smb_dname->base_name, mode);
+ smb_dname, mode);
need_re_stat = true;
}
/* Change the owner if required. */
if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
change_dir_owner_to_parent(conn, parent_dir,
- smb_dname->base_name,
+ smb_dname,
&smb_dname->st);
need_re_stat = true;
}
return status;
}
- ok = set_share_mode(lck, fsp, get_current_uid(conn),
- req ? req->mid : 0, NO_OPLOCK,
- UINT32_MAX);
+ ok = set_share_mode(
+ lck,
+ fsp,
+ get_current_uid(conn),
+ req ? req->mid : 0,
+ NO_OPLOCK,
+ NULL,
+ NULL);
if (!ok) {
TALLOC_FREE(lck);
fd_close(fsp);
}
if (NT_STATUS_IS_OK(status)) {
- /* Note that here we set the *inital* delete on close flag,
+ /* Note that here we set the *initial* delete on close flag,
not the regular one. The magic gets handled in close. */
fsp->initial_delete_on_close = True;
}
/* If inheritable_components == false,
se_create_child_secdesc()
- creates a security desriptor with a NULL dacl
+ creates a security descriptor with a NULL dacl
entry, but with SEC_DESC_DACL_PRESENT. We need
to remove that flag. */
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,
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) {
+ struct share_mode_lease *l = NULL;
l = &lck->data->leases[e->lease_idx];
- if (!smb2_lease_key_equal(&l->lease_key,
+ if (!smb2_lease_key_equal(&e->lease_key,
lease_key)) {
continue;
}
continue;
}
- send_break_message(conn->sconn->msg_ctx, e,
+ send_break_message(conn->sconn->msg_ctx, &d->id, e,
SMB2_LEASE_NONE);
/*
* Send the breaks and then return
* SMB2_LEASE_NONE in the lease handle
* to cause them to acknowledge the
- * lease break. Consulatation with
+ * lease break. Consultation with
* Microsoft engineering confirmed
* this approach is safe.
*/
files_struct *fsp = NULL;
NTSTATUS status;
- DEBUG(10,("create_file_unixpath: access_mask = 0x%x "
+ DBG_DEBUG("create_file_unixpath: access_mask = 0x%x "
"file_attributes = 0x%x, share_access = 0x%x, "
"create_disposition = 0x%x create_options = 0x%x "
"oplock_request = 0x%x private_flags = 0x%x "
- "ea_list = 0x%p, sd = 0x%p, "
+ "ea_list = %p, sd = %p, "
"fname = %s\n",
(unsigned int)access_mask,
(unsigned int)file_attributes,
(unsigned int)create_options,
(unsigned int)oplock_request,
(unsigned int)private_flags,
- ea_list, sd, smb_fname_str_dbg(smb_fname)));
+ ea_list, sd, smb_fname_str_dbg(smb_fname));
if (create_options & FILE_OPEN_BY_FILE_ID) {
status = NT_STATUS_NOT_SUPPORTED;
if (lease != NULL) {
uint16_t epoch = lease->lease_epoch;
uint16_t version = lease->lease_version;
+
+ if (req == NULL) {
+ DBG_WARNING("Got lease on internal open\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
status = lease_match(conn,
req,
&lease->lease_key,
goto fail;
}
+ /*
+ * Files or directories can't be opened DELETE_ON_CLOSE without
+ * delete access.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13358
+ */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ if ((access_mask & DELETE_ACCESS) == 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+
if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
&& is_ntfs_stream_smb_fname(smb_fname)
&& (!(private_flags & NTCREATEX_OPTIONS_PRIVATE_STREAM_DELETE))) {
uint32_t base_create_disposition;
struct smb_filename *smb_fname_base = NULL;
+ uint32_t base_privflags;
if (create_options & FILE_DIRECTORY_FILE) {
status = NT_STATUS_NOT_A_DIRECTORY;
}
}
+ base_privflags = NTCREATEX_OPTIONS_PRIVATE_STREAM_BASEOPEN;
+
/* Open the base file. */
status = create_file_unixpath(conn, NULL, smb_fname_base, 0,
FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE,
base_create_disposition,
- 0, 0, 0, NULL, 0, 0, NULL, NULL,
+ 0, 0, 0, NULL, 0,
+ base_privflags,
+ NULL, NULL,
&base_fsp, NULL);
TALLOC_FREE(smb_fname_base);
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);
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
NTSTATUS status;
if (root_dir_fid == 0 || !smb_fname) {
status = filename_convert(req,
conn,
- req->flags2 & FLAGS2_DFS_PATHNAMES,
new_base_name,
ucf_flags,
NULL,
+ NULL,
smb_fname_out);
if (!NT_STATUS_IS_OK(status)) {
goto out;
NTSTATUS status;
bool stream_name = false;
- DEBUG(10,("create_file: access_mask = 0x%x "
+ DBG_DEBUG("create_file: access_mask = 0x%x "
"file_attributes = 0x%x, share_access = 0x%x, "
"create_disposition = 0x%x create_options = 0x%x "
"oplock_request = 0x%x "
"private_flags = 0x%x "
- "root_dir_fid = 0x%x, ea_list = 0x%p, sd = 0x%p, "
+ "root_dir_fid = 0x%x, ea_list = %p, sd = %p, "
"fname = %s\n",
(unsigned int)access_mask,
(unsigned int)file_attributes,
(unsigned int)oplock_request,
(unsigned int)private_flags,
(unsigned int)root_dir_fid,
- ea_list, sd, smb_fname_str_dbg(smb_fname)));
+ ea_list, sd, smb_fname_str_dbg(smb_fname));
/*
* Calculate the filename from the root_dir_if if necessary.