From owner-svn-ports-all@freebsd.org Sun Feb 14 19:38:27 2021 Return-Path: Delivered-To: svn-ports-all@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 1360553A169; Sun, 14 Feb 2021 19:38:27 +0000 (UTC) (envelope-from tcberner@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4DdyFv01Syz4T73; Sun, 14 Feb 2021 19:38:27 +0000 (UTC) (envelope-from tcberner@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id E77CC10160; Sun, 14 Feb 2021 19:38:26 +0000 (UTC) (envelope-from tcberner@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id 11EJcQwC087247; Sun, 14 Feb 2021 19:38:26 GMT (envelope-from tcberner@FreeBSD.org) Received: (from tcberner@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id 11EJcQti087242; Sun, 14 Feb 2021 19:38:26 GMT (envelope-from tcberner@FreeBSD.org) Message-Id: <202102141938.11EJcQti087242@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: tcberner set sender to tcberner@FreeBSD.org using -f From: "Tobias C. Berner" Date: Sun, 14 Feb 2021 19:38:26 +0000 (UTC) To: ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org Subject: svn commit: r565264 - in head/devel/glib20: . files X-SVN-Group: ports-head X-SVN-Commit-Author: tcberner X-SVN-Commit-Paths: in head/devel/glib20: . files X-SVN-Commit-Revision: 565264 X-SVN-Commit-Repository: ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-ports-all@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: SVN commit messages for the ports tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 14 Feb 2021 19:38:27 -0000 Author: tcberner Date: Sun Feb 14 19:38:26 2021 New Revision: 565264 URL: https://svnweb.freebsd.org/changeset/ports/565264 Log: 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 Added: head/devel/glib20/files/gkqueuefilemonitor.c (contents, props changed) head/devel/glib20/files/kqueue_fnm.c (contents, props changed) head/devel/glib20/files/kqueue_fnm.h (contents, props changed) Modified: head/devel/glib20/Makefile Modified: head/devel/glib20/Makefile ============================================================================== --- head/devel/glib20/Makefile Sun Feb 14 19:31:00 2021 (r565263) +++ head/devel/glib20/Makefile Sun Feb 14 19:38:26 2021 (r565264) @@ -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 @@ -59,6 +61,15 @@ MESON_ARGS+= -Diconv=external .if ${ARCH} == powerpc64 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 ; \ Added: head/devel/glib20/files/gkqueuefilemonitor.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/devel/glib20/files/gkqueuefilemonitor.c Sun Feb 14 19:38:26 2021 (r565264) @@ -0,0 +1,224 @@ +/*- + * Copyright (c) 2016 - 2019 Rozhuk Ivan + * 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 + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include "glib-private.h" +#include +#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(); +} Added: head/devel/glib20/files/kqueue_fnm.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/devel/glib20/files/kqueue_fnm.c Sun Feb 14 19:38:26 2021 (r565264) @@ -0,0 +1,1431 @@ +/*- + * Copyright (c) 2016 - 2019 Rozhuk Ivan + * 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include /* open, fcntl */ +#include + +#include +#include /* malloc, exit */ +#include /* close, write, sysconf */ +#include /* bcopy, bzero, memcpy, memmove, memset, strerror... */ +#include /* opendir, readdir */ +#include +#include +#include + +#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 && *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***