Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 14 Feb 2021 19:38:26 +0000 (UTC)
From:      "Tobias C. Berner" <tcberner@FreeBSD.org>
To:        ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org
Subject:   svn commit: r565264 - in head/devel/glib20: . files
Message-ID:  <202102141938.11EJcQti087242@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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 <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();
+}

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 <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 &&

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202102141938.11EJcQti087242>