View | Details | Raw Unified | Return to bug 214338 | Differences between
and this patch

Collapse All | Expand All

(-)devel/glib20/Makefile (-1 / +12 lines)
Lines 48-54 Link Here
48
		glib-compile-resources.1 gresource.1 gdbus-codegen.1
48
		glib-compile-resources.1 gresource.1 gdbus-codegen.1
49
gobject_MAN=	glib-genmarshal.1 glib-mkenums.1 gobject-query.1
49
gobject_MAN=	glib-genmarshal.1 glib-mkenums.1 gobject-query.1
50
50
51
OPTIONS_DEFINE=	COLLATION_FIX DEBUG NLS
51
OPTIONS_DEFINE=	COLLATION_FIX DEBUG FAM_ALTBACKEND NLS
52
OPTIONS_SUB=	yes
52
OPTIONS_SUB=	yes
53
# libc collation was fixed by https://svnweb.freebsd.org/changeset/base/290494
53
# libc collation was fixed by https://svnweb.freebsd.org/changeset/base/290494
54
COLLATION_FIX_DESC=	Use ICU for UTF-8 string collation (if libc is broken)
54
COLLATION_FIX_DESC=	Use ICU for UTF-8 string collation (if libc is broken)
Lines 65-70 Link Here
65
# http://www.freebsd.org/cgi/query-pr.cgi?pr=175930 see comment by ed@
65
# http://www.freebsd.org/cgi/query-pr.cgi?pr=175930 see comment by ed@
66
#COLLATION_FIX_CONFIGURE_ENV+=	CFLAGS="-D__STDC_ISO_10646__"
66
#COLLATION_FIX_CONFIGURE_ENV+=	CFLAGS="-D__STDC_ISO_10646__"
67
67
68
FAM_ALTBACKEND_DESC=		Alternate file monitor backend
69
FAM_ALTBACKEND_USES=		autoreconf:build
70
FAM_ALTBACKEND_EXTRA_PATCHES=	${FILESDIR}/extra-patch-gio_kqueue_Makefile.am
71
72
68
.include <bsd.port.pre.mk>
73
.include <bsd.port.pre.mk>
69
74
70
# doesn't build yet
75
# doesn't build yet
Lines 98-103 Link Here
98
		s|-Werror|| ; \
103
		s|-Werror|| ; \
99
		s|#define HAVE_SYS_INOTIFY_H 1||' ${WRKSRC}/configure
104
		s|#define HAVE_SYS_INOTIFY_H 1||' ${WRKSRC}/configure
100
105
106
pre-configure-FAM_ALTBACKEND-on:
107
	@${CP} -f ${FILESDIR}/gkqueuefilemonitor.c ${WRKSRC}/gio/kqueue/gkqueuefilemonitor.c
108
	@${CP} ${FILESDIR}/kqueue_fnm.c ${WRKSRC}/gio/kqueue/kqueue_fnm.c
109
	@${CP} ${FILESDIR}/kqueue_fnm.h ${WRKSRC}/gio/kqueue/kqueue_fnm.h
110
	@(cd ${WRKSRC} && ${AUTORECONF} -fvi)
111
101
post-install:
112
post-install:
102
	@${MKDIR} ${STAGEDIR}${PREFIX}/share/GConf/gsettings
113
	@${MKDIR} ${STAGEDIR}${PREFIX}/share/GConf/gsettings
103
	@${MKDIR} ${STAGEDIR}${PREFIX}/lib/gio/modules
114
	@${MKDIR} ${STAGEDIR}${PREFIX}/lib/gio/modules
(-)devel/glib20/files/extra-patch-gio_kqueue_Makefile.am (+26 lines)
Line 0 Link Here
1
--- gio/kqueue/Makefile.am.orig	2015-10-14 14:41:16.000000000 +0300
2
+++ gio/kqueue/Makefile.am	2016-11-06 05:08:37.646089000 +0300
3
@@ -4,21 +4,8 @@
4
 
5
 libkqueue_la_SOURCES = \
6
        gkqueuefilemonitor.c \
7
-       gkqueuefilemonitor.h \
8
-       kqueue-helper.c \
9
-       kqueue-helper.h \
10
-       kqueue-thread.c \
11
-       kqueue-thread.h \
12
-       kqueue-sub.c \
13
-       kqueue-sub.h \
14
-       kqueue-missing.c \
15
-       kqueue-missing.h \
16
-       kqueue-utils.c \
17
-       kqueue-utils.h \
18
-       kqueue-exclusions.c \
19
-       kqueue-exclusions.h \
20
-       dep-list.c \
21
-       dep-list.h \
22
+       kqueue_fnm.c \
23
+       kqueue_fnm.h \
24
        $(NULL)
25
 
26
 libkqueue_la_CFLAGS = \
(-)devel/glib20/files/gkqueuefilemonitor.c (+217 lines)
Line 0 Link Here
1
/*-
2
 * Copyright (c) 2016 - 2018 Rozhuk Ivan <rozhuk.im@gmail.com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 * 1. Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 * 2. Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
 * SUCH DAMAGE.
25
 *
26
 * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
27
 *
28
 */
29
30
#include "config.h"
31
32
#include <glib-object.h>
33
#include <string.h>
34
#include <gio/gfilemonitor.h>
35
#include <gio/glocalfilemonitor.h>
36
#include <gio/giomodule.h>
37
#include "glib-private.h"
38
#include <glib-unix.h>
39
#include "kqueue_fnm.h"
40
41
/* Defaults. */
42
#ifndef KQUEUE_MON_RATE_LIMIT_TIME_INIT
43
#	define KQUEUE_MON_RATE_LIMIT_TIME_INIT		1000
44
#endif
45
#ifndef KQUEUE_MON_RATE_LIMIT_TIME_MAX
46
#	define KQUEUE_MON_RATE_LIMIT_TIME_MAX		8000
47
#endif
48
#ifndef KQUEUE_MON_RATE_LIMIT_TIME_MUL
49
#	define KQUEUE_MON_RATE_LIMIT_TIME_MUL		2
50
#endif
51
#ifndef KQUEUE_MON_MAX_DIR_FILES
52
#	define KQUEUE_MON_MAX_DIR_FILES			128
53
#endif
54
#ifndef KQUEUE_MON_LOCAL_SUBFILES
55
#	define KQUEUE_MON_LOCAL_SUBFILES		1
56
#endif
57
#ifndef KQUEUE_MON_LOCAL_SUBDIRS
58
#	define KQUEUE_MON_LOCAL_SUBDIRS			0
59
#endif
60
61
62
static GMutex			kqueue_lock;
63
static volatile kq_fnm_p	kqueue_fnm = NULL;
64
/* Exclude from file changes monitoring, watch only for dirs. */
65
static const char *non_local_fs[] = {
66
	"fusefs.sshfs",
67
	NULL
68
};
69
70
#define G_TYPE_KQUEUE_FILE_MONITOR      (g_kqueue_file_monitor_get_type())
71
#define G_KQUEUE_FILE_MONITOR(inst)     (G_TYPE_CHECK_INSTANCE_CAST((inst), \
72
					 G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor))
73
74
typedef GLocalFileMonitorClass	GKqueueFileMonitorClass;
75
76
typedef struct {
77
	GLocalFileMonitor	parent_instance;
78
	kq_fnmo_p		fnmo;
79
} GKqueueFileMonitor;
80
81
GType g_kqueue_file_monitor_get_type(void);
82
G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
83
       g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
84
               g_define_type_id,
85
               "kqueue",
86
               10))
87
88
89
static void
90
kqueue_event_handler(kq_fnm_p kfnm,
91
    kq_fnmo_p fnmo, void *udata, uint32_t event,
92
    const char *base, const char *filename, const char *new_filename) {
93
	static const uint32_t kfnm_to_glib_map[] = {
94
		0,				/* KF_EVENT_NOT_CHANGED */
95
		G_FILE_MONITOR_EVENT_CREATED,	/* KF_EVENT_CREATED */
96
		G_FILE_MONITOR_EVENT_DELETED,	/* KF_EVENT_DELETED */
97
		G_FILE_MONITOR_EVENT_RENAMED,	/* KF_EVENT_RENAMED */
98
		G_FILE_MONITOR_EVENT_CHANGED	/* KF_EVENT_CHANGED */
99
	};
100
101
	if (NULL == kfnm || NULL == filename ||
102
	    KF_EVENT_CREATED > event ||
103
	    KF_EVENT_CHANGED < event)
104
		return;
105
	g_file_monitor_source_handle_event(udata,
106
	    kfnm_to_glib_map[event],
107
	    filename, new_filename, NULL,
108
	    g_get_monotonic_time());
109
}
110
111
static gboolean
112
g_kqueue_file_monitor_is_supported(void) {
113
	kq_file_mon_settings_t kfms;
114
115
	if (NULL != kqueue_fnm)
116
		return (TRUE);
117
	/* Init only once. */
118
	g_mutex_lock(&kqueue_lock);
119
	if (NULL != kqueue_fnm) {
120
		g_mutex_unlock(&kqueue_lock);
121
		return (TRUE); /* Initialized while wait lock. */
122
	}
123
124
	memset(&kfms, 0x00, sizeof(kq_file_mon_settings_t));
125
	kfms.rate_limit_time_init = KQUEUE_MON_RATE_LIMIT_TIME_INIT;
126
	kfms.rate_limit_time_max = KQUEUE_MON_RATE_LIMIT_TIME_MAX;
127
	kfms.rate_limit_time_mul = KQUEUE_MON_RATE_LIMIT_TIME_MUL;
128
	kfms.max_dir_files = KQUEUE_MON_MAX_DIR_FILES;
129
	kfms.mon_local_subfiles = KQUEUE_MON_LOCAL_SUBFILES;
130
	kfms.mon_local_subdirs = KQUEUE_MON_LOCAL_SUBDIRS;
131
	kfms.local_fs = NULL;
132
	kfms.non_local_fs = non_local_fs;
133
134
	kqueue_fnm = kq_fnm_create(&kfms, kqueue_event_handler);
135
	if (NULL == kqueue_fnm) {
136
		g_mutex_unlock(&kqueue_lock);
137
		return (FALSE); /* Init fail. */
138
	}
139
	g_mutex_unlock(&kqueue_lock);
140
141
	return (TRUE);
142
}
143
144
static gboolean
145
g_kqueue_file_monitor_cancel(GFileMonitor *monitor) {
146
	GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(monitor);
147
148
	kq_fnm_del(kqueue_fnm, gffm->fnmo);
149
	gffm->fnmo = NULL;
150
151
	return (TRUE);
152
}
153
154
static void
155
g_kqueue_file_monitor_finalize(GObject *object) {
156
	//GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(object);
157
158
	//g_mutex_lock(&kqueue_lock);
159
	//kq_fnm_free(kqueue_fnm);
160
	//kqueue_fnm = NULL;
161
	//g_mutex_unlock(&kqueue_lock);
162
	//G_OBJECT_CLASS(g_kqueue_file_monitor_parent_class)->finalize(object);
163
}
164
165
static void
166
g_kqueue_file_monitor_start(GLocalFileMonitor *local_monitor,
167
    const gchar *dirname, const gchar *basename,
168
    const gchar *filename, GFileMonitorSource *source) {
169
	GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(local_monitor);
170
171
	g_assert(NULL != kqueue_fnm);
172
	//g_source_ref((GSource*)source);
173
174
	if (NULL == filename) {
175
		filename = dirname;
176
	}
177
	gffm->fnmo = kq_fnm_add(kqueue_fnm, filename, source);
178
}
179
180
static void
181
g_kqueue_file_monitor_init(GKqueueFileMonitor *monitor) {
182
183
}
184
185
static void
186
g_kqueue_file_monitor_class_init(GKqueueFileMonitorClass *class) {
187
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
188
	GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS(class);
189
190
	class->is_supported = g_kqueue_file_monitor_is_supported;
191
	class->start = g_kqueue_file_monitor_start;
192
	class->mount_notify = TRUE; /* TODO: ??? */
193
	file_monitor_class->cancel = g_kqueue_file_monitor_cancel;
194
	gobject_class->finalize = g_kqueue_file_monitor_finalize;
195
}
196
197
static void
198
g_kqueue_file_monitor_class_finalize(GKqueueFileMonitorClass *class) {
199
200
}
201
202
void
203
g_io_module_load(GIOModule *module) {
204
205
	g_type_module_use(G_TYPE_MODULE(module));
206
207
	g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
208
	    G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10);
209
	g_io_extension_point_implement(G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME,
210
	    G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10);
211
}
212
213
void
214
g_io_module_unload(GIOModule *module) {
215
216
	g_assert_not_reached();
217
}
(-)devel/glib20/files/kqueue_fnm.c (+1074 lines)
Line 0 Link Here
1
/*-
2
 * Copyright (c) 2016 - 2018 Rozhuk Ivan <rozhuk.im@gmail.com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 * 1. Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 * 2. Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
 * SUCH DAMAGE.
25
 *
26
 * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
27
 *
28
 */
29
30
#include <sys/param.h>
31
#include <sys/types.h>
32
#include <sys/event.h>
33
#include <sys/time.h>
34
#include <sys/stat.h>
35
#include <sys/mount.h>
36
#include <sys/fcntl.h> /* open, fcntl */
37
38
#include <inttypes.h>
39
#include <stdlib.h> /* malloc, exit */
40
#include <unistd.h> /* close, write, sysconf */
41
#include <string.h> /* bcopy, bzero, memcpy, memmove, memset, strerror... */
42
#include <dirent.h> /* opendir, readdir */
43
#include <errno.h>
44
#include <pthread.h>
45
46
#include "kqueue_fnm.h"
47
48
49
/* Preallocate items count. */
50
#ifndef FILES_ALLOC_BLK_SIZE
51
#	define FILES_ALLOC_BLK_SIZE	32
52
#endif
53
54
55
typedef struct readdir_context_s {
56
	int		fd;
57
	uint8_t		*buf;
58
	size_t		buf_size;
59
	size_t		buf_used;
60
	size_t		buf_pos;
61
} readdir_ctx_t, *readdir_ctx_p;
62
63
64
typedef struct file_info_s { /* Directory file. */
65
	int		fd;		/* For notify kqueue(). */
66
	struct dirent 	de;		/* d_reclen used for action. */
67
	struct stat	sb;
68
} file_info_t, *file_info_p;
69
70
71
typedef struct kq_file_nmon_obj_s {
72
	int		fd;		/* For notify kqueue(). */
73
	int		is_dir;
74
	int		is_local;	/* Is file system local. */
75
	struct stat	sb;
76
	char		path[(PATH_MAX + 2)];
77
	size_t		path_size;
78
	size_t		name_offset;	/* Parent path size. */
79
	uint32_t	rate_lim_cur_interval;	/* From rate_limit_time_init to rate_limit_time_max. 0 disabled. */
80
	size_t		rate_lim_ev_cnt;	/* Events count then rate_lim_cur_interval != 0 since last report. */
81
	sbintime_t	rate_lim_ev_last;	/* Last event time. */
82
	void		*udata;
83
	kq_fnm_p	kfnm;
84
	/* For dir. */
85
	file_info_p	files;
86
	volatile size_t	files_count;
87
	size_t		files_allocated;
88
} kq_fnmo_t;
89
90
91
typedef struct kq_file_nonify_monitor_s {
92
	int		fd;		/* kqueue() fd. */
93
	int		pfd[2];		/* pipe queue specific. */
94
	kfnm_event_handler_cb cb_func;	/* Callback on dir/file change. */
95
	kq_file_mon_settings_t s;
96
	sbintime_t	rate_lim_time_init; /* rate_limit_time_init */
97
	pthread_t	tid;
98
} kq_fnm_t;
99
100
101
typedef void (*kq_msg_cb)(void *arg);
102
103
typedef struct kq_file_mon_msg_pkt_s {
104
	size_t		magic;
105
	kq_msg_cb	msg_cb;
106
	void		*arg;
107
	size_t		chk_sum;
108
} kq_fnm_msg_pkt_t, *kq_fnm_msg_pkt_p;
109
110
#define KF_MSG_PKT_MAGIC	0xffddaa00
111
112
113
#ifndef O_NOATIME
114
#	define O_NOATIME	0
115
#endif
116
#ifndef O_EVTONLY
117
#	define O_EVTONLY	O_RDONLY
118
#endif
119
#define OPEN_FILE_FLAGS		(O_EVTONLY | O_NONBLOCK | O_NOFOLLOW | O_NOATIME | O_CLOEXEC)
120
121
#define EVFILT_VNODE_SUB_FLAGS	(NOTE_WRITE |				\
122
				NOTE_EXTEND |				\
123
				NOTE_ATTRIB |				\
124
				NOTE_LINK |				\
125
				NOTE_CLOSE_WRITE)
126
127
#define EVFILT_VNODE_FLAGS_ALL	(NOTE_DELETE |				\
128
				EVFILT_VNODE_SUB_FLAGS |		\
129
				NOTE_RENAME |				\
130
				NOTE_REVOKE)
131
132
#ifndef _GENERIC_DIRSIZ
133
#	define _GENERIC_DIRSIZ(__de)	MIN((__de)->d_reclen, sizeof(struct dirent))
134
#endif
135
136
#define IS_NAME_DOTS(__name)	('.' == (__name)[0] &&			\
137
				 ('\0' == (__name)[1] || 		\
138
				  ('.' == (__name)[1] && '\0' == (__name)[2])))
139
#define IS_DE_NAME_EQ(__de1, __de2)  (0 == mem_cmpn((__de1)->d_name,	\
140
						    (__de1)->d_namlen,	\
141
						    (__de2)->d_name,	\
142
						    (__de2)->d_namlen))
143
#define zalloc(__size)		calloc(1, (__size))
144
145
#if (!defined(HAVE_REALLOCARRAY) && (!defined(__FreeBSD_version) || __FreeBSD_version < 1100000))
146
#	define reallocarray(__mem, __size, __count)	realloc((__mem), ((__size) * (__count)))
147
#endif
148
149
#ifndef CLOCK_MONOTONIC_FAST
150
#	define CLOCK_MONOTONIC_FAST	CLOCK_MONOTONIC
151
#endif
152
153
154
void 	*kq_fnm_proccess_events_proc(void *data);
155
156
static inline int
157
mem_cmpn(const void *buf1, const size_t buf1_size,
158
    const void *buf2, const size_t buf2_size) {
159
160
	if (buf1_size != buf2_size)
161
		return (((buf1_size > buf2_size) ? 127 : -127));
162
	if (0 == buf1_size || buf1 == buf2)
163
		return (0);
164
	if (NULL == buf1)
165
		return (-127);
166
	if (NULL == buf2)
167
		return (127);
168
	return (memcmp(buf1, buf2, buf1_size));
169
}
170
171
static int
172
realloc_items(void **items, const size_t item_size,
173
    size_t *allocated, const size_t alloc_blk_cnt, const size_t count) {
174
	size_t allocated_prev, allocated_new;
175
	uint8_t *items_new;
176
177
	if (NULL == items || 0 == item_size || NULL == allocated ||
178
	    0 == alloc_blk_cnt)
179
		return (EINVAL);
180
	allocated_prev = (*allocated);
181
	if (NULL != (*items) &&
182
	    allocated_prev > count &&
183
	    allocated_prev <= (count + alloc_blk_cnt))
184
		return (0);
185
	allocated_new = (((count / alloc_blk_cnt) + 1) * alloc_blk_cnt);
186
	items_new = (uint8_t*)reallocarray((*items), item_size, allocated_new);
187
	if (NULL == items_new) /* Realloc fail! */
188
		return (ENOMEM);
189
190
	if (allocated_new > allocated_prev) { /* Init new mem. */
191
		memset((items_new + (allocated_prev * item_size)), 0x00,
192
		    ((allocated_new - allocated_prev) * item_size));
193
	}
194
	(*items) = items_new;
195
	(*allocated) = allocated_new;
196
197
	return (0);
198
}
199
200
201
static int
202
readdir_start(int fd, struct stat *sb, size_t exp_count, readdir_ctx_p rdd) {
203
	size_t buf_size;
204
205
	if (-1 == fd || NULL == sb || NULL == rdd)
206
		return (EINVAL);
207
	if (-1 == lseek(fd, 0, SEEK_SET))
208
		return (errno);
209
	/* Calculate buf size for getdents(). */
210
	buf_size = MAX((size_t)sb->st_size, (exp_count * sizeof(struct dirent)));
211
	if (0 == buf_size) {
212
		buf_size = (16 * PAGE_SIZE);
213
	}
214
	/* Make buf size well aligned. */
215
	if (0 != sb->st_blksize) {
216
		if (powerof2(sb->st_blksize)) {
217
			buf_size = roundup2(buf_size, sb->st_blksize);
218
		} else {
219
			buf_size = roundup(buf_size, sb->st_blksize);
220
		}
221
	} else {
222
		buf_size = round_page(buf_size);
223
	}
224
	/* Init. */
225
	memset(rdd, 0x00, sizeof(readdir_ctx_t));
226
	rdd->buf = malloc(buf_size);
227
	if (NULL == rdd->buf)
228
		return (ENOMEM);
229
	rdd->buf_size = buf_size;
230
	rdd->fd = fd;
231
232
	return (0);
233
}
234
235
static void
236
readdir_free(readdir_ctx_p rdd) {
237
238
	if (NULL == rdd || NULL == rdd->buf)
239
		return;
240
	free(rdd->buf);
241
	memset(rdd, 0x00, sizeof(readdir_ctx_t));
242
}
243
244
static int
245
readdir_next(readdir_ctx_p rdd, struct dirent *de) {
246
	int error = 0, ios;
247
	uint8_t *ptr;
248
249
	if (NULL == rdd || NULL == rdd->buf || NULL == de)
250
		return (EINVAL);
251
252
	for (;;) {
253
		if (rdd->buf_used <= rdd->buf_pos) {
254
			/* Called once if buf size calculated ok. */
255
			ios = getdents(rdd->fd, (char*)rdd->buf, (int)rdd->buf_size);
256
			if (-1 == ios) {
257
				error = errno;
258
				break;
259
			}
260
			if (0 == ios) {
261
				error = ESPIPE; /* EOF. */
262
				break;
263
			}
264
			rdd->buf_used = (size_t)ios;
265
			rdd->buf_pos = 0;
266
		}
267
		/* Keep data aligned. */
268
		ptr = (rdd->buf + rdd->buf_pos);
269
		memcpy(de, ptr, (sizeof(struct dirent) - sizeof(de->d_name)));
270
		if (0 == de->d_reclen) {
271
			error = ESPIPE; /* EOF. */
272
			break;
273
		}
274
		rdd->buf_pos += de->d_reclen;
275
#ifdef DT_WHT
276
		if (DT_WHT == de->d_type)
277
			continue;
278
#endif
279
		memcpy(de, ptr, _GENERIC_DIRSIZ(de));
280
		if (!IS_NAME_DOTS(de->d_name))
281
			return (0); /* OK. */
282
	}
283
284
	/* Err or no more files. */
285
	readdir_free(rdd);
286
287
	return (error);
288
}
289
290
291
static int
292
file_info_find_ni(file_info_p files, size_t files_count,
293
    file_info_p fi, size_t *idx) {
294
	size_t i;
295
	mode_t st_ftype;
296
297
	if (NULL == files || NULL == fi || NULL == idx)
298
		return (0);
299
	st_ftype = (S_IFMT & fi->sb.st_mode);
300
	for (i = 0; i < files_count; i ++) {
301
		if ((S_IFMT & files[i].sb.st_mode) != st_ftype)
302
			continue;
303
		if ((fi->sb.st_ino != files[i].sb.st_ino ||
304
		     fi->de.d_fileno != files[i].de.d_fileno) &&
305
		    0 == IS_DE_NAME_EQ(&fi->de, &files[i].de))
306
			continue;
307
		(*idx) = i;
308
		return (1);
309
	}
310
	(*idx) = files_count;
311
	return (0);
312
}
313
314
static int
315
file_info_find_ino(file_info_p files, size_t files_count,
316
    file_info_p fi, size_t *idx) {
317
	size_t i;
318
	mode_t st_ftype;
319
320
	if (NULL == files || NULL == fi || NULL == idx)
321
		return (0);
322
	st_ftype = (S_IFMT & fi->sb.st_mode);
323
	for (i = 0; i < files_count; i ++) {
324
		if ((S_IFMT & files[i].sb.st_mode) != st_ftype ||
325
		    fi->sb.st_ino != files[i].sb.st_ino ||
326
		    fi->de.d_fileno != files[i].de.d_fileno)
327
			continue;
328
		(*idx) = i;
329
		return (1);
330
	}
331
	(*idx) = files_count;
332
	return (0);
333
}
334
335
static int
336
file_info_find_name(file_info_p files, size_t files_count,
337
    file_info_p fi, size_t *idx) {
338
	size_t i;
339
	mode_t st_ftype;
340
341
	if (NULL == files || NULL == fi || NULL == idx)
342
		return (0);
343
	st_ftype = (S_IFMT & fi->sb.st_mode);
344
	for (i = 0; i < files_count; i ++) {
345
		if ((S_IFMT & files[i].sb.st_mode) != st_ftype ||
346
		    0 == IS_DE_NAME_EQ(&fi->de, &files[i].de))
347
			continue;
348
		(*idx) = i;
349
		return (1);
350
	}
351
	(*idx) = files_count;
352
	return (0);
353
}
354
355
static void
356
file_info_fd_close(file_info_p files, size_t files_count) {
357
	size_t i;
358
359
	if (NULL == files || 0 == files_count)
360
		return;
361
	for (i = 0; i < files_count; i ++) {
362
		if (-1 == files[i].fd)
363
			continue;
364
		close(files[i].fd);
365
		files[i].fd = -1;
366
	}
367
}
368
369
370
static int
371
is_fs_local(struct statfs *stfs, const char **local_fs, const char **non_local_fs) {
372
	size_t i;
373
374
	if (NULL == stfs)
375
		return (0);
376
	if (NULL != local_fs) {
377
		for (i = 0; NULL != local_fs[i]; i ++) {
378
			if (0 == strncmp(stfs->f_fstypename, local_fs[i],
379
			    sizeof(stfs->f_fstypename)))
380
				return (1);
381
		}
382
	}
383
	if (0 == (MNT_LOCAL & stfs->f_flags))
384
		return (0);
385
	if (NULL != non_local_fs) {
386
		for (i = 0; NULL != non_local_fs[i]; i ++) {
387
			if (0 == strncmp(stfs->f_fstypename, non_local_fs[i],
388
			    sizeof(stfs->f_fstypename)))
389
				return (0);
390
		}
391
	}
392
	return (1);
393
}
394
395
396
static void
397
kq_fnmo_rate_lim_stop(kq_fnmo_p fnmo) {
398
	struct kevent kev;
399
400
	if (NULL == fnmo || -1 == fnmo->fd || 0 == fnmo->rate_lim_cur_interval)
401
		return;
402
	fnmo->rate_lim_cur_interval = 0;
403
	fnmo->rate_lim_ev_cnt = 0;
404
	EV_SET(&kev, fnmo->fd, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
405
	kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL);
406
}
407
408
static int
409
kq_fnmo_rate_lim_shedule_next(kq_fnmo_p fnmo) {
410
	u_short flags = (EV_ADD | EV_CLEAR | EV_ONESHOT);
411
	struct kevent kev;
412
413
	if (NULL == fnmo || -1 == fnmo->fd || 0 == fnmo->kfnm->s.rate_limit_time_init)
414
		return (EINVAL);
415
	if (0 == fnmo->rate_lim_cur_interval) { /* First call. */
416
		fnmo->rate_lim_cur_interval = fnmo->kfnm->s.rate_limit_time_init;
417
	} else {
418
		if (fnmo->rate_lim_cur_interval == fnmo->kfnm->s.rate_limit_time_max)
419
			return (0); /* No need to modify timer. */
420
		/* Increase rate limit interval. */
421
		fnmo->rate_lim_cur_interval *= fnmo->kfnm->s.rate_limit_time_mul;
422
	}
423
	if (fnmo->rate_lim_cur_interval >= fnmo->kfnm->s.rate_limit_time_max) {
424
		/* Check upper limit and shedule periodic timer with upper rate limit time. */
425
		flags &= ~EV_ONESHOT;
426
		fnmo->rate_lim_cur_interval = fnmo->kfnm->s.rate_limit_time_max;
427
	}
428
	EV_SET(&kev, fnmo->fd, EVFILT_TIMER, flags,
429
	    NOTE_MSECONDS, fnmo->rate_lim_cur_interval, fnmo);
430
	if (-1 == kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL)) {
431
		fnmo->rate_lim_cur_interval = 0;
432
		return (errno);
433
	}
434
	return (0);
435
}
436
437
/* Return:
438
 * 0 for events that not handled
439
 * 1 for handled = rate limited
440
 * -1 on error.
441
 */
442
static int
443
kq_fnmo_rate_lim_check(kq_fnmo_p fnmo) {
444
	sbintime_t sbt, sbt_now;
445
	struct timespec ts;
446
447
	if (NULL == fnmo)
448
		return (-1);
449
	if (-1 == fnmo->fd ||
450
	    0 == fnmo->kfnm->s.rate_limit_time_init)
451
		return (0);
452
	if (0 != fnmo->rate_lim_cur_interval) {
453
		fnmo->rate_lim_ev_cnt ++; /* Count event, timer is active. */
454
		return (1);
455
	}
456
457
	/* Do we need to enable rate limit? */
458
	if (0 != clock_gettime(CLOCK_MONOTONIC_FAST, &ts))
459
		return (-1);
460
	sbt_now = tstosbt(ts);
461
	sbt = (fnmo->rate_lim_ev_last + fnmo->kfnm->rate_lim_time_init);
462
	fnmo->rate_lim_ev_last = sbt_now;
463
	if (sbt < sbt_now) /* Events rate to low. */
464
		return (0);
465
	/* Try to enable rate limit. */
466
	if (0 != kq_fnmo_rate_lim_shedule_next(fnmo))
467
		return (-1);
468
	/* Ok. */
469
	fnmo->rate_lim_ev_cnt ++;
470
471
	return (1);
472
}
473
474
static void
475
kq_fnmo_clean(kq_fnmo_p fnmo) {
476
477
	if (NULL == fnmo)
478
		return;
479
	if (-1 != fnmo->fd) {
480
		kq_fnmo_rate_lim_stop(fnmo);
481
		close(fnmo->fd);
482
		fnmo->fd = -1;
483
	}
484
	if (0 != fnmo->is_local) { /* Stop monitoring files/dirs. */
485
		file_info_fd_close(fnmo->files, fnmo->files_count);
486
	}
487
	free(fnmo->files);
488
	fnmo->files = NULL;
489
	fnmo->files_count = 0;
490
	fnmo->files_allocated = 0;
491
}
492
493
static void
494
kq_fnmo_free(void *arg) {
495
	kq_fnmo_p fnmo = arg;
496
497
	if (NULL == fnmo)
498
		return;
499
	kq_fnmo_clean(fnmo);
500
	free(fnmo);
501
}
502
503
static kq_fnmo_p
504
kq_fnmo_alloc(kq_fnm_p kfnm, const char *path, void *udata) {
505
	kq_fnmo_p fnmo;
506
507
	if (NULL == kfnm || NULL == path)
508
		return (NULL);
509
	fnmo = zalloc(sizeof(kq_fnmo_t));
510
	if (NULL == fnmo)
511
		return (NULL);
512
	/* Remember args. */
513
	fnmo->path_size = strlcpy(fnmo->path, path, PATH_MAX);
514
	fnmo->name_offset = fnmo->path_size;
515
	fnmo->udata = udata;
516
	fnmo->kfnm = kfnm;
517
518
	return (fnmo);
519
}
520
521
static int
522
kq_fnmo_readdir(kq_fnmo_p fnmo, size_t exp_count) {
523
	int error;
524
	struct dirent *de;
525
	file_info_p tmfi;
526
	readdir_ctx_t rdd;
527
528
	if (NULL == fnmo || 0 == fnmo->is_dir)
529
		return (EINVAL);
530
531
	free(fnmo->files);
532
	fnmo->files = NULL;
533
	fnmo->files_count = 0;
534
	fnmo->files_allocated = 0;
535
	/* Pre allocate. */
536
	if (0 != realloc_items((void**)&fnmo->files,
537
	    sizeof(file_info_t), &fnmo->files_allocated,
538
	    FILES_ALLOC_BLK_SIZE, (exp_count + 1)))
539
		return (ENOMEM);
540
541
	error = readdir_start(fnmo->fd, &fnmo->sb, exp_count, &rdd);
542
	if (0 != error)
543
		return (error);
544
	for (;;) {
545
		if (0 != realloc_items((void**)&fnmo->files,
546
		    sizeof(file_info_t), &fnmo->files_allocated,
547
		    FILES_ALLOC_BLK_SIZE, fnmo->files_count)) {
548
			free(fnmo->files);
549
			fnmo->files = NULL;
550
			fnmo->files_count = 0;
551
			fnmo->files_allocated = 0;
552
			readdir_free(&rdd);
553
			return (ENOMEM);
554
		}
555
		de = &fnmo->files[fnmo->files_count].de; /* Use short name. */
556
		/* Get file name from folder. */
557
		if (0 != readdir_next(&rdd, de))
558
			break;
559
		/* Get file attrs. */
560
		if (0 != fstatat(fnmo->fd, de->d_name,
561
		    &fnmo->files[fnmo->files_count].sb,
562
		    AT_SYMLINK_NOFOLLOW)) {
563
			memset(&fnmo->files[fnmo->files_count].sb, 0x00,
564
			    sizeof(struct stat));
565
		}
566
		fnmo->files[fnmo->files_count].fd = -1;
567
		fnmo->files_count ++;
568
	}
569
	/* Mem compact. */
570
	tmfi = reallocarray(fnmo->files, sizeof(file_info_t), (fnmo->files_count + 1));
571
	if (NULL != tmfi) { /* realloc ok. */
572
		fnmo->files = tmfi;
573
		fnmo->files_allocated = (fnmo->files_count + 1);
574
	}
575
576
	readdir_free(&rdd);
577
578
	return (0); /* OK. */
579
}
580
581
582
static void
583
kq_fnmo_fi_start(kq_fnmo_p fnmo, file_info_p fi) {
584
	struct kevent kev;
585
586
	if (NULL == fnmo || NULL == fi)
587
		return;
588
	fi->fd = openat(fnmo->fd, fi->de.d_name, OPEN_FILE_FLAGS);
589
	if (-1 == fi->fd)
590
		return;
591
	EV_SET(&kev, fi->fd, EVFILT_VNODE,
592
	    (EV_ADD | EV_CLEAR),
593
	    EVFILT_VNODE_SUB_FLAGS, 0, fnmo);
594
	kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL);
595
}
596
597
static int
598
kq_fnmo_is_fi_monitored(kq_fnmo_p fnmo, file_info_p fi) {
599
600
	if (NULL == fnmo)
601
		return (0);
602
	if (0 == fnmo->is_local ||
603
	    (0 != fnmo->kfnm->s.max_dir_files &&
604
	     fnmo->kfnm->s.max_dir_files < fnmo->files_count))
605
		return (0);
606
	if (NULL != fi &&
607
	    0 == fnmo->kfnm->s.mon_local_subdirs &&
608
	    S_ISDIR(fi->sb.st_mode))
609
		return (0);
610
	return (1);
611
}
612
613
static void
614
kq_fnmo_init(void *arg) {
615
	kq_fnmo_p fnmo = arg;
616
	size_t i;
617
	struct statfs stfs;
618
	struct kevent kev;
619
620
	if (NULL == fnmo)
621
		return;
622
	fnmo->fd = open(fnmo->path, OPEN_FILE_FLAGS);
623
	if (-1 == fnmo->fd)
624
		return;
625
	if (0 != fstat(fnmo->fd, &fnmo->sb))
626
		goto err_out;
627
	
628
	/* Get parent folder name. */
629
	if (S_ISDIR(fnmo->sb.st_mode)) {
630
		fnmo->is_dir = 1;
631
		/* Be sure that folder contain trailing '/'. */
632
		if ('/' != fnmo->path[(fnmo->path_size - 1)]) {
633
			fnmo->path[fnmo->path_size] = '/';
634
			fnmo->path_size ++;
635
			fnmo->path[fnmo->path_size] = 0;
636
		}
637
		/* Skip last '/' for parent dir search. */
638
		fnmo->name_offset = (fnmo->path_size - 1);
639
	}
640
641
	/* Is file system local? */
642
	if (0 != fnmo->is_dir &&
643
	    0 != fnmo->kfnm->s.mon_local_subfiles &&
644
	    0 == fstatfs(fnmo->fd, &stfs)) {
645
		fnmo->is_local = is_fs_local(&stfs, fnmo->kfnm->s.local_fs,
646
		    fnmo->kfnm->s.non_local_fs);
647
	}
648
649
	/* Find parent dir path size. */
650
	while (0 < fnmo->name_offset && '/' != fnmo->path[(fnmo->name_offset - 1)]) {
651
		fnmo->name_offset --;
652
	}
653
654
	/* Dir special processing. */
655
	if (0 != fnmo->is_dir) {
656
		/* Read and remember dir content. */
657
		if (0 != kq_fnmo_readdir(fnmo, 0))
658
			goto err_out;
659
	}
660
	/* Add to kqueue. */
661
	EV_SET(&kev, fnmo->fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR),
662
	    EVFILT_VNODE_FLAGS_ALL, 0, fnmo);
663
	if (-1 == kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL))
664
		goto err_out;
665
	/* Add monitor sub files/dirs, ignory errors. */
666
	/* Check twice for performance reason. */
667
	if (0 != kq_fnmo_is_fi_monitored(fnmo, NULL)) {
668
		for (i = 0; i < fnmo->files_count; i ++) {
669
			if (0 != kq_fnmo_is_fi_monitored(fnmo, &fnmo->files[i])) {
670
				kq_fnmo_fi_start(fnmo, &fnmo->files[i]);
671
			}
672
		}
673
	}
674
675
	return; /* OK. */
676
677
err_out:
678
	kq_fnmo_clean(fnmo);
679
}
680
681
static void
682
kq_handle_changes(kq_fnm_p kfnm, kq_fnmo_p fnmo) {
683
	size_t i, k, files_count;
684
	file_info_p files;
685
686
	if (NULL == kfnm || NULL == fnmo)
687
		return;
688
	if (0 != fstat(fnmo->fd, &fnmo->sb) ||
689
	    0 == fnmo->sb.st_nlink) {
690
		kq_fnmo_clean(fnmo);
691
		kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_DELETED,
692
		    fnmo->path, "", NULL);
693
		return;
694
	}
695
	if (0 == fnmo->is_dir) {
696
		fnmo->path[fnmo->name_offset] = 0;
697
		kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_CHANGED,
698
		    fnmo->path, (fnmo->path + fnmo->name_offset), NULL);
699
		fnmo->path[fnmo->name_offset] = '/';
700
		return;
701
	}
702
703
	/* Dir processing. */
704
705
	/* Save prev. */
706
	files = fnmo->files;
707
	files_count = fnmo->files_count;
708
	fnmo->files = NULL;
709
	fnmo->files_count = 0;
710
	/* Update dir. */
711
	if (0 != kq_fnmo_readdir(fnmo, files_count)) {
712
		/* Restore prev state on fail. */
713
		fnmo->files = files;
714
		fnmo->files_count = files_count;
715
		return;
716
	}
717
	/* Notify removed first. */
718
	for (i = 0; i < files_count; i ++) {
719
		if (0 != file_info_find_ni(fnmo->files, fnmo->files_count, &files[i], &k)) /* Deleted? */
720
			continue;
721
		if (-1 != files[i].fd) {
722
			close(files[i].fd);
723
			files[i].fd = -1;
724
		}
725
		kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_DELETED,
726
		    fnmo->path, files[i].de.d_name, NULL);
727
	}
728
	/* Notify. */
729
	for (i = 0; i < fnmo->files_count; i ++) {
730
		/* Is new file/folder? */
731
		if (0 == file_info_find_ino(files, files_count, &fnmo->files[i], &k) &&
732
		    0 == file_info_find_name(files, files_count, &fnmo->files[i], &k)) { /* Add new. */
733
			/* Add monitor sub files/dirs, ignory errors. */
734
			if (0 != kq_fnmo_is_fi_monitored(fnmo, &fnmo->files[i])) {
735
				kq_fnmo_fi_start(fnmo, &fnmo->files[i]);
736
			}
737
			kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_CREATED,
738
			    fnmo->path, fnmo->files[i].de.d_name, NULL);
739
			continue;
740
		}
741
		/* Keep file fd. */
742
		fnmo->files[i].fd = files[k].fd;
743
		files[k].fd = -1;
744
		/* Is renamed? */
745
		if (0 == IS_DE_NAME_EQ(&files[k].de, &fnmo->files[i].de)) {
746
			kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_RENAMED,
747
			    fnmo->path, files[k].de.d_name, fnmo->files[i].de.d_name);
748
			continue;
749
		}
750
		/* Is modified? */
751
		if (0 != memcmp(&fnmo->files[i].sb, &files[k].sb, sizeof(struct stat))) {
752
			kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_CHANGED,
753
			    fnmo->path, fnmo->files[i].de.d_name, NULL);
754
			continue;
755
		}
756
		/* Not changed. */
757
	}
758
759
	/* Prevent FD leak die to race conditions.
760
	 * All fd must be -1, check this while debuging.
761
	 */
762
	file_info_fd_close(files, files_count);
763
	free(files);
764
}
765
766
static void
767
kq_handle_rename(kq_fnm_p kfnm, kq_fnmo_p fnmo) {
768
	int up_dir_fd, found = 0;
769
	readdir_ctx_t rdd;
770
	struct dirent de;
771
	struct stat sb;
772
	char old_filename[(MAXNAMLEN + 2)];
773
	size_t old_filename_size;
774
775
	if (NULL == kfnm || NULL == fnmo)
776
		return;
777
	if (0 != fstat(fnmo->fd, &fnmo->sb) ||
778
	    0 == fnmo->sb.st_nlink) {
779
notify_removed:
780
		kq_fnmo_clean(fnmo);
781
		kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_DELETED,
782
		    fnmo->path, "", NULL);
783
		return;
784
	}
785
	/* Save old file name. */
786
	old_filename_size = (fnmo->path_size - fnmo->name_offset - (size_t)fnmo->is_dir);
787
	memcpy(old_filename,
788
	    (fnmo->path + fnmo->name_offset),
789
	    old_filename_size);
790
	old_filename[old_filename_size] = 0;
791
792
	/* Get parent folder name. */
793
	fnmo->path[fnmo->name_offset] = 0;
794
	/* Try to open. */
795
	up_dir_fd = open(fnmo->path, (OPEN_FILE_FLAGS | O_DIRECTORY));
796
	/* Restore '/' after parent folder. */
797
	fnmo->path[fnmo->name_offset] = '/';
798
	if (-1 == up_dir_fd ||
799
	    0 != fstat(up_dir_fd, &sb) ||
800
	    0 != readdir_start(up_dir_fd, &sb, 0, &rdd)) {
801
		close(up_dir_fd);
802
		return;
803
	}
804
	/* Find new name by inode. */
805
	while (0 == readdir_next(&rdd, &de)) {
806
		if (0 == fstatat(up_dir_fd, de.d_name, &sb, AT_SYMLINK_NOFOLLOW) &&
807
		    0 == memcmp(&fnmo->sb, &sb, sizeof(struct stat))) {
808
			found ++;
809
			break;
810
		}
811
	}
812
	close(up_dir_fd);
813
	if (0 == found)
814
		goto notify_removed; /* Not found. */
815
	/* Update name. */
816
	if (PATH_MAX <= (fnmo->name_offset + de.d_namlen))
817
		return; /* Too long. */
818
	memcpy((fnmo->path + fnmo->name_offset), de.d_name, de.d_namlen);
819
	fnmo->path_size = (fnmo->name_offset + de.d_namlen);
820
	/* Add last '/' for dir. */
821
	fnmo->path[fnmo->path_size] = '/';
822
	fnmo->path_size += (size_t)fnmo->is_dir;
823
	fnmo->path[fnmo->path_size] = 0;
824
	/* Notify. */
825
	kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_RENAMED,
826
	    fnmo->path, old_filename, de.d_name);
827
}
828
829
830
static void
831
kq_fnm_delay_call_process(kq_fnm_p kfnm, kq_msg_cb forced_msg_cb) {
832
	ssize_t ios;
833
	kq_fnm_msg_pkt_t msg;
834
835
	for (;;) {
836
		ios = read(kfnm->pfd[0], &msg, sizeof(msg));
837
		if (0 >= ios)
838
			return;
839
		if (sizeof(msg) != ios ||
840
		    KF_MSG_PKT_MAGIC != msg.magic ||
841
		    (((size_t)msg.msg_cb) ^ ((size_t)msg.arg)) != msg.chk_sum)
842
			continue;
843
		if (NULL != forced_msg_cb) {
844
			forced_msg_cb(msg.arg);
845
			continue;
846
		}
847
		if (NULL == msg.msg_cb)
848
			continue;
849
		msg.msg_cb(msg.arg);
850
	}
851
}
852
853
static int
854
kq_fnm_delay_call(kq_fnm_p kfnm, kq_msg_cb msg_cb,
855
    void *arg) {
856
	kq_fnm_msg_pkt_t msg;
857
858
	if (NULL == kfnm || NULL == arg)
859
		return (EINVAL);
860
	msg.magic = KF_MSG_PKT_MAGIC;
861
	msg.msg_cb = msg_cb;
862
	msg.arg = arg;
863
	msg.chk_sum = (((size_t)msg.msg_cb) ^ ((size_t)msg.arg));
864
	if (sizeof(msg) == write(kfnm->pfd[1], &msg, sizeof(msg)))
865
		return (0);
866
	return (errno);
867
}
868
869
870
static void
871
kq_fnm_free_cb(void *arg) {
872
	kq_fnm_p kfnm = arg;
873
874
	if (NULL == kfnm)
875
		return;
876
	close(kfnm->fd);
877
	kfnm->fd = -1;
878
}
879
void
880
kq_fnm_free(kq_fnm_p kfnm) {
881
882
	if (NULL == kfnm)
883
		return;
884
	kq_fnm_delay_call(kfnm, kq_fnm_free_cb, kfnm);
885
	pthread_join(kfnm->tid, NULL);
886
	/* Free all in delay calls queue. */
887
	close(kfnm->pfd[1]);
888
	kq_fnm_delay_call_process(kfnm, kq_fnmo_free);
889
	close(kfnm->pfd[0]);
890
	free(kfnm);
891
}
892
893
kq_fnm_p
894
kq_fnm_create(kq_file_mon_settings_p s, kfnm_event_handler_cb cb_func) {
895
	kq_fnm_p kfnm;
896
	struct kevent kev;
897
898
	if (NULL == s || NULL == cb_func)
899
		return (NULL);
900
	kfnm = zalloc(sizeof(kq_fnm_t));
901
	if (NULL == kfnm)
902
		return (NULL);
903
	kfnm->fd = kqueue();
904
	if (-1 == kfnm->fd)
905
		goto err_out;
906
	if (-1 == pipe2(kfnm->pfd, O_NONBLOCK))
907
		goto err_out;
908
	kfnm->cb_func = cb_func;
909
	memcpy(&kfnm->s, s, sizeof(kq_file_mon_settings_t));
910
	if (kfnm->s.rate_limit_time_init >= kfnm->s.rate_limit_time_max) {
911
		kfnm->s.rate_limit_time_max = kfnm->s.rate_limit_time_init;
912
	}
913
	if (0 == kfnm->s.rate_limit_time_mul) {
914
		kfnm->s.rate_limit_time_mul ++;
915
	}
916
	kfnm->rate_lim_time_init = mstosbt(kfnm->s.rate_limit_time_init);
917
918
	EV_SET(&kev, kfnm->pfd[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
919
	if (-1 == kevent(kfnm->fd, &kev, 1, NULL, 0, NULL))
920
		goto err_out;
921
	if (0 != pthread_create(&kfnm->tid, NULL,
922
	    kq_fnm_proccess_events_proc, kfnm))
923
		goto err_out;
924
925
	return (kfnm);
926
927
err_out:
928
	kq_fnm_free(kfnm);
929
	return (NULL);
930
}
931
932
kq_fnmo_p
933
kq_fnm_add(kq_fnm_p kfnm, const char *path, void *udata) {
934
	int error;
935
	kq_fnmo_p fnmo;
936
937
	if (NULL == kfnm || NULL == path)
938
		return (NULL);
939
	fnmo = kq_fnmo_alloc(kfnm, path, udata);
940
	if (NULL == fnmo)
941
		return (NULL);
942
	/* Shedule delay call to init. */
943
	error = kq_fnm_delay_call(kfnm, kq_fnmo_init, fnmo);
944
	if (0 != error) { /* Error, do no directly init to avoid freezes. */
945
		kq_fnmo_free(fnmo);
946
		return (NULL);
947
	}
948
	return (fnmo);
949
}
950
951
void
952
kq_fnm_del(kq_fnm_p kfnm, kq_fnmo_p fnmo) {
953
	int error;
954
955
	if (NULL == kfnm || NULL == fnmo)
956
		return;
957
	/* Cancel notifications. */
958
	kq_fnmo_rate_lim_stop(fnmo);
959
	close(fnmo->fd);
960
	fnmo->fd = -1;
961
	/* Shedule delay call to free. */
962
	error = kq_fnm_delay_call(kfnm, kq_fnmo_free, fnmo);
963
	if (0 == error)
964
		return;
965
	/* Error, free directly. */
966
	kq_fnmo_free(fnmo);
967
}
968
969
970
static void
971
kq_fnm_proccess_event(kq_fnm_p kfnm, struct kevent *kev) {
972
	kq_fnmo_p fnmo;
973
	file_info_p fi;
974
	size_t i;
975
	int is_rate_lim_checked = 0;
976
	struct stat sb;
977
978
	if (NULL == kfnm || NULL == kev)
979
		return;
980
981
	/* Handle delay calls. */
982
	if (kev->ident == (uintptr_t)kfnm->pfd[0]) {
983
		if (kev->filter == EVFILT_READ) {
984
			kq_fnm_delay_call_process(kfnm, NULL);
985
		}
986
		return;
987
	}
988
989
	if (0 == kev->udata)
990
		return; /* No associated data, skip. */
991
	fnmo = (kq_fnmo_p)kev->udata;
992
993
	/* FS delayed notifications. */
994
	if (EVFILT_TIMER == kev->filter) {
995
		if (0 == fnmo->rate_lim_ev_cnt) {
996
			/* No delayed events, disable rate limit polling. */
997
			kq_fnmo_rate_lim_stop(fnmo);
998
			return;
999
		}
1000
		fnmo->rate_lim_ev_cnt = 0; /* Reset counter. */
1001
		kq_fnmo_rate_lim_shedule_next(fnmo);
1002
		kq_handle_changes(kfnm, fnmo);
1003
		return;
1004
	}
1005
1006
	/* FS notifications. */
1007
	if (EVFILT_VNODE != kev->filter)
1008
		return; /* Unknown event, skip. */
1009
	/* Subdir/file */
1010
	if (kev->ident != (uintptr_t)fnmo->fd) {
1011
		/* Is files changes rate limited? */
1012
		if (1 == kq_fnmo_rate_lim_check(fnmo))
1013
			return;
1014
		is_rate_lim_checked ++;
1015
		/* Try to find file and check it, without call kq_handle_changes(). */
1016
		fi = NULL;
1017
		for (i = 0; i < fnmo->files_count; i ++) {
1018
			if (kev->ident != (uintptr_t)fnmo->files[i].fd)
1019
				continue;
1020
			fi = &fnmo->files[i];
1021
			break;
1022
		}
1023
		if (NULL != fi) {
1024
			/* Get file attrs. */
1025
			if (0 != fstat(fi->fd, &sb)) {
1026
				memset(&sb, 0x00, sizeof(struct stat));
1027
			}
1028
			/* Is modified? */
1029
			if (0 != memcmp(&fi->sb, &sb, sizeof(struct stat))) {
1030
				memcpy(&fi->sb, &sb, sizeof(struct stat));
1031
				kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_CHANGED,
1032
				    fnmo->path, fi->de.d_name, NULL);
1033
				return;
1034
			}
1035
		}
1036
		/* fd not found or changes not found, rescan dir. */
1037
		kev->fflags = NOTE_WRITE;
1038
	}
1039
	/* Monitored object. */
1040
	/* All flags from EVFILT_VNODE_FLAGS_ALL must be handled here. */
1041
	if (EV_ERROR & kev->flags) {
1042
		kev->fflags |= NOTE_REVOKE; /* Treat error as unmount. */
1043
	}
1044
	if (NOTE_RENAME & kev->fflags) {
1045
		kq_handle_rename(kfnm, fnmo);
1046
	}
1047
	if ((NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_CLOSE_WRITE) & kev->fflags) {
1048
		/* Only count changes, do not prevent NOTE_DELETE event handling. */
1049
		if (0 == is_rate_lim_checked &&
1050
		    1 != kq_fnmo_rate_lim_check(fnmo)) {
1051
			kq_handle_changes(kfnm, fnmo);
1052
		}
1053
	}
1054
	if ((NOTE_DELETE | NOTE_REVOKE) & kev->fflags) {
1055
		/* No report about childs. */
1056
		kq_fnmo_clean(fnmo);
1057
		kfnm->cb_func(kfnm, fnmo, fnmo->udata, KF_EVENT_DELETED,
1058
		    fnmo->path, "", NULL);
1059
	}
1060
}
1061
1062
void *
1063
kq_fnm_proccess_events_proc(void *data) {
1064
	struct kevent kev;
1065
	kq_fnm_p kfnm = data;
1066
1067
	if (NULL == kfnm)
1068
		return (NULL);
1069
	/* Get and proccess events, no wait. */
1070
	while (0 < kevent(kfnm->fd, NULL, 0, &kev, 1, NULL)) {
1071
		kq_fnm_proccess_event(kfnm, &kev);
1072
	}
1073
	return (NULL);
1074
}
(-)devel/glib20/files/kqueue_fnm.h (+74 lines)
Line 0 Link Here
1
/*-
2
 * Copyright (c) 2016 - 2018 Rozhuk Ivan <rozhuk.im@gmail.com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions
7
 * are met:
8
 * 1. Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 * 2. Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
 * SUCH DAMAGE.
25
 *
26
 * Author: Rozhuk Ivan <rozhuk.im@gmail.com>
27
 *
28
 */
29
30
#ifndef __KQUEUE_FILE_NOTIFY_MONITOR_H__
31
#define __KQUEUE_FILE_NOTIFY_MONITOR_H__
32
33
#include <sys/param.h>
34
#include <sys/types.h>
35
#include <inttypes.h>
36
37
38
typedef struct kq_file_nonify_monitor_s		*kq_fnm_p;
39
typedef struct kq_file_nmon_obj_s		*kq_fnmo_p;
40
41
typedef void (*kfnm_event_handler_cb)(kq_fnm_p kfnm,
42
					kq_fnmo_p fnmo, void *udata,
43
					uint32_t event,
44
					const char *base,
45
					const char *filename,
46
					const char *new_filename);
47
#define KF_EVENT_NOT_CHANGED	0 /* Internal use. */
48
#define KF_EVENT_CREATED	1
49
#define KF_EVENT_DELETED	2
50
#define KF_EVENT_RENAMED	3
51
#define KF_EVENT_CHANGED	4
52
53
54
typedef struct kq_file_nonify_mon_settings_s {
55
	uint32_t	rate_limit_time_init;	/* Fire events for dir min interval, mseconds. */
56
	uint32_t	rate_limit_time_max;	/* Fire events for dir max interval, mseconds. */
57
	uint32_t	rate_limit_time_mul;	/* Fire events time increment, mseconds. */
58
	size_t		max_dir_files;		/* If dir contain more than n files - do not mon files changes. */
59
	int		mon_local_subfiles;	/* Enable monitoring files changes on local file systems. */
60
	int		mon_local_subdirs;	/* Also mon for subdirs changes . */
61
	const char 	**local_fs;		/* NULL terminated fs names list that threat as local. Keep utill kq_fnm_free() return. */
62
	const char	**non_local_fs;		/* NULL terminated fs names list that threat as not local. Keep utill kq_fnm_free() return. */
63
} kq_file_mon_settings_t, *kq_file_mon_settings_p;
64
65
kq_fnm_p	kq_fnm_create(kq_file_mon_settings_p s,
66
		    kfnm_event_handler_cb cb_func);
67
void		kq_fnm_free(kq_fnm_p kfnm);
68
69
kq_fnmo_p	kq_fnm_add(kq_fnm_p kfnm,
70
			    const char *path, void *udata);
71
void		kq_fnm_del(kq_fnm_p kfnm, kq_fnmo_p fnmo);
72
73
74
#endif /* __KQUEUE_FILE_NOTIFY_MONITOR_H__ */

Return to bug 214338