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

Collapse All | Expand All

(-)devel/glib20/Makefile (-1 / +10 lines)
Lines 48-59 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=	DEBUG MANPAGES NLS
51
OPTIONS_DEFINE=	DEBUG FAM_ALTBACKEND MANPAGES NLS
52
OPTIONS_DEFAULT=	MANPAGES
52
OPTIONS_DEFAULT=	MANPAGES
53
OPTIONS_SUB=	yes
53
OPTIONS_SUB=	yes
54
54
55
DEBUG_CONFIGURE_ON=	--enable-debug=yes
55
DEBUG_CONFIGURE_ON=	--enable-debug=yes
56
56
57
FAM_ALTBACKEND_DESC=	Alternate file monitor backend
58
57
MANPAGES_BUILD_DEPENDS=		docbook-xml>4.1.2:textproc/docbook-xml \
59
MANPAGES_BUILD_DEPENDS=		docbook-xml>4.1.2:textproc/docbook-xml \
58
				docbook-xsl>0:textproc/docbook-xsl
60
				docbook-xsl>0:textproc/docbook-xsl
59
MANPAGES_USE=			GNOME=libxslt:build
61
MANPAGES_USE=			GNOME=libxslt:build
Lines 93-98 Link Here
93
		s|-Werror|| ; \
95
		s|-Werror|| ; \
94
		s|#define HAVE_SYS_INOTIFY_H 1||' ${WRKSRC}/configure
96
		s|#define HAVE_SYS_INOTIFY_H 1||' ${WRKSRC}/configure
95
97
98
post-patch-FAM_ALTBACKEND-on:
99
	@${REINPLACE_CMD} -e '/^libkqueue_la_OBJECTS =/s|$$(am_libkqueue_la_OBJECTS)|libkqueue_la-gkqueuefilemonitor.lo libkqueue_la-kqueue-helper.lo|' \
100
		${WRKSRC}/gio/kqueue/Makefile.in
101
	@${CP} -f ${FILESDIR}/gkqueuefilemonitor.c ${WRKSRC}/gio/kqueue/gkqueuefilemonitor.c
102
	@${CP} -f ${FILESDIR}/kqueue_fnm.c ${WRKSRC}/gio/kqueue/kqueue-helper.c
103
	@${CP} ${FILESDIR}/kqueue_fnm.h ${WRKSRC}/gio/kqueue/kqueue_fnm.h
104
96
do-build-MANPAGES-on:
105
do-build-MANPAGES-on:
97
.for m in glib gio gobject
106
.for m in glib gio gobject
98
. for file in ${${m}_MAN}
107
. for file in ${${m}_MAN}
(-)devel/glib20/files/gkqueuefilemonitor.c (+224 lines)
Line 0 Link Here
1
/*-
2
 * Copyright (c) 2016 - 2019 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
#ifdef __clang__
42
#pragma clang diagnostic pop
43
#endif
44
45
/* Defaults. */
46
#ifndef KQUEUE_MON_RATE_LIMIT_TIME_INIT
47
#	define KQUEUE_MON_RATE_LIMIT_TIME_INIT		1000
48
#endif
49
50
#ifndef KQUEUE_MON_RATE_LIMIT_TIME_MAX
51
#	define KQUEUE_MON_RATE_LIMIT_TIME_MAX		8000
52
#endif
53
#ifndef KQUEUE_MON_RATE_LIMIT_TIME_MUL
54
#	define KQUEUE_MON_RATE_LIMIT_TIME_MUL		2
55
#endif
56
#ifndef KQUEUE_MON_MAX_DIR_FILES
57
#	define KQUEUE_MON_MAX_DIR_FILES			128
58
#endif
59
#ifndef KQUEUE_MON_LOCAL_SUBFILES
60
#	define KQUEUE_MON_LOCAL_SUBFILES		1
61
#endif
62
#ifndef KQUEUE_MON_LOCAL_SUBDIRS
63
#	define KQUEUE_MON_LOCAL_SUBDIRS			0
64
#endif
65
66
67
static GMutex			kqueue_lock;
68
static volatile kq_fnm_p	kqueue_fnm = NULL;
69
/* Exclude from file changes monitoring, watch only for dirs. */
70
static const char *non_local_fs[] = {
71
	"fusefs.sshfs",
72
	NULL
73
};
74
75
#define G_TYPE_KQUEUE_FILE_MONITOR      (g_kqueue_file_monitor_get_type())
76
#define G_KQUEUE_FILE_MONITOR(inst)     (G_TYPE_CHECK_INSTANCE_CAST((inst), \
77
					 G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor))
78
79
typedef GLocalFileMonitorClass	GKqueueFileMonitorClass;
80
81
typedef struct {
82
	GLocalFileMonitor	parent_instance;
83
	kq_fnme_p		fnme;
84
} GKqueueFileMonitor;
85
86
GType g_kqueue_file_monitor_get_type(void);
87
G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
88
       g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
89
               g_define_type_id,
90
               "kqueue",
91
               10))
92
93
94
static void
95
kqueue_event_handler(kq_fnm_p kfnm,
96
    kq_fnme_p fnme, void *udata, uint32_t event,
97
    const char *base, const char *filename, const char *new_filename) {
98
	static const uint32_t kfnm_to_glib_map[] = {
99
		0,				/* KF_EVENT_NOT_CHANGED */
100
		G_FILE_MONITOR_EVENT_CREATED,	/* KF_EVENT_CREATED */
101
		G_FILE_MONITOR_EVENT_DELETED,	/* KF_EVENT_DELETED */
102
		G_FILE_MONITOR_EVENT_RENAMED,	/* KF_EVENT_RENAMED */
103
		G_FILE_MONITOR_EVENT_CHANGED	/* KF_EVENT_CHANGED */
104
	};
105
106
	if (NULL == kfnm || NULL == filename || 0 == filename[0] ||
107
	    strchr(filename, '/') ||
108
	    KF_EVENT_CREATED > event ||
109
	    KF_EVENT_CHANGED < event)
110
		return;
111
112
	g_file_monitor_source_handle_event(udata,
113
	    kfnm_to_glib_map[event],
114
	    filename, new_filename, NULL,
115
	    g_get_monotonic_time());
116
}
117
118
static gboolean
119
g_kqueue_file_monitor_is_supported(void) {
120
	kq_file_mon_settings_t kfms;
121
122
	if (NULL != kqueue_fnm)
123
		return (TRUE);
124
	/* Init only once. */
125
	g_mutex_lock(&kqueue_lock);
126
	if (NULL != kqueue_fnm) {
127
		g_mutex_unlock(&kqueue_lock);
128
		return (TRUE); /* Initialized while wait lock. */
129
	}
130
131
	memset(&kfms, 0x00, sizeof(kq_file_mon_settings_t));
132
	kfms.rate_limit_time_init = KQUEUE_MON_RATE_LIMIT_TIME_INIT;
133
	kfms.rate_limit_time_max = KQUEUE_MON_RATE_LIMIT_TIME_MAX;
134
	kfms.rate_limit_time_mul = KQUEUE_MON_RATE_LIMIT_TIME_MUL;
135
	kfms.max_dir_files = KQUEUE_MON_MAX_DIR_FILES;
136
	kfms.mon_local_subfiles = KQUEUE_MON_LOCAL_SUBFILES;
137
	kfms.mon_local_subdirs = KQUEUE_MON_LOCAL_SUBDIRS;
138
	kfms.local_fs = NULL;
139
	kfms.non_local_fs = non_local_fs;
140
141
	kqueue_fnm = kq_fnm_create(&kfms, kqueue_event_handler);
142
	if (NULL == kqueue_fnm) {
143
		g_mutex_unlock(&kqueue_lock);
144
		return (FALSE); /* Init fail. */
145
	}
146
	g_mutex_unlock(&kqueue_lock);
147
148
	return (TRUE);
149
}
150
151
static gboolean
152
g_kqueue_file_monitor_cancel(GFileMonitor *monitor) {
153
	GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(monitor);
154
155
	kq_fnm_del(kqueue_fnm, gffm->fnme);
156
	gffm->fnme = NULL;
157
158
	return (TRUE);
159
}
160
161
static void
162
g_kqueue_file_monitor_finalize(GObject *object) {
163
	//GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(object);
164
165
	//g_mutex_lock(&kqueue_lock);
166
	//kq_fnm_free(kqueue_fnm);
167
	//kqueue_fnm = NULL;
168
	//g_mutex_unlock(&kqueue_lock);
169
	//G_OBJECT_CLASS(g_kqueue_file_monitor_parent_class)->finalize(object);
170
}
171
172
static void
173
g_kqueue_file_monitor_start(GLocalFileMonitor *local_monitor,
174
    const gchar *dirname, const gchar *basename,
175
    const gchar *filename, GFileMonitorSource *source) {
176
	GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(local_monitor);
177
178
	g_assert(NULL != kqueue_fnm);
179
	//g_source_ref((GSource*)source);
180
181
	if (NULL == filename) {
182
		filename = dirname;
183
	}
184
	gffm->fnme = kq_fnm_add(kqueue_fnm, filename, source);
185
}
186
187
static void
188
g_kqueue_file_monitor_init(GKqueueFileMonitor *monitor) {
189
190
}
191
192
static void
193
g_kqueue_file_monitor_class_init(GKqueueFileMonitorClass *class) {
194
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
195
	GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS(class);
196
197
	class->is_supported = g_kqueue_file_monitor_is_supported;
198
	class->start = g_kqueue_file_monitor_start;
199
	class->mount_notify = TRUE; /* TODO: ??? */
200
	file_monitor_class->cancel = g_kqueue_file_monitor_cancel;
201
	gobject_class->finalize = g_kqueue_file_monitor_finalize;
202
}
203
204
static void
205
g_kqueue_file_monitor_class_finalize(GKqueueFileMonitorClass *class) {
206
207
}
208
209
void
210
g_io_module_load(GIOModule *module) {
211
212
	g_type_module_use(G_TYPE_MODULE(module));
213
214
	g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
215
	    G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10);
216
	g_io_extension_point_implement(G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME,
217
	    G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10);
218
}
219
220
void
221
g_io_module_unload(GIOModule *module) {
222
223
	g_assert_not_reached();
224
}
(-)devel/glib20/files/kqueue_fnm.c (+1239 lines)
Line 0 Link Here
1
/*-
2
 * Copyright (c) 2016 - 2019 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
#include <sys/queue.h>
38
39
#include <inttypes.h>
40
#include <stdlib.h> /* malloc, exit */
41
#include <unistd.h> /* close, write, sysconf */
42
#include <string.h> /* bcopy, bzero, memcpy, memmove, memset, strerror... */
43
#include <dirent.h> /* opendir, readdir */
44
#include <errno.h>
45
#include <pthread.h>
46
#include <glib.h>
47
48
#include "kqueue_fnm.h"
49
50
51
/* Preallocate items count. */
52
#ifndef FILES_ALLOC_BLK_SIZE
53
#	define FILES_ALLOC_BLK_SIZE	32
54
#endif
55
56
57
typedef struct readdir_context_s {
58
	int		fd;
59
	uint8_t		*buf;
60
	size_t		buf_size;
61
	size_t		buf_used;
62
	size_t		buf_pos;
63
} readdir_ctx_t, *readdir_ctx_p;
64
65
66
typedef struct file_info_s { /* Directory file. */
67
	int		fd;		/* For notify kqueue(). */
68
	struct dirent 	de;		/* d_reclen used for action. */
69
	struct stat	sb;
70
} file_info_t, *file_info_p;
71
72
73
typedef struct kq_file_nonify_monitor_obj_s	*kq_fnmo_p;
74
75
typedef struct kq_file_nonify_monitor_entry_s {
76
	TAILQ_ENTRY(kq_file_nonify_monitor_entry_s) next;
77
	kq_fnmo_p	fnmo;
78
	void		*udata;
79
	volatile int	enabled;
80
} kq_fnme_t;
81
82
TAILQ_HEAD(kq_fnme_head, kq_file_nonify_monitor_entry_s);
83
84
typedef struct kq_file_nonify_monitor_obj_s {
85
	int		fd;		/* For notify kqueue(). */
86
	int		is_dir;
87
	int		is_local;	/* Is file system local. */
88
	struct stat	sb;
89
	char		path[(PATH_MAX + 2)];
90
	size_t		path_size;
91
	size_t		name_offset;	/* Parent path size. */
92
	uint32_t	rate_lim_cur_interval;	/* From rate_limit_time_init to rate_limit_time_max. 0 disabled. */
93
	size_t		rate_lim_ev_cnt;	/* Events count then rate_lim_cur_interval != 0 since last report. */
94
	sbintime_t	rate_lim_ev_last;	/* Last event time. */
95
	void		*udata;
96
	kq_fnm_p	kfnm;
97
	struct kq_fnme_head entry_head;
98
	/* For dir. */
99
	file_info_p	files;
100
	volatile size_t	files_count;
101
	size_t		files_allocated;
102
} kq_fnmo_t;
103
104
105
typedef struct kq_file_nonify_monitor_s {
106
	int		fd;		/* kqueue() fd. */
107
	int		pfd[2];		/* pipe queue specific. */
108
	GHashTable	*fnmo_cache;
109
	kfnm_event_handler_cb cb_func;	/* Callback on dir/file change. */
110
	kq_file_mon_settings_t s;
111
	sbintime_t	rate_lim_time_init; /* rate_limit_time_init */
112
	pthread_t	tid;
113
} kq_fnm_t;
114
115
116
typedef void (*kq_msg_cb)(void *arg);
117
118
typedef struct kq_file_mon_msg_pkt_s {
119
	size_t		magic;
120
	kq_msg_cb	msg_cb;
121
	void		*arg;
122
	size_t		chk_sum;
123
} kq_fnm_msg_pkt_t, *kq_fnm_msg_pkt_p;
124
125
#define KF_MSG_PKT_MAGIC	0xffddaa00
126
127
128
#ifndef O_NOATIME
129
#	define O_NOATIME	0
130
#endif
131
#ifndef O_EVTONLY
132
#	define O_EVTONLY	O_RDONLY
133
#endif
134
#define OPEN_FILE_FLAGS		(O_EVTONLY | O_NONBLOCK | O_NOFOLLOW | O_NOATIME | O_CLOEXEC)
135
136
#ifndef NOTE_CLOSE_WRITE
137
#	define NOTE_CLOSE_WRITE	0
138
#endif
139
#define EVFILT_VNODE_SUB_FLAGS	(NOTE_WRITE |				\
140
				NOTE_EXTEND |				\
141
				NOTE_ATTRIB |				\
142
				NOTE_LINK |				\
143
				NOTE_CLOSE_WRITE)
144
145
#define EVFILT_VNODE_FLAGS_ALL	(NOTE_DELETE |				\
146
				EVFILT_VNODE_SUB_FLAGS |		\
147
				NOTE_RENAME |				\
148
				NOTE_REVOKE)
149
150
#ifndef _GENERIC_DIRSIZ
151
#	define _GENERIC_DIRSIZ(__de)	MIN((__de)->d_reclen, sizeof(struct dirent))
152
#endif
153
154
#define IS_NAME_DOTS(__name)	('.' == (__name)[0] &&			\
155
				 ('\0' == (__name)[1] || 		\
156
				  ('.' == (__name)[1] && '\0' == (__name)[2])))
157
#define IS_DE_NAME_EQ(__de1, __de2)  (0 == mem_cmpn((__de1)->d_name,	\
158
						    (__de1)->d_namlen,	\
159
						    (__de2)->d_name,	\
160
						    (__de2)->d_namlen))
161
#define zalloc(__size)		calloc(1, (__size))
162
163
#if (!defined(HAVE_REALLOCARRAY) && (!defined(__FreeBSD_version) || __FreeBSD_version < 1100000))
164
#	define reallocarray(__mem, __size, __count)	realloc((__mem), ((__size) * (__count)))
165
#endif
166
167
/* To not depend from compiler version. */
168
#define MSTOSBT(__ms)		(sbintime_t)(((int64_t)__ms * (((uint64_t)1 << 63) / 500)) >> 32)
169
170
#ifndef TAILQ_FOREACH_SAFE
171
#define	TAILQ_FOREACH_SAFE(var, head, field, tvar)			\
172
	for ((var) = TAILQ_FIRST((head));				\
173
	    (var) && ((tvar) = TAILQ_NEXT((var), field), 1);		\
174
	    (var) = (tvar))
175
#endif
176
177
#ifndef CLOCK_MONOTONIC_FAST
178
#	define CLOCK_MONOTONIC_FAST	CLOCK_MONOTONIC
179
#endif
180
181
182
void 	*kq_fnm_proccess_events_proc(void *data);
183
184
static inline int
185
mem_cmpn(const void *buf1, const size_t buf1_size,
186
    const void *buf2, const size_t buf2_size) {
187
188
	if (buf1_size != buf2_size)
189
		return (((buf1_size > buf2_size) ? 127 : -127));
190
	if (0 == buf1_size || buf1 == buf2)
191
		return (0);
192
	if (NULL == buf1)
193
		return (-127);
194
	if (NULL == buf2)
195
		return (127);
196
	return (memcmp(buf1, buf2, buf1_size));
197
}
198
199
static int
200
realloc_items(void **items, const size_t item_size,
201
    size_t *allocated, const size_t alloc_blk_cnt, const size_t count) {
202
	size_t allocated_prev, allocated_new;
203
	uint8_t *items_new;
204
205
	if (NULL == items || 0 == item_size || NULL == allocated ||
206
	    0 == alloc_blk_cnt)
207
		return (EINVAL);
208
	allocated_prev = (*allocated);
209
	if (NULL != (*items) &&
210
	    allocated_prev > count &&
211
	    allocated_prev <= (count + alloc_blk_cnt))
212
		return (0);
213
	allocated_new = (((count / alloc_blk_cnt) + 1) * alloc_blk_cnt);
214
	items_new = (uint8_t*)reallocarray((*items), item_size, allocated_new);
215
	if (NULL == items_new) /* Realloc fail! */
216
		return (ENOMEM);
217
218
	if (allocated_new > allocated_prev) { /* Init new mem. */
219
		memset((items_new + (allocated_prev * item_size)), 0x00,
220
		    ((allocated_new - allocated_prev) * item_size));
221
	}
222
	(*items) = items_new;
223
	(*allocated) = allocated_new;
224
225
	return (0);
226
}
227
228
229
static int
230
readdir_start(int fd, struct stat *sb, size_t exp_count, readdir_ctx_p rdd) {
231
	size_t buf_size;
232
233
	if (-1 == fd || NULL == sb || NULL == rdd)
234
		return (EINVAL);
235
	if (-1 == lseek(fd, 0, SEEK_SET))
236
		return (errno);
237
	/* Calculate buf size for getdents(). */
238
	buf_size = MAX((size_t)sb->st_size, (exp_count * sizeof(struct dirent)));
239
	if (0 == buf_size) {
240
		buf_size = (16 * PAGE_SIZE);
241
	}
242
	/* Make buf size well aligned. */
243
	if (0 != sb->st_blksize) {
244
		if (powerof2(sb->st_blksize)) {
245
			buf_size = roundup2(buf_size, sb->st_blksize);
246
		} else {
247
			buf_size = roundup(buf_size, sb->st_blksize);
248
		}
249
	} else {
250
		buf_size = round_page(buf_size);
251
	}
252
	/* Init. */
253
	memset(rdd, 0x00, sizeof(readdir_ctx_t));
254
	rdd->buf = malloc(buf_size);
255
	if (NULL == rdd->buf)
256
		return (ENOMEM);
257
	rdd->buf_size = buf_size;
258
	rdd->fd = fd;
259
260
	return (0);
261
}
262
263
static void
264
readdir_free(readdir_ctx_p rdd) {
265
266
	if (NULL == rdd || NULL == rdd->buf)
267
		return;
268
	free(rdd->buf);
269
	memset(rdd, 0x00, sizeof(readdir_ctx_t));
270
}
271
272
static int
273
readdir_next(readdir_ctx_p rdd, struct dirent *de) {
274
	int error = 0;
275
	ssize_t ios;
276
	uint8_t *ptr;
277
278
	if (NULL == rdd || NULL == rdd->buf || NULL == de)
279
		return (EINVAL);
280
281
	for (;;) {
282
		if (rdd->buf_used <= rdd->buf_pos) {
283
			/* Called once if buf size calculated ok. */
284
			ios = getdents(rdd->fd, (char*)rdd->buf, rdd->buf_size);
285
			if (-1 == ios) {
286
				error = errno;
287
				break;
288
			}
289
			if (0 == ios) {
290
				error = ESPIPE; /* EOF. */
291
				break;
292
			}
293
			rdd->buf_used = (size_t)ios;
294
			rdd->buf_pos = 0;
295
		}
296
		/* Keep data aligned. */
297
		ptr = (rdd->buf + rdd->buf_pos);
298
		memcpy(de, ptr, (sizeof(struct dirent) - sizeof(de->d_name)));
299
		if (0 == de->d_reclen) {
300
			error = ESPIPE; /* EOF. */
301
			break;
302
		}
303
		rdd->buf_pos += de->d_reclen;
304
#ifdef DT_WHT
305
		if (DT_WHT == de->d_type)
306
			continue;
307
#endif
308
		memcpy(de, ptr, _GENERIC_DIRSIZ(de));
309
		if (!IS_NAME_DOTS(de->d_name))
310
			return (0); /* OK. */
311
	}
312
313
	/* Err or no more files. */
314
	readdir_free(rdd);
315
316
	return (error);
317
}
318
319
320
static int
321
file_info_find_ni(file_info_p files, size_t files_count,
322
    file_info_p fi, size_t *idx) {
323
	size_t i;
324
	mode_t st_ftype;
325
326
	if (NULL == files || NULL == fi || NULL == idx)
327
		return (0);
328
	st_ftype = (S_IFMT & fi->sb.st_mode);
329
	for (i = 0; i < files_count; i ++) {
330
		if ((S_IFMT & files[i].sb.st_mode) != st_ftype)
331
			continue;
332
		if ((fi->sb.st_ino != files[i].sb.st_ino ||
333
		     fi->de.d_fileno != files[i].de.d_fileno) &&
334
		    0 == IS_DE_NAME_EQ(&fi->de, &files[i].de))
335
			continue;
336
		(*idx) = i;
337
		return (1);
338
	}
339
	(*idx) = files_count;
340
	return (0);
341
}
342
343
static int
344
file_info_find_ino(file_info_p files, size_t files_count,
345
    file_info_p fi, size_t *idx) {
346
	size_t i;
347
	mode_t st_ftype;
348
349
	if (NULL == files || NULL == fi || NULL == idx)
350
		return (0);
351
	st_ftype = (S_IFMT & fi->sb.st_mode);
352
	for (i = 0; i < files_count; i ++) {
353
		if ((S_IFMT & files[i].sb.st_mode) != st_ftype ||
354
		    fi->sb.st_ino != files[i].sb.st_ino ||
355
		    fi->de.d_fileno != files[i].de.d_fileno)
356
			continue;
357
		(*idx) = i;
358
		return (1);
359
	}
360
	(*idx) = files_count;
361
	return (0);
362
}
363
364
static int
365
file_info_find_name(file_info_p files, size_t files_count,
366
    file_info_p fi, size_t *idx) {
367
	size_t i;
368
	mode_t st_ftype;
369
370
	if (NULL == files || NULL == fi || NULL == idx)
371
		return (0);
372
	st_ftype = (S_IFMT & fi->sb.st_mode);
373
	for (i = 0; i < files_count; i ++) {
374
		if ((S_IFMT & files[i].sb.st_mode) != st_ftype ||
375
		    0 == IS_DE_NAME_EQ(&fi->de, &files[i].de))
376
			continue;
377
		(*idx) = i;
378
		return (1);
379
	}
380
	(*idx) = files_count;
381
	return (0);
382
}
383
384
static void
385
file_info_fd_close(file_info_p files, size_t files_count) {
386
	size_t i;
387
388
	if (NULL == files || 0 == files_count)
389
		return;
390
	for (i = 0; i < files_count; i ++) {
391
		if (-1 == files[i].fd)
392
			continue;
393
		close(files[i].fd);
394
		files[i].fd = -1;
395
	}
396
}
397
398
399
static int
400
is_fs_local(struct statfs *stfs, const char **local_fs, const char **non_local_fs) {
401
	size_t i;
402
403
	if (NULL == stfs)
404
		return (0);
405
	/* White listed fs. */
406
	if (NULL != local_fs) {
407
		for (i = 0; NULL != local_fs[i]; i ++) {
408
			if (0 == strncmp(stfs->f_fstypename, local_fs[i],
409
			    sizeof(stfs->f_fstypename)))
410
				return (1);
411
		}
412
	}
413
	if (0 == (MNT_LOCAL & stfs->f_flags))
414
		return (0);
415
	/* Filter out black listed fs. */
416
	if (NULL != non_local_fs) {
417
		for (i = 0; NULL != non_local_fs[i]; i ++) {
418
			if (0 == strncmp(stfs->f_fstypename, non_local_fs[i],
419
			    sizeof(stfs->f_fstypename)))
420
				return (0);
421
		}
422
	}
423
	return (1);
424
}
425
426
427
static void
428
kq_fnmo_rate_lim_stop(kq_fnmo_p fnmo) {
429
	struct kevent kev;
430
431
	if (NULL == fnmo || -1 == fnmo->fd || 0 == fnmo->rate_lim_cur_interval)
432
		return;
433
	fnmo->rate_lim_cur_interval = 0;
434
	fnmo->rate_lim_ev_cnt = 0;
435
	EV_SET(&kev, fnmo->fd, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
436
	kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL);
437
}
438
439
static int
440
kq_fnmo_rate_lim_shedule_next(kq_fnmo_p fnmo) {
441
	u_short flags = (EV_ADD | EV_CLEAR | EV_ONESHOT);
442
	struct kevent kev;
443
444
	if (NULL == fnmo || -1 == fnmo->fd || 0 == fnmo->kfnm->s.rate_limit_time_init)
445
		return (EINVAL);
446
	if (0 == fnmo->rate_lim_cur_interval) { /* First call. */
447
		fnmo->rate_lim_cur_interval = fnmo->kfnm->s.rate_limit_time_init;
448
	} else {
449
		if (fnmo->rate_lim_cur_interval == fnmo->kfnm->s.rate_limit_time_max)
450
			return (0); /* No need to modify timer. */
451
		/* Increase rate limit interval. */
452
		fnmo->rate_lim_cur_interval *= fnmo->kfnm->s.rate_limit_time_mul;
453
	}
454
	if (fnmo->rate_lim_cur_interval >= fnmo->kfnm->s.rate_limit_time_max) {
455
		/* Check upper limit and shedule periodic timer with upper rate limit time. */
456
		flags &= ~EV_ONESHOT;
457
		fnmo->rate_lim_cur_interval = fnmo->kfnm->s.rate_limit_time_max;
458
	}
459
	EV_SET(&kev, fnmo->fd, EVFILT_TIMER, flags,
460
	    NOTE_MSECONDS, fnmo->rate_lim_cur_interval, fnmo);
461
	if (-1 == kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL)) {
462
		fnmo->rate_lim_cur_interval = 0;
463
		return (errno);
464
	}
465
	if (0 != (EV_ERROR & kev.flags)) {
466
		fnmo->rate_lim_cur_interval = 0;
467
		return ((int)kev.data);
468
	}
469
	return (0);
470
}
471
472
/* Return:
473
 * 0 for events that not handled
474
 * 1 for handled = rate limited
475
 * -1 on error.
476
 */
477
static int
478
kq_fnmo_rate_lim_check(kq_fnmo_p fnmo) {
479
	sbintime_t sbt, sbt_now;
480
	struct timespec ts;
481
482
	if (NULL == fnmo)
483
		return (-1);
484
	if (-1 == fnmo->fd ||
485
	    0 == fnmo->kfnm->s.rate_limit_time_init)
486
		return (0);
487
	if (0 != fnmo->rate_lim_cur_interval) {
488
		fnmo->rate_lim_ev_cnt ++; /* Count event, timer is active. */
489
		return (1);
490
	}
491
492
	/* Do we need to enable rate limit? */
493
	if (0 != clock_gettime(CLOCK_MONOTONIC_FAST, &ts))
494
		return (-1);
495
	sbt_now = tstosbt(ts);
496
	sbt = (fnmo->rate_lim_ev_last + fnmo->kfnm->rate_lim_time_init);
497
	fnmo->rate_lim_ev_last = sbt_now;
498
	if (sbt < sbt_now) /* Events rate to low. */
499
		return (0);
500
	/* Try to enable rate limit. */
501
	if (0 != kq_fnmo_rate_lim_shedule_next(fnmo))
502
		return (-1);
503
	/* Ok. */
504
	fnmo->rate_lim_ev_cnt ++;
505
506
	return (1);
507
}
508
509
static void
510
kq_fnmo_clean(kq_fnmo_p fnmo) {
511
512
	if (NULL == fnmo)
513
		return;
514
	if (-1 != fnmo->fd) {
515
		kq_fnmo_rate_lim_stop(fnmo);
516
		close(fnmo->fd);
517
		fnmo->fd = -1;
518
	}
519
	if (0 != fnmo->is_local) { /* Stop monitoring files/dirs. */
520
		file_info_fd_close(fnmo->files, fnmo->files_count);
521
	}
522
	free(fnmo->files);
523
	fnmo->files = NULL;
524
	fnmo->files_count = 0;
525
	fnmo->files_allocated = 0;
526
}
527
528
static void
529
kq_fnmo_free(kq_fnmo_p fnmo, const int remove_from_cache) {
530
531
	if (NULL == fnmo)
532
		return;
533
	kq_fnmo_clean(fnmo);
534
	if (0 != remove_from_cache &&
535
	    NULL != fnmo->kfnm &&
536
	    g_hash_table_lookup(fnmo->kfnm->fnmo_cache, fnmo->path) == fnmo) {
537
		g_hash_table_remove(fnmo->kfnm->fnmo_cache, fnmo->path);
538
	}
539
	free(fnmo);
540
}
541
542
static kq_fnmo_p
543
kq_fnmo_alloc(kq_fnm_p kfnm, const char *path, kq_fnme_p fnme) {
544
	kq_fnmo_p fnmo;
545
546
	if (NULL == kfnm || NULL == path)
547
		return (NULL);
548
	fnmo = zalloc(sizeof(kq_fnmo_t));
549
	if (NULL == fnmo)
550
		return (NULL);
551
	fnmo->fd = -1;
552
	/* Remember args. */
553
	fnmo->path_size = strlcpy(fnmo->path, path, PATH_MAX);
554
	/* Make sure that no trailing '/'. */
555
	while (1 < fnmo->path_size && '/' == fnmo->path[(fnmo->path_size - 1)]) {
556
		fnmo->path_size --;
557
		fnmo->path[fnmo->path_size] = 0;
558
	}
559
	fnmo->name_offset = fnmo->path_size;
560
	fnmo->kfnm = kfnm;
561
	TAILQ_INIT(&fnmo->entry_head);
562
	if (NULL != fnme) {
563
		TAILQ_INSERT_HEAD(&fnmo->entry_head, fnme, next);
564
	}
565
566
	return (fnmo);
567
}
568
569
static void
570
kq_fnmo_cb_func_call(kq_fnmo_p fnmo, uint32_t event,
571
    const char *base, const char *filename, const char *new_filename) {
572
	kq_fnm_p kfnm;
573
	kq_fnme_p fnme;
574
575
	if (NULL == fnmo)
576
		return;
577
	kfnm = fnmo->kfnm;
578
	TAILQ_FOREACH(fnme, &fnmo->entry_head, next) {
579
		if (0 == fnme->enabled) /* XXX: try lock here? */
580
			continue;
581
		kfnm->cb_func(kfnm, fnme, fnme->udata, event,
582
		    base, filename, new_filename);
583
	}
584
585
}
586
587
static int
588
kq_fnmo_readdir(kq_fnmo_p fnmo, size_t exp_count) {
589
	int error;
590
	struct dirent *de;
591
	file_info_p tmfi;
592
	readdir_ctx_t rdd;
593
594
	if (NULL == fnmo || 0 == fnmo->is_dir)
595
		return (EINVAL);
596
597
	free(fnmo->files);
598
	fnmo->files = NULL;
599
	fnmo->files_count = 0;
600
	fnmo->files_allocated = 0;
601
	/* Pre allocate. */
602
	if (0 != realloc_items((void**)&fnmo->files,
603
	    sizeof(file_info_t), &fnmo->files_allocated,
604
	    FILES_ALLOC_BLK_SIZE, (exp_count + 1)))
605
		return (ENOMEM);
606
607
	error = readdir_start(fnmo->fd, &fnmo->sb, exp_count, &rdd);
608
	if (0 != error)
609
		return (error);
610
	for (;;) {
611
		if (0 != realloc_items((void**)&fnmo->files,
612
		    sizeof(file_info_t), &fnmo->files_allocated,
613
		    FILES_ALLOC_BLK_SIZE, fnmo->files_count)) {
614
			free(fnmo->files);
615
			fnmo->files = NULL;
616
			fnmo->files_count = 0;
617
			fnmo->files_allocated = 0;
618
			readdir_free(&rdd);
619
			return (ENOMEM);
620
		}
621
		de = &fnmo->files[fnmo->files_count].de; /* Use short name. */
622
		/* Get file name from folder. */
623
		if (0 != readdir_next(&rdd, de))
624
			break;
625
		/* Get file attrs. */
626
		if (0 != fstatat(fnmo->fd, de->d_name,
627
		    &fnmo->files[fnmo->files_count].sb,
628
		    AT_SYMLINK_NOFOLLOW)) {
629
			memset(&fnmo->files[fnmo->files_count].sb, 0x00,
630
			    sizeof(struct stat));
631
		}
632
		fnmo->files[fnmo->files_count].fd = -1;
633
		fnmo->files_count ++;
634
	}
635
	/* Mem compact. */
636
	tmfi = reallocarray(fnmo->files, sizeof(file_info_t), (fnmo->files_count + 1));
637
	if (NULL != tmfi) { /* realloc ok. */
638
		fnmo->files = tmfi;
639
		fnmo->files_allocated = (fnmo->files_count + 1);
640
	}
641
642
	readdir_free(&rdd);
643
644
	return (0); /* OK. */
645
}
646
647
648
static void
649
kq_fnmo_fi_start(kq_fnmo_p fnmo, file_info_p fi) {
650
	struct kevent kev;
651
652
	if (NULL == fnmo || NULL == fi)
653
		return;
654
	fi->fd = openat(fnmo->fd, fi->de.d_name, OPEN_FILE_FLAGS);
655
	if (-1 == fi->fd)
656
		return;
657
	EV_SET(&kev, fi->fd, EVFILT_VNODE,
658
	    (EV_ADD | EV_CLEAR),
659
	    EVFILT_VNODE_SUB_FLAGS, 0, fnmo);
660
	kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL);
661
}
662
663
static int
664
kq_fnmo_is_fi_monitored(kq_fnmo_p fnmo, file_info_p fi) {
665
666
	if (NULL == fnmo)
667
		return (0);
668
	if (0 == fnmo->is_local ||
669
	    (0 != fnmo->kfnm->s.max_dir_files &&
670
	     fnmo->kfnm->s.max_dir_files < fnmo->files_count))
671
		return (0);
672
	if (NULL != fi &&
673
	    0 == fnmo->kfnm->s.mon_local_subdirs &&
674
	    S_ISDIR(fi->sb.st_mode))
675
		return (0);
676
	return (1);
677
}
678
679
static int
680
kq_fnmo_init(kq_fnmo_p fnmo, const int read_dir) {
681
	int error;
682
	size_t i;
683
	struct statfs stfs;
684
	struct kevent kev;
685
686
	if (NULL == fnmo)
687
		return (EINVAL);
688
689
	fnmo->fd = open(fnmo->path, OPEN_FILE_FLAGS);
690
	if (-1 == fnmo->fd) {
691
		error = errno;
692
		goto err_out;
693
	}
694
	if (0 != fstat(fnmo->fd, &fnmo->sb)) {
695
		error = errno;
696
		goto err_out;
697
	}
698
699
	/* Get parent folder name. */
700
	if (S_ISDIR(fnmo->sb.st_mode)) {
701
		fnmo->is_dir = 1;
702
	}
703
	while (0 < fnmo->name_offset && '/' != fnmo->path[(fnmo->name_offset - 1)]) {
704
		fnmo->name_offset --;
705
	}
706
707
	/* Is file system local? */
708
	if (0 != fnmo->is_dir &&
709
	    0 != fnmo->kfnm->s.mon_local_subfiles &&
710
	    0 == fstatfs(fnmo->fd, &stfs)) {
711
		fnmo->is_local = is_fs_local(&stfs, fnmo->kfnm->s.local_fs,
712
		    fnmo->kfnm->s.non_local_fs);
713
	}
714
715
	/* Dir special processing. */
716
	if (0 != fnmo->is_dir &&
717
	    0 != read_dir) {
718
		/* Read and remember dir content. */
719
		error = kq_fnmo_readdir(fnmo, 0);
720
		if (0 != error)
721
			goto err_out;
722
	}
723
	/* Add to kqueue. */
724
	EV_SET(&kev, fnmo->fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR),
725
	    EVFILT_VNODE_FLAGS_ALL, 0, fnmo);
726
	if (-1 == kevent(fnmo->kfnm->fd, &kev, 1, NULL, 0, NULL) ||
727
	    0 != (EV_ERROR & kev.flags)) {
728
		error = errno;
729
		goto err_out;
730
	}
731
	/* Add monitor sub files/dirs, ignory errors. */
732
	/* Check twice for performance reason. */
733
	if (0 != kq_fnmo_is_fi_monitored(fnmo, NULL)) {
734
		for (i = 0; i < fnmo->files_count; i ++) {
735
			if (0 != kq_fnmo_is_fi_monitored(fnmo, &fnmo->files[i])) {
736
				kq_fnmo_fi_start(fnmo, &fnmo->files[i]);
737
			}
738
		}
739
	}
740
741
	return (0); /* OK. */
742
743
err_out:
744
	kq_fnmo_clean(fnmo);
745
	return (error);
746
}
747
748
static void
749
kq_fnme_init_cb(void *arg) {
750
	kq_fnme_p fnme = arg;
751
	kq_fnmo_p fnmo;
752
	kq_fnm_p kfnm;
753
754
	if (NULL == fnme || NULL == fnme->fnmo)
755
		return;
756
	kfnm = fnme->fnmo->kfnm;
757
	/* Is in cache? */
758
	fnmo = g_hash_table_lookup(kfnm->fnmo_cache, fnme->fnmo->path);
759
	if (NULL == fnmo) {
760
		/* Init and add to cache. */
761
		g_hash_table_insert(kfnm->fnmo_cache,
762
		    fnme->fnmo->path, fnme->fnmo);
763
		kq_fnmo_init(fnme->fnmo, 1);
764
		return;
765
	}
766
	/* Found in cache, use it. */
767
	TAILQ_REMOVE(&fnme->fnmo->entry_head, fnme, next);
768
	kq_fnmo_free(fnme->fnmo, 0); /* Prevent remove from cache. */
769
	fnme->fnmo = fnmo;
770
	TAILQ_INSERT_HEAD(&fnmo->entry_head, fnme, next);
771
}
772
773
static void
774
kq_fnme_free(kq_fnme_p fnme, const int free_parent) {
775
776
	if (NULL == fnme)
777
		return;
778
	if (NULL != fnme->fnmo) {
779
		TAILQ_REMOVE(&fnme->fnmo->entry_head, fnme, next);
780
		if (0 != free_parent &&
781
		    TAILQ_EMPTY(&fnme->fnmo->entry_head)) {
782
			kq_fnmo_free(fnme->fnmo, 1); /* Remove frome cache on free. */
783
			fnme->fnmo = NULL;
784
		}
785
	}
786
	free(fnme);
787
}
788
789
static void
790
kq_fnme_free_cb(void *arg) {
791
792
	kq_fnme_free((kq_fnme_p)arg, 1);
793
}
794
795
796
static void
797
kq_handle_notify_removed(kq_fnmo_p fnmo) {
798
	size_t i;
799
800
	if (NULL == fnmo)
801
		return;
802
803
	if (0 != fnmo->is_dir) {
804
		/* Notify removed files first. */
805
		for (i = 0; i < fnmo->files_count; i ++) {
806
			kq_fnmo_cb_func_call(fnmo, KF_EVENT_DELETED,
807
			    fnmo->path, fnmo->files[i].de.d_name, NULL);
808
		}
809
	}
810
	fnmo->path[fnmo->name_offset - 1] = 0;
811
	kq_fnmo_cb_func_call(fnmo, KF_EVENT_DELETED, fnmo->path,
812
	    (fnmo->path + fnmo->name_offset), NULL);
813
	fnmo->path[fnmo->name_offset - 1] = '/';
814
	kq_fnmo_clean(fnmo);
815
}
816
817
static void
818
kq_handle_changes(kq_fnmo_p fnmo) {
819
	size_t i, k, files_count;
820
	file_info_p files;
821
822
	if (NULL == fnmo)
823
		return;
824
	if (0 != fstat(fnmo->fd, &fnmo->sb) ||
825
	    0 == fnmo->sb.st_nlink) {
826
		kq_handle_notify_removed(fnmo);
827
		return;
828
	}
829
	if (0 == fnmo->is_dir) {
830
		fnmo->path[fnmo->name_offset - 1] = 0;
831
		kq_fnmo_cb_func_call(fnmo, KF_EVENT_CHANGED, fnmo->path,
832
		    (fnmo->path + fnmo->name_offset), NULL);
833
		fnmo->path[fnmo->name_offset - 1] = '/';
834
		return;
835
	}
836
837
	/* Dir processing. */
838
839
	/* Save prev. */
840
	files = fnmo->files;
841
	files_count = fnmo->files_count;
842
	fnmo->files = NULL;
843
	fnmo->files_count = 0;
844
	/* Update dir. */
845
	if (0 != kq_fnmo_readdir(fnmo, files_count)) {
846
		/* Restore prev state on fail. */
847
		fnmo->files = files;
848
		fnmo->files_count = files_count;
849
		return;
850
	}
851
	/* Notify removed first. */
852
	for (i = 0; i < files_count; i ++) {
853
		if (0 != file_info_find_ni(fnmo->files, fnmo->files_count, &files[i], &k)) /* Deleted? */
854
			continue;
855
		if (-1 != files[i].fd) {
856
			close(files[i].fd);
857
			files[i].fd = -1;
858
		}
859
		kq_fnmo_cb_func_call(fnmo, KF_EVENT_DELETED, fnmo->path,
860
		    files[i].de.d_name, NULL);
861
	}
862
	/* Notify. */
863
	for (i = 0; i < fnmo->files_count; i ++) {
864
		/* Is new file/folder? */
865
		if (0 == file_info_find_ino(files, files_count, &fnmo->files[i], &k) &&
866
		    0 == file_info_find_name(files, files_count, &fnmo->files[i], &k)) { /* Add new. */
867
			/* Add monitor sub files/dirs, ignory errors. */
868
			if (0 != kq_fnmo_is_fi_monitored(fnmo, &fnmo->files[i])) {
869
				kq_fnmo_fi_start(fnmo, &fnmo->files[i]);
870
			}
871
			kq_fnmo_cb_func_call(fnmo, KF_EVENT_CREATED,
872
			    fnmo->path, fnmo->files[i].de.d_name, NULL);
873
			continue;
874
		}
875
		/* Keep file fd. */
876
		fnmo->files[i].fd = files[k].fd;
877
		files[k].fd = -1;
878
		/* Is renamed? */
879
		if (0 == IS_DE_NAME_EQ(&files[k].de, &fnmo->files[i].de)) {
880
			kq_fnmo_cb_func_call(fnmo, KF_EVENT_RENAMED,
881
			    fnmo->path, files[k].de.d_name,
882
			    fnmo->files[i].de.d_name);
883
			continue;
884
		}
885
		/* Is modified? */
886
		if (0 != memcmp(&fnmo->files[i].sb, &files[k].sb, sizeof(struct stat))) {
887
			kq_fnmo_cb_func_call(fnmo, KF_EVENT_CHANGED,
888
			    fnmo->path, fnmo->files[i].de.d_name, NULL);
889
			continue;
890
		}
891
		/* Not changed. */
892
	}
893
894
	/* Prevent FD leak die to race conditions.
895
	 * All fd must be -1, check this while debuging.
896
	 */
897
	file_info_fd_close(files, files_count);
898
	free(files);
899
}
900
901
static void
902
kq_handle_rename(kq_fnmo_p fnmo) {
903
	int up_dir_fd, found = 0;
904
	readdir_ctx_t rdd;
905
	struct dirent de;
906
	struct stat sb;
907
	char old_filename[(MAXNAMLEN + 2)];
908
	size_t old_filename_size;
909
910
	if (NULL == fnmo)
911
		return;
912
	if (0 != fstat(fnmo->fd, &fnmo->sb) ||
913
	    0 == fnmo->sb.st_nlink) {
914
notify_removed:
915
		kq_handle_notify_removed(fnmo);
916
		return;
917
	}
918
	/* Save old file name. */
919
	old_filename_size = (fnmo->path_size - fnmo->name_offset);
920
	memcpy(old_filename,
921
	    (fnmo->path + fnmo->name_offset),
922
	    old_filename_size);
923
	old_filename[old_filename_size] = 0;
924
925
	/* Get parent folder name. */
926
	fnmo->path[fnmo->name_offset] = 0;
927
	/* Try to open. */
928
	up_dir_fd = open(fnmo->path, (OPEN_FILE_FLAGS | O_DIRECTORY));
929
	/* Restore '/' after parent folder. */
930
	fnmo->path[fnmo->name_offset] = '/';
931
	if (-1 == up_dir_fd ||
932
	    0 != fstat(up_dir_fd, &sb) ||
933
	    0 != readdir_start(up_dir_fd, &sb, 0, &rdd)) {
934
		close(up_dir_fd);
935
		return;
936
	}
937
	/* Find new name by inode. */
938
	while (0 == readdir_next(&rdd, &de)) {
939
		if (0 == fstatat(up_dir_fd, de.d_name, &sb, AT_SYMLINK_NOFOLLOW) &&
940
		    0 == memcmp(&fnmo->sb, &sb, sizeof(struct stat))) {
941
			found ++;
942
			break;
943
		}
944
	}
945
	close(up_dir_fd);
946
	if (0 == found)
947
		goto notify_removed; /* Not found. */
948
	/* Update name. */
949
	if (PATH_MAX <= (fnmo->name_offset + de.d_namlen))
950
		return; /* Too long. */
951
	memcpy((fnmo->path + fnmo->name_offset), de.d_name, de.d_namlen);
952
	fnmo->path_size = (fnmo->name_offset + de.d_namlen);
953
	fnmo->path[fnmo->path_size] = 0;
954
	/* Notify. */
955
	kq_fnmo_cb_func_call(fnmo, KF_EVENT_RENAMED, fnmo->path,
956
	    old_filename, de.d_name);
957
}
958
959
960
static void
961
kq_fnm_delay_call_process(kq_fnm_p kfnm, kq_msg_cb forced_msg_cb) {
962
	ssize_t ios;
963
	kq_fnm_msg_pkt_t msg;
964
965
	for (;;) {
966
		ios = read(kfnm->pfd[0], &msg, sizeof(msg));
967
		if (0 >= ios)
968
			return;
969
		if (sizeof(msg) != ios ||
970
		    KF_MSG_PKT_MAGIC != msg.magic ||
971
		    (((size_t)msg.msg_cb) ^ ((size_t)msg.arg)) != msg.chk_sum)
972
			continue;
973
		if (NULL != forced_msg_cb) {
974
			forced_msg_cb(msg.arg);
975
			continue;
976
		}
977
		if (NULL == msg.msg_cb)
978
			continue;
979
		msg.msg_cb(msg.arg);
980
	}
981
}
982
983
static int
984
kq_fnm_delay_call(kq_fnm_p kfnm, kq_msg_cb msg_cb,
985
    void *arg) {
986
	kq_fnm_msg_pkt_t msg;
987
988
	if (NULL == kfnm || NULL == arg)
989
		return (EINVAL);
990
	msg.magic = KF_MSG_PKT_MAGIC;
991
	msg.msg_cb = msg_cb;
992
	msg.arg = arg;
993
	msg.chk_sum = (((size_t)msg.msg_cb) ^ ((size_t)msg.arg));
994
	if (sizeof(msg) == write(kfnm->pfd[1], &msg, sizeof(msg)))
995
		return (0);
996
	return (errno);
997
}
998
999
1000
static void
1001
kq_fnm_free_cb(void *arg) {
1002
	kq_fnm_p kfnm = arg;
1003
1004
	if (NULL == kfnm)
1005
		return;
1006
	close(kfnm->fd);
1007
	kfnm->fd = -1;
1008
}
1009
void
1010
kq_fnm_free(kq_fnm_p kfnm) {
1011
	GHashTableIter iter;
1012
	gpointer key;
1013
	kq_fnmo_p fnmo;
1014
	kq_fnme_p fnme, fnme_temp;
1015
1016
	if (NULL == kfnm)
1017
		return;
1018
	kq_fnm_delay_call(kfnm, kq_fnm_free_cb, kfnm);
1019
	pthread_join(kfnm->tid, NULL);
1020
	/* Free all in delay calls queue. */
1021
	close(kfnm->pfd[1]);
1022
	kq_fnm_delay_call_process(kfnm, kq_fnme_free_cb);
1023
	close(kfnm->pfd[0]);
1024
	/* Remove all objects. */
1025
	g_hash_table_iter_init(&iter, kfnm->fnmo_cache);
1026
	while (g_hash_table_iter_next(&iter, &key, (gpointer)&fnmo)) {
1027
		TAILQ_FOREACH_SAFE(fnme, &fnmo->entry_head, next, fnme_temp) {
1028
			kq_fnme_free(fnme, 0); /* Do not free fnmo here. */
1029
		}
1030
		g_hash_table_iter_remove(&iter); /* Remove from cache here. */
1031
		kq_fnmo_free(fnmo, 0); /* Prevent remove from cache. */
1032
	}
1033
	g_hash_table_destroy(kfnm->fnmo_cache);
1034
1035
	free(kfnm);
1036
}
1037
1038
kq_fnm_p
1039
kq_fnm_create(kq_file_mon_settings_p s, kfnm_event_handler_cb cb_func) {
1040
	kq_fnm_p kfnm;
1041
	struct kevent kev;
1042
1043
	if (NULL == s || NULL == cb_func)
1044
		return (NULL);
1045
	kfnm = zalloc(sizeof(kq_fnm_t));
1046
	if (NULL == kfnm)
1047
		return (NULL);
1048
	kfnm->fd = kqueue();
1049
	if (-1 == kfnm->fd)
1050
		goto err_out;
1051
	if (-1 == pipe2(kfnm->pfd, O_NONBLOCK))
1052
		goto err_out;
1053
	kfnm->fnmo_cache = g_hash_table_new(g_str_hash, g_str_equal);
1054
	kfnm->cb_func = cb_func;
1055
	memcpy(&kfnm->s, s, sizeof(kq_file_mon_settings_t));
1056
	if (kfnm->s.rate_limit_time_init >= kfnm->s.rate_limit_time_max) {
1057
		kfnm->s.rate_limit_time_max = kfnm->s.rate_limit_time_init;
1058
	}
1059
	if (0 == kfnm->s.rate_limit_time_mul) {
1060
		kfnm->s.rate_limit_time_mul ++;
1061
	}
1062
	kfnm->rate_lim_time_init = MSTOSBT(kfnm->s.rate_limit_time_init);
1063
1064
	EV_SET(&kev, kfnm->pfd[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
1065
	if (-1 == kevent(kfnm->fd, &kev, 1, NULL, 0, NULL) ||
1066
	    0 != (EV_ERROR & kev.flags))
1067
		goto err_out;
1068
	if (0 != pthread_create(&kfnm->tid, NULL,
1069
	    kq_fnm_proccess_events_proc, kfnm))
1070
		goto err_out;
1071
1072
	return (kfnm);
1073
1074
err_out:
1075
	kq_fnm_free(kfnm);
1076
	return (NULL);
1077
}
1078
1079
kq_fnme_p
1080
kq_fnm_add(kq_fnm_p kfnm, const char *path, void *udata) {
1081
	int error;
1082
	kq_fnme_p fnme;
1083
1084
	if (NULL == kfnm || NULL == path)
1085
		return (NULL);
1086
	fnme = zalloc(sizeof(kq_fnme_t));
1087
	if (NULL == fnme)
1088
		return (NULL);
1089
	fnme->fnmo = kq_fnmo_alloc(kfnm, path, fnme);
1090
	if (NULL == fnme->fnmo)
1091
		goto err_out;
1092
	fnme->udata = udata;
1093
	fnme->enabled = 1;
1094
	/* Shedule delay call to init. */
1095
	error = kq_fnm_delay_call(kfnm, kq_fnme_init_cb, fnme);
1096
	if (0 != error) { /* Error, do no directly init to avoid freezes. */
1097
		kq_fnmo_free(fnme->fnmo, 0); /* Prevent remove from cache. */
1098
err_out:
1099
		kq_fnme_free(fnme, 0); /* Do not free fnmo here. */
1100
		return (NULL);
1101
	}
1102
	return (fnme);
1103
}
1104
1105
void
1106
kq_fnm_del(kq_fnm_p kfnm, kq_fnme_p fnme) {
1107
	int error;
1108
1109
	if (NULL == kfnm || NULL == fnme)
1110
		return;
1111
	/* Cancel notifications. */
1112
	/* XXX: lock here? */
1113
	fnme->enabled = 0;
1114
	/* Shedule delay call to free. */
1115
	error = kq_fnm_delay_call(kfnm, kq_fnme_free_cb, fnme);
1116
	if (0 == error)
1117
		return;
1118
	/* Error, free directly. */
1119
	kq_fnme_free(fnme, 1); /* Also free fnmo. */
1120
}
1121
1122
1123
static void
1124
kq_fnm_proccess_event(kq_fnm_p kfnm, struct kevent *kev) {
1125
	int error;
1126
	kq_fnmo_p fnmo;
1127
	file_info_p fi;
1128
	size_t i;
1129
	int is_rate_lim_checked = 0;
1130
	struct stat sb;
1131
1132
	if (NULL == kfnm || NULL == kev)
1133
		return;
1134
1135
	/* Handle delay calls. */
1136
	if (kev->ident == (uintptr_t)kfnm->pfd[0]) {
1137
		if (kev->filter == EVFILT_READ) {
1138
			kq_fnm_delay_call_process(kfnm, NULL);
1139
		}
1140
		return;
1141
	}
1142
1143
	if (0 == kev->udata)
1144
		return; /* No associated data, skip. */
1145
	fnmo = (kq_fnmo_p)kev->udata;
1146
1147
	/* FS delayed notifications. */
1148
	if (EVFILT_TIMER == kev->filter) {
1149
		if (0 == fnmo->rate_lim_ev_cnt) {
1150
			/* No delayed events, disable rate limit polling. */
1151
			kq_fnmo_rate_lim_stop(fnmo);
1152
			return;
1153
		}
1154
		fnmo->rate_lim_ev_cnt = 0; /* Reset counter. */
1155
		kq_fnmo_rate_lim_shedule_next(fnmo);
1156
		kq_handle_changes(fnmo);
1157
		return;
1158
	}
1159
1160
	/* FS notifications. */
1161
	if (EVFILT_VNODE != kev->filter)
1162
		return; /* Unknown event, skip. */
1163
	/* Subdir/file */
1164
	if (kev->ident != (uintptr_t)fnmo->fd) {
1165
		/* Is files changes rate limited? */
1166
		if (1 == kq_fnmo_rate_lim_check(fnmo))
1167
			return;
1168
		is_rate_lim_checked ++;
1169
		/* Try to find file and check it, without call kq_handle_changes(). */
1170
		fi = NULL;
1171
		for (i = 0; i < fnmo->files_count; i ++) {
1172
			if (kev->ident != (uintptr_t)fnmo->files[i].fd)
1173
				continue;
1174
			fi = &fnmo->files[i];
1175
			break;
1176
		}
1177
		if (NULL != fi) {
1178
			/* Get file attrs. */
1179
			if (0 != fstat(fi->fd, &sb)) {
1180
				memset(&sb, 0x00, sizeof(struct stat));
1181
			}
1182
			/* Is modified? */
1183
			if (0 != memcmp(&fi->sb, &sb, sizeof(struct stat))) {
1184
				memcpy(&fi->sb, &sb, sizeof(struct stat));
1185
				kq_fnmo_cb_func_call(fnmo, KF_EVENT_CHANGED,
1186
				    fnmo->path, fi->de.d_name, NULL);
1187
				return;
1188
			}
1189
		}
1190
		/* fd not found or changes not found, rescan dir. */
1191
		kev->fflags = NOTE_WRITE;
1192
	}
1193
	/* Monitored object. */
1194
	/* All flags from EVFILT_VNODE_FLAGS_ALL must be handled here. */
1195
	if (EV_ERROR & kev->flags) {
1196
		kev->fflags |= NOTE_REVOKE; /* Treat error as unmount. */
1197
	}
1198
	if (NOTE_RENAME & kev->fflags) {
1199
		kq_handle_rename(fnmo);
1200
	}
1201
	if ((NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_CLOSE_WRITE) & kev->fflags) {
1202
		/* Only count changes, do not prevent NOTE_DELETE event handling. */
1203
		if (0 == is_rate_lim_checked &&
1204
		    1 != kq_fnmo_rate_lim_check(fnmo)) {
1205
			kq_handle_changes(fnmo);
1206
		}
1207
	}
1208
	if ((NOTE_DELETE | NOTE_REVOKE) & kev->fflags) {
1209
		/* No report about childs. */
1210
		if (0 != fnmo->is_dir) {
1211
			/* Try to reopen mount point after unmount. */
1212
			error = kq_fnmo_init(fnmo, 0);
1213
			if (0 == error) {
1214
				/* This will reread dir and notify about
1215
				 * deleted and new files. */
1216
				kq_handle_changes(fnmo);
1217
			}
1218
		} else {
1219
			error = -1;
1220
		}
1221
		if (0 != error) {
1222
			kq_handle_notify_removed(fnmo);
1223
		}
1224
	}
1225
}
1226
1227
void *
1228
kq_fnm_proccess_events_proc(void *data) {
1229
	struct kevent kev;
1230
	kq_fnm_p kfnm = data;
1231
1232
	if (NULL == kfnm)
1233
		return (NULL);
1234
	/* Get and proccess events, no wait. */
1235
	while (0 < kevent(kfnm->fd, NULL, 0, &kev, 1, NULL)) {
1236
		kq_fnm_proccess_event(kfnm, &kev);
1237
	}
1238
	return (NULL);
1239
}
(-)devel/glib20/files/kqueue_fnm.h (+74 lines)
Line 0 Link Here
1
/*-
2
 * Copyright (c) 2016 - 2019 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_nonify_monitor_entry_s	*kq_fnme_p;
40
41
typedef void (*kfnm_event_handler_cb)(kq_fnm_p kfnm,
42
					kq_fnme_p fnme, 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_fnme_p	kq_fnm_add(kq_fnm_p kfnm,
70
			    const char *path, void *udata);
71
void		kq_fnm_del(kq_fnm_p kfnm, kq_fnme_p fnme);
72
73
74
#endif /* __KQUEUE_FILE_NOTIFY_MONITOR_H__ */

Return to bug 214338