X-Git-Url: http://git.samba.org/samba.git/?p=kai%2Fsamba-autobuild%2F.git;a=blobdiff_plain;f=source3%2Fsmbd%2Fopen.c;h=9c242ddba35b42cc6db6ccb2a9b166c2fa2aacfc;hp=931d76df44f22d35cb6b1e0feb5bd73b04c436e3;hb=b656ebf54afcd322cc7dcaa1a913a557027840fd;hpb=326765923f1d384e5cd8b7fda048b459c67a4bf5 diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 931d76df44f..9c242ddba35 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -4,6 +4,7 @@ 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 @@ -45,6 +46,13 @@ struct deferred_open_record { 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; }; /**************************************************************************** @@ -245,21 +253,29 @@ NTSTATUS check_parent_access(struct connection_struct *conn, 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) { @@ -268,13 +284,14 @@ NTSTATUS check_parent_access(struct connection_struct *conn, "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)) { @@ -282,7 +299,7 @@ NTSTATUS check_parent_access(struct connection_struct *conn, "%s with error %s\n", parent_dir, nt_errstr(status))); - return status; + goto out; } /* @@ -309,10 +326,49 @@ NTSTATUS check_parent_access(struct connection_struct *conn, 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; } /**************************************************************************** @@ -355,6 +411,326 @@ static NTSTATUS check_base_file_access(struct connection_struct *conn, 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. ****************************************************************************/ @@ -367,8 +743,7 @@ NTSTATUS fd_open(struct connection_struct *conn, 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. */ @@ -376,29 +751,50 @@ NTSTATUS fd_open(struct connection_struct *conn, 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; @@ -508,14 +904,14 @@ void change_file_owner_to_parent(connection_struct *conn, 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; @@ -546,8 +942,8 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, 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", @@ -556,11 +952,11 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, } /* 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; } @@ -575,7 +971,7 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, 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; } @@ -584,7 +980,8 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, 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; } @@ -593,7 +990,7 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, /* 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; @@ -609,20 +1006,23 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, 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; @@ -640,7 +1040,9 @@ static NTSTATUS fd_open_atomic(struct connection_struct *conn, 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; @@ -672,59 +1074,65 @@ static NTSTATUS fd_open_atomic(struct connection_struct *conn, * 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; } @@ -944,7 +1352,7 @@ static NTSTATUS open_file(files_struct *fsp, /* 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; } @@ -1141,6 +1549,7 @@ sa = 0x%x, share = 0x%x\n", (num), (unsigned int)(am), (unsigned int)(right), (u #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) { @@ -1160,11 +1569,11 @@ static void validate_my_share_entries(struct smbd_server_connection *sconn, 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"); } @@ -1178,8 +1587,9 @@ static void validate_my_share_entries(struct smbd_server_connection *sconn, 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", @@ -1251,7 +1661,7 @@ static NTSTATUS open_mode_check(connection_struct *conn, #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 @@ -1285,8 +1695,9 @@ static NTSTATUS open_mode_check(connection_struct *conn, */ 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]; @@ -1296,7 +1707,7 @@ NTSTATUS send_break_message(struct messaging_context *msg_ctx, 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 */ /* @@ -1451,13 +1862,26 @@ static bool delay_for_oplock(files_struct *fsp, for (i=0; inum_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) { @@ -1472,7 +1896,7 @@ static bool delay_for_oplock(files_struct *fsp, /* * 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; } @@ -1481,20 +1905,20 @@ static bool delay_for_oplock(files_struct *fsp, (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; @@ -1513,7 +1937,7 @@ static bool delay_for_oplock(files_struct *fsp, 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. */ @@ -1522,12 +1946,12 @@ static bool delay_for_oplock(files_struct *fsp, 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; @@ -1547,9 +1971,9 @@ static bool file_has_brlocks(files_struct *fsp) 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; @@ -1567,9 +1991,66 @@ int find_share_mode_lease(struct share_mode_data *d, 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; @@ -1602,90 +2083,134 @@ struct fsp_lease *find_fsp_lease(struct files_struct *new_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); @@ -1708,8 +2233,6 @@ static NTSTATUS grant_fsp_lease(struct files_struct *fsp, 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, @@ -1721,6 +2244,9 @@ static NTSTATUS grant_fsp_lease(struct files_struct *fsp, 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); @@ -1737,6 +2263,24 @@ static NTSTATUS grant_fsp_lease(struct files_struct *fsp, 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, @@ -1751,8 +2295,28 @@ static bool is_same_lease(const files_struct *fsp, 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, @@ -1766,7 +2330,8 @@ 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; @@ -1857,34 +2422,23 @@ static NTSTATUS grant_fsp_oplock_type(struct smb_request *req, 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)) { @@ -1895,10 +2449,14 @@ static NTSTATUS grant_fsp_oplock_type(struct smb_request *req, } } - 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; } @@ -1924,6 +2482,27 @@ static bool request_timed_out(struct timeval request_time, 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; @@ -1931,64 +2510,72 @@ struct defer_open_state { 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"); } @@ -2001,8 +2588,7 @@ static void defer_open_done(struct tevent_req *req) 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", @@ -2020,6 +2606,74 @@ static void defer_open_done(struct tevent_req *req) 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. @@ -2028,7 +2682,6 @@ static void defer_open_done(struct tevent_req *req) 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) { @@ -2045,10 +2698,9 @@ static bool open_match_attributes(connection_struct *conn, } 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 )); @@ -2136,8 +2788,6 @@ static void schedule_defer_open(struct share_mode_lock *lck, 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 @@ -2156,38 +2806,54 @@ static void schedule_defer_open(struct share_mode_lock *lck, 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"); } } @@ -2658,9 +3324,8 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, (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, @@ -2723,20 +3388,18 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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; } @@ -2769,6 +3432,18 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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. */ @@ -2789,10 +3464,16 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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")); @@ -2809,13 +3490,16 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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; } @@ -2823,8 +3507,10 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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); @@ -2834,16 +3520,15 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, } /* - * 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; } @@ -2876,15 +3561,15 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, * 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; } @@ -2945,15 +3630,27 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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)) { @@ -3033,7 +3730,6 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, !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 @@ -3052,20 +3748,9 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, 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); } } @@ -3211,17 +3896,19 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, /* 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; } @@ -3255,43 +3942,46 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn, */ 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); + } + } } { @@ -3380,7 +4070,7 @@ static NTSTATUS mkdir_internal(connection_struct *conn, 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; } @@ -3403,7 +4093,7 @@ static NTSTATUS mkdir_internal(connection_struct *conn, /* 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; } @@ -3731,9 +4421,14 @@ static NTSTATUS open_directory(connection_struct *conn, 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); @@ -3754,7 +4449,7 @@ static NTSTATUS open_directory(connection_struct *conn, } 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; } @@ -4210,7 +4905,7 @@ static NTSTATUS inherit_new_acl(files_struct *fsp) /* 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. */ @@ -4400,8 +5095,6 @@ static NTSTATUS lease_match(connection_struct *conn, 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, @@ -4440,15 +5133,15 @@ static NTSTATUS lease_match(connection_struct *conn, for (j=0; jnum_share_modes; j++) { struct share_mode_entry *e = &d->share_modes[j]; uint32_t e_lease_type = get_lease_type(d, e); - 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; } @@ -4460,7 +5153,7 @@ static NTSTATUS lease_match(connection_struct *conn, continue; } - send_break_message(conn->sconn->msg_ctx, e, + send_break_message(conn->sconn->msg_ctx, &d->id, e, SMB2_LEASE_NONE); /* @@ -4481,7 +5174,7 @@ static NTSTATUS lease_match(connection_struct *conn, * 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. */ @@ -4523,11 +5216,11 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, 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, @@ -4536,7 +5229,7 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, (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; @@ -4555,6 +5248,13 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, 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, @@ -4596,11 +5296,24 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, 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; @@ -4651,13 +5364,17 @@ static NTSTATUS create_file_unixpath(connection_struct *conn, } } + 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); @@ -4914,8 +5631,7 @@ NTSTATUS get_relative_fid_filename(connection_struct *conn, 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) { @@ -5008,10 +5724,10 @@ NTSTATUS get_relative_fid_filename(connection_struct *conn, 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; @@ -5048,12 +5764,12 @@ NTSTATUS create_file_default(connection_struct *conn, 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, @@ -5063,7 +5779,7 @@ NTSTATUS create_file_default(connection_struct *conn, (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.