summaryrefslogtreecommitdiff
path: root/source3/smbd/durable.c
diff options
context:
space:
mode:
authorMichael Adam <obnox@samba.org>2012-06-08 17:54:19 +0200
committerStefan Metzmacher <metze@samba.org>2012-09-08 19:48:20 +0200
commiteabe4c8fc4f8ba7ac75ceb9924cd5a90ff14470d (patch)
treeba544598380ea8a5a96f8403179081e9e0f85601 /source3/smbd/durable.c
parent1d012a6780c543620413b24237cb7e9b97a6e3bc (diff)
downloadsamba-eabe4c8fc4f8ba7ac75ceb9924cd5a90ff14470d.tar.gz
samba-eabe4c8fc4f8ba7ac75ceb9924cd5a90ff14470d.tar.bz2
samba-eabe4c8fc4f8ba7ac75ceb9924cd5a90ff14470d.zip
s3:vfs_default: add basic support for durable handle request and reconnect
We only grant durable handles for CIFS/SMB2 only access, that means "kernel oplocks", "kernel share modes" and "posix locking" need to be set to "no". For now we also don't grant durable handles if delete on close is active on the handle. Pair-Programmed-With: Stefan Metzmacher <metze@samba.org> Pair-Programmed-With: Volker Lendecke <vl@samba.org>
Diffstat (limited to 'source3/smbd/durable.c')
-rw-r--r--source3/smbd/durable.c569
1 files changed, 569 insertions, 0 deletions
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
new file mode 100644
index 0000000000..5953e1b43e
--- /dev/null
+++ b/source3/smbd/durable.c
@@ -0,0 +1,569 @@
+/*
+ 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 "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"
+
+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 kernel share modes (flocks) 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->is_directory) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp->fh->fd == -1) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (is_ntfs_stream_smb_fname(fsp->fsp_name)) {
+ /*
+ * 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 = fsp->fh->position_information;
+
+ 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 (!BATCH_OPLOCK_TYPE(fsp->oplock_type)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp->num_pending_break_messages > 0) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * For now let it be simple and do not keep
+ * delete on close files durable open
+ */
+ if (fsp->initial_delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ if (fsp->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;
+ }
+
+ /*
+ * 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) {
+ 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);
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = true;
+ 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 = fsp->fh->position_information;
+
+ 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;
+}
+
+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)
+{
+ struct share_mode_lock *lck;
+ struct share_mode_entry *e;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+ bool ok;
+ int ret;
+ int flags;
+ 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.
+ */
+
+ 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 (!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. */
+ status = create_synthetic_smb_fname(talloc_tos(),
+ cookie.base_name,
+ NULL, NULL,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ 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;
+ }
+
+ if (lck->data->num_share_modes == 0) {
+ DEBUG(1, ("vfs_default_durable_reconnect: Error: no share-mode "
+ "entry in existing share mode lock\n"));
+ TALLOC_FREE(lck);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (lck->data->num_share_modes > 1) {
+ /*
+ * It can't be durable if there is more than one handle
+ * on the file.
+ */
+ DEBUG(5, ("vfs_default_durable_reconnect: more than one "
+ "share-mode entry - can not be durable\n"));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ e = &lck->data->share_modes[0];
+
+ 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) {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "share_file_id changed %llu != %llu"
+ "(e.g. another client had opened the file)\n",
+ (unsigned long long)e->share_file_id,
+ (unsigned long long)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(), SNUM(conn))));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * TODO:
+ * add scavenger timer functionality
+ *
+ * For now we always allow the reconnect
+ */
+#if 0
+ expire_time = op->global->disconnect_time;
+ expire_time += NTTIME_MAGIC(op->global->durable_timeout_msec);
+ if (expire < now) {
+ //TODO reopen and close before telling the client...
+ }
+#endif
+
+ /*
+ * 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;
+ }
+
+ fsp->fh->private_options = e->private_options;
+ fsp->fh->gen_id = smbXsrv_open_hash(op);
+ 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->share_access = e->share_access;
+ fsp->can_read = ((fsp->access_mask & (FILE_READ_DATA)) != 0);
+ fsp->can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0);
+
+ /*
+ * TODO:
+ * Do we need to store the modified flag in the DB?
+ * How to handle update_write_time and friends
+ * during a disconnected client on a durable handle?
+ */
+ fsp->modified = false;
+ /*
+ * no durables for directories
+ */
+ fsp->is_directory = false;
+ /*
+ * For normal files, can_lock == !is_directory
+ */
+ fsp->can_lock = true;
+ /*
+ * We do not support aio write behind for smb2
+ */
+ fsp->aio_write_behind = false;
+ fsp->oplock_type = e->op_type;
+
+ fsp->initial_allocation_size = cookie.initial_allocation_size;
+ fsp->fh->position_information = cookie.position_information;
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ fsp_free(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;
+
+ e->pid = messaging_server_id(conn->sconn->msg_ctx);
+ e->op_mid = smb1req->mid;
+ e->share_file_id = fsp->fh->gen_id;
+
+ 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_free(fsp);
+ return status;
+ }
+
+ /*
+ * TODO: properly calculate open flags
+ */
+ if (fsp->can_write && fsp->can_read) {
+ flags = O_RDWR;
+ } else if (fsp->can_write) {
+ flags = O_WRONLY;
+ } else if (fsp->can_read) {
+ flags = O_RDONLY;
+ }
+
+ status = fd_open(conn, fsp, flags, 0 /* mode */);
+ 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_free(fsp);
+ return status;
+ }
+
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ if (ret == -1) {
+ 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)));
+ ret = SMB_VFS_CLOSE(fsp);
+ if (ret == -1) {
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "SMB_VFS_CLOSE failed (%s) - leaking file "
+ "descriptor\n", strerror(errno)));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp_free(fsp);
+ return status;
+
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ ret = SMB_VFS_CLOSE(fsp);
+ if (ret == -1) {
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "SMB_VFS_CLOSE failed (%s) - leaking file "
+ "descriptor\n", strerror(errno)));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp_free(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)) {
+ ret = SMB_VFS_CLOSE(fsp);
+ if (ret == -1) {
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "SMB_VFS_CLOSE failed (%s) - leaking file "
+ "descriptor\n", strerror(errno)));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp_free(fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = set_file_oplock(fsp, e->op_type);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("vfs_default_durable_reconnect failed to set oplock "
+ "after opening file: %s\n", nt_errstr(status)));
+ ret = SMB_VFS_CLOSE(fsp);
+ if (ret == -1) {
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "SMB_VFS_CLOSE failed (%s) - leaking file "
+ "descriptor\n", strerror(errno)));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp_free(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_free(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)));
+
+ /*
+ * release the sharemode lock: this writes the changes
+ */
+ lck->data->modified = true;
+ TALLOC_FREE(lck);
+
+ *result = fsp;
+ *new_cookie = new_cookie_blob;
+
+ return NT_STATUS_OK;
+}