diff options
Diffstat (limited to 'source3/locking/locking.c')
-rw-r--r-- | source3/locking/locking.c | 1359 |
1 files changed, 1359 insertions, 0 deletions
diff --git a/source3/locking/locking.c b/source3/locking/locking.c new file mode 100644 index 0000000..fa1c85a --- /dev/null +++ b/source3/locking/locking.c @@ -0,0 +1,1359 @@ +/* + Unix SMB/CIFS implementation. + Locking functions + Copyright (C) Andrew Tridgell 1992-2000 + Copyright (C) Jeremy Allison 1992-2006 + Copyright (C) Volker Lendecke 2005 + + 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/>. + + Revision History: + + 12 aug 96: Erik.Devriendt@te6.siemens.be + added support for shared memory implementation of share mode locking + + May 1997. Jeremy Allison (jallison@whistle.com). Modified share mode + locking to deal with multiple share modes per open file. + + September 1997. Jeremy Allison (jallison@whistle.com). Added oplock + support. + + rewritten completely to use new tdb code. Tridge, Dec '99 + + Added POSIX locking support. Jeremy Allison (jeremy@valinux.com), Apr. 2000. + Added Unix Extensions POSIX locking support. Jeremy Allison Mar 2006. +*/ + +#include "includes.h" +#include "lib/util/time_basic.h" +#include "system/filesys.h" +#include "lib/util/server_id.h" +#include "share_mode_lock.h" +#include "share_mode_lock_private.h" +#include "locking/proto.h" +#include "smbd/globals.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "../libcli/security/security.h" +#include "serverid.h" +#include "messages.h" +#include "util_tdb.h" +#include "../librpc/gen_ndr/ndr_open_files.h" +#include "librpc/gen_ndr/ndr_file_id.h" +#include "librpc/gen_ndr/ndr_leases_db.h" +#include "locking/leases_db.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_LOCKING + +#define NO_LOCKING_COUNT (-1) + +/**************************************************************************** + Debugging aids :-). +****************************************************************************/ + +const char *lock_type_name(enum brl_type lock_type) +{ + switch (lock_type) { + case READ_LOCK: + return "READ"; + case WRITE_LOCK: + return "WRITE"; + default: + return "other"; + } +} + +const char *lock_flav_name(enum brl_flavour lock_flav) +{ + return (lock_flav == WINDOWS_LOCK) ? "WINDOWS_LOCK" : "POSIX_LOCK"; +} + +/**************************************************************************** + Utility function called to see if a file region is locked. + Called in the read/write codepath. +****************************************************************************/ + +void init_strict_lock_struct(files_struct *fsp, + uint64_t smblctx, + br_off start, + br_off size, + enum brl_type lock_type, + enum brl_flavour lock_flav, + struct lock_struct *plock) +{ + SMB_ASSERT(lock_type == READ_LOCK || lock_type == WRITE_LOCK); + + plock->context.smblctx = smblctx; + plock->context.tid = fsp->conn->cnum; + plock->context.pid = messaging_server_id(fsp->conn->sconn->msg_ctx); + plock->start = start; + plock->size = size; + plock->fnum = fsp->fnum; + plock->lock_type = lock_type; + plock->lock_flav = lp_posix_cifsu_locktype(fsp); +} + +bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock) +{ + struct byte_range_lock *br_lck; + int strict_locking = lp_strict_locking(fsp->conn->params); + bool ret = False; + + if (plock->size == 0) { + return True; + } + + if (!lp_locking(fsp->conn->params) || !strict_locking) { + return True; + } + + if (strict_locking == Auto) { + uint32_t lease_type = fsp_lease_type(fsp); + + if ((lease_type & SMB2_LEASE_READ) && + (plock->lock_type == READ_LOCK)) + { + DBG_DEBUG("optimisation - read lease on file %s\n", + fsp_str_dbg(fsp)); + return true; + } + + if ((lease_type & SMB2_LEASE_WRITE) && + (plock->lock_type == WRITE_LOCK)) + { + DBG_DEBUG("optimisation - write lease on file %s\n", + fsp_str_dbg(fsp)); + return true; + } + } + + br_lck = brl_get_locks_readonly(fsp); + if (!br_lck) { + return true; + } + ret = brl_locktest(br_lck, plock); + + if (!ret) { + /* + * We got a lock conflict. Retry with rw locks to enable + * autocleanup. This is the slow path anyway. + */ + br_lck = brl_get_locks(talloc_tos(), fsp); + if (br_lck == NULL) { + return true; + } + ret = brl_locktest(br_lck, plock); + TALLOC_FREE(br_lck); + } + + DEBUG(10, ("strict_lock_default: flavour = %s brl start=%ju " + "len=%ju %s for fnum %ju file %s\n", + lock_flav_name(plock->lock_flav), + (uintmax_t)plock->start, (uintmax_t)plock->size, + ret ? "unlocked" : "locked", + (uintmax_t)plock->fnum, fsp_str_dbg(fsp))); + + return ret; +} + +/**************************************************************************** + Find out if a lock could be granted - return who is blocking us if we can't. +****************************************************************************/ + +NTSTATUS query_lock(files_struct *fsp, + uint64_t *psmblctx, + uint64_t *pcount, + uint64_t *poffset, + enum brl_type *plock_type, + enum brl_flavour lock_flav) +{ + struct byte_range_lock *br_lck = NULL; + + if (!fsp->fsp_flags.can_lock) { + return fsp->fsp_flags.is_directory ? + NT_STATUS_INVALID_DEVICE_REQUEST : + NT_STATUS_INVALID_HANDLE; + } + + if (!lp_locking(fsp->conn->params)) { + return NT_STATUS_OK; + } + + br_lck = brl_get_locks_readonly(fsp); + if (!br_lck) { + return NT_STATUS_NO_MEMORY; + } + + return brl_lockquery(br_lck, + psmblctx, + messaging_server_id(fsp->conn->sconn->msg_ctx), + poffset, + pcount, + plock_type, + lock_flav); +} + +static void increment_current_lock_count(files_struct *fsp, + enum brl_flavour lock_flav) +{ + if (lock_flav == WINDOWS_LOCK && + fsp->current_lock_count != NO_LOCKING_COUNT) { + /* blocking ie. pending, locks also count here, + * as this is an efficiency counter to avoid checking + * the lock db. on close. JRA. */ + + fsp->current_lock_count++; + } else { + /* Notice that this has had a POSIX lock request. + * We can't count locks after this so forget them. + */ + fsp->current_lock_count = NO_LOCKING_COUNT; + } +} + +static void decrement_current_lock_count(files_struct *fsp, + enum brl_flavour lock_flav) +{ + if (lock_flav == WINDOWS_LOCK && + fsp->current_lock_count != NO_LOCKING_COUNT) { + SMB_ASSERT(fsp->current_lock_count > 0); + fsp->current_lock_count--; + } +} + +/**************************************************************************** + Utility function called by locking requests. +****************************************************************************/ + +struct do_lock_state { + struct files_struct *fsp; + TALLOC_CTX *req_mem_ctx; + const struct GUID *req_guid; + uint64_t smblctx; + uint64_t count; + uint64_t offset; + enum brl_type lock_type; + enum brl_flavour lock_flav; + + struct server_id blocker_pid; + uint64_t blocker_smblctx; + NTSTATUS status; +}; + +static void do_lock_fn( + struct share_mode_lock *lck, + void *private_data) +{ + struct do_lock_state *state = private_data; + struct byte_range_lock *br_lck = NULL; + + br_lck = brl_get_locks_for_locking(talloc_tos(), + state->fsp, + state->req_mem_ctx, + state->req_guid); + if (br_lck == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + + state->status = brl_lock( + br_lck, + state->smblctx, + messaging_server_id(state->fsp->conn->sconn->msg_ctx), + state->offset, + state->count, + state->lock_type, + state->lock_flav, + &state->blocker_pid, + &state->blocker_smblctx); + + TALLOC_FREE(br_lck); +} + +NTSTATUS do_lock(files_struct *fsp, + TALLOC_CTX *req_mem_ctx, + const struct GUID *req_guid, + uint64_t smblctx, + uint64_t count, + uint64_t offset, + enum brl_type lock_type, + enum brl_flavour lock_flav, + struct server_id *pblocker_pid, + uint64_t *psmblctx) +{ + struct do_lock_state state = { + .fsp = fsp, + .req_mem_ctx = req_mem_ctx, + .req_guid = req_guid, + .smblctx = smblctx, + .count = count, + .offset = offset, + .lock_type = lock_type, + .lock_flav = lock_flav, + }; + NTSTATUS status; + + /* silently return ok on print files as we don't do locking there */ + if (fsp->print_file) { + return NT_STATUS_OK; + } + + if (!fsp->fsp_flags.can_lock) { + if (fsp->fsp_flags.is_directory) { + return NT_STATUS_INVALID_DEVICE_REQUEST; + } + return NT_STATUS_INVALID_HANDLE; + } + + if (!lp_locking(fsp->conn->params)) { + return NT_STATUS_OK; + } + + /* NOTE! 0 byte long ranges ARE allowed and should be stored */ + + DBG_DEBUG("lock flavour %s lock type %s start=%"PRIu64" len=%"PRIu64" " + "requested for %s file %s\n", + lock_flav_name(lock_flav), + lock_type_name(lock_type), + offset, + count, + fsp_fnum_dbg(fsp), + fsp_str_dbg(fsp)); + + status = share_mode_do_locked_vfs_allowed(fsp->file_id, + do_lock_fn, + &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("share_mode_do_locked returned %s\n", + nt_errstr(status)); + return status; + } + + if (psmblctx != NULL) { + *psmblctx = state.blocker_smblctx; + } + if (pblocker_pid != NULL) { + *pblocker_pid = state.blocker_pid; + } + + DBG_DEBUG("returning status=%s\n", nt_errstr(state.status)); + + increment_current_lock_count(fsp, lock_flav); + + return state.status; +} + +/**************************************************************************** + Utility function called by unlocking requests. +****************************************************************************/ + +NTSTATUS do_unlock(files_struct *fsp, + uint64_t smblctx, + uint64_t count, + uint64_t offset, + enum brl_flavour lock_flav) +{ + bool ok = False; + struct byte_range_lock *br_lck = NULL; + + if (!fsp->fsp_flags.can_lock) { + return fsp->fsp_flags.is_directory ? + NT_STATUS_INVALID_DEVICE_REQUEST : + NT_STATUS_INVALID_HANDLE; + } + + if (!lp_locking(fsp->conn->params)) { + return NT_STATUS_OK; + } + + DBG_DEBUG("unlock start=%"PRIu64" len=%"PRIu64" requested for %s file " + "%s\n", + offset, + count, + fsp_fnum_dbg(fsp), + fsp_str_dbg(fsp)); + + br_lck = brl_get_locks(talloc_tos(), fsp); + if (!br_lck) { + return NT_STATUS_NO_MEMORY; + } + + ok = brl_unlock(br_lck, + smblctx, + messaging_server_id(fsp->conn->sconn->msg_ctx), + offset, + count, + lock_flav); + + TALLOC_FREE(br_lck); + + if (!ok) { + DEBUG(10,("do_unlock: returning ERRlock.\n" )); + return NT_STATUS_RANGE_NOT_LOCKED; + } + + decrement_current_lock_count(fsp, lock_flav); + return NT_STATUS_OK; +} + +/**************************************************************************** + Remove any locks on this fd. Called from file_close(). +****************************************************************************/ + +void locking_close_file(files_struct *fsp, + enum file_close_type close_type) +{ + struct byte_range_lock *br_lck; + + if (!lp_locking(fsp->conn->params)) { + return; + } + + /* If we have no outstanding locks or pending + * locks then we don't need to look in the lock db. + */ + + if (fsp->current_lock_count == 0) { + return; + } + + br_lck = brl_get_locks(talloc_tos(),fsp); + + if (br_lck) { + /* + * Unlocks must trigger dbwrap_watch watchers, + * normally in smbd_do_unlocking. Here it's done + * implicitly, we're closing the file and thus remove a + * share mode. This will wake the waiters. + */ + brl_close_fnum(br_lck); + TALLOC_FREE(br_lck); + } +} + +/******************************************************************* + Print out a share mode. +********************************************************************/ + +char *share_mode_str(TALLOC_CTX *ctx, int num, + const struct file_id *id, + const struct share_mode_entry *e) +{ + struct server_id_buf tmp; + struct file_id_buf ftmp; + + return talloc_asprintf(ctx, "share_mode_entry[%d]: " + "pid = %s, share_access = 0x%x, private_options = 0x%x, " + "access_mask = 0x%x, mid = 0x%llx, type= 0x%x, gen_id = %llu, " + "uid = %u, flags = %u, file_id %s, name_hash = 0x%x", + num, + server_id_str_buf(e->pid, &tmp), + e->share_access, e->private_options, + e->access_mask, (unsigned long long)e->op_mid, + e->op_type, (unsigned long long)e->share_file_id, + (unsigned int)e->uid, (unsigned int)e->flags, + file_id_str_buf(*id, &ftmp), + (unsigned int)e->name_hash); +} + +struct rename_share_filename_state { + struct share_mode_data *data; + struct messaging_context *msg_ctx; + struct server_id self; + uint32_t orig_name_hash; + uint32_t new_name_hash; + struct file_rename_message msg; +}; + +static bool rename_lease_fn(struct share_mode_entry *e, + void *private_data) +{ + struct rename_share_filename_state *state = private_data; + struct share_mode_data *d = state->data; + NTSTATUS status; + + status = leases_db_rename(&e->client_guid, + &e->lease_key, + &d->id, + d->servicepath, + d->base_name, + d->stream_name); + + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_WARNING("Failed to rename lease key for " + "renamed file %s:%s. %s\n", + d->base_name, + d->stream_name, + nt_errstr(status)); + } + + return false; +} + +/******************************************************************* + Sets the service name and filename for rename. + At this point we emit "file renamed" messages to all + process id's that have this file open. + Based on an initial code idea from SATOH Fumiyasu <fumiya@samba.gr.jp> +********************************************************************/ + +static bool rename_share_filename_fn( + struct share_mode_entry *e, + bool *modified, + void *private_data) +{ + struct rename_share_filename_state *state = private_data; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + bool ok; + + /* + * If this is a hardlink to the inode with a different name, + * skip this. + */ + if (e->name_hash != state->orig_name_hash) { + return false; + } + e->name_hash = state->new_name_hash; + *modified = true; + + ok = server_id_equal(&e->pid, &state->self); + if (ok) { + return false; + } + + state->msg.share_file_id = e->share_file_id; + + ndr_err = ndr_push_struct_blob( + &blob, + talloc_tos(), + &state->msg, + (ndr_push_flags_fn_t)ndr_push_file_rename_message); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_DEBUG("ndr_push_file_rename_message failed: %s\n", + ndr_errstr(ndr_err)); + return false; + } + if (DEBUGLEVEL >= 10) { + struct server_id_buf tmp; + DBG_DEBUG("sending rename message to %s\n", + server_id_str_buf(e->pid, &tmp)); + NDR_PRINT_DEBUG(file_rename_message, &state->msg); + } + + messaging_send(state->msg_ctx, e->pid, MSG_SMB_FILE_RENAME, &blob); + + TALLOC_FREE(blob.data); + + return false; +} + +bool rename_share_filename(struct messaging_context *msg_ctx, + struct share_mode_lock *lck, + struct file_id id, + const char *servicepath, + uint32_t orig_name_hash, + uint32_t new_name_hash, + const struct smb_filename *smb_fname_dst) +{ + struct rename_share_filename_state state = { + .msg_ctx = msg_ctx, + .self = messaging_server_id(msg_ctx), + .orig_name_hash = orig_name_hash, + .new_name_hash = new_name_hash, + .msg.id = id, + .msg.servicepath = servicepath, + .msg.base_name = smb_fname_dst->base_name, + .msg.stream_name = smb_fname_dst->stream_name, + }; + struct share_mode_data *d = NULL; + NTSTATUS status; + bool ok; + + DEBUG(10, ("rename_share_filename: servicepath %s newname %s\n", + servicepath, smb_fname_dst->base_name)); + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "servicepath %s newname %s - %s\n", + servicepath, smb_fname_dst->base_name, + nt_errstr(status)); + return false; + } + state.data = d; + + /* + * rename_internal_fsp() and rename_internals() add './' to + * head of newname if newname does not contain a '/'. + */ + + if (strncmp(state.msg.base_name, "./", 2) == 0) { + state.msg.base_name += 2; + } + + d->servicepath = talloc_strdup(d, state.msg.servicepath); + d->base_name = talloc_strdup(d, state.msg.base_name); + d->stream_name = talloc_strdup(d, state.msg.stream_name); + if ((d->servicepath == NULL) || + (d->base_name == NULL) || + ((state.msg.stream_name != NULL) && (d->stream_name == NULL))) { + DBG_WARNING("talloc failed\n"); + return false; + } + d->modified = True; + + ok = share_mode_forall_entries( + lck, rename_share_filename_fn, &state); + if (!ok) { + DBG_WARNING("share_mode_forall_entries failed\n"); + } + + ok = share_mode_forall_leases(lck, rename_lease_fn, &state); + if (!ok) { + /* + * Ignore error here. Not sure what to do.. + */ + DBG_WARNING("share_mode_forall_leases failed\n"); + } + + return True; +} + +void get_file_infos(struct file_id id, + uint32_t name_hash, + bool *delete_on_close, + struct timespec *write_time) +{ + struct share_mode_lock *lck; + + if (delete_on_close) { + *delete_on_close = false; + } + + if (write_time) { + *write_time = make_omit_timespec(); + } + + if (!(lck = fetch_share_mode_unlocked(talloc_tos(), id))) { + return; + } + + if (delete_on_close) { + *delete_on_close = is_delete_on_close_set(lck, name_hash); + } + + if (write_time) { + *write_time = get_share_mode_write_time(lck); + } + + TALLOC_FREE(lck); +} + +bool is_valid_share_mode_entry(const struct share_mode_entry *e) +{ + int num_props = 0; + + if (e->stale) { + return false; + } + + num_props += ((e->op_type == NO_OPLOCK) ? 1 : 0); + num_props += (EXCLUSIVE_OPLOCK_TYPE(e->op_type) ? 1 : 0); + num_props += (LEVEL_II_OPLOCK_TYPE(e->op_type) ? 1 : 0); + num_props += (e->op_type == LEASE_OPLOCK); + + if ((num_props > 1) && serverid_exists(&e->pid)) { + smb_panic("Invalid share mode entry"); + } + return (num_props != 0); +} + +struct find_lease_ref_state { + const struct GUID *client_guid; + const struct smb2_lease_key *lease_key; + bool found_same; +}; + +static bool find_lease_ref_fn( + struct share_mode_entry *e, + bool *modified, + void *private_data) +{ + struct find_lease_ref_state *state = private_data; + + if (e->stale) { + return false; + } + if (e->op_type != LEASE_OPLOCK) { + return false; + } + + state->found_same = smb2_lease_equal( + &e->client_guid, + &e->lease_key, + state->client_guid, + state->lease_key); + /* + * If we found a lease reference, look no further (i.e. return true) + */ + return state->found_same; +} + +NTSTATUS remove_lease_if_stale(struct share_mode_lock *lck, + const struct GUID *client_guid, + const struct smb2_lease_key *lease_key) +{ + struct find_lease_ref_state state = { + .client_guid = client_guid, .lease_key = lease_key, + }; + struct file_id id = share_mode_lock_file_id(lck); + NTSTATUS status; + bool ok; + + ok = share_mode_forall_entries(lck, find_lease_ref_fn, &state); + if (!ok) { + DBG_ERR("share_mode_forall_entries failed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + if (state.found_same) { + return NT_STATUS_RESOURCE_IN_USE; + } + + status = leases_db_del(client_guid, lease_key, &id); + if (!NT_STATUS_IS_OK(status)) { + int level = DBGLVL_DEBUG; + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + level = DBGLVL_ERR; + } + DBG_PREFIX(level, ("leases_db_del failed: %s\n", + nt_errstr(status))); + } + return status; +} + +bool share_entry_stale_pid(struct share_mode_entry *e) +{ + struct server_id_buf buf; + bool exists; + + if (e->stale) { + return true; + } + + exists = serverid_exists(&e->pid); + if (exists) { + DBG_DEBUG("PID %s still exists\n", + server_id_str_buf(e->pid, &buf)); + return false; + } + + DBG_DEBUG("PID %s does not exist anymore\n", + server_id_str_buf(e->pid, &buf)); + + e->stale = true; + + return true; +} + +/**************************************************************************** + Adds a delete on close token. +****************************************************************************/ + +static bool add_delete_on_close_token(struct share_mode_data *d, + uint32_t name_hash, + const struct security_token *nt_tok, + const struct security_unix_token *tok) +{ + struct delete_token *tmp, *dtl; + + tmp = talloc_realloc(d, d->delete_tokens, struct delete_token, + d->num_delete_tokens+1); + if (tmp == NULL) { + return false; + } + d->delete_tokens = tmp; + dtl = &d->delete_tokens[d->num_delete_tokens]; + + dtl->name_hash = name_hash; + dtl->delete_nt_token = security_token_duplicate(d->delete_tokens, nt_tok); + if (dtl->delete_nt_token == NULL) { + return false; + } + dtl->delete_token = copy_unix_token(d->delete_tokens, tok); + if (dtl->delete_token == NULL) { + return false; + } + d->num_delete_tokens += 1; + d->modified = true; + return true; +} + +void reset_delete_on_close_lck(files_struct *fsp, + struct share_mode_lock *lck) +{ + struct share_mode_data *d = NULL; + NTSTATUS status; + uint32_t i; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s - %s\n", fsp_str_dbg(fsp), nt_errstr(status)); + smb_panic(__location__); + return; + } + + for (i=0; i<d->num_delete_tokens; i++) { + struct delete_token *dt = &d->delete_tokens[i]; + + if (dt->name_hash == fsp->name_hash) { + d->modified = true; + + /* Delete this entry. */ + TALLOC_FREE(dt->delete_nt_token); + TALLOC_FREE(dt->delete_token); + *dt = d->delete_tokens[d->num_delete_tokens-1]; + d->num_delete_tokens -= 1; + } + } +} + +struct set_delete_on_close_state { + struct messaging_context *msg_ctx; + DATA_BLOB blob; +}; + +static bool set_delete_on_close_fn( + struct share_mode_entry *e, + bool *modified, + void *private_data) +{ + struct set_delete_on_close_state *state = private_data; + NTSTATUS status; + + status = messaging_send( + state->msg_ctx, + e->pid, + MSG_SMB_NOTIFY_CANCEL_DELETED, + &state->blob); + + if (!NT_STATUS_IS_OK(status)) { + struct server_id_buf tmp; + DBG_DEBUG("messaging_send to %s returned %s\n", + server_id_str_buf(e->pid, &tmp), + nt_errstr(status)); + } + + return false; +} + +/**************************************************************************** + Sets the delete on close flag over all share modes on this file. + Modify the share mode entry for all files open + on this device and inode to tell other smbds we have + changed the delete on close flag. This will be noticed + in the close code, the last closer will delete the file + if flag is set. + This makes a copy of any struct security_unix_token into the + lck entry. This function is used when the lock is already granted. +****************************************************************************/ + +void set_delete_on_close_lck(files_struct *fsp, + struct share_mode_lock *lck, + const struct security_token *nt_tok, + const struct security_unix_token *tok) +{ + struct share_mode_data *d = NULL; + struct set_delete_on_close_state state = { + .msg_ctx = fsp->conn->sconn->msg_ctx + }; + uint32_t i; + bool ret; + enum ndr_err_code ndr_err; + NTSTATUS status; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s - %s\n", fsp_str_dbg(fsp), nt_errstr(status)); + smb_panic(__location__); + return; + } + + SMB_ASSERT(nt_tok != NULL); + SMB_ASSERT(tok != NULL); + + for (i=0; i<d->num_delete_tokens; i++) { + struct delete_token *dt = &d->delete_tokens[i]; + if (dt->name_hash == fsp->name_hash) { + d->modified = true; + + /* Replace this token with the given tok. */ + TALLOC_FREE(dt->delete_nt_token); + dt->delete_nt_token = security_token_duplicate(dt, nt_tok); + SMB_ASSERT(dt->delete_nt_token != NULL); + TALLOC_FREE(dt->delete_token); + dt->delete_token = copy_unix_token(dt, tok); + SMB_ASSERT(dt->delete_token != NULL); + + return; + } + } + + ret = add_delete_on_close_token(d, fsp->name_hash, nt_tok, tok); + SMB_ASSERT(ret); + + ndr_err = ndr_push_struct_blob( + &state.blob, + talloc_tos(), + &fsp->file_id, + (ndr_push_flags_fn_t)ndr_push_file_id); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_push_file_id failed: %s\n", + ndr_errstr(ndr_err)); + smb_panic(__location__); + } + + ret = share_mode_forall_entries( + lck, set_delete_on_close_fn, &state); + if (!ret) { + DBG_ERR("share_mode_forall_entries failed\n"); + smb_panic(__location__); + } + + TALLOC_FREE(state.blob.data); +} + +struct set_delete_on_close_locked_state { + struct files_struct *fsp; + bool delete_on_close; + const struct security_token *nt_tok; + const struct security_unix_token *tok; +}; + +static void set_delete_on_close_locked(struct share_mode_lock *lck, + void *private_data) +{ + struct set_delete_on_close_locked_state *state = + (struct set_delete_on_close_locked_state *)private_data; + + if (state->delete_on_close) { + set_delete_on_close_lck(state->fsp, + lck, + state->nt_tok, + state->tok); + } else { + reset_delete_on_close_lck(state->fsp, lck); + } + + state->fsp->fsp_flags.delete_on_close = state->delete_on_close; +} + +bool set_delete_on_close(files_struct *fsp, bool delete_on_close, + const struct security_token *nt_tok, + const struct security_unix_token *tok) +{ + struct set_delete_on_close_locked_state state = { + .fsp = fsp, + .delete_on_close = delete_on_close, + .nt_tok = nt_tok, + .tok = tok, + }; + NTSTATUS status; + + DEBUG(10,("set_delete_on_close: %s delete on close flag for " + "%s, file %s\n", + delete_on_close ? "Adding" : "Removing", fsp_fnum_dbg(fsp), + fsp_str_dbg(fsp))); + + if (fsp->fsp_flags.is_directory) { + SMB_ASSERT(!is_ntfs_stream_smb_fname(fsp->fsp_name)); + } + + status = share_mode_do_locked_vfs_denied(fsp->file_id, + set_delete_on_close_locked, + &state); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + return True; +} + +static struct delete_token *find_delete_on_close_token( + struct share_mode_data *d, uint32_t name_hash) +{ + uint32_t i; + + DBG_DEBUG("name_hash = 0x%"PRIx32"\n", name_hash); + + for (i=0; i<d->num_delete_tokens; i++) { + struct delete_token *dt = &d->delete_tokens[i]; + + DBG_DEBUG("dt->name_hash = 0x%"PRIx32"\n", + dt->name_hash); + if (dt->name_hash == name_hash) { + return dt; + } + } + return NULL; +} + +/**************************************************************************** + Return the NT token and UNIX token if there's a match. Return true if + found, false if not. +****************************************************************************/ + +bool get_delete_on_close_token(struct share_mode_lock *lck, + uint32_t name_hash, + const struct security_token **pp_nt_tok, + const struct security_unix_token **pp_tok) +{ + struct share_mode_data *d = NULL; + struct delete_token *dt; + NTSTATUS status; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + struct file_id id = share_mode_lock_file_id(lck); + struct file_id_buf id_buf; + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s name_hash=%"PRIu32" - %s\n", + file_id_str_buf(id, &id_buf), name_hash, + nt_errstr(status)); + return false; + } + + dt = find_delete_on_close_token(d, name_hash); + if (dt == NULL) { + return false; + } + *pp_nt_tok = dt->delete_nt_token; + *pp_tok = dt->delete_token; + return true; +} + +bool is_delete_on_close_set(struct share_mode_lock *lck, uint32_t name_hash) +{ + struct share_mode_data *d = NULL; + NTSTATUS status; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + struct file_id id = share_mode_lock_file_id(lck); + struct file_id_buf id_buf; + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s name_hash=%"PRIu32" - %s\n", + file_id_str_buf(id, &id_buf), name_hash, + nt_errstr(status)); + return false; + } + + return find_delete_on_close_token(d, name_hash) != NULL; +} + +struct set_sticky_write_time_state { + struct file_id fileid; + struct timespec write_time; + bool ok; +}; + +static void set_sticky_write_time_fn(struct share_mode_lock *lck, + void *private_data) +{ + struct set_sticky_write_time_state *state = private_data; + struct share_mode_data *d = NULL; + struct file_id_buf ftmp; + struct timeval_buf tbuf; + NTSTATUS status; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s id=%s - %s\n", + timespec_string_buf(&state->write_time, true, &tbuf), + file_id_str_buf(state->fileid, &ftmp), + nt_errstr(status)); + return; + } + + share_mode_set_changed_write_time(lck, state->write_time); + + state->ok = true; +} + +bool set_sticky_write_time(struct file_id fileid, struct timespec write_time) +{ + struct set_sticky_write_time_state state = { + .fileid = fileid, + .write_time = write_time, + }; + struct file_id_buf ftmp; + struct timeval_buf tbuf; + NTSTATUS status; + + status = share_mode_do_locked_vfs_denied(fileid, + set_sticky_write_time_fn, + &state); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_do_locked_vfs_denied() failed for " + "%s id=%s - %s\n", + timespec_string_buf(&write_time, true, &tbuf), + file_id_str_buf(fileid, &ftmp), + nt_errstr(status)); + return false; + } + + return state.ok; +} + +struct set_write_time_state { + struct file_id fileid; + struct timespec write_time; + bool ok; +}; + +static void set_write_time_fn(struct share_mode_lock *lck, + void *private_data) +{ + struct set_write_time_state *state = private_data; + struct share_mode_data *d = NULL; + struct file_id_buf idbuf; + struct timeval_buf tbuf; + NTSTATUS status; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s id=%s - %s\n", + timespec_string_buf(&state->write_time, true, &tbuf), + file_id_str_buf(state->fileid, &idbuf), + nt_errstr(status)); + return; + } + + share_mode_set_old_write_time(lck, state->write_time); + + state->ok = true; +} + +bool set_write_time(struct file_id fileid, struct timespec write_time) +{ + struct set_write_time_state state = { + .fileid = fileid, + .write_time = write_time, + }; + struct file_id_buf idbuf; + struct timeval_buf tbuf; + NTSTATUS status; + + status = share_mode_do_locked_vfs_denied(fileid, + set_write_time_fn, + &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("share_mode_do_locked_vfs_denied() failed for " + "%s id=%s - %s\n", + timespec_string_buf(&write_time, true, &tbuf), + file_id_str_buf(fileid, &idbuf), + nt_errstr(status)); + return false; + } + + return state.ok; +} + +struct timespec get_share_mode_write_time(struct share_mode_lock *lck) +{ + struct share_mode_data *d = NULL; + NTSTATUS status; + + status = share_mode_lock_access_private_data(lck, &d); + if (!NT_STATUS_IS_OK(status)) { + struct file_id id = share_mode_lock_file_id(lck); + struct file_id_buf id_buf; + struct timespec ts_zero = {}; + /* Any error recovery possible here ? */ + DBG_ERR("share_mode_lock_access_private_data() failed for " + "%s - %s\n", + file_id_str_buf(id, &id_buf), + nt_errstr(status)); + smb_panic(__location__); + return ts_zero; + } + + if (!null_nttime(d->changed_write_time)) { + return nt_time_to_full_timespec(d->changed_write_time); + } + return nt_time_to_full_timespec(d->old_write_time); +} + +struct file_has_open_streams_state { + bool found_one; + bool ok; +}; + +static bool file_has_open_streams_fn( + struct share_mode_entry *e, + bool *modified, + void *private_data) +{ + struct file_has_open_streams_state *state = private_data; + + if ((e->private_options & + NTCREATEX_FLAG_STREAM_BASEOPEN) == 0) { + return false; + } + + if (share_entry_stale_pid(e)) { + return false; + } + + state->found_one = true; + return true; +} + +static void file_has_open_streams_locked(struct share_mode_lock *lck, + void *private_data) +{ + struct file_has_open_streams_state *state = private_data; + + state->ok = share_mode_forall_entries(lck, + file_has_open_streams_fn, + private_data); +} + +bool file_has_open_streams(files_struct *fsp) +{ + struct file_has_open_streams_state state = { .found_one = false }; + NTSTATUS status; + + status = share_mode_do_locked_vfs_denied(fsp->file_id, + file_has_open_streams_locked, + &state); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("share_mode_do_locked_vfs_denied() failed - %s\n", + nt_errstr(status)); + return false; + } + + if (!state.ok) { + DBG_DEBUG("share_mode_forall_entries failed\n"); + return false; + } + + return state.found_one; +} + +/* + * Walk share mode entries, looking at every lease only once + */ + +struct share_mode_forall_leases_state { + TALLOC_CTX *mem_ctx; + struct leases_db_key *leases; + bool (*fn)(struct share_mode_entry *e, + void *private_data); + void *private_data; + NTSTATUS status; +}; + +static bool share_mode_forall_leases_fn( + struct share_mode_entry *e, + bool *modified, + void *private_data) +{ + struct share_mode_forall_leases_state *state = private_data; + struct leases_db_key *leases = state->leases; + size_t i, num_leases; + bool stop; + + if (e->op_type != LEASE_OPLOCK) { + return false; + } + + num_leases = talloc_array_length(leases); + + for (i=0; i<num_leases; i++) { + struct leases_db_key *l = &leases[i]; + bool same = smb2_lease_equal( + &e->client_guid, + &e->lease_key, + &l->client_guid, + &l->lease_key); + if (same) { + return false; + } + } + + leases = talloc_realloc( + state->mem_ctx, + leases, + struct leases_db_key, + num_leases+1); + if (leases == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return true; + } + leases[num_leases] = (struct leases_db_key) { + .client_guid = e->client_guid, + .lease_key = e->lease_key, + }; + state->leases = leases; + + stop = state->fn(e, state->private_data); + return stop; +} + +bool share_mode_forall_leases( + struct share_mode_lock *lck, + bool (*fn)(struct share_mode_entry *e, + void *private_data), + void *private_data) +{ + struct share_mode_forall_leases_state state = { + .mem_ctx = talloc_tos(), + .fn = fn, + .private_data = private_data + }; + bool ok; + + ok = share_mode_forall_entries( + lck, share_mode_forall_leases_fn, &state); + TALLOC_FREE(state.leases); + if (!ok) { + DBG_ERR("share_mode_forall_entries failed\n"); + return false; + } + + if (!NT_STATUS_IS_OK(state.status)) { + DBG_ERR("share_mode_forall_leases_fn returned %s\n", + nt_errstr(state.status)); + return false; + } + + return true; +} |