summaryrefslogtreecommitdiffstats
path: root/source3/smbd/durable.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/smbd/durable.c')
-rw-r--r--source3/smbd/durable.c938
1 files changed, 938 insertions, 0 deletions
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
new file mode 100644
index 0000000..5f55ff6
--- /dev/null
+++ b/source3/smbd/durable.c
@@ -0,0 +1,938 @@
+/*
+ Unix SMB/CIFS implementation.
+ Durable Handle default VFS implementation
+
+ Copyright (C) Stefan Metzmacher 2012
+ Copyright (C) Michael Adam 2012
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "messages.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+#include "serverid.h"
+#include "fake_file.h"
+#include "locking/leases_db.h"
+
+NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie_blob)
+{
+ struct connection_struct *conn = fsp->conn;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (lp_kernel_share_modes(SNUM(conn))) {
+ /*
+ * We do not support durable handles
+ * if file system sharemodes are used
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (lp_kernel_oplocks(SNUM(conn))) {
+ /*
+ * We do not support durable handles
+ * if kernel oplocks are used
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if ((fsp->current_lock_count > 0) &&
+ lp_posix_locking(fsp->conn->params))
+ {
+ /*
+ * We do not support durable handles
+ * if the handle has posix locks.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * We do not support durable handles
+ * on streams for now.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (is_fake_file(fsp->fsp_name)) {
+ /*
+ * We do not support durable handles
+ * on fake files.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = false;
+ cookie.id = fsp->file_id;
+ cookie.servicepath = conn->connectpath;
+ cookie.base_name = fsp->fsp_name->base_name;
+ cookie.initial_allocation_size = fsp->initial_allocation_size;
+ cookie.position_information = fh_get_position_information(fsp->fh);
+ cookie.update_write_time_triggered =
+ fsp->fsp_flags.update_write_time_triggered;
+ cookie.update_write_time_on_close =
+ fsp->fsp_flags.update_write_time_on_close;
+ cookie.write_time_forced = fsp->fsp_flags.write_time_forced;
+ cookie.close_write_time = full_timespec_to_nt_time(
+ &fsp->close_write_time);
+
+ cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev;
+ cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino;
+ cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode;
+ cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink;
+ cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid;
+ cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid;
+ cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev;
+ cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size;
+ cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime;
+ cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime;
+ cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime;
+ cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime;
+ cookie.stat_info.st_ex_iflags = fsp->fsp_name->st.st_ex_iflags;
+ cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize;
+ cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks;
+ cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags;
+
+ ndr_err = ndr_push_struct_blob(cookie_blob, mem_ctx, &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ struct connection_struct *conn = fsp->conn;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB new_cookie_blob = data_blob_null;
+ struct share_mode_lock *lck;
+ bool ok;
+
+ *new_cookie = data_blob_null;
+
+ ZERO_STRUCT(cookie);
+
+ ndr_err = ndr_pull_struct_blob(&old_cookie, talloc_tos(), &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!file_id_equal(&fsp->file_id, &cookie.id)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((fsp_lease_type(fsp) & SMB2_LEASE_HANDLE) == 0) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * For now let it be simple and do not keep
+ * delete on close files durable open
+ */
+ if (fsp->fsp_flags.initial_delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ if (fsp->fsp_flags.delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Ensure any pending write time updates are done. */
+ if (fsp->update_write_time_event) {
+ fsp_flush_write_time_update(fsp);
+ }
+
+ /*
+ * The above checks are done in mark_share_mode_disconnected() too
+ * but we want to avoid getting the lock if possible
+ */
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck != NULL) {
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (fsp->fsp_flags.write_time_forced) {
+ NTTIME mtime = share_mode_changed_write_time(lck);
+ ft.mtime = nt_time_to_full_timespec(mtime);
+ } else if (fsp->fsp_flags.update_write_time_on_close) {
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ ft.mtime = timespec_current();
+ } else {
+ ft.mtime = fsp->close_write_time;
+ }
+ }
+
+ if (!is_omit_timespec(&ft.mtime)) {
+ round_timespec(conn->ts_res, &ft.mtime);
+ file_ntimes(conn, fsp, &ft);
+ }
+
+ ok = mark_share_mode_disconnected(lck, fsp);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ }
+ }
+ if (lck != NULL) {
+ ok = brl_mark_disconnected(fsp);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ }
+ }
+ if (lck == NULL) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ TALLOC_FREE(lck);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = true;
+ cookie.id = fsp->file_id;
+ cookie.servicepath = conn->connectpath;
+ cookie.base_name = fsp_str_dbg(fsp);
+ cookie.initial_allocation_size = fsp->initial_allocation_size;
+ cookie.position_information = fh_get_position_information(fsp->fh);
+ cookie.update_write_time_triggered =
+ fsp->fsp_flags.update_write_time_triggered;
+ cookie.update_write_time_on_close =
+ fsp->fsp_flags.update_write_time_on_close;
+ cookie.write_time_forced = fsp->fsp_flags.write_time_forced;
+ cookie.close_write_time = full_timespec_to_nt_time(
+ &fsp->close_write_time);
+
+ cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev;
+ cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino;
+ cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode;
+ cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink;
+ cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid;
+ cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid;
+ cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev;
+ cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size;
+ cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime;
+ cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime;
+ cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime;
+ cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime;
+ cookie.stat_info.st_ex_iflags = fsp->fsp_name->st.st_ex_iflags;
+ cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize;
+ cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks;
+ cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags;
+
+ ndr_err = ndr_push_struct_blob(&new_cookie_blob, mem_ctx, &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(&new_cookie_blob);
+ return status;
+ }
+
+ *new_cookie = new_cookie_blob;
+ return NT_STATUS_OK;
+}
+
+
+/**
+ * Check whether a cookie-stored struct info is the same
+ * as a given SMB_STRUCT_STAT, as coming with the fsp.
+ */
+static bool vfs_default_durable_reconnect_check_stat(
+ struct vfs_default_durable_stat *cookie_st,
+ SMB_STRUCT_STAT *fsp_st,
+ const char *name)
+{
+ int ret;
+
+ if (cookie_st->st_ex_mode != fsp_st->st_ex_mode) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_mode",
+ (unsigned long long)cookie_st->st_ex_mode,
+ (unsigned long long)fsp_st->st_ex_mode));
+ return false;
+ }
+
+ if (cookie_st->st_ex_nlink != fsp_st->st_ex_nlink) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_nlink",
+ (unsigned long long)cookie_st->st_ex_nlink,
+ (unsigned long long)fsp_st->st_ex_nlink));
+ return false;
+ }
+
+ if (cookie_st->st_ex_uid != fsp_st->st_ex_uid) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_uid",
+ (unsigned long long)cookie_st->st_ex_uid,
+ (unsigned long long)fsp_st->st_ex_uid));
+ return false;
+ }
+
+ if (cookie_st->st_ex_gid != fsp_st->st_ex_gid) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_gid",
+ (unsigned long long)cookie_st->st_ex_gid,
+ (unsigned long long)fsp_st->st_ex_gid));
+ return false;
+ }
+
+ if (cookie_st->st_ex_rdev != fsp_st->st_ex_rdev) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_rdev",
+ (unsigned long long)cookie_st->st_ex_rdev,
+ (unsigned long long)fsp_st->st_ex_rdev));
+ return false;
+ }
+
+ if (cookie_st->st_ex_size != fsp_st->st_ex_size) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_size",
+ (unsigned long long)cookie_st->st_ex_size,
+ (unsigned long long)fsp_st->st_ex_size));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_atime,
+ &fsp_st->st_ex_atime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_atime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_atime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_atime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_mtime,
+ &fsp_st->st_ex_mtime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_mtime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_mtime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_mtime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_ctime,
+ &fsp_st->st_ex_ctime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_ctime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_ctime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_ctime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_btime,
+ &fsp_st->st_ex_btime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_btime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_btime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_btime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ if (cookie_st->st_ex_iflags != fsp_st->st_ex_iflags) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_calculated_birthtime",
+ (unsigned long long)cookie_st->st_ex_iflags,
+ (unsigned long long)fsp_st->st_ex_iflags));
+ return false;
+ }
+
+ if (cookie_st->st_ex_blksize != fsp_st->st_ex_blksize) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_blksize",
+ (unsigned long long)cookie_st->st_ex_blksize,
+ (unsigned long long)fsp_st->st_ex_blksize));
+ return false;
+ }
+
+ if (cookie_st->st_ex_blocks != fsp_st->st_ex_blocks) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_blocks",
+ (unsigned long long)cookie_st->st_ex_blocks,
+ (unsigned long long)fsp_st->st_ex_blocks));
+ return false;
+ }
+
+ if (cookie_st->st_ex_flags != fsp_st->st_ex_flags) {
+ DBG_WARNING(" (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%"PRIu32" != stat:%"PRIu32", "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_flags",
+ cookie_st->st_ex_flags,
+ fsp_st->st_ex_flags);
+ return false;
+ }
+
+ return true;
+}
+
+static bool durable_reconnect_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct share_mode_entry *dst_e = private_data;
+
+ if (dst_e->pid.pid != 0) {
+ DBG_INFO("Found more than one entry, invalidating previous\n");
+ dst_e->pid.pid = 0;
+ return true; /* end the loop through share mode entries */
+ }
+ *dst_e = *e;
+ return false; /* Look at potential other entries */
+}
+
+NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ files_struct **result,
+ DATA_BLOB *new_cookie)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct share_mode_lock *lck;
+ struct share_mode_entry e;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+ bool ok;
+ int ret;
+ struct vfs_open_how how = { .flags = 0, };
+ struct file_id file_id;
+ struct smb_filename *smb_fname = NULL;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB new_cookie_blob = data_blob_null;
+
+ *result = NULL;
+ *new_cookie = data_blob_null;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * the checks for kernel oplocks
+ * and similar things are done
+ * in the vfs_default_durable_cookie()
+ * call below.
+ */
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &old_cookie,
+ talloc_tos(),
+ &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!cookie.allow_reconnect) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (strcmp(cookie.servicepath, conn->connectpath) != 0) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ cookie.base_name,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_LSTAT(conn, smb_fname);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to lstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ if (!file_id_equal(&cookie.id, &file_id)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * 1. check entry in locking.tdb
+ */
+
+ lck = get_existing_share_mode_lock(mem_ctx, file_id);
+ if (lck == NULL) {
+ DEBUG(5, ("vfs_default_durable_reconnect: share-mode lock "
+ "not obtained from db\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ e = (struct share_mode_entry) { .pid.pid = 0 };
+
+ ok = share_mode_forall_entries(lck, durable_reconnect_fn, &e);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_entries failed\n");
+ TALLOC_FREE(lck);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (e.pid.pid == 0) {
+ DBG_WARNING("Did not find a unique valid share mode entry\n");
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!server_id_is_disconnected(&e.pid)) {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "reconnect for handle that was not marked "
+ "disconnected (e.g. smbd or cluster node died)\n"));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (e.share_file_id != op->global->open_persistent_id) {
+ DBG_INFO("denying durable "
+ "share_file_id changed %"PRIu64" != %"PRIu64" "
+ "(e.g. another client had opened the file)\n",
+ e.share_file_id,
+ op->global->open_persistent_id);
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if ((e.access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) &&
+ !CAN_WRITE(conn))
+ {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "share[%s] is not writeable anymore\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * 2. proceed with opening file
+ */
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("vfs_default_durable_reconnect: failed to create "
+ "new fsp: %s\n", nt_errstr(status)));
+ TALLOC_FREE(lck);
+ return status;
+ }
+
+ fh_set_private_options(fsp->fh, e.private_options);
+ fsp->file_id = file_id;
+ fsp->file_pid = smb1req->smbpid;
+ fsp->vuid = smb1req->vuid;
+ fsp->open_time = e.time;
+ fsp->access_mask = e.access_mask;
+ fsp->fsp_flags.can_read = ((fsp->access_mask & FILE_READ_DATA) != 0);
+ fsp->fsp_flags.can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0);
+ fsp->fnum = op->local_id;
+ fsp_set_gen_id(fsp);
+
+ /*
+ * TODO:
+ * Do we need to store the modified flag in the DB?
+ */
+ fsp->fsp_flags.modified = false;
+ /*
+ * no durables for directories
+ */
+ fsp->fsp_flags.is_directory = false;
+ /*
+ * For normal files, can_lock == !is_directory
+ */
+ fsp->fsp_flags.can_lock = true;
+ /*
+ * We do not support aio write behind for smb2
+ */
+ fsp->fsp_flags.aio_write_behind = false;
+ fsp->oplock_type = e.op_type;
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ uint32_t current_state;
+ uint16_t lease_version, epoch;
+
+ /*
+ * Ensure the existing client guid matches the
+ * stored one in the share_mode_entry.
+ */
+ if (!GUID_equal(fsp_client_guid(fsp),
+ &e.client_guid)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = leases_db_get(
+ &e.client_guid,
+ &e.lease_key,
+ &file_id,
+ &current_state, /* current_state */
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &lease_version, /* lease_version */
+ &epoch); /* epoch */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ fsp->lease = find_fsp_lease(
+ fsp,
+ &e.lease_key,
+ current_state,
+ lease_version,
+ epoch);
+ if (fsp->lease == NULL) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ fsp->initial_allocation_size = cookie.initial_allocation_size;
+ fh_set_position_information(fsp->fh, cookie.position_information);
+ fsp->fsp_flags.update_write_time_triggered =
+ cookie.update_write_time_triggered;
+ fsp->fsp_flags.update_write_time_on_close =
+ cookie.update_write_time_on_close;
+ fsp->fsp_flags.write_time_forced = cookie.write_time_forced;
+ fsp->close_write_time = nt_time_to_full_timespec(
+ cookie.close_write_time);
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "fsp_set_smb_fname failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ op->compat = fsp;
+ fsp->op = op;
+
+ ok = reset_share_mode_entry(
+ lck,
+ e.pid,
+ e.share_file_id,
+ messaging_server_id(conn->sconn->msg_ctx),
+ smb1req->mid,
+ fh_get_gen_id(fsp->fh));
+ if (!ok) {
+ DBG_DEBUG("Could not set new share_mode_entry values\n");
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ok = brl_reconnect_disconnected(fsp);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(1, ("vfs_default_durable_reconnect: "
+ "failed to reopen brlocks: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ /*
+ * TODO: properly calculate open flags
+ */
+ if (fsp->fsp_flags.can_write && fsp->fsp_flags.can_read) {
+ how.flags = O_RDWR;
+ } else if (fsp->fsp_flags.can_write) {
+ how.flags = O_WRONLY;
+ } else if (fsp->fsp_flags.can_read) {
+ how.flags = O_RDONLY;
+ }
+
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ DEBUG(1, ("vfs_default_durable_reconnect: failed to open "
+ "file: %s\n", nt_errstr(status)));
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ /*
+ * We now check the stat info stored in the cookie against
+ * the current stat data from the file we just opened.
+ * If any detail differs, we deny the durable reconnect,
+ * because in that case it is very likely that someone
+ * opened the file while the handle was disconnected,
+ * which has to be interpreted as an oplock break.
+ */
+
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ if (ret == -1) {
+ NTSTATUS close_status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to fstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+ if (!file_id_equal(&cookie.id, &file_id)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ (void)fdos_mode(fsp);
+
+ ok = vfs_default_durable_reconnect_check_stat(&cookie.stat_info,
+ &fsp->fsp_name->st,
+ fsp_str_dbg(fsp));
+ if (!ok) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ DEBUG(1, ("vfs_default_durable_reconnect: "
+ "vfs_default_durable_cookie - %s\n",
+ nt_errstr(status)));
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ smb1req->chain_fsp = fsp;
+ smb1req->smb2req->compat_chain_fsp = fsp;
+
+ DEBUG(10, ("vfs_default_durable_reconnect: opened file '%s'\n",
+ fsp_str_dbg(fsp)));
+
+ TALLOC_FREE(lck);
+
+ fsp->fsp_flags.is_fsa = true;
+
+ *result = fsp;
+ *new_cookie = new_cookie_blob;
+
+ return NT_STATUS_OK;
+}