From 7dac0598ec6edb63f15a7cce7c231f56f9ab7f7d Mon Sep 17 00:00:00 2001 From: Andrew Kroeger Date: Sat, 16 Feb 2008 15:08:28 -0600 Subject: registry: Implement recursive deletes for ldb-backed registry. When deleting a registry key that contains subkeys or values, Windows performs a recursive deletion that removes any subkeys or values. This update makes deletes for an ldb-backed registry consistent with Windows. Under ldb, the deletion is done using an explicit transaction. If an error occurs during the deletion the entire transaction is cancelled, leaving the registry as it was before the deletions started. (This used to be commit ca796c8fb10598674a5eef31d15863e79bcf3db8) --- source4/lib/registry/ldb.c | 143 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 27 deletions(-) (limited to 'source4/lib') diff --git a/source4/lib/registry/ldb.c b/source4/lib/registry/ldb.c index 0c8a55396e..31b78d8246 100644 --- a/source4/lib/registry/ldb.c +++ b/source4/lib/registry/ldb.c @@ -440,33 +440,6 @@ static WERROR ldb_add_key(TALLOC_CTX *mem_ctx, const struct hive_key *parent, return WERR_OK; } -static WERROR ldb_del_key(const struct hive_key *key, const char *name) -{ - int ret; - struct ldb_key_data *parentkd = talloc_get_type(key, struct ldb_key_data); - struct ldb_dn *ldap_path; - TALLOC_CTX *mem_ctx = talloc_init("ldb_del_key"); - - ldap_path = reg_path_to_ldb(mem_ctx, key, name, NULL); - - ret = ldb_delete(parentkd->ldb, ldap_path); - - talloc_free(mem_ctx); - - if (ret == LDB_ERR_NO_SUCH_OBJECT) { - return WERR_BADFILE; - } else if (ret != LDB_SUCCESS) { - DEBUG(1, ("ldb_del_key: %s\n", ldb_errstring(parentkd->ldb))); - return WERR_FOOBAR; - } - - /* reset cache */ - talloc_free(parentkd->subkeys); - parentkd->subkeys = NULL; - - return WERR_OK; -} - static WERROR ldb_del_value (struct hive_key *key, const char *child) { int ret; @@ -499,6 +472,122 @@ static WERROR ldb_del_value (struct hive_key *key, const char *child) return WERR_OK; } +static WERROR ldb_del_key(const struct hive_key *key, const char *name) +{ + int i, ret; + struct ldb_key_data *parentkd = talloc_get_type(key, struct ldb_key_data); + struct ldb_dn *ldap_path; + TALLOC_CTX *mem_ctx = talloc_init("ldb_del_key"); + struct ldb_context *c = parentkd->ldb; + struct ldb_result *res_keys; + struct ldb_result *res_vals; + WERROR werr; + struct hive_key *hk; + + /* Verify key exists by opening it */ + werr = ldb_open_key(mem_ctx, key, name, &hk); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(mem_ctx); + return werr; + } + + ldap_path = reg_path_to_ldb(mem_ctx, key, name, NULL); + if (!ldap_path) { + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + /* Search for subkeys */ + ret = ldb_search(c, ldap_path, LDB_SCOPE_ONELEVEL, + "(key=*)", NULL, &res_keys); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting subkeys for '%s': %s\n", + ldb_dn_get_linearized(ldap_path), ldb_errstring(c))); + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + /* Search for values */ + ret = ldb_search(c, ldap_path, LDB_SCOPE_ONELEVEL, + "(value=*)", NULL, &res_vals); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("Error getting values for '%s': %s\n", + ldb_dn_get_linearized(ldap_path), ldb_errstring(c))); + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + /* Start an explicit transaction */ + ret = ldb_transaction_start(c); + + if (ret != LDB_SUCCESS) { + DEBUG(0, ("ldb_transaction_start: %s\n", ldb_errstring(c))); + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + if (res_keys->count || res_vals->count) + { + /* Delete any subkeys */ + for (i = 0; i < res_keys->count; i++) + { + werr = ldb_del_key(hk, ldb_msg_find_attr_as_string( + res_keys->msgs[i], + "key", NULL)); + if (!W_ERROR_IS_OK(werr)) { + ret = ldb_transaction_cancel(c); + talloc_free(mem_ctx); + return werr; + } + } + + /* Delete any values */ + for (i = 0; i < res_vals->count; i++) + { + werr = ldb_del_value(hk, ldb_msg_find_attr_as_string( + res_vals->msgs[i], + "value", NULL)); + if (!W_ERROR_IS_OK(werr)) { + ret = ldb_transaction_cancel(c); + talloc_free(mem_ctx); + return werr; + } + } + } + + /* Delete the key itself */ + ret = ldb_delete(c, ldap_path); + + if (ret != LDB_SUCCESS) + { + DEBUG(1, ("ldb_del_key: %s\n", ldb_errstring(c))); + ret = ldb_transaction_cancel(c); + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + /* Commit the transaction */ + ret = ldb_transaction_commit(c); + + if (ret != LDB_SUCCESS) + { + DEBUG(0, ("ldb_transaction_commit: %s\n", ldb_errstring(c))); + ret = ldb_transaction_cancel(c); + talloc_free(mem_ctx); + return WERR_FOOBAR; + } + + talloc_free(mem_ctx); + + /* reset cache */ + talloc_free(parentkd->subkeys); + parentkd->subkeys = NULL; + + return WERR_OK; +} + static WERROR ldb_set_value(struct hive_key *parent, const char *name, uint32_t type, const DATA_BLOB data) -- cgit From 2bbd319cafe64935682799240b9c0aa9ff4d7e7a Mon Sep 17 00:00:00 2001 From: Andrew Kroeger Date: Sat, 16 Feb 2008 15:15:50 -0600 Subject: registry: Implement recursive deletes for dir-backed registry. When deleting a registry key that contains subkeys or values, Windows performs a recursive deletion that removes any subkeys or values. This update makes deletes for an dir-backed registry consistent with Windows. The dir-backed registry relies on the underlying filesystem, which does not generally have transactional integrity when performing multiple operations. Therefore, if an error occurs during the recursive deletion, the dir-backed registry may be left in an inconsistent state. (This used to be commit 6b5fbf7e4e38342bcd80e63f46cd295f89ab1ee9) --- source4/lib/registry/dir.c | 60 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) (limited to 'source4/lib') diff --git a/source4/lib/registry/dir.c b/source4/lib/registry/dir.c index 27cae8c490..dc3717e886 100644 --- a/source4/lib/registry/dir.c +++ b/source4/lib/registry/dir.c @@ -55,18 +55,66 @@ static WERROR reg_dir_add_key(TALLOC_CTX *mem_ctx, return WERR_GENERAL_FAILURE; } +static WERROR reg_dir_delete_recursive(const char *name) +{ + DIR *d; + struct dirent *e; + WERROR werr; + + d = opendir(name); + if (d == NULL) { + DEBUG(3,("Unable to open '%s': %s\n", name, + strerror(errno))); + return WERR_BADFILE; + } + + while((e = readdir(d))) { + char *path; + struct stat stbuf; + + if (ISDOT(e->d_name) || ISDOTDOT(e->d_name)) + continue; + + path = talloc_asprintf(name, "%s/%s", name, e->d_name); + if (!path) + return WERR_NOMEM; + + stat(path, &stbuf); + + if (!S_ISDIR(stbuf.st_mode)) { + if (unlink(path) < 0) { + talloc_free(path); + closedir(d); + return WERR_GENERAL_FAILURE; + } + } else { + werr = reg_dir_delete_recursive(path); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(path); + closedir(d); + return werr; + } + } + + talloc_free(path); + } + closedir(d); + + if (rmdir(name) == 0) + return WERR_OK; + else if (errno == ENOENT) + return WERR_BADFILE; + else + return WERR_GENERAL_FAILURE; +} + static WERROR reg_dir_del_key(const struct hive_key *k, const char *name) { struct dir_key *dk = talloc_get_type(k, struct dir_key); char *child = talloc_asprintf(NULL, "%s/%s", dk->path, name); WERROR ret; - if (rmdir(child) == 0) - ret = WERR_OK; - else if (errno == ENOENT) - ret = WERR_BADFILE; - else - ret = WERR_GENERAL_FAILURE; + ret = reg_dir_delete_recursive(child); talloc_free(child); -- cgit From 79eea32976d2991319c9d0ad32a150f34b029f99 Mon Sep 17 00:00:00 2001 From: Andrew Kroeger Date: Sat, 16 Feb 2008 15:19:00 -0600 Subject: registry: Implement recursive deletes for regf-backed registry. When deleting a registry key that contains subkeys or values, Windows performs a recursive deletion that removes any subkeys or values. This update makes deletes for an regf-backed registry consistent with Windows. The regf-backed registry does not have transactional integrity when performing multiple operations. Therefore, if an error occurs during the recursive deletion, the regf-backed registry may be left in an inconsistent state. (This used to be commit b0321bad290d1a9399b4aba140a622e3af6d7575) --- source4/lib/registry/regf.c | 53 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) (limited to 'source4/lib') diff --git a/source4/lib/registry/regf.c b/source4/lib/registry/regf.c index 15b60745f0..6a35799a7e 100644 --- a/source4/lib/registry/regf.c +++ b/source4/lib/registry/regf.c @@ -1618,10 +1618,55 @@ static WERROR regf_del_key(const struct hive_key *parent, const char *name) return WERR_BADFILE; } - if (key->nk->subkeys_offset != -1 || - key->nk->values_offset != -1) { - DEBUG(0, ("Key '%s' is not empty.\n", name)); - return WERR_FILE_EXISTS; + if (key->nk->subkeys_offset != -1) { + char *sk_name; + struct hive_key *sk = (struct hive_key *)key; + int i = key->nk->num_subkeys; + while (i--) { + /* Get subkey information. */ + error = regf_get_subkey_by_index(parent_nk, sk, 0, + (const char **)&sk_name, + NULL, NULL); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't retrieve subkey by index.\n")); + return error; + } + + /* Delete subkey. */ + error = regf_del_key(sk, sk_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't delete key '%s'.\n", sk_name)); + return error; + } + + talloc_free(sk_name); + } + } + + if (key->nk->values_offset != -1) { + char *val_name; + struct hive_key *sk = (struct hive_key *)key; + DATA_BLOB data; + int i = key->nk->num_values; + while (i--) { + /* Get value information. */ + error = regf_get_value(parent_nk, sk, 0, + (const char **)&val_name, + NULL, &data); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't retrieve value by index.\n")); + return error; + } + + /* Delete value. */ + error = regf_del_value(sk, val_name); + if (!W_ERROR_IS_OK(error)) { + DEBUG(0, ("Can't delete value '%s'.\n", val_name)); + return error; + } + + talloc_free(val_name); + } } /* Delete it from the subkey list. */ -- cgit From a13395c8731db614e543e60d3da67873c3164ea2 Mon Sep 17 00:00:00 2001 From: Andrew Kroeger Date: Sat, 16 Feb 2008 15:21:26 -0600 Subject: registry: Add an explicit test for recursive deletion. (This used to be commit 5e905804993df4c2ac28090d056e6db6bb63ac44) --- source4/lib/registry/tests/hive.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'source4/lib') diff --git a/source4/lib/registry/tests/hive.c b/source4/lib/registry/tests/hive.c index 4d27e83a74..915782694f 100644 --- a/source4/lib/registry/tests/hive.c +++ b/source4/lib/registry/tests/hive.c @@ -110,6 +110,38 @@ static bool test_add_subkey(struct torture_context *tctx, return true; } +static bool test_del_recursive(struct torture_context *tctx, + const void *test_data) +{ + WERROR error; + struct hive_key *subkey; + struct hive_key *subkey2; + const struct hive_key *root = (const struct hive_key *)test_data; + TALLOC_CTX *mem_ctx = tctx; + uint32_t data = 42; + + /* Create a new key under the root */ + error = hive_key_add_name(mem_ctx, root, "Parent Key", NULL, + NULL, &subkey); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + /* Create a new key under "Parent Key" */ + error = hive_key_add_name(mem_ctx, subkey, "Child Key", NULL, + NULL, &subkey2); + torture_assert_werr_ok(tctx, error, "hive_key_add_name"); + + /* Create a new value under "Child Key" */ + error = hive_key_set_value(subkey2, "Answer Recursive", REG_DWORD, + data_blob_talloc(mem_ctx, &data, sizeof(data))); + torture_assert_werr_ok(tctx, error, "hive_key_set_value"); + + /* Deleting "Parent Key" will also delete "Child Key" and the value. */ + error = hive_key_del(root, "Parent Key"); + torture_assert_werr_ok(tctx, error, "hive_key_del"); + + return true; +} + static bool test_flush_key(struct torture_context *tctx, void *test_data) { struct hive_key *root = (struct hive_key *)test_data; @@ -272,6 +304,11 @@ static void tcase_add_tests(struct torture_tcase *tcase) test_add_subkey); torture_tcase_add_simple_test(tcase, "flush_key", test_flush_key); + /* test_del_recursive() test must run before test_keyinfo_root(). + test_keyinfo_root() checks the number of subkeys, which verifies + the recursive delete worked properly. */ + torture_tcase_add_simple_test_const(tcase, "del_recursive", + test_del_recursive); torture_tcase_add_simple_test_const(tcase, "get_info", test_keyinfo_root); torture_tcase_add_simple_test(tcase, "get_info_nums", -- cgit