FreeBSD Bugzilla – Attachment 218219 Details for
Bug 214338
[PATCH] devel/glib20: new kqueue() backend for file monitoring
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
patch
glib20.patch (text/plain), 48.54 KB, created by
Ivan Rozhuk
on 2020-09-23 19:40:49 UTC
(
hide
)
Description:
patch
Filename:
MIME Type:
Creator:
Ivan Rozhuk
Created:
2020-09-23 19:40:49 UTC
Size:
48.54 KB
patch
obsolete
>Index: devel/glib20/Makefile >=================================================================== >--- devel/glib20/Makefile (revision 549773) >+++ devel/glib20/Makefile (working copy) >@@ -44,10 +44,12 @@ > glib-compile-resources.1 gresource.1 gdbus-codegen.1 > gobject_MAN= glib-genmarshal.1 glib-mkenums.1 gobject-query.1 > >-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 >@@ -68,6 +70,15 @@ > 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' \ >Index: devel/glib20/files/gkqueuefilemonitor.c >=================================================================== >--- devel/glib20/files/gkqueuefilemonitor.c (nonexistent) >+++ devel/glib20/files/gkqueuefilemonitor.c (working copy) >@@ -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(); >+} >Index: devel/glib20/files/kqueue_fnm.c >=================================================================== >--- devel/glib20/files/kqueue_fnm.c (nonexistent) >+++ devel/glib20/files/kqueue_fnm.c (working copy) >@@ -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); >+} >Index: devel/glib20/files/kqueue_fnm.h >=================================================================== >--- devel/glib20/files/kqueue_fnm.h (nonexistent) >+++ devel/glib20/files/kqueue_fnm.h (working copy) >@@ -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__ */
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 214338
:
176801
|
178938
|
181557
|
181816
|
182423
|
182749
|
184001
|
184287
|
184290
|
184301
|
184456
|
188088
|
188134
|
188144
|
188261
|
188262
|
190711
|
190712
|
191448
|
195450
|
195570
|
195597
|
195598
|
195618
|
195619
|
195649
|
195650
|
198889
|
198900
|
199221
|
199229
|
202832
|
202839
|
202935
|
202948
|
204279
|
204331
|
218219
|
222442