aboutsummaryrefslogtreecommitdiff
path: root/devel/glib20
diff options
context:
space:
mode:
authorTobias C. Berner <tcberner@FreeBSD.org>2021-02-14 19:38:26 +0000
committerTobias C. Berner <tcberner@FreeBSD.org>2021-02-14 19:38:26 +0000
commit720acf293fe86ea4381513295bd2a9d4f09c471e (patch)
tree979a4f09eb66c83512cd413c53f5953b4d6dd05a /devel/glib20
parent5b941ad3f324dc2f3188be1b3966468dbcb1023f (diff)
downloadports-720acf293fe86ea4381513295bd2a9d4f09c471e.tar.gz
ports-720acf293fe86ea4381513295bd2a9d4f09c471e.zip
devel/glib20: add alternative file monitoring backend
This altnerative file monitoring backend implemented by the submitter may lead to a big decrease in cpu time usage in for example thunar. Consider it experimental, but worth a try, if your having issues with high cpu. PR: 214338 Submitted by: rozhuk.im@gmail.com
Notes
Notes: svn path=/head/; revision=565264
Diffstat (limited to 'devel/glib20')
-rw-r--r--devel/glib20/Makefile13
-rw-r--r--devel/glib20/files/gkqueuefilemonitor.c224
-rw-r--r--devel/glib20/files/kqueue_fnm.c1431
-rw-r--r--devel/glib20/files/kqueue_fnm.h74
4 files changed, 1741 insertions, 1 deletions
diff --git a/devel/glib20/Makefile b/devel/glib20/Makefile
index 57833c492aa3..d6dc9fae7c48 100644
--- a/devel/glib20/Makefile
+++ b/devel/glib20/Makefile
@@ -35,10 +35,12 @@ PORTSCOUT= limitw:1,even
_LIBVERSION= 0.6600.7
PLIST_SUB= LIBVERSION=${_LIBVERSION}
-OPTIONS_DEFINE= DEBUG MANPAGES NLS
+OPTIONS_DEFINE= DEBUG FAM_ALTBACKEND MANPAGES NLS
OPTIONS_DEFAULT= MANPAGES
OPTIONS_SUB= yes
+FAM_ALTBACKEND_DESC= Alternate file monitor backend
+
MANPAGES_BUILD_DEPENDS= docbook-xml>4.1.2:textproc/docbook-xml \
docbook-xsl>0:textproc/docbook-xsl
MANPAGES_USE= GNOME=libxslt:build
@@ -60,6 +62,15 @@ MESON_ARGS+= -Diconv=external
EXTRA_PATCHES= ${FILESDIR}/extra-arch-powerpc64
.endif
+pre-configure-FAM_ALTBACKEND-on:
+ @${REINPLACE_CMD} -e 's|kqueue-helper.c|kqueue_fnm.c|g ; \
+ s|.*kqueue-missing.c.*||g ; \
+ s|.*dep-list.c.*||g' \
+ ${WRKSRC}/gio/kqueue/meson.build
+ @${CP} -f ${FILESDIR}/gkqueuefilemonitor.c ${WRKSRC}/gio/kqueue/gkqueuefilemonitor.c
+ @${CP} ${FILESDIR}/kqueue_fnm.c ${WRKSRC}/gio/kqueue/kqueue_fnm.c
+ @${CP} ${FILESDIR}/kqueue_fnm.h ${WRKSRC}/gio/kqueue/kqueue_fnm.h
+
post-patch:
${REINPLACE_CMD} -e 's|/usr/local|${LOCALBASE}|g ; \
s|/usr/share/locale/locale|${LOCALBASE}/share/locale/locale|g' \
diff --git a/devel/glib20/files/gkqueuefilemonitor.c b/devel/glib20/files/gkqueuefilemonitor.c
new file mode 100644
index 000000000000..8f89b139e89e
--- /dev/null
+++ b/devel/glib20/files/gkqueuefilemonitor.c
@@ -0,0 +1,224 @@
+/*-
+ * Copyright (c) 2016 - 2019 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
+ *
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <string.h>
+#include <gio/gfilemonitor.h>
+#include <gio/glocalfilemonitor.h>
+#include <gio/giomodule.h>
+#include "glib-private.h"
+#include <glib-unix.h>
+#include "kqueue_fnm.h"
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+/* Defaults. */
+#ifndef KQUEUE_MON_RATE_LIMIT_TIME_INIT
+# define KQUEUE_MON_RATE_LIMIT_TIME_INIT 1000
+#endif
+
+#ifndef KQUEUE_MON_RATE_LIMIT_TIME_MAX
+# define KQUEUE_MON_RATE_LIMIT_TIME_MAX 4000
+#endif
+#ifndef KQUEUE_MON_RATE_LIMIT_TIME_MUL
+# define KQUEUE_MON_RATE_LIMIT_TIME_MUL 2
+#endif
+#ifndef KQUEUE_MON_MAX_DIR_FILES
+# define KQUEUE_MON_MAX_DIR_FILES 128
+#endif
+#ifndef KQUEUE_MON_LOCAL_SUBFILES
+# define KQUEUE_MON_LOCAL_SUBFILES 1
+#endif
+#ifndef KQUEUE_MON_LOCAL_SUBDIRS
+# define KQUEUE_MON_LOCAL_SUBDIRS 0
+#endif
+
+
+static GMutex kqueue_lock;
+static volatile kq_fnm_p kqueue_fnm = NULL;
+/* Exclude from file changes monitoring, watch only for dirs. */
+static const char *non_local_fs[] = {
+ "fusefs.sshfs",
+ NULL
+};
+
+#define G_TYPE_KQUEUE_FILE_MONITOR (g_kqueue_file_monitor_get_type())
+#define G_KQUEUE_FILE_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST((inst), \
+ G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor))
+
+typedef GLocalFileMonitorClass GKqueueFileMonitorClass;
+
+typedef struct {
+ GLocalFileMonitor parent_instance;
+ kq_fnme_p fnme;
+} GKqueueFileMonitor;
+
+GType g_kqueue_file_monitor_get_type(void);
+G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
+ g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ "kqueue",
+ 10))
+
+
+static void
+kqueue_event_handler(kq_fnm_p kfnm,
+ kq_fnme_p fnme, void *udata, uint32_t event,
+ const char *base, const char *filename, const char *new_filename) {
+ static const uint32_t kfnm_to_glib_map[] = {
+ 0, /* KF_EVENT_NOT_CHANGED */
+ G_FILE_MONITOR_EVENT_CREATED, /* KF_EVENT_CREATED */
+ G_FILE_MONITOR_EVENT_DELETED, /* KF_EVENT_DELETED */
+ G_FILE_MONITOR_EVENT_RENAMED, /* KF_EVENT_RENAMED */
+ G_FILE_MONITOR_EVENT_CHANGED /* KF_EVENT_CHANGED */
+ };
+
+ if (NULL == kfnm || NULL == filename || 0 == filename[0] ||
+ strchr(filename, '/') ||
+ KF_EVENT_CREATED > event ||
+ KF_EVENT_CHANGED < event)
+ return;
+
+ g_file_monitor_source_handle_event(udata,
+ kfnm_to_glib_map[event],
+ filename, new_filename, NULL,
+ g_get_monotonic_time());
+}
+
+static gboolean
+g_kqueue_file_monitor_is_supported(void) {
+ kq_file_mon_settings_t kfms;
+
+ if (NULL != kqueue_fnm)
+ return (TRUE);
+ /* Init only once. */
+ g_mutex_lock(&kqueue_lock);
+ if (NULL != kqueue_fnm) {
+ g_mutex_unlock(&kqueue_lock);
+ return (TRUE); /* Initialized while wait lock. */
+ }
+
+ memset(&kfms, 0x00, sizeof(kq_file_mon_settings_t));
+ kfms.rate_limit_time_init = KQUEUE_MON_RATE_LIMIT_TIME_INIT;
+ kfms.rate_limit_time_max = KQUEUE_MON_RATE_LIMIT_TIME_MAX;
+ kfms.rate_limit_time_mul = KQUEUE_MON_RATE_LIMIT_TIME_MUL;
+ kfms.max_dir_files = KQUEUE_MON_MAX_DIR_FILES;
+ kfms.mon_local_subfiles = KQUEUE_MON_LOCAL_SUBFILES;
+ kfms.mon_local_subdirs = KQUEUE_MON_LOCAL_SUBDIRS;
+ kfms.local_fs = NULL;
+ kfms.non_local_fs = non_local_fs;
+
+ kqueue_fnm = kq_fnm_create(&kfms, kqueue_event_handler);
+ if (NULL == kqueue_fnm) {
+ g_mutex_unlock(&kqueue_lock);
+ return (FALSE); /* Init fail. */
+ }
+ g_mutex_unlock(&kqueue_lock);
+
+ return (TRUE);
+}
+
+static gboolean
+g_kqueue_file_monitor_cancel(GFileMonitor *monitor) {
+ GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(monitor);
+
+ kq_fnm_del(kqueue_fnm, gffm->fnme);
+ gffm->fnme = NULL;
+
+ return (TRUE);
+}
+
+static void
+g_kqueue_file_monitor_finalize(GObject *object) {
+ //GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(object);
+
+ //g_mutex_lock(&kqueue_lock);
+ //kq_fnm_free(kqueue_fnm);
+ //kqueue_fnm = NULL;
+ //g_mutex_unlock(&kqueue_lock);
+ //G_OBJECT_CLASS(g_kqueue_file_monitor_parent_class)->finalize(object);
+}
+
+static void
+g_kqueue_file_monitor_start(GLocalFileMonitor *local_monitor,
+ const gchar *dirname, const gchar *basename,
+ const gchar *filename, GFileMonitorSource *source) {
+ GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(local_monitor);
+
+ g_assert(NULL != kqueue_fnm);
+ //g_source_ref((GSource*)source);
+
+ if (NULL == filename) {
+ filename = dirname;
+ }
+ gffm->fnme = kq_fnm_add(kqueue_fnm, filename, source);
+}
+
+static void
+g_kqueue_file_monitor_init(GKqueueFileMonitor *monitor) {
+
+}
+
+static void
+g_kqueue_file_monitor_class_init(GKqueueFileMonitorClass *class) {
+ GObjectClass *gobject_class = G_OBJECT_CLASS(class);
+ GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS(class);
+
+ class->is_supported = g_kqueue_file_monitor_is_supported;
+ class->start = g_kqueue_file_monitor_start;
+ class->mount_notify = TRUE; /* TODO: ??? */
+ file_monitor_class->cancel = g_kqueue_file_monitor_cancel;
+ gobject_class->finalize = g_kqueue_file_monitor_finalize;
+}
+
+static void
+g_kqueue_file_monitor_class_finalize(GKqueueFileMonitorClass *class) {
+
+}
+
+void
+g_io_module_load(GIOModule *module) {
+
+ g_type_module_use(G_TYPE_MODULE(module));
+
+ g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
+ G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10);
+ g_io_extension_point_implement(G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME,
+ G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10);
+}
+
+void
+g_io_module_unload(GIOModule *module) {
+
+ g_assert_not_reached();
+}
diff --git a/devel/glib20/files/kqueue_fnm.c b/devel/glib20/files/kqueue_fnm.c
new file mode 100644
index 000000000000..8e8cf8e85bfe
--- /dev/null
+++ b/devel/glib20/files/kqueue_fnm.c
@@ -0,0 +1,1431 @@
+/*-
+ * Copyright (c) 2016 - 2019 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#include <sys/fcntl.h> /* open, fcntl */
+#include <sys/queue.h>
+
+#include <inttypes.h>
+#include <stdlib.h> /* malloc, exit */
+#include <unistd.h> /* close, write, sysconf */
+#include <string.h> /* bcopy, bzero, memcpy, memmove, memset, strerror... */
+#include <dirent.h> /* opendir, readdir */
+#include <errno.h>
+#include <pthread.h>
+#include <glib.h>
+
+#include "kqueue_fnm.h"
+
+
+/* Preallocate items count. */
+#ifndef FILES_ALLOC_BLK_SIZE
+# define FILES_ALLOC_BLK_SIZE 32
+#endif
+
+
+typedef struct readdir_context_s {
+ int fd;
+ uint8_t *buf;
+ size_t buf_size;
+ size_t buf_used;
+ size_t buf_pos;
+} readdir_ctx_t, *readdir_ctx_p;
+
+
+typedef struct file_info_s { /* Directory file. */
+ int fd; /* For notify kqueue(). */
+ struct dirent de; /* d_reclen used for action. */
+ struct stat sb;
+} file_info_t, *file_info_p;
+
+
+typedef struct kq_file_nonify_monitor_obj_s *kq_fnmo_p;
+
+typedef struct kq_file_nonify_monitor_entry_s {
+ TAILQ_ENTRY(kq_file_nonify_monitor_entry_s) next;
+ kq_fnmo_p fnmo;
+ void *udata;
+ volatile int enabled;
+} kq_fnme_t;
+
+TAILQ_HEAD(kq_fnme_head, kq_file_nonify_monitor_entry_s);
+
+typedef struct kq_file_nonify_monitor_obj_s {
+ int fd; /* For notify kqueue(). */
+ int is_dir;
+ int is_local; /* Is file system local. */
+ int is_removed; /* File/dir deleted, reported and can be only free. */
+ int is_cached; /* Added to fnmo_cache. */
+ struct stat sb;
+ char path[(PATH_MAX + sizeof(void*))];
+ size_t path_size;
+ size_t name_offset; /* Parent path size. */
+ uint32_t rate_lim_cur_interval; /* From rate_limit_time_init to rate_limit_time_max. 0 disabled. */
+ size_t rate_lim_ev_cnt; /* Events count then rate_lim_cur_interval != 0 since last report. */
+ sbintime_t rate_lim_ev_last; /* Last event time. */
+ void *udata;
+ kq_fnm_p kfnm;
+ struct kq_fnme_head entry_head;
+ /* For dir. */
+ file_info_p files;
+ volatile size_t files_count;
+ size_t files_allocated;
+} kq_fnmo_t;
+
+
+typedef struct kq_file_nonify_monitor_s {
+ int fd; /* kqueue() fd. */
+ int pfd[2]; /* pipe queue specific. */
+ GHashTable *fnmo_cache;
+ struct statfs *mounts;
+ size_t mounts_count;
+ kfnm_event_handler_cb cb_func; /* Callback on dir/file change. */
+ kq_file_mon_settings_t s;
+ sbintime_t rate_lim_time_init; /* rate_limit_time_init */
+ pthread_t tid;
+} kq_fnm_t;
+
+
+typedef void (*kq_msg_cb)(void *arg);
+
+typedef struct kq_file_mon_msg_pkt_s {
+ size_t magic;
+ kq_msg_cb msg_cb;
+ void *arg;
+ size_t chk_sum;
+} kq_fnm_msg_pkt_t, *kq_fnm_msg_pkt_p;
+
+#define KF_MSG_PKT_MAGIC 0xffddaa00
+
+
+#ifndef O_NOATIME
+# define O_NOATIME 0
+#endif
+#ifndef O_EVTONLY
+# define O_EVTONLY O_RDONLY
+#endif
+#define OPEN_FILE_FLAGS (O_EVTONLY | O_NONBLOCK | O_NOFOLLOW | O_NOATIME | O_CLOEXEC)
+
+#ifndef NOTE_CLOSE_WRITE
+# define NOTE_CLOSE_WRITE 0
+#endif
+#define EVFILT_VNODE_SUB_FLAGS (NOTE_WRITE | \
+ NOTE_EXTEND | \
+ NOTE_ATTRIB | \
+ NOTE_LINK | \
+ NOTE_CLOSE_WRITE)
+
+#define EVFILT_VNODE_FLAGS_ALL (NOTE_DELETE | \
+ EVFILT_VNODE_SUB_FLAGS | \
+ NOTE_RENAME | \
+ NOTE_REVOKE)
+
+#ifndef _GENERIC_DIRSIZ
+# define _GENERIC_DIRSIZ(__de) MIN((__de)->d_reclen, sizeof(struct dirent))
+#endif
+
+#define IS_NAME_DOTS(__name) ('.' == (__name)[0] && \
+ ('\0' == (__name)[1] || \
+ ('.' == (__name)[1] && '\0' == (__name)[2])))
+#define IS_DE_NAME_EQ(__de1, __de2) (0 == mem_cmpn((__de1)->d_name, \
+ (__de1)->d_namlen, \
+ (__de2)->d_name, \
+ (__de2)->d_namlen))
+#define zalloc(__size) calloc(1, (__size))
+
+#if (!defined(HAVE_REALLOCARRAY) && (!defined(__FreeBSD_version) || __FreeBSD_version < 1100000))
+# define reallocarray(__mem, __size, __count) realloc((__mem), ((__size) * (__count)))
+#endif
+
+/* To not depend from compiler version. */
+#define MSTOSBT(__ms) ((sbintime_t)((((uint64_t)1 << 32) * (uint64_t)(__ms)) / 1000))
+
+#ifndef TAILQ_FOREACH_SAFE
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+#endif
+
+#ifndef CLOCK_MONOTONIC_FAST
+# define CLOCK_MONOTONIC_FAST CLOCK_MONOTONIC
+#endif
+
+
+void *kq_fnm_proccess_events_proc(void *data);
+
+static inline int
+mem_cmpn(const void *buf1, const size_t buf1_size,
+ const void *buf2, const size_t buf2_size) {
+
+ if (buf1_size != buf2_size)
+ return (((buf1_size > buf2_size) ? 127 : -127));
+ if (0 == buf1_size || buf1 == buf2)
+ return (0);
+ if (NULL == buf1)
+ return (-127);
+ if (NULL == buf2)
+ return (127);
+ return (memcmp(buf1, buf2, buf1_size));
+}
+
+static int
+realloc_items(void **items, const size_t item_size,
+ size_t *allocated, const size_t alloc_blk_cnt, const size_t count) {
+ size_t allocated_prev, allocated_new;
+ uint8_t *items_new;
+
+ if (NULL == items || 0 == item_size || NULL == allocated ||
+ 0 == alloc_blk_cnt)
+ return (EINVAL);
+ allocated_prev = (*allocated);
+ if (NULL != (*items) &&
+ allocated_prev > count &&
+ allocated_prev <= (count + alloc_blk_cnt))
+ return (0);
+ allocated_new = (((count / alloc_blk_cnt) + 1) * alloc_blk_cnt);
+ items_new = (uint8_t*)reallocarray((*items), item_size, allocated_new);
+ if (NULL == items_new) /* Realloc fail! */
+ return (ENOMEM);
+
+ if (allocated_new > allocated_prev) { /* Init new mem. */
+ memset((items_new + (allocated_prev * item_size)), 0x00,
+ ((allocated_new - allocated_prev) * item_size));
+ }
+ (*items) = items_new;
+ (*allocated) = allocated_new;
+
+ return (0);
+}
+
+/* Like getmntinfo() but allow free mem. */
+static int
+getmntinfo_ex(struct statfs **mntbufp, int mode) {
+ int ret;
+ struct statfs *buf;
+
+ if (NULL == mntbufp)
+ return (EINVAL);
+ /* Request count. */
+ ret = getfsstat(NULL, 0, mode);
+ if (-1 == ret)
+ return (ret);
+ /* Alloc mem. */
+ buf = calloc((ret + 1), sizeof(struct statfs));
+ if (NULL == buf)
+ return (-1);
+ /* Request data. */
+ ret = getfsstat(buf, ((ret + 1) * sizeof(struct statfs)), mode);
+ if (-1 == ret) {
+ free(buf);
+ buf = NULL;
+ }
+ (*mntbufp) = buf;
+
+ return (ret);
+}
+
+static int
+mounts_find_name(struct statfs *mounts, size_t mounts_count,
+ const char *mntonname) {
+ size_t i;
+
+ if (NULL == mounts || NULL == mntonname)
+ return (0);
+ for (i = 0; i < mounts_count; i ++) {
+ if (0 != strncmp(mntonname, mounts[i].f_mntonname, MNAMELEN))
+ continue;
+ return (1);
+ }
+ return (0);
+}
+
+
+static int
+readdir_start(int fd, struct stat *sb, size_t exp_count, readdir_ctx_p rdd) {
+ size_t buf_size;
+
+ if (-1 == fd || NULL == sb || NULL == rdd)
+ return (EINVAL);
+ if (-1 == lseek(fd, 0, SEEK_SET))
+ return (errno);
+ /* Calculate buf size for getdents(). */
+ buf_size = MAX((size_t)sb->st_size, (exp_count * sizeof(struct dirent)));
+ if (0 == buf_size) {
+ buf_size = (16 * PAGE_SIZE);
+ }
+ /* Make buf size well aligned. */
+ if (0 != sb->st_blksize) {
+ if (powerof2(sb->st_blksize)) {
+ buf_size = roundup2(buf_size, sb->st_blksize);
+ } else {
+ buf_size = roundup(buf_size, sb->st_blksize);
+ }
+ } else {
+ buf_size = round_page(buf_size);
+ }
+ /* Init. */
+ memset(rdd, 0x00, sizeof(readdir_ctx_t));
+ rdd->buf = malloc(buf_size);
+ if (NULL == rdd->buf)
+ return (ENOMEM);
+ rdd->buf_size = buf_size;
+ rdd->fd = fd;
+
+ return (0);
+}
+
+static void
+readdir_free(readdir_ctx_p rdd) {
+
+ if (NULL == rdd || NULL == rdd->buf)
+ return;
+ free(rdd->buf);
+ memset(rdd, 0x00, sizeof(readdir_ctx_t));
+}
+
+static int
+readdir_next(readdir_ctx_p rdd, struct dirent *de) {
+ int error = 0;
+ ssize_t ios;
+ uint8_t *ptr;
+
+ if (NULL == rdd || NULL == rdd->buf || NULL == de)
+ return (EINVAL);
+
+ for (;;) {
+ if (rdd->buf_used <= rdd->buf_pos) {
+ /* Called once if buf size calculated ok. */
+ ios = getdents(rdd->fd, (char*)rdd->buf, rdd->buf_size);
+ if (-1 == ios) {
+ error = errno;
+ break;
+ }
+ if (0 == ios) {
+ error = ESPIPE; /* EOF. */
+ break;
+ }
+ rdd->buf_used = (size_t)ios;
+ rdd->buf_pos = 0;
+ }
+ /* Keep data aligned. */
+ ptr = (rdd->buf + rdd->buf_pos);
+ memcpy(de, ptr, (sizeof(struct dirent) - sizeof(de->d_name)));
+ if (0 == de->d_reclen) {
+ error = ESPIPE; /* EOF. */
+ break;
+ }
+ rdd->buf_pos += de->d_reclen;
+#ifdef DT_WHT
+ if (DT_WHT == de->d_type)
+ continue;
+#endif
+ if (0 == de->d_namlen)
+ continue; /* Empty. */
+ memcpy(de, ptr, _GENERIC_DIRSIZ(de));
+ if (0 == de->d_name[0])
+ continue; /* Empty. */
+ if (IS_NAME_DOTS(de->d_name))
+ continue; /* Dots. */
+ return (0); /* OK. */
+ }
+
+ /* Err or no more files. */
+ readdir_free(rdd);
+
+ return (error);
+}
+
+
+static int
+file_info_find_ni(file_info_p files, size_t files_count,
+ file_info_p fi, size_t *idx) {
+ size_t i;
+ mode_t st_ftype;
+
+ if (NULL == files || NULL == fi || NULL == idx)
+ return (0);
+ st_ftype = (S_IFMT & fi->sb.st_mode);
+ for (i = 0; i < files_count; i ++) {
+ if ((S_IFMT & files[i].sb.st_mode) != st_ftype)
+ continue;
+ if ((fi->sb.st_ino != files[i].sb.st_ino ||
+ fi->de.d_fileno != files[i].de.d_fileno) &&
+ 0 == IS_DE_NAME_EQ(&fi->de, &files[i].de))
+ continue;
+ (*idx) = i;
+ return (1);
+ }
+ (*idx) = files_count;
+ return (0);
+}
+
+static int
+file_info_find_ino(file_info_p files, size_t files_count,
+ file_info_p fi, size_t *idx) {
+ size_t i;
+ mode_t st_ftype;
+
+ if (NULL == files || NULL == fi || NULL == idx)
+ return (0);
+ st_ftype = (S_IFMT & fi->sb.st_mode);
+ for (i = 0; i < files_count; i ++) {
+ if ((S_IFMT & files[i].sb.st_mode) != st_ftype ||
+ fi->sb.st_ino != files[i].sb.st_ino ||
+ fi->de.d_fileno != files[i].de.d_fileno)
+ continue;
+ (*idx) = i;
+ return (1);
+ }
+ (*idx) = files_count;
+ return (0);
+}
+
+static int
+file_info_find_name(file_info_p files, size_t files_count,
+ file_info_p fi, size_t *idx) {
+ size_t i;
+ mode_t st_ftype;
+
+ if (NULL == files || NULL == fi || NULL == idx)
+ return (0);
+ st_ftype = (S_IFMT & fi->sb.st_mode);
+ for (i = 0; i < files_count; i ++) {
+ if ((S_IFMT & files[i].sb.st_mode) != st_ftype ||
+ 0 == IS_DE_NAME_EQ(&fi->de, &files[i].de))
+ continue;
+ (*idx) = i;
+ return (1);
+ }
+ (*idx) = files_count;
+ return (0);
+}
+
+static void
+file_info_fd_close(file_info_p files, size_t files_count) {
+ size_t i;
+
+ if (NULL == files || 0 == files_count)
+ return;
+ for (i = 0; i < files_count; i ++) {
+ if (-1 == files[i].fd)
+ continue;
+ close(files[i].fd);
+ files[i].fd = -1;
+ }
+}
+
+
+static int
+is_fs_local(struct statfs *stfs, const char **local_fs, const char **non_local_fs) {
+ size_t i;
+
+ if (NULL == stfs)
+ return (0);
+ /* White listed fs. */
+ if (NULL != local_fs) {
+ for (i = 0; NULL != local_fs[i]; i ++) {
+ if (0 == strncmp(stfs->f_fstypename, local_fs[i],
+ sizeof(stfs->f_fstypename)))
+ return (1);
+ }
+ }
+ if (0 == (MNT_LOCAL & stfs->f_flags))
+ return (0);
+ /* Filter out black listed fs. */
+ if (NULL != non_local_fs) {
+ for (i = 0; NULL != non_local_fs[i]; i ++) {
+ if (0 == strncmp(stfs->f_fstypename, non_local_fs[i],
+ sizeof(stfs->f_fstypename)))
+ return (0);
+ }
+ }
+ return (1);
+}
+
+
+static void
+kq_fnmo_rate_lim_stop(kq_fnmo_p fnmo) {
+ struct kevent kev;
+
+ if (NULL == fnmo || 0 == fnmo->rate_lim_cur_interval)
+ return;
+ fnmo->rate_lim_cur_interval = 0;
+ fnmo->rate_lim_ev_cnt = 0;
+ EV_SET(&kev, (uintptr_t)fnmo, EVFILT_TIMER,
+ (EV_DELETE | EV_CLEAR), 0, 0, NULL);
+ kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL);
+}
+
+static int
+kq_fnmo_rate_lim_shedule_next(kq_fnmo_p fnmo) {
+ u_short flags = (EV_ADD | EV_CLEAR | EV_ONESHOT);
+ struct kevent kev;
+
+ if (NULL == fnmo || -1 == fnmo->fd ||
+ 0 == fnmo->kfnm->s.rate_limit_time_init)
+ return (EINVAL);
+ if (0 == fnmo->rate_lim_cur_interval) { /* First call. */
+ fnmo->rate_lim_cur_interval = fnmo->kfnm->s.rate_limit_time_init;
+ } else {
+ if (fnmo->rate_lim_cur_interval == fnmo->kfnm->s.rate_limit_time_max)
+ return (0); /* No need to modify timer. */
+ /* Increase rate limit interval. */
+ fnmo->rate_lim_cur_interval *= fnmo->kfnm->s.rate_limit_time_mul;
+ }
+ if (fnmo->rate_lim_cur_interval >= fnmo->kfnm->s.rate_limit_time_max) {
+ /* Check upper limit and shedule periodic timer with upper rate limit time. */
+ flags &= ~EV_ONESHOT;
+ fnmo->rate_lim_cur_interval = fnmo->kfnm->s.rate_limit_time_max;
+ }
+ /* Setup timer. */
+ EV_SET(&kev, (uintptr_t)fnmo, EVFILT_TIMER, flags,
+ NOTE_MSECONDS, fnmo->rate_lim_cur_interval, fnmo);
+ if (-1 == kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL)) {
+ fnmo->rate_lim_cur_interval = 0;
+ return (errno);
+ }
+ if (0 != (EV_ERROR & kev.flags)) {
+ fnmo->rate_lim_cur_interval = 0;
+ return ((int)kev.data);
+ }
+ return (0);
+}
+
+/* Return:
+ * 0 for events that not handled
+ * 1 for handled = rate limited
+ * -1 on error.
+ */
+static int
+kq_fnmo_rate_lim_check(kq_fnmo_p fnmo) {
+ sbintime_t sbt, sbt_now;
+ struct timespec ts;
+
+ if (NULL == fnmo)
+ return (-1);
+ if (-1 == fnmo->fd ||
+ 0 == fnmo->kfnm->s.rate_limit_time_init)
+ return (0);
+ if (0 != fnmo->rate_lim_cur_interval) {
+ fnmo->rate_lim_ev_cnt ++; /* Count event, timer is active. */
+ return (1);
+ }
+
+ /* Do we need to enable rate limit? */
+ if (0 != clock_gettime(CLOCK_MONOTONIC_FAST, &ts))
+ return (-1);
+ sbt_now = tstosbt(ts);
+ sbt = (fnmo->rate_lim_ev_last + fnmo->kfnm->rate_lim_time_init);
+ fnmo->rate_lim_ev_last = sbt_now;
+ if (sbt < sbt_now) /* Events rate to low. */
+ return (0);
+ /* Try to enable rate limit. */
+ if (0 != kq_fnmo_rate_lim_shedule_next(fnmo))
+ return (-1);
+ /* Ok. */
+ fnmo->rate_lim_ev_cnt ++;
+
+ return (1);
+}
+
+static void
+kq_fnmo_clean(kq_fnmo_p fnmo) {
+
+ if (NULL == fnmo)
+ return;
+
+ kq_fnmo_rate_lim_stop(fnmo);
+ if (-1 != fnmo->fd) {
+ close(fnmo->fd);
+ fnmo->fd = -1;
+ }
+ fnmo->is_removed ++;
+
+ /* Stop monitoring files/dirs. */
+ file_info_fd_close(fnmo->files, fnmo->files_count);
+ free(fnmo->files);
+ fnmo->files = NULL;
+ fnmo->files_count = 0;
+ fnmo->files_allocated = 0;
+}
+
+static void
+kq_fnmo_free(kq_fnmo_p fnmo) {
+
+ if (NULL == fnmo)
+ return;
+
+ kq_fnmo_clean(fnmo);
+
+ if (0 != fnmo->is_cached &&
+ NULL != fnmo->kfnm &&
+ g_hash_table_lookup(fnmo->kfnm->fnmo_cache, fnmo->path) == fnmo) {
+ g_hash_table_remove(fnmo->kfnm->fnmo_cache, fnmo->path);
+ }
+ free(fnmo);
+}
+
+static kq_fnmo_p
+kq_fnmo_alloc(kq_fnm_p kfnm, const char *path, kq_fnme_p fnme) {
+ kq_fnmo_p fnmo;
+
+ if (NULL == kfnm || NULL == path)
+ return (NULL);
+ fnmo = zalloc(sizeof(kq_fnmo_t));
+ if (NULL == fnmo)
+ return (NULL);
+ fnmo->fd = -1;
+ /* Remember args. */
+ fnmo->path_size = strlcpy(fnmo->path, path, PATH_MAX);
+ /* Make sure that no trailing '/'. */
+ while (1 < fnmo->path_size && '/' == fnmo->path[(fnmo->path_size - 1)]) {
+ fnmo->path_size --;
+ fnmo->path[fnmo->path_size] = 0;
+ }
+ /* Get parent folder name. */
+ fnmo->name_offset = fnmo->path_size;
+ while (0 < fnmo->name_offset && '/' != fnmo->path[(fnmo->name_offset - 1)]) {
+ fnmo->name_offset --;
+ }
+ fnmo->kfnm = kfnm;
+ TAILQ_INIT(&fnmo->entry_head);
+ if (NULL != fnme) {
+ TAILQ_INSERT_HEAD(&fnmo->entry_head, fnme, next);
+ }
+
+ return (fnmo);
+}
+
+static void
+kq_fnmo_cb_func_call(kq_fnmo_p fnmo, uint32_t event,
+ const char *base, const char *filename, const char *new_filename) {
+ kq_fnm_p kfnm;
+ kq_fnme_p fnme;
+
+ if (NULL == fnmo)
+ return;
+ kfnm = fnmo->kfnm;
+ TAILQ_FOREACH(fnme, &fnmo->entry_head, next) {
+ if (0 == fnme->enabled) /* XXX: try lock here? */
+ continue;
+ kfnm->cb_func(kfnm, fnme, fnme->udata, event,
+ base, filename, new_filename);
+ }
+
+}
+
+static int
+kq_fnmo_readdir(kq_fnmo_p fnmo, size_t exp_count) {
+ int error;
+ struct dirent *de;
+ file_info_p tmfi;
+ readdir_ctx_t rdd;
+
+ if (NULL == fnmo || -1 == fnmo->fd || 0 == fnmo->is_dir)
+ return (EINVAL);
+
+ free(fnmo->files);
+ fnmo->files = NULL;
+ fnmo->files_count = 0;
+ fnmo->files_allocated = 0;
+ /* Pre allocate. */
+ if (0 != realloc_items((void**)&fnmo->files,
+ sizeof(file_info_t), &fnmo->files_allocated,
+ FILES_ALLOC_BLK_SIZE, (exp_count + 1)))
+ return (ENOMEM);
+
+ error = readdir_start(fnmo->fd, &fnmo->sb, exp_count, &rdd);
+ if (0 != error)
+ return (error);
+ for (;;) {
+ if (0 != realloc_items((void**)&fnmo->files,
+ sizeof(file_info_t), &fnmo->files_allocated,
+ FILES_ALLOC_BLK_SIZE, fnmo->files_count)) {
+ free(fnmo->files);
+ fnmo->files = NULL;
+ fnmo->files_count = 0;
+ fnmo->files_allocated = 0;
+ readdir_free(&rdd);
+ return (ENOMEM);
+ }
+ de = &fnmo->files[fnmo->files_count].de; /* Use short name. */
+ /* Get file name from folder. */
+ if (0 != readdir_next(&rdd, de))
+ break;
+ /* Get file attrs. */
+ if (0 != fstatat(fnmo->fd, de->d_name,
+ &fnmo->files[fnmo->files_count].sb,
+ AT_SYMLINK_NOFOLLOW)) {
+ if (ENOENT == errno)
+ continue; /* File deleted. */
+ memset(&fnmo->files[fnmo->files_count].sb, 0x00,
+ sizeof(struct stat));
+ }
+ fnmo->files[fnmo->files_count].fd = -1;
+ fnmo->files_count ++;
+ }
+ /* Mem compact. */
+ tmfi = reallocarray(fnmo->files, sizeof(file_info_t), (fnmo->files_count + 1));
+ if (NULL != tmfi) { /* realloc ok. */
+ fnmo->files = tmfi;
+ fnmo->files_allocated = (fnmo->files_count + 1);
+ }
+
+ readdir_free(&rdd);
+
+ return (0); /* OK. */
+}
+
+
+static void
+kq_fnmo_fi_start(kq_fnmo_p fnmo, file_info_p fi) {
+ struct kevent kev;
+
+ if (NULL == fnmo || -1 == fnmo->fd || NULL == fi)
+ return;
+ fi->fd = openat(fnmo->fd, fi->de.d_name, OPEN_FILE_FLAGS);
+ if (-1 == fi->fd)
+ return;
+ EV_SET(&kev, fi->fd, EVFILT_VNODE,
+ (EV_ADD | EV_CLEAR),
+ EVFILT_VNODE_SUB_FLAGS, 0, fnmo);
+ kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL);
+}
+
+static int
+kq_fnmo_is_fi_monitored(kq_fnmo_p fnmo, file_info_p fi) {
+
+ if (NULL == fnmo)
+ return (0);
+ if (0 == fnmo->is_local ||
+ (0 != fnmo->kfnm->s.max_dir_files &&
+ fnmo->kfnm->s.max_dir_files < fnmo->files_count))
+ return (0);
+ if (NULL != fi &&
+ 0 == fnmo->kfnm->s.mon_local_subdirs &&
+ S_ISDIR(fi->sb.st_mode))
+ return (0);
+ return (1);
+}
+
+static int
+kq_fnmo_reopen_fd(kq_fnmo_p fnmo) {
+ int error, fd;
+ struct statfs stfs;
+ struct kevent kev;
+
+ if (NULL == fnmo)
+ return (EINVAL);
+
+ /* Close fd and stop timer. */
+ kq_fnmo_rate_lim_stop(fnmo);
+ if (-1 != fnmo->fd) {
+ close(fnmo->fd);
+ fnmo->fd = -1;
+ }
+
+ fd = open(fnmo->path, OPEN_FILE_FLAGS);
+ if (-1 == fd)
+ return (errno);
+ if (0 != fstat(fd, &fnmo->sb)) {
+ error = errno;
+ goto err_out;
+ }
+ if (0 == fnmo->sb.st_nlink) {
+ error = ENOENT;
+ goto err_out;
+ }
+ if (S_ISDIR(fnmo->sb.st_mode)) {
+ fnmo->is_dir = 1;
+ /* Is file system local? */
+ if (0 != fnmo->kfnm->s.mon_local_subfiles &&
+ 0 == fstatfs(fd, &stfs)) {
+ fnmo->is_local = is_fs_local(&stfs, fnmo->kfnm->s.local_fs,
+ fnmo->kfnm->s.non_local_fs);
+ }
+ }
+
+ /* Add to kqueue. */
+ EV_SET(&kev, fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR),
+ EVFILT_VNODE_FLAGS_ALL, 0, fnmo);
+ if (-1 == kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL) ||
+ 0 != (EV_ERROR & kev.flags)) {
+ error = errno;
+ goto err_out;
+ }
+
+ fnmo->is_removed = 0;
+ fnmo->fd = fd;
+
+ return (0); /* OK. */
+
+err_out:
+ close(fd);
+ return (error);
+}
+
+static int
+kq_fnmo_init(kq_fnmo_p fnmo) {
+ int error;
+ size_t i;
+
+ if (NULL == fnmo)
+ return (EINVAL);
+
+ error = kq_fnmo_reopen_fd(fnmo);
+ if (0 != error)
+ goto err_out;
+
+ if (0 == fnmo->is_dir)
+ return (0); /* OK. */
+ /* Dir special processing. */
+
+ /* Read and remember dir content. */
+ error = kq_fnmo_readdir(fnmo, 0);
+ if (0 != error)
+ goto err_out;
+ /* Add monitor sub files/dirs, ignory errors. */
+ /* Check twice for performance reason. */
+ if (0 != kq_fnmo_is_fi_monitored(fnmo, NULL)) {
+ for (i = 0; i < fnmo->files_count; i ++) {
+ if (0 != kq_fnmo_is_fi_monitored(fnmo, &fnmo->files[i])) {
+ kq_fnmo_fi_start(fnmo, &fnmo->files[i]);
+ }
+ }
+ }
+
+ return (0); /* OK. */
+
+err_out:
+ kq_fnmo_clean(fnmo);
+ return (error);
+}
+
+static void
+kq_fnme_init_cb(void *arg) {
+ kq_fnme_p fnme = arg;
+ kq_fnmo_p fnmo;
+ kq_fnm_p kfnm;
+
+ if (NULL == fnme || NULL == fnme->fnmo)
+ return;
+ kfnm = fnme->fnmo->kfnm;
+ /* Is in cache? */
+ fnmo = g_hash_table_lookup(kfnm->fnmo_cache, fnme->fnmo->path);
+ if (NULL == fnmo) {
+ /* Init and add to cache. */
+ g_hash_table_insert(kfnm->fnmo_cache,
+ fnme->fnmo->path, fnme->fnmo);
+ fnme->fnmo->is_cached ++;
+ kq_fnmo_init(fnme->fnmo);
+ return;
+ }
+ /* Found in cache, use it. */
+ TAILQ_REMOVE(&fnme->fnmo->entry_head, fnme, next);
+ kq_fnmo_free(fnme->fnmo);
+ fnme->fnmo = fnmo;
+ TAILQ_INSERT_HEAD(&fnmo->entry_head, fnme, next);
+ if (-1 == fnmo->fd) {
+ /* Re init existing, no notify. */
+ kq_fnmo_init(fnme->fnmo);
+ }
+}
+
+static void
+kq_fnme_free(kq_fnme_p fnme) {
+
+ if (NULL == fnme)
+ return;
+ if (NULL != fnme->fnmo) {
+ TAILQ_REMOVE(&fnme->fnmo->entry_head, fnme, next);
+ if (TAILQ_EMPTY(&fnme->fnmo->entry_head)) {
+ kq_fnmo_free(fnme->fnmo);
+ }
+ }
+ free(fnme);
+}
+
+static void
+kq_fnme_free_cb(void *arg) {
+
+ kq_fnme_free((kq_fnme_p)arg);
+}
+
+
+static void
+kq_handle_notify_removed(kq_fnmo_p fnmo) {
+ size_t i;
+
+ if (NULL == fnmo || 0 != fnmo->is_removed)
+ return;
+
+ if (0 != fnmo->is_dir) {
+ /* Notify removed files first. */
+ for (i = 0; i < fnmo->files_count; i ++) {
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_DELETED,
+ fnmo->path, fnmo->files[i].de.d_name, NULL);
+ }
+ }
+ fnmo->path[fnmo->name_offset - 1] = 0;
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_DELETED, fnmo->path,
+ (fnmo->path + fnmo->name_offset), NULL);
+ fnmo->path[fnmo->name_offset - 1] = '/';
+ kq_fnmo_clean(fnmo);
+}
+
+static void
+kq_handle_changes(kq_fnmo_p fnmo) {
+ size_t i, k, files_count;
+ file_info_p files;
+
+ if (NULL == fnmo || -1 == fnmo->fd)
+ return;
+ if (0 != fstat(fnmo->fd, &fnmo->sb) ||
+ 0 == fnmo->sb.st_nlink) {
+ kq_handle_notify_removed(fnmo);
+ return;
+ }
+ if (0 == fnmo->is_dir) {
+ fnmo->path[fnmo->name_offset - 1] = 0;
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_CHANGED, fnmo->path,
+ (fnmo->path + fnmo->name_offset), NULL);
+ fnmo->path[fnmo->name_offset - 1] = '/';
+ return;
+ }
+
+ /* Dir processing. */
+
+ /* Save prev. */
+ files = fnmo->files;
+ files_count = fnmo->files_count;
+ fnmo->files = NULL;
+ fnmo->files_count = 0;
+ /* Update dir. */
+ if (0 != kq_fnmo_readdir(fnmo, files_count)) {
+ /* Restore prev state on fail. */
+ fnmo->files = files;
+ fnmo->files_count = files_count;
+ return;
+ }
+
+ /* Notify removed first. */
+ for (i = 0; i < files_count; i ++) {
+ if (0 != file_info_find_ni(fnmo->files, fnmo->files_count,
+ &files[i], &k)) /* Deleted? */
+ continue;
+ if (-1 != files[i].fd) {
+ close(files[i].fd);
+ files[i].fd = -1;
+ }
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_DELETED, fnmo->path,
+ files[i].de.d_name, NULL);
+ }
+ /* Notify. */
+ for (i = 0; i < fnmo->files_count; i ++) {
+ /* Is new file/folder? */
+ if (0 == file_info_find_ino(files, files_count, &fnmo->files[i], &k) &&
+ 0 == file_info_find_name(files, files_count, &fnmo->files[i], &k)) { /* Add new. */
+ /* Add monitor sub files/dirs, ignory errors. */
+ if (0 != kq_fnmo_is_fi_monitored(fnmo, &fnmo->files[i])) {
+ kq_fnmo_fi_start(fnmo, &fnmo->files[i]);
+ }
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_CREATED,
+ fnmo->path, fnmo->files[i].de.d_name, NULL);
+ continue;
+ }
+ /* Keep file fd. */
+ fnmo->files[i].fd = files[k].fd;
+ files[k].fd = -1;
+ /* Is renamed? */
+ if (0 == IS_DE_NAME_EQ(&files[k].de, &fnmo->files[i].de)) {
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_RENAMED,
+ fnmo->path, files[k].de.d_name,
+ fnmo->files[i].de.d_name);
+ continue;
+ }
+ /* Is modified? */
+ if (0 != memcmp(&fnmo->files[i].sb, &files[k].sb, sizeof(struct stat))) {
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_CHANGED,
+ fnmo->path, fnmo->files[i].de.d_name, NULL);
+ continue;
+ }
+ /* Not changed. */
+ }
+
+ /* Prevent FD leak die to race conditions.
+ * All fd must be -1, check this while debuging.
+ */
+ file_info_fd_close(files, files_count);
+ free(files);
+}
+
+static int
+kq_handle_rename(kq_fnmo_p fnmo) {
+ int error, up_dir_fd, found = 0;
+ readdir_ctx_t rdd;
+ struct dirent de;
+ struct stat sb;
+ char old_filename[(MAXNAMLEN + sizeof(void*))];
+ size_t old_filename_size;
+
+ if (NULL == fnmo || -1 == fnmo->fd)
+ return (EINVAL);
+ if (0 != fstat(fnmo->fd, &fnmo->sb) ||
+ 0 == fnmo->sb.st_nlink) {
+notify_removed_errno:
+ error = errno;
+notify_removed:
+ kq_handle_notify_removed(fnmo);
+ return (error);
+ }
+ /* Save old file name. */
+ old_filename_size = (fnmo->path_size - fnmo->name_offset);
+ memcpy(old_filename,
+ (fnmo->path + fnmo->name_offset),
+ old_filename_size);
+ old_filename[old_filename_size] = 0;
+
+ /* Get parent folder name. */
+ fnmo->path[fnmo->name_offset] = 0;
+ /* Try to open. */
+ up_dir_fd = open(fnmo->path, (OPEN_FILE_FLAGS | O_DIRECTORY));
+ /* Restore '/' after parent folder. */
+ fnmo->path[fnmo->name_offset] = '/';
+ if (-1 == up_dir_fd)
+ goto notify_removed_errno;
+ if (0 != fstat(up_dir_fd, &sb)) {
+ close(up_dir_fd);
+ goto notify_removed_errno;
+ }
+ error = readdir_start(up_dir_fd, &sb, 0, &rdd);
+ if (0 != error) {
+ close(up_dir_fd);
+ goto notify_removed;
+ }
+ /* Find new name by inode. */
+ while (0 == readdir_next(&rdd, &de)) {
+ if (0 == fstatat(up_dir_fd, de.d_name, &sb, AT_SYMLINK_NOFOLLOW) &&
+ 0 == memcmp(&fnmo->sb, &sb, sizeof(struct stat))) {
+ found ++;
+ break;
+ }
+ }
+ close(up_dir_fd);
+ if (0 == found) {
+ error = ENOENT;
+ goto notify_removed; /* Not found. */
+ }
+ /* Update name. */
+ if (PATH_MAX < (fnmo->name_offset + de.d_namlen)) {
+ error = EINVAL;
+ goto notify_removed;
+ }
+ memcpy((fnmo->path + fnmo->name_offset), de.d_name, de.d_namlen);
+ fnmo->path_size = (fnmo->name_offset + de.d_namlen);
+ fnmo->path[fnmo->path_size] = 0;
+ /* Notify. */
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_RENAMED, fnmo->path,
+ old_filename, de.d_name);
+
+ return (0);
+}
+
+
+static void
+kq_fnm_delay_call_process(kq_fnm_p kfnm, kq_msg_cb forced_msg_cb) {
+ ssize_t ios;
+ kq_fnm_msg_pkt_t msg;
+
+ for (;;) {
+ ios = read(kfnm->pfd[0], &msg, sizeof(msg));
+ if (0 >= ios)
+ return;
+ if (sizeof(msg) != ios ||
+ KF_MSG_PKT_MAGIC != msg.magic ||
+ (((size_t)msg.msg_cb) ^ ((size_t)msg.arg)) != msg.chk_sum)
+ continue;
+ if (NULL != forced_msg_cb) {
+ forced_msg_cb(msg.arg);
+ continue;
+ }
+ if (NULL == msg.msg_cb)
+ continue;
+ msg.msg_cb(msg.arg);
+ }
+}
+
+static int
+kq_fnm_delay_call(kq_fnm_p kfnm, kq_msg_cb msg_cb,
+ void *arg) {
+ kq_fnm_msg_pkt_t msg;
+
+ if (NULL == kfnm || NULL == arg)
+ return (EINVAL);
+ msg.magic = KF_MSG_PKT_MAGIC;
+ msg.msg_cb = msg_cb;
+ msg.arg = arg;
+ msg.chk_sum = (((size_t)msg.msg_cb) ^ ((size_t)msg.arg));
+ if (sizeof(msg) == write(kfnm->pfd[1], &msg, sizeof(msg)))
+ return (0);
+ return (errno);
+}
+
+
+static void
+kq_fnm_free_cb(void *arg) {
+ kq_fnm_p kfnm = arg;
+
+ if (NULL == kfnm)
+ return;
+ close(kfnm->fd);
+ kfnm->fd = -1;
+}
+void
+kq_fnm_free(kq_fnm_p kfnm) {
+ GHashTableIter iter;
+ gpointer key;
+ kq_fnmo_p fnmo;
+ kq_fnme_p fnme, fnme_temp;
+
+ if (NULL == kfnm)
+ return;
+ kq_fnm_delay_call(kfnm, kq_fnm_free_cb, kfnm);
+ pthread_join(kfnm->tid, NULL);
+ /* Free all in delay calls queue. */
+ close(kfnm->pfd[1]);
+ kq_fnm_delay_call_process(kfnm, kq_fnme_free_cb);
+ close(kfnm->pfd[0]);
+ /* Remove all objects. */
+ g_hash_table_iter_init(&iter, kfnm->fnmo_cache);
+ while (g_hash_table_iter_next(&iter, &key, (gpointer)&fnmo)) {
+ TAILQ_FOREACH_SAFE(fnme, &fnmo->entry_head, next, fnme_temp) {
+ TAILQ_REMOVE(&fnmo->entry_head, fnme, next);
+ fnme->fnmo = NULL; /* Do not free fnmo here. */
+ kq_fnme_free(fnme);
+ }
+ g_hash_table_iter_remove(&iter); /* Remove from cache here. */
+ /* Prevent remove from cache or g_hash_table_iter_next() will fail. */
+ fnmo->is_cached = 0;
+ kq_fnmo_free(fnmo);
+ }
+ g_hash_table_destroy(kfnm->fnmo_cache);
+
+ free(kfnm->mounts);
+ free(kfnm);
+}
+
+kq_fnm_p
+kq_fnm_create(kq_file_mon_settings_p s, kfnm_event_handler_cb cb_func) {
+ kq_fnm_p kfnm;
+ struct kevent kev;
+
+ if (NULL == s || NULL == cb_func)
+ return (NULL);
+ kfnm = zalloc(sizeof(kq_fnm_t));
+ if (NULL == kfnm)
+ return (NULL);
+ kfnm->fd = kqueue();
+ if (-1 == kfnm->fd)
+ goto err_out;
+ if (-1 == pipe2(kfnm->pfd, O_NONBLOCK))
+ goto err_out;
+ kfnm->fnmo_cache = g_hash_table_new(g_str_hash, g_str_equal);
+ kfnm->cb_func = cb_func;
+ memcpy(&kfnm->s, s, sizeof(kq_file_mon_settings_t));
+ if (kfnm->s.rate_limit_time_init >= kfnm->s.rate_limit_time_max) {
+ kfnm->s.rate_limit_time_max = kfnm->s.rate_limit_time_init;
+ }
+ if (0 == kfnm->s.rate_limit_time_mul) {
+ kfnm->s.rate_limit_time_mul ++;
+ }
+ kfnm->rate_lim_time_init = MSTOSBT(kfnm->s.rate_limit_time_init);
+
+ EV_SET(&kev, kfnm->pfd[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
+ if (-1 == kevent(kfnm->fd, &kev, 1, NULL, 0, NULL) ||
+ 0 != (EV_ERROR & kev.flags))
+ goto err_out;
+ EV_SET(&kev, 0, EVFILT_FS, EV_ADD, 0, 0, NULL);
+ if (-1 == kevent(kfnm->fd, &kev, 1, NULL, 0, NULL) ||
+ 0 != (EV_ERROR & kev.flags))
+ goto err_out;
+ /* Get initial mounts points. */
+ kfnm->mounts_count = (size_t)getmntinfo_ex(&kfnm->mounts, MNT_WAIT);
+
+ if (0 != pthread_create(&kfnm->tid, NULL,
+ kq_fnm_proccess_events_proc, kfnm))
+ goto err_out;
+
+ return (kfnm);
+
+err_out:
+ kq_fnm_free(kfnm);
+ return (NULL);
+}
+
+kq_fnme_p
+kq_fnm_add(kq_fnm_p kfnm, const char *path, void *udata) {
+ int error;
+ kq_fnme_p fnme;
+
+ if (NULL == kfnm || NULL == path)
+ return (NULL);
+ fnme = zalloc(sizeof(kq_fnme_t));
+ if (NULL == fnme)
+ return (NULL);
+ fnme->fnmo = kq_fnmo_alloc(kfnm, path, fnme);
+ if (NULL == fnme->fnmo)
+ goto err_out;
+ fnme->udata = udata;
+ fnme->enabled = 1;
+ /* Shedule delay call to init. */
+ error = kq_fnm_delay_call(kfnm, kq_fnme_init_cb, fnme);
+ if (0 != error) { /* Error, do no directly init to avoid freezes. */
+ kq_fnmo_free(fnme->fnmo);
+err_out:
+ fnme->fnmo = NULL;
+ kq_fnme_free(fnme);
+ return (NULL);
+ }
+ return (fnme);
+}
+
+void
+kq_fnm_del(kq_fnm_p kfnm, kq_fnme_p fnme) {
+ int error;
+
+ if (NULL == kfnm || NULL == fnme)
+ return;
+ /* Cancel notifications. */
+ /* XXX: lock here? */
+ fnme->enabled = 0;
+ /* Shedule delay call to free. */
+ error = kq_fnm_delay_call(kfnm, kq_fnme_free_cb, fnme);
+ if (0 == error)
+ return;
+ /* Error, free directly. */
+ kq_fnme_free(fnme);
+}
+
+
+static void
+kq_fnm_handle_mnt_point_change(kq_fnm_p kfnm, const char *mntonname) {
+ int error;
+ GHashTableIter iter;
+ gpointer key;
+ kq_fnmo_p fnmo;
+ size_t mntonname_size;
+
+ if (NULL == kfnm || NULL == mntonname || 0 == mntonname[0])
+ return;
+ mntonname_size = strnlen(mntonname, MNAMELEN);
+ /* Remove all objects. */
+ g_hash_table_iter_init(&iter, kfnm->fnmo_cache);
+ while (g_hash_table_iter_next(&iter, &key, (gpointer)&fnmo)) {
+ if (NULL == fnmo || -1 == fnmo->fd) /* Do not reopen deleted. */
+ continue;
+ if (mntonname_size > fnmo->path_size)
+ continue;
+ if (0 != memcmp(fnmo->path, mntonname, mntonname_size))
+ continue;
+ /* Try to reopen mount point after unmount. */
+ error = kq_fnmo_reopen_fd(fnmo);
+ if (0 != error) {
+ kq_handle_notify_removed(fnmo);
+ continue;
+ }
+ /* Reread dir and notify about deleted and new files. */
+ kq_handle_changes(fnmo);
+ }
+}
+static void
+kq_handle_mount_changes(kq_fnm_p kfnm) {
+ size_t i, mounts_count;
+ struct statfs *mounts;
+
+ if (NULL == kfnm)
+ return;
+
+ /* Save prev. */
+ mounts = kfnm->mounts;
+ mounts_count = kfnm->mounts_count;
+ kfnm->mounts = NULL;
+ kfnm->mounts_count = (size_t)getmntinfo_ex(&kfnm->mounts, MNT_WAIT);
+
+ /* Removed mounts. */
+ for (i = 0; i < mounts_count; i ++) {
+ if (0 != mounts_find_name(kfnm->mounts, kfnm->mounts_count,
+ mounts[i].f_mntonname))
+ continue;
+ kq_fnm_handle_mnt_point_change(kfnm, mounts[i].f_mntonname);
+ }
+ /* New mounts. */
+ for (i = 0; i < kfnm->mounts_count; i ++) {
+ if (0 != mounts_find_name(mounts, mounts_count,
+ kfnm->mounts[i].f_mntonname))
+ continue;
+ kq_fnm_handle_mnt_point_change(kfnm, kfnm->mounts[i].f_mntonname);
+ }
+
+ /* Free old mounts. */
+ free(mounts);
+}
+
+
+static void
+kq_fnm_proccess_event(kq_fnm_p kfnm, struct kevent *kev) {
+ int error;
+ kq_fnmo_p fnmo;
+ file_info_p fi;
+ size_t i;
+ int is_rate_lim_checked = 0;
+ struct stat sb;
+
+ if (NULL == kfnm || NULL == kev)
+ return;
+
+ /* Handle delay calls. */
+ if (kev->ident == (uintptr_t)kfnm->pfd[0]) {
+ if (EVFILT_READ == kev->filter) {
+ kq_fnm_delay_call_process(kfnm, NULL);
+ }
+ return;
+ }
+
+ /* Handle mount/unmount events. */
+ if (EVFILT_FS == kev->filter) {
+ kq_handle_mount_changes(kfnm);
+ return;
+ }
+
+ if (0 == kev->udata)
+ return; /* No associated data, skip. */
+ fnmo = (kq_fnmo_p)kev->udata;
+
+ /* FS delayed notifications. */
+ if (EVFILT_TIMER == kev->filter) {
+ if (0 == fnmo->rate_lim_ev_cnt) {
+ /* No delayed events, disable rate limit polling. */
+ kq_fnmo_rate_lim_stop(fnmo);
+ return;
+ }
+ fnmo->rate_lim_ev_cnt = 0; /* Reset counter. */
+ kq_fnmo_rate_lim_shedule_next(fnmo);
+ kq_handle_changes(fnmo);
+ return;
+ }
+
+ /* FS notifications. */
+ if (EVFILT_VNODE != kev->filter)
+ return; /* Unknown event, skip. */
+ /* Subdir/file */
+ if (kev->ident != (uintptr_t)fnmo->fd) {
+ /* Is files changes rate limited? */
+ if (1 == kq_fnmo_rate_lim_check(fnmo))
+ return;
+ is_rate_lim_checked ++;
+ /* Try to find file and check it, without call kq_handle_changes(). */
+ fi = NULL;
+ for (i = 0; i < fnmo->files_count; i ++) {
+ if (kev->ident != (uintptr_t)fnmo->files[i].fd)
+ continue;
+ fi = &fnmo->files[i];
+ break;
+ }
+ if (NULL != fi) {
+ /* Get file attrs. */
+ if (0 != fstat(fi->fd, &sb)) {
+ memset(&sb, 0x00, sizeof(struct stat));
+ }
+ /* Is modified? */
+ if (0 != memcmp(&fi->sb, &sb, sizeof(struct stat))) {
+ memcpy(&fi->sb, &sb, sizeof(struct stat));
+ kq_fnmo_cb_func_call(fnmo, KF_EVENT_CHANGED,
+ fnmo->path, fi->de.d_name, NULL);
+ return;
+ }
+ }
+ /* fd not found or changes not found, rescan dir. */
+ kev->fflags = NOTE_WRITE;
+ }
+ /* Monitored object. */
+ /* All flags from EVFILT_VNODE_FLAGS_ALL must be handled here. */
+ if (EV_ERROR & kev->flags) {
+ kev->fflags |= NOTE_REVOKE; /* Treat error as unmount. */
+ }
+ if ((NOTE_DELETE | NOTE_REVOKE) & kev->fflags) {
+ /* No ratelimit here. */
+ if (0 != fnmo->is_dir) {
+ /* Try to reopen mount point after unmount. */
+ error = kq_fnmo_reopen_fd(fnmo);
+ if (0 == error) {
+ /* This will reread dir and notify about
+ * deleted and new files. */
+ kq_handle_changes(fnmo);
+ }
+ } else {
+ error = -1;
+ }
+ if (0 != error) {
+ kq_handle_notify_removed(fnmo);
+ }
+ return; /* All events processed. */
+ }
+ if (NOTE_RENAME & kev->fflags) {
+ error = kq_handle_rename(fnmo);
+ if (0 != error)
+ return;
+ }
+ if ((NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_CLOSE_WRITE) & kev->fflags) {
+ if (0 == is_rate_lim_checked &&
+ 1 != kq_fnmo_rate_lim_check(fnmo)) {
+ kq_handle_changes(fnmo);
+ }
+ }
+}
+
+void *
+kq_fnm_proccess_events_proc(void *data) {
+ struct kevent kev;
+ kq_fnm_p kfnm = data;
+
+ if (NULL == kfnm)
+ return (NULL);
+ /* Get and proccess events, no wait. */
+ while (0 < kevent(kfnm->fd, NULL, 0, &kev, 1, NULL)) {
+ kq_fnm_proccess_event(kfnm, &kev);
+ }
+ return (NULL);
+}
diff --git a/devel/glib20/files/kqueue_fnm.h b/devel/glib20/files/kqueue_fnm.h
new file mode 100644
index 000000000000..b97aa37a9159
--- /dev/null
+++ b/devel/glib20/files/kqueue_fnm.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2016 - 2019 Rozhuk Ivan <rozhuk.im@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
+ *
+ */
+
+#ifndef __KQUEUE_FILE_NOTIFY_MONITOR_H__
+#define __KQUEUE_FILE_NOTIFY_MONITOR_H__
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+
+typedef struct kq_file_nonify_monitor_s *kq_fnm_p;
+typedef struct kq_file_nonify_monitor_entry_s *kq_fnme_p;
+
+typedef void (*kfnm_event_handler_cb)(kq_fnm_p kfnm,
+ kq_fnme_p fnme, void *udata,
+ uint32_t event,
+ const char *base,
+ const char *filename,
+ const char *new_filename);
+#define KF_EVENT_NOT_CHANGED 0 /* Internal use. */
+#define KF_EVENT_CREATED 1
+#define KF_EVENT_DELETED 2
+#define KF_EVENT_RENAMED 3
+#define KF_EVENT_CHANGED 4
+
+
+typedef struct kq_file_nonify_mon_settings_s {
+ uint32_t rate_limit_time_init; /* Fire events for dir min interval, mseconds. */
+ uint32_t rate_limit_time_max; /* Fire events for dir max interval, mseconds. */
+ uint32_t rate_limit_time_mul; /* Fire events time increment, mseconds. */
+ size_t max_dir_files; /* If dir contain more than n files - do not mon files changes. */
+ int mon_local_subfiles; /* Enable monitoring files changes on local file systems. */
+ int mon_local_subdirs; /* Also mon for subdirs changes . */
+ const char **local_fs; /* NULL terminated fs names list that threat as local. Keep utill kq_fnm_free() return. */
+ const char **non_local_fs; /* NULL terminated fs names list that threat as not local. Keep utill kq_fnm_free() return. */
+} kq_file_mon_settings_t, *kq_file_mon_settings_p;
+
+kq_fnm_p kq_fnm_create(kq_file_mon_settings_p s,
+ kfnm_event_handler_cb cb_func);
+void kq_fnm_free(kq_fnm_p kfnm);
+
+kq_fnme_p kq_fnm_add(kq_fnm_p kfnm,
+ const char *path, void *udata);
+void kq_fnm_del(kq_fnm_p kfnm, kq_fnme_p fnme);
+
+
+#endif /* __KQUEUE_FILE_NOTIFY_MONITOR_H__ */