Make utility function reg_deletekey_recursive_internal() static.
[kai/samba.git] / source3 / registry / reg_api.c
1 /*
2  *  Unix SMB/CIFS implementation.
3  *  Virtual Windows Registry Layer
4  *  Copyright (C) Volker Lendecke 2006
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /* Attempt to wrap the existing API in a more winreg.idl-like way */
21
22 #include "includes.h"
23
24 #undef DBGC_CLASS
25 #define DBGC_CLASS DBGC_REGISTRY
26
27 static WERROR fill_value_cache(struct registry_key *key)
28 {
29         if (key->values != NULL) {
30                 if (!reg_values_need_update(key->key, key->values)) {
31                         return WERR_OK;
32                 }
33         }
34
35         if (!(key->values = TALLOC_ZERO_P(key, REGVAL_CTR))) {
36                 return WERR_NOMEM;
37         }
38         if (fetch_reg_values(key->key, key->values) == -1) {
39                 TALLOC_FREE(key->values);
40                 return WERR_BADFILE;
41         }
42
43         return WERR_OK;
44 }
45
46 static WERROR fill_subkey_cache(struct registry_key *key)
47 {
48         if (key->subkeys != NULL) {
49                 if (!reg_subkeys_need_update(key->key, key->subkeys)) {
50                         return WERR_OK;
51                 }
52         }
53
54         if (!(key->subkeys = TALLOC_ZERO_P(key, REGSUBKEY_CTR))) {
55                 return WERR_NOMEM;
56         }
57
58         if (fetch_reg_keys(key->key, key->subkeys) == -1) {
59                 TALLOC_FREE(key->subkeys);
60                 return WERR_NO_MORE_ITEMS;
61         }
62
63         return WERR_OK;
64 }
65
66 static int regkey_destructor(REGISTRY_KEY *key)
67 {
68         return regdb_close();
69 }
70
71 static WERROR regkey_open_onelevel(TALLOC_CTX *mem_ctx, 
72                                    struct registry_key *parent,
73                                    const char *name,
74                                    const struct nt_user_token *token,
75                                    uint32 access_desired,
76                                    struct registry_key **pregkey)
77 {
78         WERROR          result = WERR_OK;
79         struct registry_key *regkey;
80         REGISTRY_KEY *key;
81         REGSUBKEY_CTR   *subkeys = NULL;
82
83         DEBUG(7,("regkey_open_onelevel: name = [%s]\n", name));
84
85         SMB_ASSERT(strchr(name, '\\') == NULL);
86
87         if (!(regkey = TALLOC_ZERO_P(mem_ctx, struct registry_key)) ||
88             !(regkey->token = dup_nt_token(regkey, token)) ||
89             !(regkey->key = TALLOC_ZERO_P(regkey, REGISTRY_KEY))) {
90                 result = WERR_NOMEM;
91                 goto done;
92         }
93
94         if ( !(W_ERROR_IS_OK(result = regdb_open())) ) {
95                 goto done;
96         }
97
98         key = regkey->key;
99         talloc_set_destructor(key, regkey_destructor);
100                 
101         /* initialization */
102         
103         key->type = REG_KEY_GENERIC;
104
105         if (name[0] == '\0') {
106                 /*
107                  * Open a copy of the parent key
108                  */
109                 if (!parent) {
110                         result = WERR_BADFILE;
111                         goto done;
112                 }
113                 key->name = talloc_strdup(key, parent->key->name);
114         }
115         else {
116                 /*
117                  * Normal subkey open
118                  */
119                 key->name = talloc_asprintf(key, "%s%s%s",
120                                             parent ? parent->key->name : "",
121                                             parent ? "\\": "",
122                                             name);
123         }
124
125         if (key->name == NULL) {
126                 result = WERR_NOMEM;
127                 goto done;
128         }
129
130         /* Tag this as a Performance Counter Key */
131
132         if( StrnCaseCmp(key->name, KEY_HKPD, strlen(KEY_HKPD)) == 0 )
133                 key->type = REG_KEY_HKPD;
134         
135         /* Look up the table of registry I/O operations */
136
137         if ( !(key->hook = reghook_cache_find( key->name )) ) {
138                 DEBUG(0,("reg_open_onelevel: Failed to assigned a "
139                          "REGISTRY_HOOK to [%s]\n", key->name ));
140                 result = WERR_BADFILE;
141                 goto done;
142         }
143
144         /* check if the path really exists; failed is indicated by -1 */
145         /* if the subkey count failed, bail out */
146
147         if ( !(subkeys = TALLOC_ZERO_P( key, REGSUBKEY_CTR )) ) {
148                 result = WERR_NOMEM;
149                 goto done;
150         }
151
152         if ( fetch_reg_keys( key, subkeys ) == -1 )  {
153                 result = WERR_BADFILE;
154                 goto done;
155         }
156
157         TALLOC_FREE( subkeys );
158
159         if ( !regkey_access_check( key, access_desired, &key->access_granted,
160                                    token ) ) {
161                 result = WERR_ACCESS_DENIED;
162                 goto done;
163         }
164
165         *pregkey = regkey;
166         result = WERR_OK;
167         
168 done:
169         if ( !W_ERROR_IS_OK(result) ) {
170                 TALLOC_FREE(regkey);
171         }
172
173         return result;
174 }
175
176 WERROR reg_openhive(TALLOC_CTX *mem_ctx, const char *hive,
177                     uint32 desired_access,
178                     const struct nt_user_token *token,
179                     struct registry_key **pkey)
180 {
181         SMB_ASSERT(hive != NULL);
182         SMB_ASSERT(hive[0] != '\0');
183         SMB_ASSERT(strchr(hive, '\\') == NULL);
184
185         return regkey_open_onelevel(mem_ctx, NULL, hive, token, desired_access,
186                                     pkey);
187 }
188
189 WERROR reg_openkey(TALLOC_CTX *mem_ctx, struct registry_key *parent,
190                    const char *name, uint32 desired_access,
191                    struct registry_key **pkey)
192 {
193         struct registry_key *direct_parent = parent;
194         WERROR err;
195         char *p, *path, *to_free;
196         size_t len;
197
198         if (!(path = SMB_STRDUP(name))) {
199                 return WERR_NOMEM;
200         }
201         to_free = path;
202
203         len = strlen(path);
204
205         if ((len > 0) && (path[len-1] == '\\')) {
206                 path[len-1] = '\0';
207         }
208
209         while ((p = strchr(path, '\\')) != NULL) {
210                 char *name_component;
211                 struct registry_key *tmp;
212
213                 if (!(name_component = SMB_STRNDUP(path, (p - path)))) {
214                         err = WERR_NOMEM;
215                         goto error;
216                 }
217
218                 err = regkey_open_onelevel(mem_ctx, direct_parent,
219                                            name_component, parent->token,
220                                            SEC_RIGHTS_ENUM_SUBKEYS, &tmp);
221                 SAFE_FREE(name_component);
222
223                 if (!W_ERROR_IS_OK(err)) {
224                         goto error;
225                 }
226                 if (direct_parent != parent) {
227                         TALLOC_FREE(direct_parent);
228                 }
229
230                 direct_parent = tmp;
231                 path = p+1;
232         }
233
234         err = regkey_open_onelevel(mem_ctx, direct_parent, path, parent->token,
235                                    desired_access, pkey);
236  error:
237         if (direct_parent != parent) {
238                 TALLOC_FREE(direct_parent);
239         }
240         SAFE_FREE(to_free);
241         return err;
242 }
243
244 WERROR reg_enumkey(TALLOC_CTX *mem_ctx, struct registry_key *key,
245                    uint32 idx, char **name, NTTIME *last_write_time)
246 {
247         WERROR err;
248
249         if (!(key->key->access_granted & SEC_RIGHTS_ENUM_SUBKEYS)) {
250                 return WERR_ACCESS_DENIED;
251         }
252
253         if (!W_ERROR_IS_OK(err = fill_subkey_cache(key))) {
254                 return err;
255         }
256
257         if (idx >= key->subkeys->num_subkeys) {
258                 return WERR_NO_MORE_ITEMS;
259         }
260
261         if (!(*name = talloc_strdup(mem_ctx, key->subkeys->subkeys[idx]))) {
262                 return WERR_NOMEM;
263         }
264
265         if (last_write_time) {
266                 *last_write_time = 0;
267         }
268
269         return WERR_OK;
270 }
271
272 WERROR reg_enumvalue(TALLOC_CTX *mem_ctx, struct registry_key *key,
273                      uint32 idx, char **pname, struct registry_value **pval)
274 {
275         struct registry_value *val;
276         WERROR err;
277
278         if (!(key->key->access_granted & SEC_RIGHTS_QUERY_VALUE)) {
279                 return WERR_ACCESS_DENIED;
280         }
281
282         if (!(W_ERROR_IS_OK(err = fill_value_cache(key)))) {
283                 return err;
284         }
285
286         if (idx >= key->values->num_values) {
287                 return WERR_NO_MORE_ITEMS;
288         }
289
290         err = registry_pull_value(mem_ctx, &val,
291                                   key->values->values[idx]->type,
292                                   key->values->values[idx]->data_p,
293                                   key->values->values[idx]->size,
294                                   key->values->values[idx]->size);
295         if (!W_ERROR_IS_OK(err)) {
296                 return err;
297         }
298
299         if (pname
300             && !(*pname = talloc_strdup(
301                          mem_ctx, key->values->values[idx]->valuename))) {
302                 SAFE_FREE(val);
303                 return WERR_NOMEM;
304         }
305
306         *pval = val;
307         return WERR_OK;
308 }
309
310 WERROR reg_queryvalue(TALLOC_CTX *mem_ctx, struct registry_key *key,
311                       const char *name, struct registry_value **pval)
312 {
313         WERROR err;
314         uint32 i;
315
316         if (!(key->key->access_granted & SEC_RIGHTS_QUERY_VALUE)) {
317                 return WERR_ACCESS_DENIED;
318         }
319
320         if (!(W_ERROR_IS_OK(err = fill_value_cache(key)))) {
321                 return err;
322         }
323
324         for (i=0; i<key->values->num_values; i++) {
325                 if (strequal(key->values->values[i]->valuename, name)) {
326                         return reg_enumvalue(mem_ctx, key, i, NULL, pval);
327                 }
328         }
329
330         return WERR_BADFILE;
331 }
332
333 WERROR reg_queryinfokey(struct registry_key *key, uint32_t *num_subkeys,
334                         uint32_t *max_subkeylen, uint32_t *max_subkeysize, 
335                         uint32_t *num_values, uint32_t *max_valnamelen, 
336                         uint32_t *max_valbufsize, uint32_t *secdescsize,
337                         NTTIME *last_changed_time)
338 {
339         uint32 i, max_size;
340         size_t max_len;
341         TALLOC_CTX *mem_ctx;
342         WERROR err;
343         struct security_descriptor *secdesc;
344
345         if (!(key->key->access_granted & SEC_RIGHTS_QUERY_VALUE)) {
346                 return WERR_ACCESS_DENIED;
347         }
348
349         if (!W_ERROR_IS_OK(fill_subkey_cache(key)) ||
350             !W_ERROR_IS_OK(fill_value_cache(key))) {
351                 return WERR_BADFILE;
352         }
353
354         max_len = 0;
355         for (i=0; i<key->subkeys->num_subkeys; i++) {
356                 max_len = MAX(max_len, strlen(key->subkeys->subkeys[i]));
357         }
358
359         *num_subkeys = key->subkeys->num_subkeys;
360         *max_subkeylen = max_len;
361         *max_subkeysize = 0;    /* Class length? */
362
363         max_len = 0;
364         max_size = 0;
365         for (i=0; i<key->values->num_values; i++) {
366                 max_len = MAX(max_len,
367                               strlen(key->values->values[i]->valuename));
368                 max_size = MAX(max_size, key->values->values[i]->size);
369         }
370
371         *num_values = key->values->num_values;
372         *max_valnamelen = max_len;
373         *max_valbufsize = max_size;
374
375         if (!(mem_ctx = talloc_new(key))) {
376                 return WERR_NOMEM;
377         }
378
379         err = regkey_get_secdesc(mem_ctx, key->key, &secdesc);
380         if (!W_ERROR_IS_OK(err)) {
381                 TALLOC_FREE(mem_ctx);
382                 return err;
383         }
384
385         *secdescsize = ndr_size_security_descriptor(secdesc, 0);
386         TALLOC_FREE(mem_ctx);
387
388         *last_changed_time = 0;
389
390         return WERR_OK;
391 }
392
393 WERROR reg_createkey(TALLOC_CTX *ctx, struct registry_key *parent,
394                      const char *subkeypath, uint32 desired_access,
395                      struct registry_key **pkey,
396                      enum winreg_CreateAction *paction)
397 {
398         struct registry_key *key = parent;
399         struct registry_key *create_parent;
400         TALLOC_CTX *mem_ctx;
401         char *path, *end;
402         WERROR err;
403         REGSUBKEY_CTR *subkeys;
404
405         if (!(mem_ctx = talloc_new(ctx))) return WERR_NOMEM;
406
407         if (!(path = talloc_strdup(mem_ctx, subkeypath))) {
408                 err = WERR_NOMEM;
409                 goto done;
410         }
411
412         while ((end = strchr(path, '\\')) != NULL) {
413                 struct registry_key *tmp;
414                 enum winreg_CreateAction action;
415
416                 *end = '\0';
417
418                 err = reg_createkey(mem_ctx, key, path,
419                                     SEC_RIGHTS_ENUM_SUBKEYS, &tmp, &action);
420                 if (!W_ERROR_IS_OK(err)) {
421                         goto done;
422                 }
423
424                 if (key != parent) {
425                         TALLOC_FREE(key);
426                 }
427
428                 key = tmp;
429                 path = end+1;
430         }
431
432         /*
433          * At this point, "path" contains the one-element subkey of "key". We
434          * can try to open it.
435          */
436
437         err = reg_openkey(ctx, key, path, desired_access, pkey);
438         if (W_ERROR_IS_OK(err)) {
439                 if (paction != NULL) {
440                         *paction = REG_OPENED_EXISTING_KEY;
441                 }
442                 goto done;
443         }
444
445         if (!W_ERROR_EQUAL(err, WERR_BADFILE)) {
446                 /*
447                  * Something but "notfound" has happened, so bail out
448                  */
449                 goto done;
450         }
451
452         /*
453          * We have to make a copy of the current key, as we opened it only
454          * with ENUM_SUBKEY access.
455          */
456
457         err = reg_openkey(mem_ctx, key, "", SEC_RIGHTS_CREATE_SUBKEY,
458                           &create_parent);
459         if (!W_ERROR_IS_OK(err)) {
460                 goto done;
461         }
462
463         /*
464          * Actually create the subkey
465          */
466
467         if (!(subkeys = TALLOC_ZERO_P(mem_ctx, REGSUBKEY_CTR))) {
468                 err = WERR_NOMEM;
469                 goto done;
470         }
471
472         err = fill_subkey_cache(create_parent);
473         if (!W_ERROR_IS_OK(err)) goto done;
474
475         err = regsubkey_ctr_addkey(create_parent->subkeys, path);
476         if (!W_ERROR_IS_OK(err)) goto done;
477
478         if (!store_reg_keys(create_parent->key, create_parent->subkeys)) {
479                 TALLOC_FREE(create_parent->subkeys);
480                 err = WERR_REG_IO_FAILURE;
481                 goto done;
482         }
483
484         /*
485          * Now open the newly created key
486          */
487
488         err = reg_openkey(ctx, create_parent, path, desired_access, pkey);
489         if (W_ERROR_IS_OK(err) && (paction != NULL)) {
490                 *paction = REG_CREATED_NEW_KEY;
491         }
492
493  done:
494         TALLOC_FREE(mem_ctx);
495         return err;
496 }
497
498 WERROR reg_deletekey(struct registry_key *parent, const char *path)
499 {
500         WERROR err;
501         TALLOC_CTX *mem_ctx;
502         char *name, *end;
503         int num_subkeys;
504         struct registry_key *tmp_key, *key;
505
506         if (!(mem_ctx = talloc_init("reg_createkey"))) return WERR_NOMEM;
507
508         if (!(name = talloc_strdup(mem_ctx, path))) {
509                 err = WERR_NOMEM;
510                 goto error;
511         }
512
513         /* check if the key has subkeys */
514         err = reg_openkey(mem_ctx, parent, name, REG_KEY_READ, &key);
515         if (!W_ERROR_IS_OK(err)) {
516                 goto error;
517         }
518         if (!W_ERROR_IS_OK(err = fill_subkey_cache(key))) {
519                 goto error;
520         }
521         if (key->subkeys->num_subkeys > 0) {
522                 err = WERR_ACCESS_DENIED;
523                 goto error;
524         }
525
526         /* no subkeys - proceed with delete */
527         if ((end = strrchr(name, '\\')) != NULL) {
528                 *end = '\0';
529
530                 err = reg_openkey(mem_ctx, parent, name,
531                                   SEC_RIGHTS_CREATE_SUBKEY, &tmp_key);
532                 if (!W_ERROR_IS_OK(err)) {
533                         goto error;
534                 }
535
536                 parent = tmp_key;
537                 name = end+1;
538         }
539
540         if (name[0] == '\0') {
541                 err = WERR_INVALID_PARAM;
542                 goto error;
543         }
544
545         if (!W_ERROR_IS_OK(err = fill_subkey_cache(parent))) {
546                 goto error;
547         }
548
549         num_subkeys = parent->subkeys->num_subkeys;
550
551         if (regsubkey_ctr_delkey(parent->subkeys, name) == num_subkeys) {
552                 err = WERR_BADFILE;
553                 goto error;
554         }
555
556         if (!store_reg_keys(parent->key, parent->subkeys)) {
557                 TALLOC_FREE(parent->subkeys);
558                 err = WERR_REG_IO_FAILURE;
559                 goto error;
560         }
561
562         regkey_set_secdesc(key->key, NULL);
563
564         err = WERR_OK;
565
566  error:
567         TALLOC_FREE(mem_ctx);
568         return err;
569 }
570
571 WERROR reg_setvalue(struct registry_key *key, const char *name,
572                     const struct registry_value *val)
573 {
574         WERROR err;
575         DATA_BLOB value_data;
576         int res;
577
578         if (!(key->key->access_granted & SEC_RIGHTS_SET_VALUE)) {
579                 return WERR_ACCESS_DENIED;
580         }
581
582         if (!W_ERROR_IS_OK(err = fill_value_cache(key))) {
583                 return err;
584         }
585
586         err = registry_push_value(key, val, &value_data);
587         if (!W_ERROR_IS_OK(err)) {
588                 return err;
589         }
590
591         res = regval_ctr_addvalue(key->values, name, val->type,
592                                   (char *)value_data.data, value_data.length);
593         TALLOC_FREE(value_data.data);
594
595         if (res == 0) {
596                 TALLOC_FREE(key->values);
597                 return WERR_NOMEM;
598         }
599
600         if (!store_reg_values(key->key, key->values)) {
601                 TALLOC_FREE(key->values);
602                 return WERR_REG_IO_FAILURE;
603         }
604
605         return WERR_OK;
606 }
607
608 WERROR reg_deletevalue(struct registry_key *key, const char *name)
609 {
610         WERROR err;
611
612         if (!(key->key->access_granted & SEC_RIGHTS_SET_VALUE)) {
613                 return WERR_ACCESS_DENIED;
614         }
615
616         if (!W_ERROR_IS_OK(err = fill_value_cache(key))) {
617                 return err;
618         }
619
620         regval_ctr_delvalue(key->values, name);
621
622         if (!store_reg_values(key->key, key->values)) {
623                 TALLOC_FREE(key->values);
624                 return WERR_REG_IO_FAILURE;
625         }
626
627         return WERR_OK;
628 }
629
630 WERROR reg_deleteallvalues(struct registry_key *key)
631 {
632         WERROR err;
633         int i;
634
635         if (!(key->key->access_granted & SEC_RIGHTS_SET_VALUE)) {
636                 return WERR_ACCESS_DENIED;
637         }
638
639         if (!W_ERROR_IS_OK(err = fill_value_cache(key))) {
640                 return err;
641         }
642
643         for (i=0; i<key->values->num_values; i++) {
644                 regval_ctr_delvalue(key->values, key->values->values[i]->valuename);
645         }
646
647         if (!store_reg_values(key->key, key->values)) {
648                 TALLOC_FREE(key->values);
649                 return WERR_REG_IO_FAILURE;
650         }
651
652         return WERR_OK;
653 }
654
655 /*
656  * Utility function to open a complete registry path including the hive
657  * prefix. This should become the replacement function for
658  * regkey_open_internal.
659  */
660
661 WERROR reg_open_path(TALLOC_CTX *mem_ctx, const char *orig_path,
662                      uint32 desired_access, const struct nt_user_token *token,
663                      struct registry_key **pkey)
664 {
665         struct registry_key *hive, *key;
666         char *path, *p;
667         WERROR err;
668
669         if (!(path = SMB_STRDUP(orig_path))) {
670                 return WERR_NOMEM;
671         }
672
673         p = strchr(path, '\\');
674
675         if ((p == NULL) || (p[1] == '\0')) {
676                 /*
677                  * No key behind the hive, just return the hive
678                  */
679
680                 err = reg_openhive(mem_ctx, path, desired_access, token,
681                                    &hive);
682                 if (!W_ERROR_IS_OK(err)) {
683                         SAFE_FREE(path);
684                         return err;
685                 }
686                 SAFE_FREE(path);
687                 *pkey = hive;
688                 return WERR_OK;
689         }
690
691         *p = '\0';
692
693         err = reg_openhive(mem_ctx, path, SEC_RIGHTS_ENUM_SUBKEYS, token,
694                            &hive);
695         if (!W_ERROR_IS_OK(err)) {
696                 SAFE_FREE(path);
697                 return err;
698         }
699
700         err = reg_openkey(mem_ctx, hive, p+1, desired_access, &key);
701
702         TALLOC_FREE(hive);
703         SAFE_FREE(path);
704
705         if (!W_ERROR_IS_OK(err)) {
706                 return err;
707         }
708
709         *pkey = key;
710         return WERR_OK;
711 }
712
713 /*
714  * Utility function to delete a registry key with all its subkeys.
715  * Note that reg_deletekey returns ACCESS_DENIED when called on a
716  * key that has subkeys.
717  */
718 static WERROR reg_deletekey_recursive_internal(TALLOC_CTX *ctx,
719                                                struct registry_key *parent,
720                                                const char *path,
721                                                bool del_key)
722 {
723         TALLOC_CTX *mem_ctx = NULL;
724         WERROR werr = WERR_OK;
725         struct registry_key *key;
726         char *subkey_name = NULL;
727
728         mem_ctx = talloc_new(ctx);
729         if (mem_ctx == NULL) {
730                 werr = WERR_NOMEM;
731                 goto done;
732         }
733
734         /* recurse through subkeys first */
735         werr = reg_openkey(mem_ctx, parent, path, REG_KEY_ALL, &key);
736         if (!W_ERROR_IS_OK(werr)) {
737                 goto done;
738         }
739
740         while (W_ERROR_IS_OK(werr = reg_enumkey(mem_ctx, key, 0,
741                                                 &subkey_name, NULL)))
742         {
743                 werr = reg_deletekey_recursive_internal(mem_ctx, key,
744                                                         subkey_name,
745                                                         true);
746                 if (!W_ERROR_IS_OK(werr)) {
747                         goto done;
748                 }
749         }
750         if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
751                 DEBUG(1, ("reg_deletekey_recursive_internal: "
752                           "Error enumerating subkeys: %s\n",
753                           dos_errstr(werr)));
754                 goto done;
755         }
756
757         werr = WERR_OK;
758
759         if (del_key) {
760                 /* now delete the actual key */
761                 werr = reg_deletekey(parent, path);
762         }
763
764 done:
765         TALLOC_FREE(mem_ctx);
766         return werr;
767 }
768
769 WERROR reg_deletekey_recursive(TALLOC_CTX *ctx,
770                                struct registry_key *parent,
771                                const char *path)
772 {
773         return reg_deletekey_recursive_internal(ctx, parent, path, true);
774 }
775
776 WERROR reg_deletesubkeys_recursive(TALLOC_CTX *ctx,
777                                    struct registry_key *parent,
778                                    const char *path)
779 {
780         return reg_deletekey_recursive_internal(ctx, parent, path, false);
781 }