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