clamonacc/inotif/inotif.c
5ae59b95
 /*
206dbaef
  *  Copyright (C) 2019-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5ae59b95
  *
  *  Authors: Mickey Sola
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  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, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <signal.h>
 #include <pthread.h>
 #include <string.h>
 #include <errno.h>
 #include <stdbool.h>
 
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
5ae59b95
 #include <sys/fanotify.h>
 #include <sys/inotify.h>
7bc021ff
 #endif
953a43f3
 
9e20cdf6
 // libclamav
 #include "clamav.h"
 #include "scanners.h"
 
 // shared
 #include "optparser.h"
 #include "output.h"
72c10bd1
 
9e20cdf6
 // clamd
 #include "server.h"
 #include "clamd_others.h"
 #include "scanner.h"
 
 #include "../fanotif/fanotif.h"
72c10bd1
 #include "hash.h"
 #include "inotif.h"
78b1b1b4
 #include "../scan/thread.h"
9e20cdf6
 #include "../scan/onas_queue.h"
78b1b1b4
 #include "../misc/utils.h"
5ae59b95
 
9e20cdf6
 #if defined(HAVE_SYS_FANOTIFY_H)
7ad7211e
 
d98d6fdb
 static int onas_ddd_init_ht(uint32_t ht_size);
 static int onas_ddd_init_wdlt(uint64_t nwatches);
 static int onas_ddd_grow_wdlt();
 
 static int onas_ddd_watch(const char *pathname, int fan_fd, uint64_t fan_mask, int in_fd, uint64_t in_mask);
72fd33c8
 static int onas_ddd_watch_hierarchy(const char *pathname, size_t len, int fd, uint64_t mask, uint32_t type);
d98d6fdb
 static int onas_ddd_unwatch(const char *pathname, int fan_fd, int in_fd);
72fd33c8
 static int onas_ddd_unwatch_hierarchy(const char *pathname, size_t len, int fd, uint32_t type);
d98d6fdb
 
7534d83e
 static void onas_ddd_handle_in_moved_to(struct onas_context *ctx, const char *path, const char *child_path, const struct inotify_event *event, int wd, uint64_t in_mask);
 static void onas_ddd_handle_in_create(struct onas_context *ctx, const char *path, const char *child_path, const struct inotify_event *event, int wd, uint64_t in_mask);
 static void onas_ddd_handle_in_moved_from(struct onas_context *ctx, const char *path, const char *child_path, const struct inotify_event *event, int wd);
 static void onas_ddd_handle_in_delete(struct onas_context *ctx, const char *path, const char *child_path, const struct inotify_event *event, int wd);
7ad7211e
 static void onas_ddd_handle_extra_scanning(struct onas_context *ctx, const char *pathname, int extra_options);
7aa76467
 static void onas_ddd_exit(void *arg);
d98d6fdb
 
5ae59b95
 /* TODO: Unglobalize these. */
 static struct onas_ht *ddd_ht;
 static char **wdlt;
 static uint32_t wdlt_len;
abbe4c4b
 static int onas_in_fd;
953a43f3
 extern pthread_t ddd_pid;
5ae59b95
 
72fd33c8
 static int onas_ddd_init_ht(uint32_t ht_size)
 {
5ae59b95
 
72fd33c8
     if (ht_size <= 0)
         ht_size = ONAS_DEFAULT_HT_SIZE;
5ae59b95
 
72fd33c8
     return onas_ht_init(&ddd_ht, ht_size);
5ae59b95
 }
 
4c1f1522
 /**
  * @brief Initialize watch descriptor lookup table which we use alongside inotify to keep track of which open watchpoints correspond to which objects
  */
72fd33c8
 static int onas_ddd_init_wdlt(uint64_t nwatches)
 {
c316a8c9
 
72fd33c8
     if (nwatches <= 0) return CL_EARG;
5ae59b95
 
72fd33c8
     wdlt = (char **)cli_calloc(nwatches << 1, sizeof(char *));
     if (!wdlt) return CL_EMEM;
5ae59b95
 
72fd33c8
     wdlt_len = nwatches << 1;
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
4c1f1522
 /**
  * @brief Initialize watch descriptor lookup table which we use alongside inotify to keep track of which open watchpoints correspond to which objects
  */
72fd33c8
 static int onas_ddd_grow_wdlt()
 {
5ae59b95
 
72fd33c8
     char **ptr = NULL;
5ae59b95
 
72fd33c8
     ptr = (char **)cli_realloc(wdlt, wdlt_len << 1);
     if (ptr) {
         wdlt = ptr;
         memset(&ptr[wdlt_len], 0, sizeof(char *) * (wdlt_len - 1));
     } else {
         return CL_EMEM;
     }
5ae59b95
 
72fd33c8
     wdlt_len <<= 1;
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
 /* TODO: Support configuration for changing/setting number of inotify watches. */
72fd33c8
 int onas_ddd_init(uint64_t nwatches, size_t ht_size)
 {
5ae59b95
 
72fd33c8
     const char *nwatch_file = "/proc/sys/fs/inotify/max_user_watches";
     int nwfd                = 0;
     int ret                 = 0;
     char nwatch_str[MAX_WATCH_LEN];
     char *p  = NULL;
     nwatches = 0;
5ae59b95
 
72fd33c8
     nwfd = open(nwatch_file, O_RDONLY);
     if (nwfd < 0) return CL_EOPEN;
5ae59b95
 
72fd33c8
     ret = read(nwfd, nwatch_str, MAX_WATCH_LEN);
     close(nwfd);
     if (ret < 0) return CL_EREAD;
5ae59b95
 
72fd33c8
     nwatches = strtol(nwatch_str, &p, 10);
5ae59b95
 
72fd33c8
     ret = onas_ddd_init_wdlt(nwatches);
     if (ret) return ret;
5ae59b95
 
72fd33c8
     ret = onas_ddd_init_ht(ht_size);
     if (ret) return ret;
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
4c1f1522
 /**
  * @brief convenience function for adding both inotify and fanotify watchpoints for a single path in one go
  */
72fd33c8
 static int onas_ddd_watch(const char *pathname, int fan_fd, uint64_t fan_mask, int in_fd, uint64_t in_mask)
 {
     if (!pathname || fan_fd <= 0 || in_fd <= 0) return CL_ENULLARG;
5ae59b95
 
72fd33c8
     int ret    = CL_SUCCESS;
     size_t len = strlen(pathname);
5ae59b95
 
72fd33c8
     ret = onas_ddd_watch_hierarchy(pathname, len, in_fd, in_mask, ONAS_IN);
     if (ret) return ret;
5ae59b95
 
72fd33c8
     ret = onas_ddd_watch_hierarchy(pathname, len, fan_fd, fan_mask, ONAS_FAN);
     if (ret) return ret;
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
4c1f1522
 /**
  * @brief recursively adds a hierarchy from the hash table and all watches of a single type to specified object
  *
  * @param pathname  the directory to start watching
  * @param len       the size of pathname in bytes
  * @param fd        the fanotify or inotify file descriptor
  * @param mask      options for watching the path
  * @param type      specifies whether or not to add inotify or fanotify watchpoints and the type of fd passed
  */
72fd33c8
 static int onas_ddd_watch_hierarchy(const char *pathname, size_t len, int fd, uint64_t mask, uint32_t type)
 {
5ae59b95
 
72fd33c8
     if (!pathname || fd <= 0 || !type) return CL_ENULLARG;
5ae59b95
 
72fd33c8
     if (type == (ONAS_IN | ONAS_FAN)) return CL_EARG;
5ae59b95
 
72fd33c8
     struct onas_hnode *hnode  = NULL;
     struct onas_element *elem = NULL;
     int wd                    = 0;
5ae59b95
 
72fd33c8
     if (onas_ht_get(ddd_ht, pathname, len, &elem) != CL_SUCCESS) return CL_EARG;
5ae59b95
 
72fd33c8
     hnode = elem->data;
5ae59b95
 
72fd33c8
     if (type & ONAS_IN) {
         wd = inotify_add_watch(fd, pathname, (uint32_t)mask);
5ae59b95
 
72fd33c8
         if (wd < 0) return CL_EARG;
5ae59b95
 
72fd33c8
         if ((uint32_t)wd >= wdlt_len) {
             onas_ddd_grow_wdlt();
         }
5ae59b95
 
72fd33c8
         /* Link the hash node to the watch descriptor lookup table */
         hnode->wd = wd;
         wdlt[wd]  = hnode->pathname;
5ae59b95
 
72fd33c8
         hnode->watched |= ONAS_INWATCH;
     } else if (type & ONAS_FAN) {
         if (fanotify_mark(fd, FAN_MARK_ADD, mask, AT_FDCWD, hnode->pathname) < 0) return CL_EARG;
         hnode->watched |= ONAS_FANWATCH;
     } else {
         return CL_EARG;
     }
5ae59b95
 
4c1f1522
     /* recursively watch all children */
72fd33c8
     struct onas_lnode *curr = hnode->childhead;
5ae59b95
 
72fd33c8
     while (curr->next != hnode->childtail) {
         curr = curr->next;
5ae59b95
 
72fd33c8
         size_t size      = len + strlen(curr->dirname) + 2;
         char *child_path = (char *)cli_malloc(size);
         if (child_path == NULL)
             return CL_EMEM;
         if (hnode->pathname[len - 1] == '/')
             snprintf(child_path, --size, "%s%s", hnode->pathname, curr->dirname);
         else
             snprintf(child_path, size, "%s/%s", hnode->pathname, curr->dirname);
5ae59b95
 
72fd33c8
         if (onas_ddd_watch_hierarchy(child_path, strlen(child_path), fd, mask, type)) {
             return CL_EARG;
         }
         free(child_path);
     }
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
4c1f1522
 /**
  * @brief convenience function for removing both inotify and fanotify watchpoints for a single path in one go
  */
72fd33c8
 static int onas_ddd_unwatch(const char *pathname, int fan_fd, int in_fd)
 {
     if (!pathname || fan_fd <= 0 || in_fd <= 0) return CL_ENULLARG;
5ae59b95
 
72fd33c8
     int ret    = CL_SUCCESS;
     size_t len = strlen(pathname);
5ae59b95
 
72fd33c8
     ret = onas_ddd_unwatch_hierarchy(pathname, len, in_fd, ONAS_IN);
     if (ret) return ret;
5ae59b95
 
72fd33c8
     ret = onas_ddd_unwatch_hierarchy(pathname, len, fan_fd, ONAS_FAN);
     if (ret) return ret;
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
4c1f1522
 /**
  * @brief recursively removes a hierarchy from the hash table and drops all watches of a single type from linked objects
  *
  * @param pathname  the directory to stop watching
  * @param len       the size of pathname in bytes
  * @param fd        the fanotify or inotify file descriptor
  * @param type      specifies whether or not to remove inotify or fanotify watchpoints and the type of fd passed
  */
72fd33c8
 static int onas_ddd_unwatch_hierarchy(const char *pathname, size_t len, int fd, uint32_t type)
 {
5ae59b95
 
72fd33c8
     if (!pathname || fd <= 0 || !type) return CL_ENULLARG;
5ae59b95
 
72fd33c8
     if (type == (ONAS_IN | ONAS_FAN)) return CL_EARG;
5ae59b95
 
72fd33c8
     struct onas_hnode *hnode  = NULL;
     struct onas_element *elem = NULL;
     int wd                    = 0;
5ae59b95
 
72fd33c8
     if (onas_ht_get(ddd_ht, pathname, len, &elem)) return CL_EARG;
5ae59b95
 
72fd33c8
     hnode = elem->data;
5ae59b95
 
72fd33c8
     if (type & ONAS_IN) {
         wd = hnode->wd;
5ae59b95
 
72fd33c8
         if (!inotify_rm_watch(fd, wd)) return CL_EARG;
5ae59b95
 
72fd33c8
         /* Unlink the hash node from the watch descriptor lookup table */
         hnode->wd = 0;
         wdlt[wd]  = NULL;
5ae59b95
 
72fd33c8
         hnode->watched = ONAS_STOPWATCH;
     } else if (type & ONAS_FAN) {
         if (fanotify_mark(fd, FAN_MARK_REMOVE, 0, AT_FDCWD, hnode->pathname) < 0) return CL_EARG;
         hnode->watched = ONAS_STOPWATCH;
     } else {
         return CL_EARG;
     }
5ae59b95
 
4c1f1522
     /* free all children recursively */
72fd33c8
     struct onas_lnode *curr = hnode->childhead;
5ae59b95
 
72fd33c8
     while (curr->next != hnode->childtail) {
         curr = curr->next;
5ae59b95
 
72fd33c8
         size_t size      = len + strlen(curr->dirname) + 2;
         char *child_path = (char *)cli_malloc(size);
         if (child_path == NULL)
             return CL_EMEM;
         if (hnode->pathname[len - 1] == '/')
             snprintf(child_path, --size, "%s%s", hnode->pathname, curr->dirname);
         else
             snprintf(child_path, size, "%s/%s", hnode->pathname, curr->dirname);
5ae59b95
 
72fd33c8
         onas_ddd_unwatch_hierarchy(child_path, strlen(child_path), fd, type);
         free(child_path);
     }
5ae59b95
 
72fd33c8
     return CL_SUCCESS;
5ae59b95
 }
 
4fee702f
 cl_error_t onas_enable_inotif_ddd(struct onas_context **ctx)
 {
953a43f3
 
4fee702f
     pthread_attr_t ddd_attr;
     int32_t thread_started = 1;
953a43f3
 
4fee702f
     if (!ctx || !*ctx) {
         logg("!ClamInotif: unable to start clamonacc. (bad context)\n");
         return CL_EARG;
     }
953a43f3
 
4fee702f
     if ((*ctx)->ddd_enabled) {
         do {
             if (pthread_attr_init(&ddd_attr)) break;
             pthread_attr_setdetachstate(&ddd_attr, PTHREAD_CREATE_JOINABLE);
             thread_started = pthread_create(&ddd_pid, &ddd_attr, onas_ddd_th, *ctx);
         } while (0);
     }
953a43f3
 
4fee702f
     if (0 != thread_started) {
         /* Failed to create thread */
         logg("!ClamInotif: Unable to start dynamic directory determination ... \n");
         return CL_ECREAT;
     }
953a43f3
 
4fee702f
     return CL_SUCCESS;
953a43f3
 }
 
4fee702f
 void *onas_ddd_th(void *arg)
 {
     struct onas_context *ctx = (struct onas_context *)arg;
     sigset_t sigset;
     const struct optstruct *pt;
     uint64_t in_mask = IN_ONLYDIR | IN_MOVE | IN_DELETE | IN_CREATE;
     fd_set rfds;
     char buf[4096];
     ssize_t bread;
     const struct inotify_event *event;
     int ret, len, idx;
 
     char **include_list = NULL;
     char **exclude_list = NULL;
     int num_exdirs, num_indirs;
     cl_error_t err;
 
     /* ignore all signals */
     sigfillset(&sigset);
     sigdelset(&sigset, SIGUSR1);
     sigdelset(&sigset, SIGUSR2);
     /* The behavior of a process is undefined after it ignores a
5ae59b95
 	 * SIGFPE, SIGILL, SIGSEGV, or SIGBUS signal */
4fee702f
     sigdelset(&sigset, SIGFPE);
     sigdelset(&sigset, SIGILL);
     sigdelset(&sigset, SIGTERM);
     sigdelset(&sigset, SIGINT);
7ee85372
 #ifdef SIGBUS
4fee702f
     sigdelset(&sigset, SIGBUS);
5ae59b95
 #endif
72fd33c8
 
4fee702f
     logg("*ClamInotif: starting inotify event loop ...\n");
 
     onas_in_fd = inotify_init1(IN_NONBLOCK);
     if (onas_in_fd == -1) {
         logg("!ClamInotif: could not init inotify\n");
         return NULL;
     }
 
     ret = onas_ddd_init(0, ONAS_DEFAULT_HT_SIZE);
     if (ret) {
         logg("!ClamInotif: failed to initialize DDD system\n");
         return NULL;
     }
 
     logg("*ClamInotif: dynamically determining directory hierarchy...\n");
     /* Add provided paths recursively. */
 
     if (!optget(ctx->opts, "watch-list")->enabled && !optget(ctx->clamdopts, "OnAccessIncludePath")->enabled) {
         logg("!ClamInotif: Please specify at least one path with OnAccessIncludePath\n");
         return NULL;
     }
5ae59b95
 
4fee702f
     if ((pt = optget(ctx->clamdopts, "OnAccessIncludePath"))->enabled) {
 
         while (pt) {
             if (!strcmp(pt->strarg, "/")) {
                 logg("!ClamInotif: not including path '%s' while DDD is enabled\n", pt->strarg);
                 logg("!ClamInotif: please use the OnAccessMountPath option to watch '%s'\n", pt->strarg);
                 pt = (struct optstruct *)pt->nextarg;
                 continue;
             }
             if (onas_ht_get(ddd_ht, pt->strarg, strlen(pt->strarg), NULL) != CL_SUCCESS) {
                 if (onas_ht_add_hierarchy(ddd_ht, pt->strarg)) {
                     logg("!ClamInotif: can't include '%s'\n", pt->strarg);
                     return NULL;
                 } else {
                     logg("ClamInotif: watching '%s' (and all sub-directories)\n", pt->strarg);
                 }
             }
 
             pt = (struct optstruct *)pt->nextarg;
         }
     }
 
     if ((pt = optget(ctx->opts, "watch-list"))->enabled) {
 
         num_indirs = 0;
         err        = CL_SUCCESS;
 
         include_list = onas_get_opt_list(pt->strarg, &num_indirs, &err);
         if (NULL == include_list) {
             logg("!ClamInotif: could not parse include list (%d)\n", err);
             return NULL;
         }
 
         idx = 0;
         while (NULL != include_list[idx]) {
             if (onas_ht_get(ddd_ht, include_list[idx], strlen(include_list[idx]), NULL) != CL_SUCCESS) {
                 if (onas_ht_add_hierarchy(ddd_ht, include_list[idx])) {
                     logg("!ClamInotif: can't include '%s'\n", include_list[idx]);
                     return NULL;
                 } else {
                     logg("ClamInotif: watching '%s' (and all sub-directories)\n", include_list[idx]);
                 }
             }
 
             idx++;
         }
     }
 
     /* Remove provided paths recursively. */
     if ((pt = optget(ctx->clamdopts, "OnAccessExcludePath"))->enabled) {
         while (pt) {
             size_t ptlen = strlen(pt->strarg);
             if (onas_ht_get(ddd_ht, pt->strarg, ptlen, NULL) == CL_SUCCESS) {
                 if (onas_ht_rm_hierarchy(ddd_ht, pt->strarg, ptlen, 0)) {
                     logg("!ClamInotif: can't exclude '%s'\n", pt->strarg);
                     return NULL;
                 } else
                     logg("ClamInotif: excluding '%s' (and all sub-directories)\n", pt->strarg);
             }
 
             pt = (struct optstruct *)pt->nextarg;
         }
     }
 
     if ((pt = optget(ctx->opts, "exclude-list"))->enabled) {
 
         num_exdirs = 0;
         err        = CL_SUCCESS;
 
         exclude_list = onas_get_opt_list(pt->strarg, &num_exdirs, &err);
         if (NULL == exclude_list) {
             logg("!ClamInotif: could not parse exclude list (%d)\n", err);
             return NULL;
         }
 
         idx = 0;
         while (exclude_list[idx] != NULL) {
             if (onas_ht_get(ddd_ht, exclude_list[idx], strlen(exclude_list[idx]), NULL) == CL_SUCCESS) {
                 if (onas_ht_rm_hierarchy(ddd_ht, exclude_list[idx], strlen(exclude_list[idx]), 0)) {
                     logg("!ClamInotif: can't exclude '%s'\n", exclude_list[idx]);
                     return NULL;
                 } else {
                     logg("ClamInotif: excluding '%s' (and all sub-directories)\n", exclude_list[idx]);
                 }
             }
 
             idx++;
         }
     }
 
     /* Watch provided paths recursively */
     if ((pt = optget(ctx->clamdopts, "OnAccessIncludePath"))->enabled) {
         while (pt) {
             errno        = 0;
             size_t ptlen = strlen(pt->strarg);
             if (onas_ht_get(ddd_ht, pt->strarg, ptlen, NULL) == CL_SUCCESS) {
88ede306
                 err = onas_ddd_watch(pt->strarg, ctx->fan_fd, ctx->fan_mask, onas_in_fd, in_mask);
                 if (err) {
4fee702f
 
                     if (0 == errno) {
                         logg("!ClamInotif: could not watch path '%s', %d\n ", pt->strarg, err);
                     } else {
                         logg("!ClamInotif: could not watch path '%s', %s\n", pt->strarg, strerror(errno));
                         if (errno == EINVAL && optget(ctx->clamdopts, "OnAccessPrevention")->enabled) {
                             logg("*ClamInotif: when using the OnAccessPrevention option, please ensure your kernel\n\t\t\twas compiled with CONFIG_FANOTIFY_ACCESS_PERMISSIONS set to Y\n");
 
                             kill(getpid(), SIGTERM);
                         }
                         if (errno == ENOSPC) {
 
                             logg("*ClamInotif: you likely do not have enough inotify watchpoints available ... run the follow command to increase available watchpoints and try again ...\n");
                             logg("*\t $ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p\n");
 
                             kill(getpid(), SIGTERM);
                         }
                     }
                 }
                 pt = (struct optstruct *)pt->nextarg;
             }
         }
     }
 
     if (NULL != include_list) {
         idx = 0;
         while (NULL != include_list[idx]) {
             errno          = 0;
             uint64_t ptlen = strlen(include_list[idx]);
             if (onas_ht_get(ddd_ht, include_list[idx], ptlen, NULL) == CL_SUCCESS) {
88ede306
                 err = onas_ddd_watch(include_list[idx], ctx->fan_fd, ctx->fan_mask, onas_in_fd, in_mask);
                 if (err) {
4fee702f
                     if (0 == errno) {
                         logg("!ClamInotif: could not watch path '%s', %d\n ", include_list[idx], err);
                     } else {
                         logg("!ClamInotif: could not watch path '%s', %s\n", include_list[idx], strerror(errno));
                         if (errno == EINVAL && optget(ctx->clamdopts, "OnAccessPrevention")->enabled) {
                             logg("*ClamInotif: when using the OnAccessPrevention option, please ensure your kernel\n\t\t\twas compiled with CONFIG_FANOTIFY_ACCESS_PERMISSIONS set to Y\n");
 
                             kill(getpid(), SIGTERM);
                         }
                         if (errno == ENOSPC) {
 
                             logg("*ClamInotif: you likely do not have enough inotify watchpoints available ... run the follow command to increase available watchpoints and try again ...\n");
                             logg("*\t $ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p\n");
 
                             kill(getpid(), SIGTERM);
                         }
                     }
                 }
             }
             idx++;
         }
     }
 
     if (optget(ctx->clamdopts, "OnAccessExtraScanning")->enabled) {
         logg("ClamInotif: extra scanning on inotify events enabled\n");
     }
 
     FD_ZERO(&rfds);
     FD_SET(onas_in_fd, &rfds);
 
     pthread_cleanup_push(onas_ddd_exit, NULL);
 
     while (1) {
         do {
             ret = select(onas_in_fd + 1, &rfds, NULL, NULL, NULL);
         } while (ret == -1 && errno == EINTR);
 
         while ((bread = read(onas_in_fd, buf, sizeof(buf))) > 0) {
             pthread_testcancel();
             /* Handle events. */
             int wd;
             char *p           = buf;
             const char *path  = NULL;
             const char *child = NULL;
             for (; p < buf + bread; p += sizeof(struct inotify_event) + event->len) {
 
                 event = (const struct inotify_event *)p;
                 wd    = event->wd;
                 path  = wdlt[wd];
                 child = event->name;
 
                 if (path == NULL) {
                     logg("*ClamInotif: watch descriptor not found in lookup table ... skipping\n");
                     continue;
                 }
 
                 len              = strlen(path);
                 size_t size      = strlen(child) + len + 2;
                 char *child_path = (char *)cli_malloc(size);
                 if (child_path == NULL) {
                     logg("*ClamInotif: could not allocate space for child path ... aborting\n");
                     return NULL;
                 }
 
                 if (path[len - 1] == '/') {
                     snprintf(child_path, --size, "%s%s", path, child);
                 } else {
                     snprintf(child_path, size, "%s/%s", path, child);
                 }
 
                 if (event->mask & IN_DELETE) {
                     onas_ddd_handle_in_delete(ctx, path, child_path, event, wd);
 
                 } else if (event->mask & IN_MOVED_FROM) {
                     onas_ddd_handle_in_moved_from(ctx, path, child_path, event, wd);
 
                 } else if (event->mask & IN_CREATE) {
                     onas_ddd_handle_in_create(ctx, path, child_path, event, wd, in_mask);
 
                 } else if (event->mask & IN_MOVED_TO) {
                     onas_ddd_handle_in_moved_to(ctx, path, child_path, event, wd, in_mask);
                 }
 
                 free(child_path);
                 child_path = NULL;
             }
         }
     }
 
     logg("*ClamInotif: exiting inotify event thread\n");
     pthread_cleanup_pop(1);
     return NULL;
 }
7534d83e
 
 static void onas_ddd_handle_in_delete(struct onas_context *ctx,
4fee702f
                                       const char *path, const char *child_path, const struct inotify_event *event, int wd)
 {
7ee85372
 
4fee702f
     struct stat s;
     if (stat(child_path, &s) == 0 && S_ISREG(s.st_mode)) return;
     if (!(event->mask & IN_ISDIR)) return;
7ee85372
 
4fee702f
     logg("*ClamInotif: DELETE - removing %s from %s with wd:%d\n", child_path, path, wd);
     onas_ddd_unwatch(child_path, ctx->fan_fd, onas_in_fd);
     onas_ht_rm_hierarchy(ddd_ht, child_path, strlen(child_path), 0);
7ee85372
 
4fee702f
     return;
7ee85372
 }
 
7534d83e
 static void onas_ddd_handle_in_moved_from(struct onas_context *ctx,
4fee702f
                                           const char *path, const char *child_path, const struct inotify_event *event, int wd)
 {
7ee85372
 
4fee702f
     struct stat s;
     if (stat(child_path, &s) == 0 && S_ISREG(s.st_mode)) return;
     if (!(event->mask & IN_ISDIR)) return;
7ee85372
 
4fee702f
     logg("*ClamInotif: MOVED_FROM - removing %s from %s with wd:%d\n", child_path, path, wd);
     onas_ddd_unwatch(child_path, ctx->fan_fd, onas_in_fd);
     onas_ht_rm_hierarchy(ddd_ht, child_path, strlen(child_path), 0);
7ee85372
 
4fee702f
     return;
7ee85372
 }
 
7534d83e
 static void onas_ddd_handle_in_create(struct onas_context *ctx,
4fee702f
                                       const char *path, const char *child_path, const struct inotify_event *event, int wd, uint64_t in_mask)
 {
7ee85372
 
4fee702f
     struct stat s;
3d9620db
 
4fee702f
     if (optget(ctx->clamdopts, "OnAccessExtraScanning")->enabled) {
         if (stat(child_path, &s) == 0 && S_ISREG(s.st_mode)) {
             onas_ddd_handle_extra_scanning(ctx, child_path, ONAS_SCTH_B_FILE);
7ee85372
 
4fee702f
         } else if (event->mask & IN_ISDIR) {
             logg("*ClamInotif: CREATE - adding %s to %s with wd:%d\n", child_path, path, wd);
             onas_ddd_handle_extra_scanning(ctx, child_path, ONAS_SCTH_B_DIR);
7ad7211e
 
4fee702f
             onas_ht_add_hierarchy(ddd_ht, child_path);
             onas_ddd_watch(child_path, ctx->fan_fd, ctx->fan_mask, onas_in_fd, in_mask);
         }
     } else {
         if (stat(child_path, &s) == 0 && S_ISREG(s.st_mode)) return;
         if (!(event->mask & IN_ISDIR)) return;
7ee85372
 
4fee702f
         logg("*ClamInotif: MOVED_TO - adding %s to %s with wd:%d\n", child_path, path, wd);
         onas_ht_add_hierarchy(ddd_ht, child_path);
         onas_ddd_watch(child_path, ctx->fan_fd, ctx->fan_mask, onas_in_fd, in_mask);
     }
7ee85372
 
4fee702f
     return;
7ee85372
 }
 
7534d83e
 static void onas_ddd_handle_in_moved_to(struct onas_context *ctx,
4fee702f
                                         const char *path, const char *child_path, const struct inotify_event *event, int wd, uint64_t in_mask)
 {
7ee85372
 
4fee702f
     struct stat s;
     if (optget(ctx->clamdopts, "OnAccessExtraScanning")->enabled) {
         if (stat(child_path, &s) == 0 && S_ISREG(s.st_mode)) {
             onas_ddd_handle_extra_scanning(ctx, child_path, ONAS_SCTH_B_FILE);
7ad7211e
 
4fee702f
         } else if (event->mask & IN_ISDIR) {
             logg("*ClamInotif: MOVED_TO - adding %s to %s with wd:%d\n", child_path, path, wd);
             onas_ddd_handle_extra_scanning(ctx, child_path, ONAS_SCTH_B_DIR);
7ee85372
 
4fee702f
             onas_ht_add_hierarchy(ddd_ht, child_path);
             onas_ddd_watch(child_path, ctx->fan_fd, ctx->fan_mask, onas_in_fd, in_mask);
         }
     } else {
         if (stat(child_path, &s) == 0 && S_ISREG(s.st_mode)) return;
         if (!(event->mask & IN_ISDIR)) return;
7ee85372
 
4fee702f
         logg("*ClamInotif: MOVED_TO - adding %s to %s with wd:%d\n", child_path, path, wd);
         onas_ht_add_hierarchy(ddd_ht, child_path);
         onas_ddd_watch(child_path, ctx->fan_fd, ctx->fan_mask, onas_in_fd, in_mask);
     }
7ee85372
 
4fee702f
     return;
7ee85372
 }
 
4fee702f
 static void onas_ddd_handle_extra_scanning(struct onas_context *ctx, const char *pathname, int extra_options)
 {
72fd33c8
 
4fee702f
     struct onas_scan_event *event_data;
7ad7211e
 
4fee702f
     event_data = (struct onas_scan_event *)cli_calloc(1, sizeof(struct onas_scan_event));
     if (NULL == event_data) {
         logg("!ClamInotif: could not allocate memory for event data struct\n");
     }
7ad7211e
 
4fee702f
     /* general mapping */
     onas_map_context_info_to_event_data(ctx, &event_data);
     event_data->pathname = cli_strdup(pathname);
     event_data->bool_opts |= ONAS_SCTH_B_SCAN;
 
     /* inotify specific stuffs */
     event_data->bool_opts |= ONAS_SCTH_B_INOTIFY;
     extra_options &ONAS_SCTH_B_FILE ? event_data->bool_opts |= ONAS_SCTH_B_FILE : extra_options;
     extra_options &ONAS_SCTH_B_DIR ? event_data->bool_opts |= ONAS_SCTH_B_DIR : extra_options;
 
     logg("*ClamInotif: attempting to feed consumer queue\n");
     /* feed consumer queue */
     if (CL_SUCCESS != onas_queue_event(event_data)) {
         logg("!ClamInotif: error occurred while feeding consumer queue extra event ... continuing ...\n");
         return;
     }
72fd33c8
 
4fee702f
     return;
7ad7211e
 }
7ee85372
 
e2f59af3
 static void onas_ddd_exit(__attribute__((unused)) void *arg)
4fee702f
 {
     logg("*ClamInotif: onas_ddd_exit()\n");
7ee85372
 
4fee702f
     if (onas_in_fd) {
         close(onas_in_fd);
     }
     onas_in_fd = 0;
abbe4c4b
 
4fee702f
     if (ddd_ht) {
         onas_free_ht(ddd_ht);
     }
     ddd_ht = NULL;
7d83fa29
 
4fee702f
     if (wdlt) {
         free(wdlt);
     }
     wdlt = NULL;
7ee85372
 
4fee702f
     logg("ClamInotif: stopped\n");
5ae59b95
 }
7aa76467
 
f87f08cb
 #endif