diff options
Diffstat (limited to 'source3/locking/locking.c')
-rw-r--r-- | source3/locking/locking.c | 1511 |
1 files changed, 1511 insertions, 0 deletions
diff --git a/source3/locking/locking.c b/source3/locking/locking.c new file mode 100644 index 0000000000..368ab1687c --- /dev/null +++ b/source3/locking/locking.c @@ -0,0 +1,1511 @@ +/* + 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. + + rewrtten 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_LOCKING + +#define NO_LOCKING_COUNT (-1) + +/* the locking database handle */ +static struct db_context *lock_db; + +/**************************************************************************** + 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"; + case PENDING_READ_LOCK: + return "PENDING_READ"; + case PENDING_WRITE_LOCK: + return "PENDING_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. +****************************************************************************/ + +bool is_locked(files_struct *fsp, + uint32 smbpid, + SMB_BIG_UINT count, + SMB_BIG_UINT offset, + enum brl_type lock_type) +{ + int strict_locking = lp_strict_locking(fsp->conn->params); + enum brl_flavour lock_flav = lp_posix_cifsu_locktype(fsp); + bool ret = True; + + if (count == 0) { + return False; + } + + if (!lp_locking(fsp->conn->params) || !strict_locking) { + return False; + } + + if (strict_locking == Auto) { + if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type) && (lock_type == READ_LOCK || lock_type == WRITE_LOCK)) { + DEBUG(10,("is_locked: optimisation - exclusive oplock on file %s\n", fsp->fsp_name )); + ret = False; + } else if ((fsp->oplock_type == LEVEL_II_OPLOCK) && + (lock_type == READ_LOCK)) { + DEBUG(10,("is_locked: optimisation - level II oplock on file %s\n", fsp->fsp_name )); + ret = False; + } else { + struct byte_range_lock *br_lck = brl_get_locks_readonly(talloc_tos(), fsp); + if (!br_lck) { + return False; + } + ret = !brl_locktest(br_lck, + smbpid, + procid_self(), + offset, + count, + lock_type, + lock_flav); + TALLOC_FREE(br_lck); + } + } else { + struct byte_range_lock *br_lck = brl_get_locks_readonly(talloc_tos(), fsp); + if (!br_lck) { + return False; + } + ret = !brl_locktest(br_lck, + smbpid, + procid_self(), + offset, + count, + lock_type, + lock_flav); + TALLOC_FREE(br_lck); + } + + DEBUG(10,("is_locked: flavour = %s brl start=%.0f len=%.0f %s for fnum %d file %s\n", + lock_flav_name(lock_flav), + (double)offset, (double)count, ret ? "locked" : "unlocked", + fsp->fnum, fsp->fsp_name )); + + 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, + uint32 *psmbpid, + SMB_BIG_UINT *pcount, + SMB_BIG_UINT *poffset, + enum brl_type *plock_type, + enum brl_flavour lock_flav) +{ + struct byte_range_lock *br_lck = NULL; + NTSTATUS status = NT_STATUS_LOCK_NOT_GRANTED; + + if (!fsp->can_lock) { + return fsp->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(talloc_tos(), fsp); + if (!br_lck) { + return NT_STATUS_NO_MEMORY; + } + + status = brl_lockquery(br_lck, + psmbpid, + procid_self(), + poffset, + pcount, + plock_type, + lock_flav); + + TALLOC_FREE(br_lck); + return status; +} + +/**************************************************************************** + Utility function called by locking requests. +****************************************************************************/ + +struct byte_range_lock *do_lock(struct messaging_context *msg_ctx, + files_struct *fsp, + uint32 lock_pid, + SMB_BIG_UINT count, + SMB_BIG_UINT offset, + enum brl_type lock_type, + enum brl_flavour lock_flav, + bool blocking_lock, + NTSTATUS *perr, + uint32 *plock_pid) +{ + struct byte_range_lock *br_lck = NULL; + + if (!fsp->can_lock) { + *perr = fsp->is_directory ? NT_STATUS_INVALID_DEVICE_REQUEST : NT_STATUS_INVALID_HANDLE; + return NULL; + } + + if (!lp_locking(fsp->conn->params)) { + *perr = NT_STATUS_OK; + return NULL; + } + + /* NOTE! 0 byte long ranges ARE allowed and should be stored */ + + DEBUG(10,("do_lock: lock flavour %s lock type %s start=%.0f len=%.0f requested for fnum %d file %s\n", + lock_flav_name(lock_flav), lock_type_name(lock_type), + (double)offset, (double)count, fsp->fnum, fsp->fsp_name )); + + br_lck = brl_get_locks(talloc_tos(), fsp); + if (!br_lck) { + *perr = NT_STATUS_NO_MEMORY; + return NULL; + } + + *perr = brl_lock(msg_ctx, + br_lck, + lock_pid, + procid_self(), + offset, + count, + lock_type, + lock_flav, + blocking_lock, + plock_pid); + + 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; + } + + return br_lck; +} + +/**************************************************************************** + Utility function called by unlocking requests. +****************************************************************************/ + +NTSTATUS do_unlock(struct messaging_context *msg_ctx, + files_struct *fsp, + uint32 lock_pid, + SMB_BIG_UINT count, + SMB_BIG_UINT offset, + enum brl_flavour lock_flav) +{ + bool ok = False; + struct byte_range_lock *br_lck = NULL; + + if (!fsp->can_lock) { + return fsp->is_directory ? NT_STATUS_INVALID_DEVICE_REQUEST : NT_STATUS_INVALID_HANDLE; + } + + if (!lp_locking(fsp->conn->params)) { + return NT_STATUS_OK; + } + + DEBUG(10,("do_unlock: unlock start=%.0f len=%.0f requested for fnum %d file %s\n", + (double)offset, (double)count, fsp->fnum, fsp->fsp_name )); + + br_lck = brl_get_locks(talloc_tos(), fsp); + if (!br_lck) { + return NT_STATUS_NO_MEMORY; + } + + ok = brl_unlock(msg_ctx, + br_lck, + lock_pid, + procid_self(), + offset, + count, + lock_flav); + + TALLOC_FREE(br_lck); + + if (!ok) { + DEBUG(10,("do_unlock: returning ERRlock.\n" )); + return NT_STATUS_RANGE_NOT_LOCKED; + } + + if (lock_flav == WINDOWS_LOCK && + fsp->current_lock_count != NO_LOCKING_COUNT) { + SMB_ASSERT(fsp->current_lock_count > 0); + fsp->current_lock_count--; + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Cancel any pending blocked locks. +****************************************************************************/ + +NTSTATUS do_lock_cancel(files_struct *fsp, + uint32 lock_pid, + SMB_BIG_UINT count, + SMB_BIG_UINT offset, + enum brl_flavour lock_flav) +{ + bool ok = False; + struct byte_range_lock *br_lck = NULL; + + if (!fsp->can_lock) { + return fsp->is_directory ? + NT_STATUS_INVALID_DEVICE_REQUEST : NT_STATUS_INVALID_HANDLE; + } + + if (!lp_locking(fsp->conn->params)) { + return NT_STATUS_DOS(ERRDOS, ERRcancelviolation); + } + + DEBUG(10,("do_lock_cancel: cancel start=%.0f len=%.0f requested for fnum %d file %s\n", + (double)offset, (double)count, fsp->fnum, fsp->fsp_name )); + + br_lck = brl_get_locks(talloc_tos(), fsp); + if (!br_lck) { + return NT_STATUS_NO_MEMORY; + } + + ok = brl_lock_cancel(br_lck, + lock_pid, + procid_self(), + offset, + count, + lock_flav); + + TALLOC_FREE(br_lck); + + if (!ok) { + DEBUG(10,("do_lock_cancel: returning ERRcancelviolation.\n" )); + return NT_STATUS_DOS(ERRDOS, ERRcancelviolation); + } + + if (lock_flav == WINDOWS_LOCK && + fsp->current_lock_count != NO_LOCKING_COUNT) { + SMB_ASSERT(fsp->current_lock_count > 0); + fsp->current_lock_count--; + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Remove any locks on this fd. Called from file_close(). +****************************************************************************/ + +void locking_close_file(struct messaging_context *msg_ctx, + files_struct *fsp) +{ + struct byte_range_lock *br_lck; + + if (!lp_locking(fsp->conn->params)) { + return; + } + + /* If we have not 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) { + cancel_pending_lock_requests_by_fid(fsp, br_lck); + brl_close_fnum(msg_ctx, br_lck); + TALLOC_FREE(br_lck); + } +} + +/**************************************************************************** + Initialise the locking functions. +****************************************************************************/ + +static bool locking_init_internal(bool read_only) +{ + brl_init(read_only); + + if (lock_db) + return True; + + lock_db = db_open(NULL, lock_path("locking.tdb"), + lp_open_files_db_hash_size(), + TDB_DEFAULT|TDB_VOLATILE|TDB_CLEAR_IF_FIRST, + read_only?O_RDONLY:O_RDWR|O_CREAT, 0644); + + if (!lock_db) { + DEBUG(0,("ERROR: Failed to initialise locking database\n")); + return False; + } + + if (!posix_locking_init(read_only)) + return False; + + return True; +} + +bool locking_init(void) +{ + return locking_init_internal(false); +} + +bool locking_init_readonly(void) +{ + return locking_init_internal(true); +} + +/******************************************************************* + Deinitialize the share_mode management. +******************************************************************/ + +bool locking_end(void) +{ + brl_shutdown(); + TALLOC_FREE(lock_db); + return true; +} + +/******************************************************************* + Form a static locking key for a dev/inode pair. +******************************************************************/ + +static TDB_DATA locking_key(const struct file_id *id, struct file_id *tmp) +{ + *tmp = *id; + return make_tdb_data((const uint8_t *)tmp, sizeof(*tmp)); +} + +/******************************************************************* + Print out a share mode. +********************************************************************/ + +char *share_mode_str(TALLOC_CTX *ctx, int num, const struct share_mode_entry *e) +{ + return talloc_asprintf(ctx, "share_mode_entry[%d]: %s " + "pid = %s, share_access = 0x%x, private_options = 0x%x, " + "access_mask = 0x%x, mid = 0x%x, type= 0x%x, gen_id = %lu, " + "uid = %u, flags = %u, file_id %s", + num, + e->op_type == UNUSED_SHARE_MODE_ENTRY ? "UNUSED" : "", + procid_str_static(&e->pid), + e->share_access, e->private_options, + e->access_mask, e->op_mid, e->op_type, e->share_file_id, + (unsigned int)e->uid, (unsigned int)e->flags, + file_id_string_tos(&e->id)); +} + +/******************************************************************* + Print out a share mode table. +********************************************************************/ + +static void print_share_mode_table(struct locking_data *data) +{ + int num_share_modes = data->u.s.num_share_mode_entries; + struct share_mode_entry *shares = + (struct share_mode_entry *)(data + 1); + int i; + + for (i = 0; i < num_share_modes; i++) { + struct share_mode_entry entry; + char *str; + + /* + * We need to memcpy the entry here due to alignment + * restrictions that are not met when directly accessing + * shares[i] + */ + + memcpy(&entry, &shares[i], sizeof(struct share_mode_entry)); + str = share_mode_str(talloc_tos(), i, &entry); + + DEBUG(10,("print_share_mode_table: %s\n", str ? str : "")); + TALLOC_FREE(str); + } +} + +/******************************************************************* + Get all share mode entries for a dev/inode pair. +********************************************************************/ + +static bool parse_share_modes(TDB_DATA dbuf, struct share_mode_lock *lck) +{ + struct locking_data data; + int i; + + if (dbuf.dsize < sizeof(struct locking_data)) { + smb_panic("parse_share_modes: buffer too short"); + } + + memcpy(&data, dbuf.dptr, sizeof(data)); + + lck->delete_on_close = data.u.s.delete_on_close; + lck->old_write_time = data.u.s.old_write_time; + lck->changed_write_time = data.u.s.changed_write_time; + lck->num_share_modes = data.u.s.num_share_mode_entries; + + DEBUG(10, ("parse_share_modes: delete_on_close: %d, owrt: %s, " + "cwrt: %s, tok: %u, num_share_modes: %d\n", + lck->delete_on_close, + timestring(debug_ctx(), + convert_timespec_to_time_t(lck->old_write_time)), + timestring(debug_ctx(), + convert_timespec_to_time_t( + lck->changed_write_time)), + (unsigned int)data.u.s.delete_token_size, + lck->num_share_modes)); + + if ((lck->num_share_modes < 0) || (lck->num_share_modes > 1000000)) { + DEBUG(0, ("invalid number of share modes: %d\n", + lck->num_share_modes)); + smb_panic("parse_share_modes: invalid number of share modes"); + } + + lck->share_modes = NULL; + + if (lck->num_share_modes != 0) { + + if (dbuf.dsize < (sizeof(struct locking_data) + + (lck->num_share_modes * + sizeof(struct share_mode_entry)))) { + smb_panic("parse_share_modes: buffer too short"); + } + + lck->share_modes = (struct share_mode_entry *) + TALLOC_MEMDUP(lck, + dbuf.dptr+sizeof(struct locking_data), + lck->num_share_modes * + sizeof(struct share_mode_entry)); + + if (lck->share_modes == NULL) { + smb_panic("parse_share_modes: talloc failed"); + } + } + + /* Get any delete token. */ + if (data.u.s.delete_token_size) { + uint8 *p = dbuf.dptr + sizeof(struct locking_data) + + (lck->num_share_modes * + sizeof(struct share_mode_entry)); + + if ((data.u.s.delete_token_size < sizeof(uid_t) + sizeof(gid_t)) || + ((data.u.s.delete_token_size - sizeof(uid_t)) % sizeof(gid_t)) != 0) { + DEBUG(0, ("parse_share_modes: invalid token size %d\n", + data.u.s.delete_token_size)); + smb_panic("parse_share_modes: invalid token size"); + } + + lck->delete_token = TALLOC_P(lck, UNIX_USER_TOKEN); + if (!lck->delete_token) { + smb_panic("parse_share_modes: talloc failed"); + } + + /* Copy out the uid and gid. */ + memcpy(&lck->delete_token->uid, p, sizeof(uid_t)); + p += sizeof(uid_t); + memcpy(&lck->delete_token->gid, p, sizeof(gid_t)); + p += sizeof(gid_t); + + /* Any supplementary groups ? */ + lck->delete_token->ngroups = (data.u.s.delete_token_size > (sizeof(uid_t) + sizeof(gid_t))) ? + ((data.u.s.delete_token_size - + (sizeof(uid_t) + sizeof(gid_t)))/sizeof(gid_t)) : 0; + + if (lck->delete_token->ngroups) { + /* Make this a talloc child of lck->delete_token. */ + lck->delete_token->groups = TALLOC_ARRAY(lck->delete_token, gid_t, + lck->delete_token->ngroups); + if (!lck->delete_token) { + smb_panic("parse_share_modes: talloc failed"); + } + + for (i = 0; i < lck->delete_token->ngroups; i++) { + memcpy(&lck->delete_token->groups[i], p, sizeof(gid_t)); + p += sizeof(gid_t); + } + } + + } else { + lck->delete_token = NULL; + } + + /* Save off the associated service path and filename. */ + lck->servicepath = (const char *)dbuf.dptr + sizeof(struct locking_data) + + (lck->num_share_modes * sizeof(struct share_mode_entry)) + + data.u.s.delete_token_size; + + lck->filename = (const char *)dbuf.dptr + sizeof(struct locking_data) + + (lck->num_share_modes * sizeof(struct share_mode_entry)) + + data.u.s.delete_token_size + + strlen(lck->servicepath) + 1; + + /* + * Ensure that each entry has a real process attached. + */ + + for (i = 0; i < lck->num_share_modes; i++) { + struct share_mode_entry *entry_p = &lck->share_modes[i]; + char *str = NULL; + if (DEBUGLEVEL >= 10) { + str = share_mode_str(NULL, i, entry_p); + } + DEBUG(10,("parse_share_modes: %s\n", + str ? str : "")); + if (!process_exists(entry_p->pid)) { + DEBUG(10,("parse_share_modes: deleted %s\n", + str ? str : "")); + entry_p->op_type = UNUSED_SHARE_MODE_ENTRY; + lck->modified = True; + } + TALLOC_FREE(str); + } + + return True; +} + +static TDB_DATA unparse_share_modes(struct share_mode_lock *lck) +{ + TDB_DATA result; + int num_valid = 0; + int i; + struct locking_data *data; + ssize_t offset; + ssize_t sp_len; + uint32 delete_token_size; + + result.dptr = NULL; + result.dsize = 0; + + for (i=0; i<lck->num_share_modes; i++) { + if (!is_unused_share_mode_entry(&lck->share_modes[i])) { + num_valid += 1; + } + } + + if (num_valid == 0) { + return result; + } + + sp_len = strlen(lck->servicepath); + delete_token_size = (lck->delete_token ? + (sizeof(uid_t) + sizeof(gid_t) + (lck->delete_token->ngroups*sizeof(gid_t))) : 0); + + result.dsize = sizeof(*data) + + lck->num_share_modes * sizeof(struct share_mode_entry) + + delete_token_size + + sp_len + 1 + + strlen(lck->filename) + 1; + result.dptr = TALLOC_ARRAY(lck, uint8, result.dsize); + + if (result.dptr == NULL) { + smb_panic("talloc failed"); + } + + data = (struct locking_data *)result.dptr; + ZERO_STRUCTP(data); + data->u.s.num_share_mode_entries = lck->num_share_modes; + data->u.s.delete_on_close = lck->delete_on_close; + data->u.s.old_write_time = lck->old_write_time; + data->u.s.changed_write_time = lck->changed_write_time; + data->u.s.delete_token_size = delete_token_size; + + DEBUG(10,("unparse_share_modes: del: %d, owrt: %s cwrt: %s, tok: %u, " + "num: %d\n", data->u.s.delete_on_close, + timestring(debug_ctx(), + convert_timespec_to_time_t(lck->old_write_time)), + timestring(debug_ctx(), + convert_timespec_to_time_t( + lck->changed_write_time)), + (unsigned int)data->u.s.delete_token_size, + data->u.s.num_share_mode_entries)); + + memcpy(result.dptr + sizeof(*data), lck->share_modes, + sizeof(struct share_mode_entry)*lck->num_share_modes); + offset = sizeof(*data) + + sizeof(struct share_mode_entry)*lck->num_share_modes; + + /* Store any delete on close token. */ + if (lck->delete_token) { + uint8 *p = result.dptr + offset; + + memcpy(p, &lck->delete_token->uid, sizeof(uid_t)); + p += sizeof(uid_t); + + memcpy(p, &lck->delete_token->gid, sizeof(gid_t)); + p += sizeof(gid_t); + + for (i = 0; i < lck->delete_token->ngroups; i++) { + memcpy(p, &lck->delete_token->groups[i], sizeof(gid_t)); + p += sizeof(gid_t); + } + offset = p - result.dptr; + } + + safe_strcpy((char *)result.dptr + offset, lck->servicepath, + result.dsize - offset - 1); + offset += sp_len + 1; + safe_strcpy((char *)result.dptr + offset, lck->filename, + result.dsize - offset - 1); + + if (DEBUGLEVEL >= 10) { + print_share_mode_table(data); + } + + return result; +} + +static int share_mode_lock_destructor(struct share_mode_lock *lck) +{ + NTSTATUS status; + TDB_DATA data; + + if (!lck->modified) { + return 0; + } + + data = unparse_share_modes(lck); + + if (data.dptr == NULL) { + if (!lck->fresh) { + /* There has been an entry before, delete it */ + + status = lck->record->delete_rec(lck->record); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("delete_rec returned %s\n", + nt_errstr(status))); + smb_panic("could not delete share entry"); + } + } + goto done; + } + + status = lck->record->store(lck->record, data, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("store returned %s\n", nt_errstr(status))); + smb_panic("could not store share mode entry"); + } + + done: + + return 0; +} + +static bool fill_share_mode_lock(struct share_mode_lock *lck, + struct file_id id, + const char *servicepath, + const char *fname, + TDB_DATA share_mode_data, + const struct timespec *old_write_time) +{ + /* Ensure we set every field here as the destructor must be + valid even if parse_share_modes fails. */ + + lck->servicepath = NULL; + lck->filename = NULL; + lck->id = id; + lck->num_share_modes = 0; + lck->share_modes = NULL; + lck->delete_token = NULL; + lck->delete_on_close = False; + ZERO_STRUCT(lck->old_write_time); + ZERO_STRUCT(lck->changed_write_time); + lck->fresh = False; + lck->modified = False; + + lck->fresh = (share_mode_data.dptr == NULL); + + if (lck->fresh) { + if (fname == NULL || servicepath == NULL + || old_write_time == NULL) { + return False; + } + lck->filename = talloc_strdup(lck, fname); + lck->servicepath = talloc_strdup(lck, servicepath); + if (lck->filename == NULL || lck->servicepath == NULL) { + DEBUG(0, ("talloc failed\n")); + return False; + } + lck->old_write_time = *old_write_time; + } else { + if (!parse_share_modes(share_mode_data, lck)) { + DEBUG(0, ("Could not parse share modes\n")); + return False; + } + } + + return True; +} + +struct share_mode_lock *get_share_mode_lock(TALLOC_CTX *mem_ctx, + const struct file_id id, + const char *servicepath, + const char *fname, + const struct timespec *old_write_time) +{ + struct share_mode_lock *lck; + struct file_id tmp; + TDB_DATA key = locking_key(&id, &tmp); + + if (!(lck = TALLOC_P(mem_ctx, struct share_mode_lock))) { + DEBUG(0, ("talloc failed\n")); + return NULL; + } + + if (!(lck->record = lock_db->fetch_locked(lock_db, lck, key))) { + DEBUG(3, ("Could not lock share entry\n")); + TALLOC_FREE(lck); + return NULL; + } + + if (!fill_share_mode_lock(lck, id, servicepath, fname, + lck->record->value, old_write_time)) { + DEBUG(3, ("fill_share_mode_lock failed\n")); + TALLOC_FREE(lck); + return NULL; + } + + talloc_set_destructor(lck, share_mode_lock_destructor); + + return lck; +} + +struct share_mode_lock *fetch_share_mode_unlocked(TALLOC_CTX *mem_ctx, + const struct file_id id, + const char *servicepath, + const char *fname) +{ + struct share_mode_lock *lck; + struct file_id tmp; + TDB_DATA key = locking_key(&id, &tmp); + TDB_DATA data; + + if (!(lck = TALLOC_P(mem_ctx, struct share_mode_lock))) { + DEBUG(0, ("talloc failed\n")); + return NULL; + } + + if (lock_db->fetch(lock_db, lck, key, &data) == -1) { + DEBUG(3, ("Could not fetch share entry\n")); + TALLOC_FREE(lck); + return NULL; + } + + if (!fill_share_mode_lock(lck, id, servicepath, fname, data, NULL)) { + DEBUG(3, ("fill_share_mode_lock failed\n")); + TALLOC_FREE(lck); + return NULL; + } + + return lck; +} + +/******************************************************************* + 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> +********************************************************************/ + +bool rename_share_filename(struct messaging_context *msg_ctx, + struct share_mode_lock *lck, + const char *servicepath, + const char *newname) +{ + size_t sp_len; + size_t fn_len; + size_t msg_len; + char *frm = NULL; + int i; + + DEBUG(10, ("rename_share_filename: servicepath %s newname %s\n", + servicepath, newname)); + + /* + * rename_internal_fsp() and rename_internals() add './' to + * head of newname if newname does not contain a '/'. + */ + while (newname[0] && newname[1] && newname[0] == '.' && newname[1] == '/') { + newname += 2; + } + + lck->servicepath = talloc_strdup(lck, servicepath); + lck->filename = talloc_strdup(lck, newname); + if (lck->filename == NULL || lck->servicepath == NULL) { + DEBUG(0, ("rename_share_filename: talloc failed\n")); + return False; + } + lck->modified = True; + + sp_len = strlen(lck->servicepath); + fn_len = strlen(lck->filename); + + msg_len = MSG_FILE_RENAMED_MIN_SIZE + sp_len + 1 + fn_len + 1; + + /* Set up the name changed message. */ + frm = TALLOC_ARRAY(lck, char, msg_len); + if (!frm) { + return False; + } + + push_file_id_16(frm, &lck->id); + + DEBUG(10,("rename_share_filename: msg_len = %u\n", (unsigned int)msg_len )); + + safe_strcpy(&frm[16], lck->servicepath, sp_len); + safe_strcpy(&frm[16 + sp_len + 1], lck->filename, fn_len); + + /* Send the messages. */ + for (i=0; i<lck->num_share_modes; i++) { + struct share_mode_entry *se = &lck->share_modes[i]; + if (!is_valid_share_mode_entry(se)) { + continue; + } + /* But not to ourselves... */ + if (procid_is_me(&se->pid)) { + continue; + } + + DEBUG(10,("rename_share_filename: sending rename message to pid %s " + "file_id %s sharepath %s newname %s\n", + procid_str_static(&se->pid), + file_id_string_tos(&lck->id), + lck->servicepath, lck->filename )); + + messaging_send_buf(msg_ctx, se->pid, MSG_SMB_FILE_RENAME, + (uint8 *)frm, msg_len); + } + + return True; +} + +void get_file_infos(struct file_id id, + bool *delete_on_close, + struct timespec *write_time) +{ + struct share_mode_lock *lck; + + if (delete_on_close) { + *delete_on_close = false; + } + + if (write_time) { + ZERO_STRUCTP(write_time); + } + + if (!(lck = fetch_share_mode_unlocked(talloc_tos(), id, NULL, NULL))) { + return; + } + + if (delete_on_close) { + *delete_on_close = lck->delete_on_close; + } + + if (write_time) { + struct timespec wt; + + wt = lck->changed_write_time; + if (null_timespec(wt)) { + wt = lck->old_write_time; + } + + *write_time = wt; + } + + TALLOC_FREE(lck); +} + +bool is_valid_share_mode_entry(const struct share_mode_entry *e) +{ + int num_props = 0; + + if (e->op_type == UNUSED_SHARE_MODE_ENTRY) { + /* cope with dead entries from the process not + existing. These should not be considered valid, + otherwise we end up doing zero timeout sharing + violation */ + 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); + + SMB_ASSERT(num_props <= 1); + return (num_props != 0); +} + +bool is_deferred_open_entry(const struct share_mode_entry *e) +{ + return (e->op_type == DEFERRED_OPEN_ENTRY); +} + +bool is_unused_share_mode_entry(const struct share_mode_entry *e) +{ + return (e->op_type == UNUSED_SHARE_MODE_ENTRY); +} + +/******************************************************************* + Fill a share mode entry. +********************************************************************/ + +static void fill_share_mode_entry(struct share_mode_entry *e, + files_struct *fsp, + uid_t uid, uint16 mid, uint16 op_type) +{ + ZERO_STRUCTP(e); + e->pid = procid_self(); + e->share_access = fsp->share_access; + e->private_options = fsp->fh->private_options; + e->access_mask = fsp->access_mask; + e->op_mid = mid; + e->op_type = op_type; + e->time.tv_sec = fsp->open_time.tv_sec; + e->time.tv_usec = fsp->open_time.tv_usec; + e->id = fsp->file_id; + e->share_file_id = fsp->fh->gen_id; + e->uid = (uint32)uid; + e->flags = fsp->posix_open ? SHARE_MODE_FLAG_POSIX_OPEN : 0; +} + +static void fill_deferred_open_entry(struct share_mode_entry *e, + const struct timeval request_time, + struct file_id id, uint16 mid) +{ + ZERO_STRUCTP(e); + e->pid = procid_self(); + e->op_mid = mid; + e->op_type = DEFERRED_OPEN_ENTRY; + e->time.tv_sec = request_time.tv_sec; + e->time.tv_usec = request_time.tv_usec; + e->id = id; + e->uid = (uint32)-1; + e->flags = 0; +} + +static void add_share_mode_entry(struct share_mode_lock *lck, + const struct share_mode_entry *entry) +{ + int i; + + for (i=0; i<lck->num_share_modes; i++) { + struct share_mode_entry *e = &lck->share_modes[i]; + if (is_unused_share_mode_entry(e)) { + *e = *entry; + break; + } + } + + if (i == lck->num_share_modes) { + /* No unused entry found */ + ADD_TO_ARRAY(lck, struct share_mode_entry, *entry, + &lck->share_modes, &lck->num_share_modes); + } + lck->modified = True; +} + +void set_share_mode(struct share_mode_lock *lck, files_struct *fsp, + uid_t uid, uint16 mid, uint16 op_type, bool initial_delete_on_close_allowed) +{ + struct share_mode_entry entry; + fill_share_mode_entry(&entry, fsp, uid, mid, op_type); + if (initial_delete_on_close_allowed) { + entry.flags |= SHARE_MODE_ALLOW_INITIAL_DELETE_ON_CLOSE; + } + add_share_mode_entry(lck, &entry); +} + +void add_deferred_open(struct share_mode_lock *lck, uint16 mid, + struct timeval request_time, + struct file_id id) +{ + struct share_mode_entry entry; + fill_deferred_open_entry(&entry, request_time, id, mid); + add_share_mode_entry(lck, &entry); +} + +/******************************************************************* + Check if two share mode entries are identical, ignoring oplock + and mid info and desired_access. (Removed paranoia test - it's + not automatically a logic error if they are identical. JRA.) +********************************************************************/ + +static bool share_modes_identical(struct share_mode_entry *e1, + struct share_mode_entry *e2) +{ + /* We used to check for e1->share_access == e2->share_access here + as well as the other fields but 2 different DOS or FCB opens + sharing the same share mode entry may validly differ in + fsp->share_access field. */ + + return (procid_equal(&e1->pid, &e2->pid) && + file_id_equal(&e1->id, &e2->id) && + e1->share_file_id == e2->share_file_id ); +} + +static bool deferred_open_identical(struct share_mode_entry *e1, + struct share_mode_entry *e2) +{ + return (procid_equal(&e1->pid, &e2->pid) && + (e1->op_mid == e2->op_mid) && + file_id_equal(&e1->id, &e2->id)); +} + +static struct share_mode_entry *find_share_mode_entry(struct share_mode_lock *lck, + struct share_mode_entry *entry) +{ + int i; + + for (i=0; i<lck->num_share_modes; i++) { + struct share_mode_entry *e = &lck->share_modes[i]; + if (is_valid_share_mode_entry(entry) && + is_valid_share_mode_entry(e) && + share_modes_identical(e, entry)) { + return e; + } + if (is_deferred_open_entry(entry) && + is_deferred_open_entry(e) && + deferred_open_identical(e, entry)) { + return e; + } + } + return NULL; +} + +/******************************************************************* + Del the share mode of a file for this process. Return the number of + entries left. +********************************************************************/ + +bool del_share_mode(struct share_mode_lock *lck, files_struct *fsp) +{ + struct share_mode_entry entry, *e; + + /* Don't care about the pid owner being correct here - just a search. */ + fill_share_mode_entry(&entry, fsp, (uid_t)-1, 0, NO_OPLOCK); + + e = find_share_mode_entry(lck, &entry); + if (e == NULL) { + return False; + } + + e->op_type = UNUSED_SHARE_MODE_ENTRY; + lck->modified = True; + return True; +} + +void del_deferred_open_entry(struct share_mode_lock *lck, uint16 mid) +{ + struct share_mode_entry entry, *e; + + fill_deferred_open_entry(&entry, timeval_zero(), + lck->id, mid); + + e = find_share_mode_entry(lck, &entry); + if (e == NULL) { + return; + } + + e->op_type = UNUSED_SHARE_MODE_ENTRY; + lck->modified = True; +} + +/******************************************************************* + Remove an oplock mid and mode entry from a share mode. +********************************************************************/ + +bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp) +{ + struct share_mode_entry entry, *e; + + /* Don't care about the pid owner being correct here - just a search. */ + fill_share_mode_entry(&entry, fsp, (uid_t)-1, 0, NO_OPLOCK); + + e = find_share_mode_entry(lck, &entry); + if (e == NULL) { + return False; + } + + e->op_mid = 0; + e->op_type = NO_OPLOCK; + lck->modified = True; + return True; +} + +/******************************************************************* + Downgrade a oplock type from exclusive to level II. +********************************************************************/ + +bool downgrade_share_oplock(struct share_mode_lock *lck, files_struct *fsp) +{ + struct share_mode_entry entry, *e; + + /* Don't care about the pid owner being correct here - just a search. */ + fill_share_mode_entry(&entry, fsp, (uid_t)-1, 0, NO_OPLOCK); + + e = find_share_mode_entry(lck, &entry); + if (e == NULL) { + return False; + } + + e->op_type = LEVEL_II_OPLOCK; + lck->modified = True; + return True; +} + +/**************************************************************************** + Deal with the internal needs of setting the delete on close flag. Note that + as the tdb locking is recursive, it is safe to call this from within + open_file_ntcreate. JRA. +****************************************************************************/ + +NTSTATUS can_set_delete_on_close(files_struct *fsp, bool delete_on_close, + uint32 dosmode) +{ + if (!delete_on_close) { + return NT_STATUS_OK; + } + + /* + * Only allow delete on close for writable files. + */ + + if ((dosmode & aRONLY) && + !lp_delete_readonly(SNUM(fsp->conn))) { + DEBUG(10,("can_set_delete_on_close: file %s delete on close " + "flag set but file attribute is readonly.\n", + fsp->fsp_name )); + return NT_STATUS_CANNOT_DELETE; + } + + /* + * Only allow delete on close for writable shares. + */ + + if (!CAN_WRITE(fsp->conn)) { + DEBUG(10,("can_set_delete_on_close: file %s delete on " + "close flag set but write access denied on share.\n", + fsp->fsp_name )); + return NT_STATUS_ACCESS_DENIED; + } + + /* + * Only allow delete on close for files/directories opened with delete + * intent. + */ + + if (!(fsp->access_mask & DELETE_ACCESS)) { + DEBUG(10,("can_set_delete_on_close: file %s delete on " + "close flag set but delete access denied.\n", + fsp->fsp_name )); + return NT_STATUS_ACCESS_DENIED; + } + + /* Don't allow delete on close for non-empty directories. */ + if (fsp->is_directory) { + return can_delete_directory(fsp->conn, fsp->fsp_name); + } + + return NT_STATUS_OK; +} + +/**************************************************************************** + Do we have an open file handle that created this entry ? +****************************************************************************/ + +bool can_set_initial_delete_on_close(const struct share_mode_lock *lck) +{ + int i; + + for (i=0; i<lck->num_share_modes; i++) { + if (lck->share_modes[i].flags & SHARE_MODE_ALLOW_INITIAL_DELETE_ON_CLOSE) { + return True; + } + } + return False; +} + +/************************************************************************* + Return a talloced copy of a UNIX_USER_TOKEN. NULL on fail. + (Should this be in locking.c.... ?). +*************************************************************************/ + +static UNIX_USER_TOKEN *copy_unix_token(TALLOC_CTX *ctx, UNIX_USER_TOKEN *tok) +{ + UNIX_USER_TOKEN *cpy; + + if (tok == NULL) { + return NULL; + } + + cpy = TALLOC_P(ctx, UNIX_USER_TOKEN); + if (!cpy) { + return NULL; + } + + cpy->uid = tok->uid; + cpy->gid = tok->gid; + cpy->ngroups = tok->ngroups; + if (tok->ngroups) { + /* Make this a talloc child of cpy. */ + cpy->groups = TALLOC_ARRAY(cpy, gid_t, tok->ngroups); + if (!cpy->groups) { + return NULL; + } + memcpy(cpy->groups, tok->groups, tok->ngroups * sizeof(gid_t)); + } + return cpy; +} + +/**************************************************************************** + Replace the delete on close token. +****************************************************************************/ + +void set_delete_on_close_token(struct share_mode_lock *lck, UNIX_USER_TOKEN *tok) +{ + TALLOC_FREE(lck->delete_token); /* Also deletes groups... */ + + /* Copy the new token (can be NULL). */ + lck->delete_token = copy_unix_token(lck, tok); + lck->modified = True; +} + +/**************************************************************************** + 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 UNIX_USER_TOKEN into the + lck entry. This function is used when the lock is already granted. +****************************************************************************/ + +void set_delete_on_close_lck(struct share_mode_lock *lck, bool delete_on_close, UNIX_USER_TOKEN *tok) +{ + if (lck->delete_on_close != delete_on_close) { + set_delete_on_close_token(lck, tok); + lck->delete_on_close = delete_on_close; + if (delete_on_close) { + SMB_ASSERT(lck->delete_token != NULL); + } + lck->modified = True; + } +} + +bool set_delete_on_close(files_struct *fsp, bool delete_on_close, UNIX_USER_TOKEN *tok) +{ + struct share_mode_lock *lck; + + DEBUG(10,("set_delete_on_close: %s delete on close flag for " + "fnum = %d, file %s\n", + delete_on_close ? "Adding" : "Removing", fsp->fnum, + fsp->fsp_name )); + + lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL, + NULL); + if (lck == NULL) { + return False; + } + + set_delete_on_close_lck(lck, delete_on_close, tok); + + if (fsp->is_directory) { + send_stat_cache_delete_message(fsp->fsp_name); + } + + TALLOC_FREE(lck); + return True; +} + +/**************************************************************************** + Sets the allow initial delete on close flag for this share mode. +****************************************************************************/ + +bool set_allow_initial_delete_on_close(struct share_mode_lock *lck, files_struct *fsp, bool delete_on_close) +{ + struct share_mode_entry entry, *e; + + /* Don't care about the pid owner being correct here - just a search. */ + fill_share_mode_entry(&entry, fsp, (uid_t)-1, 0, NO_OPLOCK); + + e = find_share_mode_entry(lck, &entry); + if (e == NULL) { + return False; + } + + if (delete_on_close) { + e->flags |= SHARE_MODE_ALLOW_INITIAL_DELETE_ON_CLOSE; + } else { + e->flags &= ~SHARE_MODE_ALLOW_INITIAL_DELETE_ON_CLOSE; + } + lck->modified = True; + return True; +} + +bool set_sticky_write_time(struct file_id fileid, struct timespec write_time) +{ + struct share_mode_lock *lck; + + DEBUG(5,("set_sticky_write_time: %s id=%s\n", + timestring(debug_ctx(), + convert_timespec_to_time_t(write_time)), + file_id_string_tos(&fileid))); + + lck = get_share_mode_lock(NULL, fileid, NULL, NULL, NULL); + if (lck == NULL) { + return False; + } + + if (timespec_compare(&lck->changed_write_time, &write_time) != 0) { + lck->modified = True; + lck->changed_write_time = write_time; + } + + TALLOC_FREE(lck); + return True; +} + +bool set_write_time(struct file_id fileid, struct timespec write_time) +{ + struct share_mode_lock *lck; + + DEBUG(5,("set_write_time: %s id=%s\n", + timestring(debug_ctx(), + convert_timespec_to_time_t(write_time)), + file_id_string_tos(&fileid))); + + lck = get_share_mode_lock(NULL, fileid, NULL, NULL, NULL); + if (lck == NULL) { + return False; + } + + if (timespec_compare(&lck->old_write_time, &write_time) != 0) { + lck->modified = True; + lck->old_write_time = write_time; + } + + TALLOC_FREE(lck); + return True; +} + + +struct forall_state { + void (*fn)(const struct share_mode_entry *entry, + const char *sharepath, + const char *fname, + void *private_data); + void *private_data; +}; + +static int traverse_fn(struct db_record *rec, void *_state) +{ + struct forall_state *state = (struct forall_state *)_state; + struct locking_data *data; + struct share_mode_entry *shares; + const char *sharepath; + const char *fname; + int i; + + /* Ensure this is a locking_key record. */ + if (rec->key.dsize != sizeof(struct file_id)) + return 0; + + data = (struct locking_data *)rec->value.dptr; + shares = (struct share_mode_entry *)(rec->value.dptr + sizeof(*data)); + sharepath = (const char *)rec->value.dptr + sizeof(*data) + + data->u.s.num_share_mode_entries*sizeof(*shares) + + data->u.s.delete_token_size; + fname = (const char *)rec->value.dptr + sizeof(*data) + + data->u.s.num_share_mode_entries*sizeof(*shares) + + data->u.s.delete_token_size + + strlen(sharepath) + 1; + + for (i=0;i<data->u.s.num_share_mode_entries;i++) { + state->fn(&shares[i], sharepath, fname, + state->private_data); + } + return 0; +} + +/******************************************************************* + Call the specified function on each entry under management by the + share mode system. +********************************************************************/ + +int share_mode_forall(void (*fn)(const struct share_mode_entry *, const char *, + const char *, void *), + void *private_data) +{ + struct forall_state state; + + if (lock_db == NULL) + return 0; + + state.fn = fn; + state.private_data = private_data; + + return lock_db->traverse_read(lock_db, traverse_fn, (void *)&state); +} |