1 /*
2  *  Copyright (C) 2015-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *
4  *  Authors: Mickey Sola
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 version 2 as
8  *  published by the Free Software Foundation.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  *  MA 02110-1301, USA.
19  */
20 
21 #if HAVE_CONFIG_H
22 #include "clamav-config.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <pthread.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <stdbool.h>
35 
36 #if defined(HAVE_SYS_FANOTIFY_H)
37 #include <sys/fanotify.h>
38 #endif
39 
40 // libclamav
41 #include "clamav.h"
42 #include "scanners.h"
43 #include "str.h"
44 
45 // shared
46 #include "optparser.h"
47 #include "output.h"
48 
49 // clamd
50 #include "server.h"
51 #include "clamd_others.h"
52 #include "scanner.h"
53 
54 #include "../fanotif/fanotif.h"
55 #include "hash.h"
56 #include "inotif.h"
57 #include "../misc/priv_fts.h"
58 
59 #if defined(HAVE_SYS_FANOTIFY_H)
60 
61 static struct onas_bucket *onas_bucket_init();
62 static void onas_free_bucket(struct onas_bucket *bckt);
63 static int onas_bucket_insert(struct onas_bucket *bckt, struct onas_element *elem);
64 static int onas_bucket_remove(struct onas_bucket *bckt, struct onas_element *elem);
65 
66 static int onas_add_hashnode_child(struct onas_hnode *node, const char *dirname);
67 
68 static struct onas_lnode *onas_listnode_init(void);
69 
70 static struct onas_hnode *onas_hashnode_init(void);
71 
72 /**
73  * The data structure described and implemented below is a hash table with elements that also act as relational nodes
74  * in a tree. This allows for average case constant time retrieval of nodes, and recursive operation on a node and all
75  * it's children and parents. The memory cost for this speed of relational retrieval is necessarily high, as every node
76  * must also keep track of it's children in a key-accessible way. To cut down on memory costs, children of nodes are not
77  * themselves key accessible, but must be combined with their parent in a constant-time operation to be retrieved from
78  * the table.
79  *
80  * Further optimization to retrieval and space management may include storing direct address to given children nodes, but
81  * such a design will create further complexitiy and time cost at insertion--which must also be as fast as possible in
82  * order to accomadate the real-time nature of security event processing.
83  *
84  * To date, the hashing function itself has not been well studied, and as such buckets were implemented from the start to
85  * help account for any potential collission issues in its design, as a measure to help offset any major time sinks during
86  * insertion.
87  *
88  * One last important note about this hash table is that to avoid massive slowdowns, it does not grow, but instead relies on
89  * buckets and a generous default size to distribute that load. Slight hit to retrieval time is a fair cost to pay to avoid
90  * total loss of service in a real-time system. Future work here might include automatically confiuguring initial hashtable
91  * size to align with the system being monitored, or max inotify watch points since that's our hard limit anyways.
92  */
93 
onas_hshift(uint32_t hash)94 static inline uint32_t onas_hshift(uint32_t hash)
95 {
96 
97     hash = ~hash;
98 
99     hash += (hash << 15);
100     hash ^= (hash >> 12);
101     hash += (hash << 2);
102     hash ^= (hash >> 4);
103     hash += (hash << 3);
104     hash += (hash << 11);
105     hash ^= (hash >> 16);
106 
107     return hash;
108 }
109 
110 /**
111  * @brief inline wrapper for onaccess inotify hashing function
112  *
113  * @param key       the string to be hashed
114  * @param keylen    size of the string
115  * @param size      the size of the hashtable
116  */
onas_hash(const char * key,size_t keylen,uint32_t size)117 static inline int onas_hash(const char *key, size_t keylen, uint32_t size)
118 {
119 
120     uint32_t hash = 1;
121     uint32_t i;
122 
123     for (i = 0; i < keylen; i++) {
124         hash += key[i];
125         hash = onas_hshift(hash);
126     }
127 
128     return hash & (size - 1);
129 }
130 
131 /**
132  * @brief initialises a bucketed hash table, pre-grown to the given size
133  */
onas_ht_init(struct onas_ht ** ht,uint32_t size)134 int onas_ht_init(struct onas_ht **ht, uint32_t size)
135 {
136 
137     if (size == 0 || (size & (~size + 1)) != size) return CL_EARG;
138 
139     *ht = (struct onas_ht *)cli_malloc(sizeof(struct onas_ht));
140     if (!(*ht)) return CL_EMEM;
141 
142     **ht = (struct onas_ht){
143         .htable = NULL,
144         .size   = size,
145         .nbckts = 0,
146     };
147 
148     if (!((*ht)->htable = (struct onas_bucket **)cli_calloc(size, sizeof(struct onas_bucket *)))) {
149         onas_free_ht(*ht);
150         return CL_EMEM;
151     }
152 
153     return CL_SUCCESS;
154 }
155 
onas_free_ht(struct onas_ht * ht)156 void onas_free_ht(struct onas_ht *ht)
157 {
158 
159     if (!ht || ht->size == 0) return;
160 
161     if (!ht->htable) {
162         free(ht);
163         return;
164     }
165 
166     uint32_t i = 0;
167     for (i = 0; i < ht->size; i++) {
168         onas_free_bucket(ht->htable[i]);
169         ht->htable[i] = NULL;
170     }
171 
172     free(ht->htable);
173     ht->htable = NULL;
174 
175     free(ht);
176 
177     return;
178 }
179 
onas_bucket_init()180 static struct onas_bucket *onas_bucket_init()
181 {
182 
183     struct onas_bucket *bckt = (struct onas_bucket *)cli_malloc(sizeof(struct onas_bucket));
184     if (!bckt) return NULL;
185 
186     *bckt = (struct onas_bucket){
187         .size = 0,
188         .head = NULL,
189         .tail = NULL};
190 
191     return bckt;
192 }
193 
onas_free_bucket(struct onas_bucket * bckt)194 static void onas_free_bucket(struct onas_bucket *bckt)
195 {
196 
197     if (!bckt) return;
198 
199     uint32_t i                = 0;
200     struct onas_element *curr = NULL;
201 
202     for (i = 0; i < bckt->size; i++) {
203         curr       = bckt->head;
204         bckt->head = curr->next;
205         onas_free_element(curr);
206         curr = NULL;
207     }
208 
209     free(bckt);
210 
211     return;
212 }
213 /**
214  * @brief the hash table uses buckets to store lists of key/value pairings
215  */
onas_element_init(struct onas_hnode * value,const char * key,size_t klen)216 struct onas_element *onas_element_init(struct onas_hnode *value, const char *key, size_t klen)
217 {
218 
219     struct onas_element *elem = (struct onas_element *)cli_malloc(sizeof(struct onas_element));
220     if (!elem) return NULL;
221 
222     *elem = (struct onas_element){
223         .key  = key,
224         .klen = klen,
225         .data = value,
226         .next = NULL,
227         .prev = NULL};
228 
229     return elem;
230 }
231 
onas_free_element(struct onas_element * elem)232 void onas_free_element(struct onas_element *elem)
233 {
234 
235     if (!elem) return;
236 
237     onas_free_hashnode(elem->data);
238 
239     elem->prev = NULL;
240     elem->next = NULL;
241 
242     free(elem);
243 
244     return;
245 }
246 
onas_ht_insert(struct onas_ht * ht,struct onas_element * elem)247 int onas_ht_insert(struct onas_ht *ht, struct onas_element *elem)
248 {
249 
250     if (!ht || !elem || !elem->key) return CL_ENULLARG;
251 
252     int idx                  = onas_hash(elem->key, elem->klen, ht->size);
253     struct onas_bucket *bckt = ht->htable[idx];
254 
255     int ret        = 0;
256     uint32_t bsize = 0;
257 
258     if (bckt == NULL) {
259         ht->htable[idx] = onas_bucket_init();
260         bckt            = ht->htable[idx];
261     }
262 
263     bsize = bckt->size;
264     ret   = onas_bucket_insert(bckt, elem);
265 
266     if (ret == CL_SUCCESS)
267         if (bsize < bckt->size)
268             ht->nbckts++;
269 
270     return ret;
271 }
272 
onas_bucket_insert(struct onas_bucket * bckt,struct onas_element * elem)273 static int onas_bucket_insert(struct onas_bucket *bckt, struct onas_element *elem)
274 {
275     if (!bckt || !elem) return CL_ENULLARG;
276 
277     if (bckt->size == 0) {
278         bckt->head = elem;
279         bckt->tail = elem;
280         elem->prev = NULL;
281         elem->next = NULL;
282         bckt->size++;
283     } else {
284         struct onas_element *btail = bckt->tail;
285 
286         btail->next = elem;
287         elem->prev  = btail;
288         elem->next  = NULL;
289         bckt->tail  = elem;
290         bckt->size++;
291     }
292 
293     return CL_SUCCESS;
294 }
295 
296 /**
297  * @brief Checks if key exists and optionally stores address to the element corresponding to the key within elem
298  */
onas_ht_get(struct onas_ht * ht,const char * key,size_t klen,struct onas_element ** elem)299 int onas_ht_get(struct onas_ht *ht, const char *key, size_t klen, struct onas_element **elem)
300 {
301 
302     if (elem) *elem = NULL;
303 
304     if (!ht || !key || klen <= 0) return CL_ENULLARG;
305 
306     struct onas_bucket *bckt = ht->htable[onas_hash(key, klen, ht->size)];
307 
308     if (!bckt || bckt->size == 0) return CL_EARG;
309 
310     struct onas_element *curr = bckt->head;
311 
312     while (curr && strcmp(curr->key, key)) {
313         curr = curr->next;
314     }
315 
316     if (!curr) return CL_EARG;
317 
318     if (elem) *elem = curr;
319 
320     return CL_SUCCESS;
321 }
322 
323 /**
324  * @brief Removes the element corresponding to key from the hashtable and optionally returns a pointer to the removed element.
325  */
onas_ht_remove(struct onas_ht * ht,const char * key,size_t klen,struct onas_element ** relem)326 int onas_ht_remove(struct onas_ht *ht, const char *key, size_t klen, struct onas_element **relem)
327 {
328     if (!ht || !key || klen <= 0) return CL_ENULLARG;
329 
330     struct onas_bucket *bckt = ht->htable[onas_hash(key, klen, ht->size)];
331 
332     if (!bckt) return CL_EARG;
333 
334     struct onas_element *elem = NULL;
335     onas_ht_get(ht, key, klen, &elem);
336 
337     if (!elem) return CL_EARG;
338 
339     int ret = onas_bucket_remove(bckt, elem);
340 
341     if (relem) *relem = elem;
342 
343     return ret;
344 }
345 
onas_bucket_remove(struct onas_bucket * bckt,struct onas_element * elem)346 static int onas_bucket_remove(struct onas_bucket *bckt, struct onas_element *elem)
347 {
348     if (!bckt || !elem) return CL_ENULLARG;
349 
350     struct onas_element *curr = bckt->head;
351 
352     while (curr && curr != elem) {
353         curr = curr->next;
354     }
355 
356     if (!curr) return CL_EARG;
357 
358     if (bckt->head == elem) {
359         bckt->head = elem->next;
360         if (bckt->head) bckt->head->prev = NULL;
361 
362         elem->next = NULL;
363     } else if (bckt->tail == elem) {
364         bckt->tail = elem->prev;
365         if (bckt->tail) bckt->tail->next = NULL;
366 
367         elem->prev = NULL;
368     } else {
369         struct onas_element *tmp = NULL;
370 
371         tmp = elem->prev;
372         if (tmp) {
373             tmp->next = elem->next;
374             tmp       = elem->next;
375             tmp->prev = elem->prev;
376         }
377 
378         elem->prev = NULL;
379         elem->next = NULL;
380     }
381 
382     bckt->size--;
383 
384     return CL_SUCCESS;
385 }
386 
387 /* Dealing with hash nodes and list nodes */
388 
389 /**
390  * @brief Function to initialize hashnode, which is the data value we're storing in the hash table
391  */
onas_hashnode_init(void)392 static struct onas_hnode *onas_hashnode_init(void)
393 {
394     struct onas_hnode *hnode = NULL;
395     if (!(hnode = (struct onas_hnode *)cli_malloc(sizeof(struct onas_hnode)))) {
396         return NULL;
397     }
398 
399     *hnode = (struct onas_hnode){
400         .pathlen       = 0,
401         .pathname      = NULL,
402         .prnt_pathlen  = 0,
403         .prnt_pathname = NULL,
404         .childhead     = NULL,
405         .childtail     = NULL,
406         .wd            = 0,
407         .watched       = 0};
408 
409     if (!(hnode->childhead = (struct onas_lnode *)onas_listnode_init())) {
410         onas_free_hashnode(hnode);
411         return NULL;
412     }
413 
414     if (!(hnode->childtail = (struct onas_lnode *)onas_listnode_init())) {
415         onas_free_hashnode(hnode);
416         return NULL;
417     }
418 
419     hnode->childhead->next = (struct onas_lnode *)hnode->childtail;
420     hnode->childtail->prev = (struct onas_lnode *)hnode->childhead;
421 
422     return hnode;
423 }
424 
425 /**
426  * @brief Function to initialize listnodes, which ultimately allow us to traverse this datastructure like a tree
427  */
onas_listnode_init(void)428 static struct onas_lnode *onas_listnode_init(void)
429 {
430     struct onas_lnode *lnode = NULL;
431     if (!(lnode = (struct onas_lnode *)cli_malloc(sizeof(struct onas_lnode)))) {
432         return NULL;
433     }
434 
435     *lnode = (struct onas_lnode){
436         .dirname = NULL,
437         .next    = NULL,
438         .prev    = NULL};
439 
440     return lnode;
441 }
442 
443 /**
444  * @brief Function to free hashnodes
445  */
onas_free_hashnode(struct onas_hnode * hnode)446 void onas_free_hashnode(struct onas_hnode *hnode)
447 {
448     if (!hnode) return;
449 
450     onas_free_dirlist(hnode->childhead);
451     hnode->childhead = NULL;
452 
453     free(hnode->pathname);
454     hnode->pathname = NULL;
455 
456     free(hnode->prnt_pathname);
457     hnode->prnt_pathname = NULL;
458 
459     free(hnode);
460 
461     return;
462 }
463 
464 /**
465  * @brief Function to free list of listnode
466  */
onas_free_dirlist(struct onas_lnode * head)467 void onas_free_dirlist(struct onas_lnode *head)
468 {
469     if (!head) return;
470     struct onas_lnode *curr = head;
471     struct onas_lnode *tmp  = curr;
472 
473     while (curr) {
474         tmp = curr->next;
475         onas_free_listnode(curr);
476         curr = tmp;
477     }
478 
479     return;
480 }
481 
482 /**
483  * @brief Function to free a single listnode
484  */
onas_free_listnode(struct onas_lnode * lnode)485 void onas_free_listnode(struct onas_lnode *lnode)
486 {
487     if (!lnode) return;
488 
489     lnode->next = NULL;
490     lnode->prev = NULL;
491 
492     free(lnode->dirname);
493     lnode->dirname = NULL;
494 
495     free(lnode);
496 
497     return;
498 }
499 
500 /**
501  * @brief Function to add a single value to a hashnode's listnode
502  */
onas_add_hashnode_child(struct onas_hnode * node,const char * dirname)503 static int onas_add_hashnode_child(struct onas_hnode *node, const char *dirname)
504 {
505     if (!node || !dirname) return CL_ENULLARG;
506 
507     struct onas_lnode *child = onas_listnode_init();
508     if (!child) return CL_EMEM;
509 
510     size_t n       = strlen(dirname);
511     child->dirname = CLI_STRNDUP(dirname, n);
512 
513     onas_add_listnode(node->childtail, child);
514 
515     return CL_SUCCESS;
516 }
517 
518 /**
519  * @brief Function to add a dir_listnode to a list
520  */
onas_add_listnode(struct onas_lnode * tail,struct onas_lnode * node)521 int onas_add_listnode(struct onas_lnode *tail, struct onas_lnode *node)
522 {
523     if (!tail || !node) return CL_ENULLARG;
524 
525     struct onas_lnode *tmp = tail->prev;
526 
527     tmp->next  = node;
528     node->prev = tail->prev;
529 
530     node->next = tail;
531     tail->prev = node;
532 
533     return CL_SUCCESS;
534 }
535 
536 /**
537  * @brief Function to remove a listnode based on dirname.
538  */
onas_rm_listnode(struct onas_lnode * head,const char * dirname)539 cl_error_t onas_rm_listnode(struct onas_lnode *head, const char *dirname)
540 {
541     if (!dirname || !head) return CL_ENULLARG;
542 
543     struct onas_lnode *curr = head;
544     size_t n                = strlen(dirname);
545 
546     while ((curr = curr->next)) {
547         if (NULL == curr->dirname) {
548             logg("*ClamHash: node's directory name is NULL!\n");
549             return CL_ERROR;
550         } else if (!strncmp(curr->dirname, dirname, n)) {
551             if (curr->next != NULL)
552                 curr->next->prev = curr->prev;
553             if (curr->prev != NULL)
554                 curr->prev->next = curr->next;
555             onas_free_listnode(curr);
556 
557             return CL_SUCCESS;
558         }
559     }
560 
561     return CL_ERROR;
562 }
563 
564 /*** Dealing with parent/child relationships in the table. ***/
565 
566 /**
567  * @brief Determines parent of given directory and returns a copy based on full pathname.
568  */
onas_get_parent(const char * pathname,size_t len)569 inline static char *onas_get_parent(const char *pathname, size_t len)
570 {
571     if (!pathname || len <= 1) return NULL;
572 
573     int idx   = len - 2;
574     char *ret = NULL;
575 
576     while (idx >= 0 && pathname[idx] != '/') {
577         idx--;
578     }
579 
580     if (idx == 0) {
581         idx++;
582     }
583 
584     ret = CLI_STRNDUP(pathname, idx);
585     if (!ret) {
586         errno = ENOMEM;
587         return NULL;
588     }
589 
590     return ret;
591 }
592 
593 /**
594  * @brief Gets the index at which the name of directory begins from the full pathname.
595  */
onas_get_dirname_idx(const char * pathname,size_t len)596 inline static int onas_get_dirname_idx(const char *pathname, size_t len)
597 {
598     if (!pathname || len <= 1) return -1;
599 
600     int idx = len - 2;
601 
602     while (idx >= 0 && pathname[idx] != '/') {
603         idx--;
604     }
605 
606     if (pathname[idx] == '/')
607         return idx + 1;
608 
609     return idx;
610 }
611 
612 /**
613  * @brief Emancipates the specified child from the specified parent directory, typical done after a delete or move event
614  *
615  * @param ht        the hashtable structure
616  * @param prntpath  the full path of the parent director to be used hashed and used as a key to retrieve the corresponding entry from the table
617  * @param prntlen   the length of the parent path in bytes
618  * @param childpath the path of the child to be deassociated with the passed parent
619  * @param childlen  the length of the child path in bytes
620  */
onas_ht_rm_child(struct onas_ht * ht,const char * prntpath,size_t prntlen,const char * childpath,size_t childlen)621 int onas_ht_rm_child(struct onas_ht *ht, const char *prntpath, size_t prntlen, const char *childpath, size_t childlen)
622 {
623 
624     if (!ht || !prntpath || prntlen <= 0 || !childpath || childlen <= 1) return CL_ENULLARG;
625 
626     struct onas_element *elem = NULL;
627     struct onas_hnode *hnode  = NULL;
628     int idx                   = onas_get_dirname_idx(childpath, childlen);
629     int ret                   = 0;
630 
631     if (idx <= 0) return CL_SUCCESS;
632 
633     if (onas_ht_get(ht, prntpath, prntlen, &elem) != CL_SUCCESS) return CL_EARG;
634 
635     hnode = elem->data;
636 
637     if (CL_SUCCESS != (ret = onas_rm_listnode(hnode->childhead, &(childpath[idx])))) {
638         return CL_EARG;
639     }
640 
641     return CL_SUCCESS;
642 }
643 
644 /**
645  * @brief The specified parent adds the specified child to its list, typical done after a create, or move event
646  *
647  * @param ht        the hashtable structure
648  * @param prntpath  the full path of the parent director to be used hashed and used as a key to retrieve the corresponding entry from the table
649  * @param prntlen   the length of the parent path in bytes
650  * @param childpath the path of the child to be associated with the passed parent
651  * @param childlen  the length of the child path in bytes
652  */
onas_ht_add_child(struct onas_ht * ht,const char * prntpath,size_t prntlen,const char * childpath,size_t childlen)653 int onas_ht_add_child(struct onas_ht *ht, const char *prntpath, size_t prntlen, const char *childpath, size_t childlen)
654 {
655     if (!ht || !prntpath || prntlen <= 0 || !childpath || childlen <= 1) return CL_ENULLARG;
656 
657     struct onas_element *elem = NULL;
658     struct onas_hnode *hnode  = NULL;
659     int idx                   = onas_get_dirname_idx(childpath, childlen);
660 
661     if (idx <= 0) return CL_SUCCESS;
662 
663     if (onas_ht_get(ht, prntpath, prntlen, &elem)) return CL_EARG;
664     hnode = elem->data;
665 
666     return onas_add_hashnode_child(hnode, &(childpath[idx]));
667 }
668 
669 /*** Dealing with hierarchy changes. ***/
670 
671 /**
672  * @brief Adds the hierarchy under pathname to the tree and allocates all necessary memory.
673  */
onas_ht_add_hierarchy(struct onas_ht * ht,const char * pathname)674 int onas_ht_add_hierarchy(struct onas_ht *ht, const char *pathname)
675 {
676     if (!ht || !pathname) return CL_ENULLARG;
677 
678     int ret           = 0;
679     FTS *ftsp         = NULL;
680     int ftspopts      = FTS_PHYSICAL | FTS_XDEV;
681     FTSENT *curr      = NULL;
682     FTSENT *childlist = NULL;
683 
684     size_t len = strlen(pathname);
685     char *prnt = onas_get_parent(pathname, len);
686     if (prnt) onas_ht_add_child(ht, prnt, strlen(prnt), pathname, len);
687     free(prnt);
688 
689     char *const pathargv[] = {(char *)pathname, NULL};
690     if (!(ftsp = _priv_fts_open(pathargv, ftspopts, NULL))) {
691         logg("!ClamHash: could not open '%s'\n", pathname);
692         ret = CL_EARG;
693         goto out;
694     }
695 
696     while ((curr = _priv_fts_read(ftsp))) {
697 
698         struct onas_hnode *hnode = NULL;
699 
700         /* May want to handle other options in the future. */
701         switch (curr->fts_info) {
702             case FTS_D:
703                 hnode = onas_hashnode_init();
704                 if (!hnode) {
705                     ret = CL_EMEM;
706                     goto out;
707                 }
708 
709                 hnode->pathlen  = curr->fts_pathlen;
710                 hnode->pathname = CLI_STRNDUP(curr->fts_path, hnode->pathlen);
711 
712                 hnode->prnt_pathname = onas_get_parent(hnode->pathname, hnode->pathlen);
713                 if (hnode->prnt_pathname)
714                     hnode->prnt_pathlen = strlen(hnode->prnt_pathname);
715                 else
716                     hnode->prnt_pathlen = 0;
717                 break;
718             default:
719                 continue;
720         }
721 
722         if ((childlist = _priv_fts_children(ftsp, 0))) {
723             do {
724                 if (childlist->fts_info == FTS_D) {
725                     if (CL_EMEM == onas_add_hashnode_child(hnode, childlist->fts_name)) {
726 
727                         ret = CL_EMEM;
728                         goto out;
729                     }
730                 }
731             } while ((childlist = childlist->fts_link));
732         }
733 
734         struct onas_element *elem = onas_element_init(hnode, hnode->pathname, hnode->pathlen);
735         if (!elem) {
736             ret = CL_EMEM;
737             goto out;
738         }
739 
740         if (onas_ht_insert(ht, elem)) {
741 
742             ret = -1;
743             goto out;
744         }
745     }
746 
747 out:
748     if (ftsp) {
749         _priv_fts_close(ftsp);
750     }
751 
752     if (ret) {
753         return ret;
754     }
755 
756     return CL_SUCCESS;
757 }
758 
759 /**
760  * @brief Removes the underlying hierarchy from the tree and frees all associated memory.
761  */
onas_ht_rm_hierarchy(struct onas_ht * ht,const char * pathname,size_t len,int level)762 int onas_ht_rm_hierarchy(struct onas_ht *ht, const char *pathname, size_t len, int level)
763 {
764     if (!ht || !pathname || len <= 0) return CL_ENULLARG;
765 
766     struct onas_hnode *hnode  = NULL;
767     struct onas_element *elem = NULL;
768     char *prntname            = NULL;
769     size_t prntlen            = 0;
770 
771     if (onas_ht_get(ht, pathname, len, &elem)) return CL_EARG;
772 
773     hnode = elem->data;
774 
775     struct onas_lnode *curr = hnode->childhead;
776 
777     if (level == 0) {
778         if (!(prntname = onas_get_parent(pathname, len))) return CL_EARG;
779 
780         prntlen = strlen(prntname);
781         if (onas_ht_rm_child(ht, prntname, prntlen, pathname, len)) return CL_EARG;
782 
783         free(prntname);
784     }
785 
786     while (curr->next != hnode->childtail) {
787         curr = curr->next;
788 
789         size_t size      = len + strlen(curr->dirname) + 2;
790         char *child_path = (char *)cli_malloc(size);
791         if (child_path == NULL)
792             return CL_EMEM;
793         if (hnode->pathname[len - 1] == '/')
794             snprintf(child_path, size, "%s%s", hnode->pathname, curr->dirname);
795         else
796             snprintf(child_path, size, "%s/%s", hnode->pathname, curr->dirname);
797         onas_ht_rm_hierarchy(ht, child_path, size, level + 1);
798         free(child_path);
799     }
800 
801     onas_ht_remove(ht, pathname, len, NULL);
802     onas_free_element(elem);
803 
804     return CL_SUCCESS;
805 }
806 #endif
807