1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-25 23:21:54 +03:00
samba-mirror/source3/locking/leases_db.c
Stefan Metzmacher 87fddbad78 smbd/locking: make use of the same tdb hash_size and flags for all SMB related tdb's
It's good to have a consistent set of hash_size/flags for all aspects of
an open file handle. Currently we're using 4 databases:
smbXsrv_open_global.tdb, leases.tdb, locking.tdb and brlock.tdb.

While at it also crank up the hashsize if the smbXsrv_tcon and smbXsrv_session
TDBs. The default TDB hash size is insanely small and disk space is cheap these
days, by going with the much larger hash size we get O(1) lookup instead of O(n)
for moderate to large loads with a few thousand objects.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>

Autobuild-User(master): Stefan Metzmacher <metze@samba.org>
Autobuild-Date(master): Mon Dec 19 16:40:15 UTC 2022 on sn-devel-184
2022-12-19 16:40:15 +00:00

725 lines
18 KiB
C

/*
Unix SMB/CIFS implementation.
Map lease keys to file ids
Copyright (C) Volker Lendecke 2013
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 "locking/leases_db.h"
#include "dbwrap/dbwrap.h"
#include "dbwrap/dbwrap_open.h"
#include "util_tdb.h"
#include "ndr.h"
#include "librpc/gen_ndr/ndr_leases_db.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_LOCKING
/* the leases database handle */
static struct db_context *leases_db;
bool leases_db_init(bool read_only)
{
char *db_path;
if (leases_db) {
return true;
}
db_path = lock_path(talloc_tos(), "leases.tdb");
if (db_path == NULL) {
return false;
}
leases_db = db_open(NULL, db_path,
SMBD_VOLATILE_TDB_HASH_SIZE,
SMBD_VOLATILE_TDB_FLAGS |
TDB_SEQNUM,
read_only ? O_RDONLY : O_RDWR|O_CREAT, 0644,
DBWRAP_LOCK_ORDER_4, DBWRAP_FLAG_NONE);
TALLOC_FREE(db_path);
if (leases_db == NULL) {
DEBUG(1, ("ERROR: Failed to initialise leases database\n"));
return false;
}
return true;
}
struct leases_db_key_buf {
uint8_t buf[32];
};
static TDB_DATA leases_db_key(struct leases_db_key_buf *buf,
const struct GUID *client_guid,
const struct smb2_lease_key *lease_key)
{
struct leases_db_key db_key = {
.client_guid = *client_guid,
.lease_key = *lease_key };
DATA_BLOB blob = { .data = buf->buf, .length = sizeof(buf->buf) };
enum ndr_err_code ndr_err;
if (DEBUGLEVEL >= 10) {
DBG_DEBUG("\n");
NDR_PRINT_DEBUG(leases_db_key, &db_key);
}
ndr_err = ndr_push_struct_into_fixed_blob(
&blob, &db_key, (ndr_push_flags_fn_t)ndr_push_leases_db_key);
SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
return (TDB_DATA) { .dptr = buf->buf, .dsize = sizeof(buf->buf) };
}
struct leases_db_do_locked_state {
void (*fn)(struct leases_db_value *value,
bool *modified,
void *private_data);
void *private_data;
NTSTATUS status;
};
static void leases_db_do_locked_fn(
struct db_record *rec,
TDB_DATA db_value,
void *private_data)
{
struct leases_db_do_locked_state *state = private_data;
DATA_BLOB blob = { .data = db_value.dptr, .length = db_value.dsize };
struct leases_db_value *value = NULL;
enum ndr_err_code ndr_err;
bool modified = false;
value = talloc_zero(talloc_tos(), struct leases_db_value);
if (value == NULL) {
state->status = NT_STATUS_NO_MEMORY;
goto done;
}
if (blob.length != 0) {
ndr_err = ndr_pull_struct_blob_all(
&blob,
value,
value,
(ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
DBG_ERR("ndr_pull_struct_blob_failed: %s\n",
ndr_errstr(ndr_err));
state->status = ndr_map_error2ntstatus(ndr_err);
goto done;
}
}
state->fn(value, &modified, state->private_data);
if (!modified) {
goto done;
}
if (value->num_files == 0) {
state->status = dbwrap_record_delete(rec);
if (!NT_STATUS_IS_OK(state->status)) {
DBG_ERR("dbwrap_record_delete returned %s\n",
nt_errstr(state->status));
}
goto done;
}
ndr_err = ndr_push_struct_blob(
&blob,
value,
value,
(ndr_push_flags_fn_t)ndr_push_leases_db_value);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
DBG_ERR("ndr_push_struct_blob_failed: %s\n",
ndr_errstr(ndr_err));
state->status = ndr_map_error2ntstatus(ndr_err);
goto done;
}
if (DEBUGLEVEL >= 10) {
DBG_DEBUG("\n");
NDR_PRINT_DEBUG(leases_db_value, value);
}
db_value = make_tdb_data(blob.data, blob.length);
state->status = dbwrap_record_store(rec, db_value, 0);
if (!NT_STATUS_IS_OK(state->status)) {
DBG_ERR("dbwrap_record_store returned %s\n",
nt_errstr(state->status));
}
done:
TALLOC_FREE(value);
}
static NTSTATUS leases_db_do_locked(
const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
void (*fn)(struct leases_db_value *value,
bool *modified,
void *private_data),
void *private_data)
{
struct leases_db_key_buf keybuf;
TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key);
struct leases_db_do_locked_state state = {
.fn = fn, .private_data = private_data,
};
NTSTATUS status;
if (!leases_db_init(false)) {
return NT_STATUS_INTERNAL_ERROR;
}
status = dbwrap_do_locked(
leases_db, db_key, leases_db_do_locked_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
return state.status;
}
struct leases_db_add_state {
const struct file_id *id;
uint32_t current_state;
uint16_t lease_version;
uint16_t epoch;
const char *servicepath;
const char *base_name;
const char *stream_name;
NTSTATUS status;
};
static void leases_db_add_fn(
struct leases_db_value *value, bool *modified, void *private_data)
{
struct leases_db_add_state *state = private_data;
struct leases_db_file *tmp = NULL;
uint32_t i;
/* id must be unique. */
for (i = 0; i < value->num_files; i++) {
if (file_id_equal(state->id, &value->files[i].id)) {
state->status = NT_STATUS_OBJECT_NAME_COLLISION;
return;
}
}
if (value->num_files == 0) {
/* new record */
value->current_state = state->current_state;
value->lease_version = state->lease_version;
value->epoch = state->epoch;
}
tmp = talloc_realloc(
value,
value->files,
struct leases_db_file,
value->num_files + 1);
if (tmp == NULL) {
state->status = NT_STATUS_NO_MEMORY;
return;
}
value->files = tmp;
value->files[value->num_files] = (struct leases_db_file) {
.id = *state->id,
.servicepath = state->servicepath,
.base_name = state->base_name,
.stream_name = state->stream_name,
};
value->num_files += 1;
*modified = true;
}
NTSTATUS leases_db_add(const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
const struct file_id *id,
uint32_t current_state,
uint16_t lease_version,
uint16_t epoch,
const char *servicepath,
const char *base_name,
const char *stream_name)
{
struct leases_db_add_state state = {
.id = id,
.current_state = current_state,
.lease_version = lease_version,
.epoch = epoch,
.servicepath = servicepath,
.base_name = base_name,
.stream_name = stream_name,
};
NTSTATUS status;
status = leases_db_do_locked(
client_guid, lease_key, leases_db_add_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("leases_db_do_locked failed: %s\n",
nt_errstr(status));
return status;
}
return state.status;
}
struct leases_db_del_state {
const struct file_id *id;
NTSTATUS status;
};
static void leases_db_del_fn(
struct leases_db_value *value, bool *modified, void *private_data)
{
struct leases_db_del_state *state = private_data;
uint32_t i;
for (i = 0; i < value->num_files; i++) {
if (file_id_equal(state->id, &value->files[i].id)) {
break;
}
}
if (i == value->num_files) {
state->status = NT_STATUS_NOT_FOUND;
return;
}
value->files[i] = value->files[value->num_files-1];
value->num_files -= 1;
*modified = true;
}
NTSTATUS leases_db_del(const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
const struct file_id *id)
{
struct leases_db_del_state state = { .id = id };
NTSTATUS status;
status = leases_db_do_locked(
client_guid, lease_key, leases_db_del_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("leases_db_do_locked failed: %s\n",
nt_errstr(status));
return status;
}
return state.status;
}
struct leases_db_fetch_state {
void (*parser)(uint32_t num_files,
const struct leases_db_file *files,
void *private_data);
void *private_data;
NTSTATUS status;
};
static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data)
{
struct leases_db_fetch_state *state =
(struct leases_db_fetch_state *)private_data;
DATA_BLOB blob = { .data = data.dptr, .length = data.dsize };
enum ndr_err_code ndr_err;
struct leases_db_value *value;
value = talloc(talloc_tos(), struct leases_db_value);
if (value == NULL) {
state->status = NT_STATUS_NO_MEMORY;
return;
}
ndr_err = ndr_pull_struct_blob_all(
&blob, value, value,
(ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n",
__func__, ndr_errstr(ndr_err)));
TALLOC_FREE(value);
state->status = ndr_map_error2ntstatus(ndr_err);
return;
}
if (DEBUGLEVEL >= 10) {
DEBUG(10, ("%s:\n", __func__));
NDR_PRINT_DEBUG(leases_db_value, value);
}
state->parser(value->num_files,
value->files,
state->private_data);
TALLOC_FREE(value);
state->status = NT_STATUS_OK;
}
NTSTATUS leases_db_parse(const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
void (*parser)(uint32_t num_files,
const struct leases_db_file *files,
void *private_data),
void *private_data)
{
struct leases_db_key_buf keybuf;
TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key);
struct leases_db_fetch_state state;
NTSTATUS status;
if (!leases_db_init(true)) {
return NT_STATUS_INTERNAL_ERROR;
}
state = (struct leases_db_fetch_state) {
.parser = parser,
.private_data = private_data,
.status = NT_STATUS_OK
};
status = dbwrap_parse_record(leases_db, db_key, leases_db_parser,
&state);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
return state.status;
}
struct leases_db_rename_state {
const struct file_id *id;
const char *servicename_new;
const char *filename_new;
const char *stream_name_new;
NTSTATUS status;
};
static void leases_db_rename_fn(
struct leases_db_value *value, bool *modified, void *private_data)
{
struct leases_db_rename_state *state = private_data;
struct leases_db_file *file = NULL;
uint32_t i;
/* id must exist. */
for (i = 0; i < value->num_files; i++) {
if (file_id_equal(state->id, &value->files[i].id)) {
break;
}
}
if (i == value->num_files) {
state->status = NT_STATUS_NOT_FOUND;
return;
}
file = &value->files[i];
file->servicepath = state->servicename_new;
file->base_name = state->filename_new;
file->stream_name = state->stream_name_new;
*modified = true;
}
NTSTATUS leases_db_rename(const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
const struct file_id *id,
const char *servicename_new,
const char *filename_new,
const char *stream_name_new)
{
struct leases_db_rename_state state = {
.id = id,
.servicename_new = servicename_new,
.filename_new = filename_new,
.stream_name_new = stream_name_new,
};
NTSTATUS status;
status = leases_db_do_locked(
client_guid, lease_key, leases_db_rename_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("leases_db_do_locked failed: %s\n",
nt_errstr(status));
return status;
}
return state.status;
}
struct leases_db_set_state {
uint32_t current_state;
bool breaking;
uint32_t breaking_to_requested;
uint32_t breaking_to_required;
uint16_t lease_version;
uint16_t epoch;
};
static void leases_db_set_fn(
struct leases_db_value *value, bool *modified, void *private_data)
{
struct leases_db_set_state *state = private_data;
if (value->num_files == 0) {
DBG_WARNING("leases_db_set on new entry\n");
return;
}
value->current_state = state->current_state;
value->breaking = state->breaking;
value->breaking_to_requested = state->breaking_to_requested;
value->breaking_to_required = state->breaking_to_required;
value->lease_version = state->lease_version;
value->epoch = state->epoch;
*modified = true;
}
NTSTATUS leases_db_set(const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
uint32_t current_state,
bool breaking,
uint32_t breaking_to_requested,
uint32_t breaking_to_required,
uint16_t lease_version,
uint16_t epoch)
{
struct leases_db_set_state state = {
.current_state = current_state,
.breaking = breaking,
.breaking_to_requested = breaking_to_requested,
.breaking_to_required = breaking_to_required,
.lease_version = lease_version,
.epoch = epoch,
};
NTSTATUS status;
status = leases_db_do_locked(
client_guid, lease_key, leases_db_set_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("leases_db_do_locked failed: %s\n",
nt_errstr(status));
return status;
}
return NT_STATUS_OK;
}
struct leases_db_get_state {
const struct file_id *file_id;
uint32_t *current_state;
bool *breaking;
uint32_t *breaking_to_requested;
uint32_t *breaking_to_required;
uint16_t *lease_version;
uint16_t *epoch;
NTSTATUS status;
};
static void leases_db_get_fn(TDB_DATA key, TDB_DATA data, void *private_data)
{
struct leases_db_get_state *state = private_data;
DATA_BLOB blob = { .data = data.dptr, .length = data.dsize };
enum ndr_err_code ndr_err;
struct leases_db_value *value;
uint32_t i;
value = talloc(talloc_tos(), struct leases_db_value);
if (value == NULL) {
state->status = NT_STATUS_NO_MEMORY;
return;
}
ndr_err = ndr_pull_struct_blob_all(
&blob, value, value,
(ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
DBG_ERR("ndr_pull_struct_blob_failed: %s\n",
ndr_errstr(ndr_err));
TALLOC_FREE(value);
state->status = ndr_map_error2ntstatus(ndr_err);
return;
}
if (DEBUGLEVEL >= 10) {
DBG_DEBUG("\n");
NDR_PRINT_DEBUG(leases_db_value, value);
}
/* id must exist. */
for (i = 0; i < value->num_files; i++) {
if (file_id_equal(state->file_id, &value->files[i].id)) {
break;
}
}
if (i == value->num_files) {
state->status = NT_STATUS_NOT_FOUND;
TALLOC_FREE(value);
return;
}
if (state->current_state != NULL) {
*state->current_state = value->current_state;
};
if (state->breaking != NULL) {
*state->breaking = value->breaking;
};
if (state->breaking_to_requested != NULL) {
*state->breaking_to_requested = value->breaking_to_requested;
};
if (state->breaking_to_required != NULL) {
*state->breaking_to_required = value->breaking_to_required;
};
if (state->lease_version != NULL) {
*state->lease_version = value->lease_version;
};
if (state->epoch != NULL) {
*state->epoch = value->epoch;
};
TALLOC_FREE(value);
state->status = NT_STATUS_OK;
}
NTSTATUS leases_db_get(const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
const struct file_id *file_id,
uint32_t *current_state,
bool *breaking,
uint32_t *breaking_to_requested,
uint32_t *breaking_to_required,
uint16_t *lease_version,
uint16_t *epoch)
{
struct leases_db_get_state state = {
.file_id = file_id,
.current_state = current_state,
.breaking = breaking,
.breaking_to_requested = breaking_to_requested,
.breaking_to_required = breaking_to_required,
.lease_version = lease_version,
.epoch = epoch,
};
struct leases_db_key_buf keybuf;
TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key);
NTSTATUS status;
if (!leases_db_init(true)) {
return NT_STATUS_INTERNAL_ERROR;
}
status = dbwrap_parse_record(
leases_db, db_key, leases_db_get_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
return state.status;
}
struct leases_db_get_current_state_state {
int seqnum;
uint32_t current_state;
NTSTATUS status;
};
/*
* This function is an optimization that
* relies on the fact that the
* smb2_lease_state current_state
* (which is a uint32_t size)
* from struct leases_db_value is the first
* entry in the ndr-encoded struct leases_db_value.
* Read it without having to ndr decode all
* the values in struct leases_db_value.
*/
static void leases_db_get_current_state_fn(
TDB_DATA key, TDB_DATA data, void *private_data)
{
struct leases_db_get_current_state_state *state = private_data;
struct ndr_pull ndr;
enum ndr_err_code ndr_err;
if (data.dsize < sizeof(uint32_t)) {
state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
return;
}
state->seqnum = dbwrap_get_seqnum(leases_db);
ndr = (struct ndr_pull) {
.data = data.dptr, .data_size = data.dsize,
};
ndr_err = ndr_pull_uint32(&ndr, NDR_SCALARS, &state->current_state);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
state->status = ndr_map_error2ntstatus(ndr_err);
}
}
NTSTATUS leases_db_get_current_state(
const struct GUID *client_guid,
const struct smb2_lease_key *lease_key,
int *database_seqnum,
uint32_t *current_state)
{
struct leases_db_get_current_state_state state = { 0 };
struct leases_db_key_buf keybuf;
TDB_DATA db_key = { 0 };
NTSTATUS status;
if (!leases_db_init(true)) {
return NT_STATUS_INTERNAL_ERROR;
}
state.seqnum = dbwrap_get_seqnum(leases_db);
if (*database_seqnum == state.seqnum) {
return NT_STATUS_OK;
}
db_key = leases_db_key(&keybuf, client_guid, lease_key);
status = dbwrap_parse_record(
leases_db, db_key, leases_db_get_current_state_fn, &state);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
*database_seqnum = state.seqnum;
*current_state = state.current_state;
return NT_STATUS_OK;
}
NTSTATUS leases_db_copy_file_ids(TALLOC_CTX *mem_ctx,
uint32_t num_files,
const struct leases_db_file *files,
struct file_id **pp_ids)
{
uint32_t i;
struct file_id *ids = talloc_array(mem_ctx,
struct file_id,
num_files);
if (ids == NULL) {
return NT_STATUS_NO_MEMORY;
}
for (i = 0; i < num_files; i++) {
ids[i] = files[i].id;
}
*pp_ids = ids;
return NT_STATUS_OK;
}