From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/rpc_server/fss/srv_fss_state.c | 698 +++++++++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 source3/rpc_server/fss/srv_fss_state.c (limited to 'source3/rpc_server/fss/srv_fss_state.c') diff --git a/source3/rpc_server/fss/srv_fss_state.c b/source3/rpc_server/fss/srv_fss_state.c new file mode 100644 index 0000000..8597c36 --- /dev/null +++ b/source3/rpc_server/fss/srv_fss_state.c @@ -0,0 +1,698 @@ +/* + * File Server Remote VSS Protocol (FSRVP) persistent server state + * + * Copyright (C) David Disseldorp 2012-2015 + * + * 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 . + */ + +#include "source3/include/includes.h" +#include +#include "source3/include/util_tdb.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_open.h" +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/ndr_fsrvp_state.h" +#include "srv_fss_private.h" + +#define FSS_DB_KEY_VERSION "db_version" +#define FSS_DB_KEY_CONTEXT "context" +#define FSS_DB_KEY_SC_SET_COUNT "sc_set_count" +#define FSS_DB_KEY_PFX_SC_SET "sc_set/" +#define FSS_DB_KEY_PFX_SC "sc/" +#define FSS_DB_KEY_PFX_SMAP "smap/" + +static NTSTATUS fss_state_smap_store(TALLOC_CTX *mem_ctx, + struct db_context *db, + const char *sc_key_str, + struct fss_sc_smap *smap) +{ + NTSTATUS status; + TDB_DATA val; + const char *smap_key_str; + struct fsrvp_state_smap smap_state; + enum ndr_err_code ndr_ret; + DATA_BLOB smap_state_blob; + + /* becomes sc_set/@sc_set_id/sc/@sc_id/smap/@sc_share_name */ + smap_key_str = talloc_asprintf(mem_ctx, "%s/%s%s", sc_key_str, + FSS_DB_KEY_PFX_SMAP, + smap->sc_share_name); + if (smap_key_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + smap_state.share_name = smap->share_name; + smap_state.sc_share_name = smap->sc_share_name; + /* @smap->sc_share_comment may be null if not exposed. */ + if (smap->sc_share_comment != NULL) { + smap_state.sc_share_comment = smap->sc_share_comment; + } else { + smap_state.sc_share_comment = ""; + } + smap_state.is_exposed = smap->is_exposed; + + ndr_ret = ndr_push_struct_blob(&smap_state_blob, mem_ctx, + &smap_state, + (ndr_push_flags_fn_t)ndr_push_fsrvp_state_smap); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + val.dsize = smap_state_blob.length; + val.dptr = smap_state_blob.data; + + status = dbwrap_store(db, string_term_tdb_data(smap_key_str), val, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_store(TALLOC_CTX *mem_ctx, + struct db_context *db, + const char *sc_set_key_str, + struct fss_sc *sc) +{ + NTSTATUS status; + TDB_DATA val; + const char *sc_key_str; + struct fsrvp_state_sc sc_state; + struct fss_sc_smap *smap; + enum ndr_err_code ndr_ret; + DATA_BLOB sc_state_blob; + + /* becomes sc_set/@sc_set.id/sc/@sc_id */ + sc_key_str = talloc_asprintf(mem_ctx, "%s/%s%s", sc_set_key_str, + FSS_DB_KEY_PFX_SC, sc->id_str); + if (sc_key_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sc_state.id_str = sc->id_str; + sc_state.volume_name = sc->volume_name; + /* @sc->sc_path may be null if not committed, store empty str */ + sc_state.sc_path = (sc->sc_path ? sc->sc_path : ""); + sc_state.create_ts = sc->create_ts; + sc_state.smaps_count = sc->smaps_count; + + ndr_ret = ndr_push_struct_blob(&sc_state_blob, mem_ctx, + &sc_state, + (ndr_push_flags_fn_t)ndr_push_fsrvp_state_sc); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + val.dsize = sc_state_blob.length; + val.dptr = sc_state_blob.data; + + status = dbwrap_store(db, string_term_tdb_data(sc_key_str), val, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (smap = sc->smaps; smap; smap = smap->next) { + status = fss_state_smap_store(mem_ctx, db, sc_key_str, smap); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_set_store(TALLOC_CTX *mem_ctx, + struct db_context *db, + struct fss_sc_set *sc_set) +{ + NTSTATUS status; + TDB_DATA val; + const char *sc_set_key_str; + struct fss_sc *sc; + struct fsrvp_state_sc_set sc_set_state; + DATA_BLOB sc_set_state_blob; + enum ndr_err_code ndr_ret; + + sc_set_key_str = talloc_asprintf(mem_ctx, "%s%s", + FSS_DB_KEY_PFX_SC_SET, + sc_set->id_str); + if (sc_set_key_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sc_set_state.id_str = sc_set->id_str; + sc_set_state.state = sc_set->state; + sc_set_state.context = sc_set->context; + sc_set_state.scs_count = sc_set->scs_count; + + ndr_ret = ndr_push_struct_blob(&sc_set_state_blob, mem_ctx, + &sc_set_state, + (ndr_push_flags_fn_t)ndr_push_fsrvp_state_sc_set); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + val.dsize = sc_set_state_blob.length; + val.dptr = sc_set_state_blob.data; + + status = dbwrap_store(db, string_term_tdb_data(sc_set_key_str), val, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (sc = sc_set->scs; sc; sc = sc->next) { + status = fss_state_sc_store(mem_ctx, db, sc_set_key_str, sc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + return NT_STATUS_OK; +} + +/* + * write out the current fsrvp server state to a TDB. This clears any content + * currently written to the TDB. + */ +_PRIVATE_ NTSTATUS fss_state_store(TALLOC_CTX *mem_ctx, + struct fss_sc_set *sc_sets, + uint32_t sc_sets_count, + const char *db_path) +{ + TALLOC_CTX *tmp_ctx; + struct db_context *db; + NTSTATUS status; + int ret; + struct fss_sc_set *sc_set; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + db = db_open(tmp_ctx, db_path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, + 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + if (db == NULL) { + DEBUG(0, ("Failed to open fss state database %s\n", db_path)); + status = NT_STATUS_ACCESS_DENIED; + goto err_ctx_free; + } + + ret = dbwrap_wipe(db); + if (ret != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + + status = dbwrap_store_int32_bystring(db, FSS_DB_KEY_VERSION, + FSRVP_STATE_DB_VERSION); + if (!NT_STATUS_IS_OK(status)) { + goto err_db_free; + } + + ret = dbwrap_transaction_start(db); + if (ret != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + + status = dbwrap_store_int32_bystring(db, FSS_DB_KEY_SC_SET_COUNT, + sc_sets_count); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_trans_cancel; + } + + for (sc_set = sc_sets; sc_set; sc_set = sc_set->next) { + status = fss_state_sc_set_store(tmp_ctx, db, sc_set); + if (!NT_STATUS_IS_OK(status)) { + goto err_trans_cancel; + } + } + + ret = dbwrap_transaction_commit(db); + if (ret != 0) { + status = NT_STATUS_UNSUCCESSFUL; + goto err_trans_cancel; + } + + talloc_free(db); + talloc_free(tmp_ctx); + return NT_STATUS_OK; + +err_trans_cancel: + dbwrap_transaction_cancel(db); +err_db_free: + talloc_free(db); +err_ctx_free: + talloc_free(tmp_ctx); + return status; +} + +static NTSTATUS fss_state_smap_retrieve(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_smap **smap_out) +{ + struct fss_sc_smap *smap; + struct fsrvp_state_smap smap_state; + DATA_BLOB smap_state_blob; + enum ndr_err_code ndr_ret; + + smap_state_blob.length = val->dsize; + smap_state_blob.data = val->dptr; + + ndr_ret = ndr_pull_struct_blob(&smap_state_blob, mem_ctx, &smap_state, + (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_smap); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + smap = talloc_zero(mem_ctx, struct fss_sc_smap); + if (smap == NULL) { + return NT_STATUS_NO_MEMORY; + } + + smap->share_name = talloc_strdup(smap, smap_state.share_name); + if (smap->share_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* store the full path so that the hierarchy can be rebuilt */ + smap->sc_share_name = talloc_strdup(smap, (char *)key->dptr); + if (smap->sc_share_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* sc_share_comment may be empty, keep null in such a case */ + if (strlen(smap_state.sc_share_comment) > 0) { + smap->sc_share_comment = talloc_strdup(smap, + smap_state.sc_share_comment); + if (smap->sc_share_comment == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + smap->is_exposed = smap_state.is_exposed; + + *smap_out = smap; + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_retrieve(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc **sc_out) +{ + struct fss_sc *sc; + struct fsrvp_state_sc sc_state; + DATA_BLOB sc_state_blob; + enum ndr_err_code ndr_ret; + + sc_state_blob.length = val->dsize; + sc_state_blob.data = val->dptr; + + ndr_ret = ndr_pull_struct_blob(&sc_state_blob, mem_ctx, &sc_state, + (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_sc); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + sc = talloc_zero(mem_ctx, struct fss_sc); + if (sc == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* store the full path so that the hierarchy can be rebuilt */ + sc->id_str = talloc_strdup(sc, (char *)key->dptr); + if (sc->id_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sc->volume_name = talloc_strdup(sc, sc_state.volume_name); + if (sc->volume_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* sc_path may be empty, keep null in such a case */ + if (strlen(sc_state.sc_path) > 0) { + sc->sc_path = talloc_strdup(sc, sc_state.sc_path); + if (sc->sc_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + sc->create_ts = sc_state.create_ts; + sc->smaps_count = sc_state.smaps_count; + + *sc_out = sc; + return NT_STATUS_OK; +} + +static NTSTATUS fss_state_sc_set_retrieve(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_set **sc_set_out) +{ + struct fss_sc_set *sc_set; + struct fsrvp_state_sc_set sc_set_state; + DATA_BLOB sc_set_state_blob; + enum ndr_err_code ndr_ret; + + sc_set_state_blob.length = val->dsize; + sc_set_state_blob.data = val->dptr; + + ndr_ret = ndr_pull_struct_blob(&sc_set_state_blob, mem_ctx, + &sc_set_state, + (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_sc_set); + if (ndr_ret != NDR_ERR_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + sc_set = talloc_zero(mem_ctx, struct fss_sc_set); + if (sc_set == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* store the full path so that the hierarchy can be rebuilt */ + sc_set->id_str = talloc_strdup(sc_set, (char *)key->dptr); + if (sc_set->id_str == NULL) { + return NT_STATUS_NO_MEMORY; + } + sc_set->state = sc_set_state.state; + sc_set->context = sc_set_state.context; + sc_set->scs_count = sc_set_state.scs_count; + + *sc_set_out = sc_set; + return NT_STATUS_OK; +} + +struct fss_traverse_state { + TALLOC_CTX *mem_ctx; + struct fss_sc_smap *smaps; + uint32_t smaps_count; + struct fss_sc *scs; + uint32_t scs_count; + struct fss_sc_set *sc_sets; + uint32_t sc_sets_count; + NTSTATUS (*smap_retrieve)(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_smap **smap_out); + NTSTATUS (*sc_retrieve)(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc **sc_out); + NTSTATUS (*sc_set_retrieve)(TALLOC_CTX *mem_ctx, + TDB_DATA *key, + TDB_DATA *val, + struct fss_sc_set **sc_set_out); +}; + +static int fss_state_retrieve_traverse(struct db_record *rec, + void *private_data) +{ + NTSTATUS status; + struct fss_traverse_state *trv_state + = (struct fss_traverse_state *)private_data; + TDB_DATA key = dbwrap_record_get_key(rec); + TDB_DATA val = dbwrap_record_get_value(rec); + + /* order of checking is important here */ + if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SMAP) != NULL) { + struct fss_sc_smap *smap; + status = trv_state->smap_retrieve(trv_state->mem_ctx, + &key, &val, &smap); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + DLIST_ADD_END(trv_state->smaps, smap); + trv_state->smaps_count++; + } else if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SC) != NULL) { + struct fss_sc *sc; + status = trv_state->sc_retrieve(trv_state->mem_ctx, + &key, &val, &sc); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + DLIST_ADD_END(trv_state->scs, sc); + trv_state->scs_count++; + } else if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SC_SET) != NULL) { + struct fss_sc_set *sc_set; + status = trv_state->sc_set_retrieve(trv_state->mem_ctx, + &key, &val, &sc_set); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + DLIST_ADD_END(trv_state->sc_sets, sc_set); + trv_state->sc_sets_count++; + } else { + /* global context and db vers */ + DEBUG(4, ("Ignoring fss srv db entry with key %s\n", key.dptr)); + } + + return 0; +} + +static bool fss_state_smap_is_child(struct fss_sc *sc, + struct fss_sc_smap *smap) +{ + return (strstr(smap->sc_share_name, sc->id_str) != NULL); +} + +static NTSTATUS fss_state_hierarchize_smaps(struct fss_traverse_state *trv_state, + struct fss_sc *sc) +{ + struct fss_sc_smap *smap; + struct fss_sc_smap *smap_n; + uint32_t smaps_moved = 0; + + for (smap = trv_state->smaps; smap; smap = smap_n) { + smap_n = smap->next; + if (!fss_state_smap_is_child(sc, smap)) + continue; + + /* smap mem should be owned by parent sc */ + talloc_steal(sc, smap); + DLIST_REMOVE(trv_state->smaps, smap); + trv_state->smaps_count--; + DLIST_ADD_END(sc->smaps, smap); + smaps_moved++; + + /* last component of the tdb key path is the sc share name */ + SMB_ASSERT(strrchr(smap->sc_share_name, '/') != NULL); + smap->sc_share_name = strrchr(smap->sc_share_name, '/') + 1; + } + + if (sc->smaps_count != smaps_moved) { + DEBUG(0, ("Inconsistent smaps_count, expected %u, moved %u\n", + sc->smaps_count, smaps_moved)); + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static bool fss_state_sc_is_child(struct fss_sc_set *sc_set, + struct fss_sc *sc) +{ + return (strstr(sc->id_str, sc_set->id_str) != NULL); +} + +static NTSTATUS fss_state_hierarchize_scs(struct fss_traverse_state *trv_state, + struct fss_sc_set *sc_set) +{ + NTSTATUS status; + struct fss_sc *sc; + struct fss_sc *sc_n; + uint32_t scs_moved = 0; + + for (sc = trv_state->scs; sc; sc = sc_n) { + sc_n = sc->next; + if (!fss_state_sc_is_child(sc_set, sc)) + continue; + + /* sc mem should be owned by parent sc_set */ + talloc_steal(sc_set, sc); + DLIST_REMOVE(trv_state->scs, sc); + trv_state->scs_count--; + DLIST_ADD_END(sc_set->scs, sc); + scs_moved++; + + sc->sc_set = sc_set; + + /* last component of the tdb key path is the sc GUID str */ + SMB_ASSERT(strrchr(sc->id_str, '/') != NULL); + sc->id_str = strrchr(sc->id_str, '/') + 1; + + status = GUID_from_string(sc->id_str, &sc->id); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = fss_state_hierarchize_smaps(trv_state, sc); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + } + + if (sc_set->scs_count != scs_moved) { + DEBUG(0, ("Inconsistent scs_count, expected %u, moved %u\n", + sc_set->scs_count, scs_moved)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_out; + } + + return NT_STATUS_OK; + +err_out: + return status; +} + +static NTSTATUS fss_state_hierarchize(struct fss_traverse_state *trv_state, + struct fss_sc_set **sc_sets, + uint32_t *sc_sets_count) +{ + NTSTATUS status; + struct fss_sc_set *sc_set; + struct fss_sc_set *sc_set_n; + uint32_t i = 0; + + *sc_sets = NULL; + for (sc_set = trv_state->sc_sets; sc_set; sc_set = sc_set_n) { + sc_set_n = sc_set->next; + /* sc_set mem already owned by trv_state->mem_ctx */ + DLIST_REMOVE(trv_state->sc_sets, sc_set); + trv_state->sc_sets_count--; + DLIST_ADD_END(*sc_sets, sc_set); + i++; + + /* last component of the tdb key path is the sc_set GUID str */ + SMB_ASSERT(strrchr(sc_set->id_str, '/') != NULL); + sc_set->id_str = strrchr(sc_set->id_str, '/') + 1; + + status = GUID_from_string(sc_set->id_str, &sc_set->id); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + + status = fss_state_hierarchize_scs(trv_state, sc_set); + if (!NT_STATUS_IS_OK(status)) { + goto err_out; + } + } + *sc_sets_count = i; + return NT_STATUS_OK; + +err_out: + return status; +} + +_PRIVATE_ NTSTATUS fss_state_retrieve(TALLOC_CTX *mem_ctx, + struct fss_sc_set **sc_sets, + uint32_t *sc_sets_count, + const char *db_path) +{ + struct db_context *db; + NTSTATUS status; + struct fss_traverse_state trv_state; + int err; + int rec_count; + int vers; + *sc_sets = NULL; + *sc_sets_count = 0; + + memset(&trv_state, 0, sizeof(trv_state)); + trv_state.mem_ctx = talloc_new(mem_ctx); + if (trv_state.mem_ctx == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + /* set callbacks for unmarshalling on-disk structures */ + trv_state.smap_retrieve = fss_state_smap_retrieve; + trv_state.sc_retrieve = fss_state_sc_retrieve; + trv_state.sc_set_retrieve = fss_state_sc_set_retrieve; + + db = db_open(trv_state.mem_ctx, db_path, 0, TDB_DEFAULT, + O_RDONLY, 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + err = errno; + if ((db == NULL) && (err == ENOENT)) { + DEBUG(4, ("fss state TDB does not exist for retrieval\n")); + status = NT_STATUS_OK; + goto err_ts_free; + } else if (db == NULL) { + DEBUG(0, ("Failed to open fss state TDB: %s\n", + strerror(err))); + status = NT_STATUS_ACCESS_DENIED; + goto err_ts_free; + } + + status = dbwrap_fetch_int32_bystring(db, FSS_DB_KEY_VERSION, + &vers); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("failed to fetch version from fss state tdb: %s\n", + nt_errstr(status))); + goto err_db_free; + } else if (vers != FSRVP_STATE_DB_VERSION) { + DEBUG(0, ("Unsupported fss tdb version %d, expected %d\n", + vers, FSRVP_STATE_DB_VERSION)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + + status = dbwrap_traverse_read(db, + fss_state_retrieve_traverse, + &trv_state, + &rec_count); + if (!NT_STATUS_IS_OK(status)) { + goto err_db_free; + } + + status = fss_state_hierarchize(&trv_state, sc_sets, sc_sets_count); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to form fss state hierarchy\n")); + goto err_db_free; + } + + /* check whether anything was left without a parent */ + if (trv_state.sc_sets_count != 0) { + DEBUG(0, ("%d shadow copy set orphans in %s tdb\n", + trv_state.sc_sets_count, db_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + if (trv_state.scs_count != 0) { + DEBUG(0, ("%d shadow copy orphans in %s tdb\n", + trv_state.scs_count, db_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + if (trv_state.smaps_count != 0) { + DEBUG(0, ("%d share map orphans in %s tdb\n", + trv_state.smaps_count, db_path)); + status = NT_STATUS_UNSUCCESSFUL; + goto err_db_free; + } + talloc_free(db); + + return NT_STATUS_OK; + +err_db_free: + talloc_free(db); +err_ts_free: + talloc_free(trv_state.mem_ctx); +err_out: + return status; +} -- cgit v1.2.3