Index: devel/glib20/Makefile =================================================================== --- devel/glib20/Makefile (revision 454512) +++ devel/glib20/Makefile (working copy) @@ -48,7 +48,7 @@ glib-compile-resources.1 gresource.1 gdbus-codegen.1 gobject_MAN= glib-genmarshal.1 glib-mkenums.1 gobject-query.1 -OPTIONS_DEFINE= COLLATION_FIX DEBUG NLS +OPTIONS_DEFINE= COLLATION_FIX DEBUG FAM_ALTBACKEND NLS OPTIONS_SUB= yes # libc collation was fixed by https://svnweb.freebsd.org/changeset/base/290494 COLLATION_FIX_DESC= Use ICU for UTF-8 string collation (if libc is broken) @@ -65,6 +65,11 @@ # http://www.freebsd.org/cgi/query-pr.cgi?pr=175930 see comment by ed@ #COLLATION_FIX_CONFIGURE_ENV+= CFLAGS="-D__STDC_ISO_10646__" +FAM_ALTBACKEND_DESC= Alternate file monitor backend +FAM_ALTBACKEND_USES+= autoreconf:build +FAM_ALTBACKEND_EXTRA_PATCHES= ${FILESDIR}/extra-patch-gio_kqueue_Makefile.am + + .include # doesn't build yet @@ -98,6 +103,12 @@ s|-Werror|| ; \ s|#define HAVE_SYS_INOTIFY_H 1||' ${WRKSRC}/configure +post-patch-FAM_ALTBACKEND-on: + @${CP} -f ${FILESDIR}/gkqueuefilemonitor.c.in ${WRKSRC}/gio/kqueue/gkqueuefilemonitor.c + @${CP} ${FILESDIR}/kqueue_fnm.c.in ${WRKSRC}/gio/kqueue/kqueue_fnm.c + @${CP} ${FILESDIR}/kqueue_fnm.h.in ${WRKSRC}/gio/kqueue/kqueue_fnm.h + @(cd ${WRKSRC} && ${AUTORECONF} -fvi) + post-install: @${MKDIR} ${STAGEDIR}${PREFIX}/share/GConf/gsettings @${MKDIR} ${STAGEDIR}${PREFIX}/lib/gio/modules Index: devel/glib20/files/extra-patch-gio_kqueue_Makefile.am =================================================================== --- devel/glib20/files/extra-patch-gio_kqueue_Makefile.am (nonexistent) +++ devel/glib20/files/extra-patch-gio_kqueue_Makefile.am (working copy) @@ -0,0 +1,26 @@ +--- gio/kqueue/Makefile.am.orig 2015-10-14 14:41:16.000000000 +0300 ++++ gio/kqueue/Makefile.am 2016-11-06 05:08:37.646089000 +0300 +@@ -4,21 +4,8 @@ + + libkqueue_la_SOURCES = \ + gkqueuefilemonitor.c \ +- gkqueuefilemonitor.h \ +- kqueue-helper.c \ +- kqueue-helper.h \ +- kqueue-thread.c \ +- kqueue-thread.h \ +- kqueue-sub.c \ +- kqueue-sub.h \ +- kqueue-missing.c \ +- kqueue-missing.h \ +- kqueue-utils.c \ +- kqueue-utils.h \ +- kqueue-exclusions.c \ +- kqueue-exclusions.h \ +- dep-list.c \ +- dep-list.h \ ++ kqueue_fnm.c \ ++ kqueue_fnm.h \ + $(NULL) + + libkqueue_la_CFLAGS = \ Index: devel/glib20/files/gkqueuefilemonitor.c.in =================================================================== --- devel/glib20/files/gkqueuefilemonitor.c.in (nonexistent) +++ devel/glib20/files/gkqueuefilemonitor.c.in (working copy) @@ -0,0 +1,201 @@ +/*- + * Copyright (c) 2016 - 2017 Rozhuk Ivan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Rozhuk Ivan + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include "glib-private.h" +#include +#include "kqueue_fnm.h" + +/* Defaults. */ +#ifndef KQUEUE_USE_THREAD +#define KQUEUE_USE_THREAD 1 +#endif + +#ifndef KQUEUE_MON_LOCAL_SUBFILES +#define KQUEUE_MON_LOCAL_SUBFILES 1 +#endif + + +static GMutex kqueue_lock; +static volatile kqueue_fnm_p kqueue_fnm = NULL; + +#define G_TYPE_KQUEUE_FILE_MONITOR (g_kqueue_file_monitor_get_type()) +#define G_KQUEUE_FILE_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST((inst), \ + G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor)) + +typedef GLocalFileMonitorClass GKqueueFileMonitorClass; + +typedef struct { + GLocalFileMonitor parent_instance; + kqueue_file_mon_data_p fmd; +} GKqueueFileMonitor; + +GType g_kqueue_file_monitor_get_type(void); +G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR, + g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME, + g_define_type_id, + "kqueue", + 10)) + + +static void +kqueue_event_handler(kqueue_fnm_p kfnm, + kqueue_file_mon_data_p fmd, void *udata, uint32_t event, + const char *base, const char *filename, const char *new_filename) { + static const uint32_t kfnm_to_glib_map[] = { + 0, /* KF_EVENT_NOT_CHANGED */ + G_FILE_MONITOR_EVENT_CREATED, /* KF_EVENT_CREATED */ + G_FILE_MONITOR_EVENT_DELETED, /* KF_EVENT_DELETED */ + G_FILE_MONITOR_EVENT_RENAMED, /* KF_EVENT_RENAMED */ + G_FILE_MONITOR_EVENT_CHANGED /* KF_EVENT_CHANGED */ + }; + + if (NULL == kfnm || NULL == filename || + KF_EVENT_CREATED > event || + KF_EVENT_CHANGED < event) + return; + g_file_monitor_source_handle_event(udata, + kfnm_to_glib_map[event], + filename, new_filename, NULL, + g_get_monotonic_time()); +} + +static gboolean +g_kqueue_file_monitor_is_supported(void) { + kqueue_file_mon_settings_t kfms; + + if (NULL != kqueue_fnm) + return (TRUE); + /* Init only once. */ + g_mutex_lock(&kqueue_lock); + if (NULL != kqueue_fnm) { + g_mutex_unlock(&kqueue_lock); + return (TRUE); /* Initialized while wait lock. */ + } + + kfms.use_thread = KQUEUE_USE_THREAD; + kfms.mon_local_subfiles = KQUEUE_MON_LOCAL_SUBFILES; + kqueue_fnm = kqueue_fnm_create(&kfms, kqueue_event_handler); + if (NULL == kqueue_fnm) { + g_mutex_unlock(&kqueue_lock); + return (FALSE); /* Init fail. */ + } + g_mutex_unlock(&kqueue_lock); + + return (TRUE); +} + +static gboolean +g_kqueue_file_monitor_cancel(GFileMonitor *monitor) { + GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(monitor); + + kqueue_fnm_del(kqueue_fnm, gffm->fmd); + gffm->fmd = NULL; + + return (TRUE); +} + +static void +g_kqueue_file_monitor_finalize(GObject *object) { + GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(object); + + kqueue_fnm_del(kqueue_fnm, gffm->fmd); + gffm->fmd = NULL; +} + +static void +g_kqueue_file_monitor_start(GLocalFileMonitor *local_monitor, + const gchar *dirname, const gchar *basename, + const gchar *filename, GFileMonitorSource *source) { + GKqueueFileMonitor *gffm = G_KQUEUE_FILE_MONITOR(local_monitor); + + g_assert(NULL != kqueue_fnm); + g_source_ref((GSource*)source); + + if (NULL == filename) { + filename = dirname; + } + gffm->fmd = kqueue_fnm_add(kqueue_fnm, filename, source); +} + +static void +g_kqueue_file_monitor_init(GKqueueFileMonitor *monitor) { + +} + +static void +g_kqueue_file_monitor_class_init(GKqueueFileMonitorClass *class) { + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS(class); + + class->is_supported = g_kqueue_file_monitor_is_supported; + class->start = g_kqueue_file_monitor_start; + class->mount_notify = TRUE; /* TODO: ??? */ + file_monitor_class->cancel = g_kqueue_file_monitor_cancel; + gobject_class->finalize = g_kqueue_file_monitor_finalize; +} + +static void +g_kqueue_file_monitor_class_finalize(GKqueueFileMonitorClass *class) { + +} + +void +g_io_module_load(GIOModule *module) { + + g_type_module_use(G_TYPE_MODULE(module)); + + g_io_extension_point_implement(G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME, + G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10); + g_io_extension_point_implement(G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME, + G_TYPE_KQUEUE_FILE_MONITOR, "kqueue", 10); +} + +void +g_io_module_unload(GIOModule *module) { + + g_assert_not_reached(); +} + +char ** +g_io_module_query(void) { + char *eps[] = { + G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME, + G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME, + NULL + }; + + return (g_strdupv(eps)); +} Index: devel/glib20/files/kqueue_fnm.c.in =================================================================== --- devel/glib20/files/kqueue_fnm.c.in (nonexistent) +++ devel/glib20/files/kqueue_fnm.c.in (working copy) @@ -0,0 +1,866 @@ +/*- + * Copyright (c) 2016 - 2017 Rozhuk Ivan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Rozhuk Ivan + * + */ + +#include +#include +#include +#include +#include +#include /* open, fcntl */ + +#include +#include /* malloc, exit */ +#include /* close, write, sysconf */ +#include /* bcopy, bzero, memcpy, memmove, memset, strerror... */ +#include /* opendir, readdir */ +#include +#include + +#include "kqueue_fnm.h" + + +/* Preallocate items count. */ +#ifndef FILES_ALLOC_BLK_SIZE +# define FILES_ALLOC_BLK_SIZE 32 +#endif + +#define EVENT_UDATA_TYPE_FILE_INFO 0 +#define EVENT_UDATA_TYPE_FILE_MON_DATA 1 + + +typedef struct file_info_s { /* Directory file. */ + uint32_t utype; /* Allways EVENT_UDATA_TYPE_FILE_INFO */ + int fd; /* For notify kqueue(). */ + struct dirent de; /* d_reclen used for action. */ + struct stat sb; + kqueue_file_mon_data_p fmd; +} file_info_t, *file_info_p; + + +typedef struct readdir_data_s { + int fd; + uint8_t *buf; + size_t buf_size; + size_t buf_used; + size_t buf_pos; +} readdir_data_t, *readdir_data_p; + + +typedef struct kqueue_file_mon_data_s { + uint32_t utype; /* Allways EVENT_UDATA_TYPE_FILE_MON_DATA */ + int fd; /* For notify kqueue(). */ + int is_dir; + int is_local; /* Is file system local. */ + char path[(PATH_MAX + 2)]; + size_t path_size; + size_t name_offset; /* Parent path size. */ + size_t st_blksize; + void *udata; + kqueue_fnm_p kfnm; + /* For dir. */ + file_info_p files; + volatile size_t files_count; + size_t files_allocated; +} kqueue_file_mon_data_t; + + +typedef struct kqueue_file_nonify_monitor_s { + int fd; /* kqueue() fd. */ + int pfd[2]; /* pipe queue specific. */ + uint8_t *tmpbuf; + size_t tmpbuf_size; + kfnm_event_handler_cb cb_func; /* Callback on dir/file change. */ + kqueue_file_mon_settings_t s; + pthread_t tid; +} kqueue_fnm_t; + + +typedef void (*kq_msg_cb)(kqueue_file_mon_data_p fmd); + +typedef struct kqueue_file_mon_msg_pkt_s { + size_t magic; + kq_msg_cb msg_cb; + kqueue_file_mon_data_p fmd; + size_t chk_sum; +} kqueue_fnm_msg_pkt_t, *kqueue_fnm_msg_pkt_p; + +#define KF_MSG_PKT_MAGIC 0xffddaa00 + + +#ifndef O_NOATIME +# define O_NOATIME 0 +#endif +#ifndef O_EVTONLY +# define O_EVTONLY O_RDONLY +#endif +#define OPEN_FILE_FLAGS (O_EVTONLY | O_NONBLOCK | O_NOFOLLOW | O_NOATIME | O_CLOEXEC) + +#define EVFILT_VNODE_FLAGS_ALL (NOTE_DELETE | \ + NOTE_WRITE | \ + NOTE_EXTEND | \ + NOTE_ATTRIB | \ + NOTE_LINK | \ + NOTE_RENAME | \ + NOTE_REVOKE | \ + NOTE_CLOSE_WRITE) +#define EVFILT_VNODE_SUB_FLAGS (NOTE_WRITE | \ + NOTE_EXTEND | \ + NOTE_ATTRIB | \ + NOTE_LINK | \ + NOTE_CLOSE_WRITE) + +#ifndef _GENERIC_DIRSIZ +# define _GENERIC_DIRSIZ(__de) MIN((__de)->d_reclen, sizeof(struct dirent)) +#endif + +#define IS_NAME_DOTS(__name) ('.' == (__name)[0] && \ + ('\0' == (__name)[1] || \ + ('.' == (__name)[1] && '\0' == (__name)[2]))) +#define IS_DE_NAME_EQ(__de1, __de2) (0 == mem_cmpn((__de1)->d_name, \ + (__de1)->d_namlen, \ + (__de2)->d_name, \ + (__de2)->d_namlen)) +#define zalloc(__size) calloc(1, (__size)) + +void *kqueue_fnm_proccess_events_proc(void *data); + +static inline int +mem_cmpn(const void *buf1, const size_t buf1_size, + const void *buf2, const size_t buf2_size) { + + if (buf1_size != buf2_size) + return (((buf1_size > buf2_size) ? 127 : -127)); + if (0 == buf1_size || buf1 == buf2) + return (0); + if (NULL == buf1) + return (-127); + if (NULL == buf2) + return (127); + return (memcmp(buf1, buf2, buf1_size)); +} + +static inline int +realloc_items(void **items, const size_t item_size, + size_t *allocated, const size_t alloc_blk_cnt, const size_t count) { + size_t allocated_prev, allocated_new; + uint8_t *items_new; + + if (NULL == items || 0 == item_size || NULL == allocated || + 0 == alloc_blk_cnt) + return (EINVAL); + allocated_prev = (*allocated); + if (NULL != (*items) && + allocated_prev > count && + allocated_prev <= (count + alloc_blk_cnt)) + return (0); + allocated_new = (((count / alloc_blk_cnt) + 1) * alloc_blk_cnt); +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 1100000) || defined(HAVE_REALLOCARRAY) + items_new = (uint8_t*)reallocarray((*items), item_size, allocated_new); +#else /* For old BSD systems. */ + items_new = (uint8_t*)realloc((*items), (item_size * allocated_new)); +#endif + if (NULL == items_new) /* Realloc fail! */ + return (ENOMEM); + + if (allocated_new > allocated_prev) { /* Init new mem. */ + memset((items_new + (allocated_prev * item_size)), 0x00, + ((allocated_new - allocated_prev) * item_size)); + } + (*items) = items_new; + (*allocated) = allocated_new; + + return (0); +} + + +static int +readdir_data_start(int fd, uint8_t *buf, size_t buf_size, readdir_data_p rdd) { + + if (-1 == fd || NULL == buf || 0 == buf_size || NULL == rdd) + return (EINVAL); + if (-1 == lseek(fd, 0, SEEK_SET)) + return (errno); + memset(rdd, 0x00, sizeof(readdir_data_t)); + rdd->fd = fd; + rdd->buf = buf; + rdd->buf_size = buf_size; + + return (0); +} + +static int +readdir_data_next(readdir_data_p rdd, struct dirent *de) { + int ios; + uint8_t *ptr; + + if (NULL == rdd || NULL == de) + return (EINVAL); + +retry: + if (rdd->buf_used <= rdd->buf_pos) { + ios = getdents(rdd->fd, (char*)rdd->buf, (int)rdd->buf_size); + if (-1 == ios) + return (errno); + rdd->buf_used = (size_t)ios; + rdd->buf_pos = 0; + if (0 == ios) + return (ESPIPE); /* EOF. */ + } + /* Keep data aligned. */ + ptr = (rdd->buf + rdd->buf_pos); + memcpy(de, ptr, (sizeof(struct dirent) - sizeof(de->d_name))); + if (0 == de->d_reclen) + return (ESPIPE); /* EOF. */ + rdd->buf_pos += de->d_reclen; +#ifdef DT_WHT + if (DT_WHT == de->d_type) + goto retry; +#endif + memcpy(de, ptr, _GENERIC_DIRSIZ(de)); + if (IS_NAME_DOTS(de->d_name)) + goto retry; + return (0); +} + + +static int +file_info_find_de_fileno(file_info_p files, size_t files_count, + struct dirent *de, size_t *idx) { + size_t i; + + if (NULL == files || NULL == de || NULL == idx) + return (0); + for (i = 0; i < files_count; i ++) { + if (de->d_type != files[i].de.d_type || + de->d_fileno != files[i].de.d_fileno) + continue; + (*idx) = i; + return (1); + } + (*idx) = files_count; + return (0); +} + +static int +file_info_find_de_name(file_info_p files, size_t files_count, + const size_t skip_idx, struct dirent *de, size_t *idx) { + size_t i; + + if (NULL == files || NULL == de || NULL == idx) + return (0); + for (i = 0; i < files_count; i ++) { + if (i == skip_idx || + de->d_type != files[i].de.d_type || + 0 == IS_DE_NAME_EQ(de, &files[i].de)) + continue; + (*idx) = i; + return (1); + } + (*idx) = files_count; + return (0); +} + + +static int +is_fs_local(struct statfs *stfs) { + + if (NULL == stfs) + return (0); + if (0 == (MNT_LOCAL & stfs->f_flags)) + return (0); + /* fusefs exceptions check. */ + if (0 != memcmp(stfs->f_fstypename, "fusefs.", 7)) + return (1); /* Not fusefs. */ + if (0 == memcmp(&stfs->f_fstypename[7], "sshfs", 5)) + return (0); + return (1); +} + + +static void +kqueue_file_mon_data_clean(kqueue_file_mon_data_p fmd) { + size_t i; + + if (NULL == fmd) + return; + if (-1 != fmd->fd) { + close(fmd->fd); + fmd->fd = -1; + } + if (0 != fmd->is_local) { /* Stop monitoring files/dirs. */ + for (i = 0; i < fmd->files_count; i ++) { + if (-1 == fmd->files[i].fd) + continue; + close(fmd->files[i].fd); + } + } + fmd->files_count = 0; + fmd->files_allocated = 0; + free(fmd->files); + fmd->files = NULL; +} + +static void +kqueue_file_mon_data_free(kqueue_file_mon_data_p fmd) { + size_t i; + + if (NULL == fmd) + return; + if (-1 != fmd->fd) { + close(fmd->fd); + } + if (0 != fmd->is_local) { /* Stop monitoring files/dirs. */ + for (i = 0; i < fmd->files_count; i ++) { + if (-1 == fmd->files[i].fd) + continue; + close(fmd->files[i].fd); + } + } + free(fmd->files); + free(fmd); +} + +static kqueue_file_mon_data_p +kqueue_file_mon_data_alloc(kqueue_fnm_p kfnm, const char *path, void *udata) { + kqueue_file_mon_data_p fmd; + + if (NULL == kfnm || NULL == path) + return (NULL); + fmd = zalloc(sizeof(kqueue_file_mon_data_t)); + if (NULL == fmd) + return (NULL); + fmd->utype = EVENT_UDATA_TYPE_FILE_MON_DATA; + /* Remember args. */ + fmd->path_size = strlcpy(fmd->path, path, PATH_MAX); + fmd->name_offset = fmd->path_size; + fmd->udata = udata; + fmd->kfnm = kfnm; + + return (fmd); +} + +static int +kqueue_file_mon_data_readdir(kqueue_file_mon_data_p fmd) { + int error; + struct dirent *de; + uint8_t *tmpbuf; + readdir_data_t rdd; + + if (NULL == fmd || 0 == fmd->is_dir) + return (EINVAL); + + /* Get temp buf. */ + if (fmd->st_blksize > fmd->kfnm->tmpbuf_size) { + tmpbuf = realloc(fmd->kfnm->tmpbuf, fmd->st_blksize); + if (NULL == tmpbuf) + return (ENOMEM); + fmd->kfnm->tmpbuf = tmpbuf; + fmd->kfnm->tmpbuf_size = fmd->st_blksize; + } + + fmd->files_count = 0; + fmd->files_allocated = 0; + free(fmd->files); + fmd->files = NULL; + + error = readdir_data_start(fmd->fd, fmd->kfnm->tmpbuf, fmd->st_blksize, &rdd); + if (0 != error) + return (error); + for (;;) { + if (0 != realloc_items((void**)&fmd->files, + sizeof(file_info_t), &fmd->files_allocated, + FILES_ALLOC_BLK_SIZE, fmd->files_count)) { + fmd->files_count = 0; + fmd->files_allocated = 0; + free(fmd->files); + fmd->files = NULL; + return (ENOMEM); + } + de = &fmd->files[fmd->files_count].de; /* Use short name. */ + /* Get file name from folder. */ + if (0 != readdir_data_next(&rdd, de)) + break; + /* Get file attrs. */ + if (0 != fstatat(fmd->fd, de->d_name, + &fmd->files[fmd->files_count].sb, + AT_SYMLINK_NOFOLLOW)) { + memset(&fmd->files[fmd->files_count].sb, 0x00, + sizeof(struct stat)); + } + fmd->files[fmd->files_count].utype = EVENT_UDATA_TYPE_FILE_INFO; + fmd->files[fmd->files_count].fd = -1; + fmd->files[fmd->files_count].fmd = fmd; + fmd->files_count ++; + } + + return (0); /* OK. */ +} + + +static void +kqueue_file_mon_data_init(kqueue_file_mon_data_p fmd) { + size_t i; + struct stat sb; + struct statfs stfs; + struct kevent kev; + + if (NULL == fmd) + return; + fmd->fd = open(fmd->path, OPEN_FILE_FLAGS); + if (-1 == fmd->fd) + return; + if (0 != fstat(fmd->fd, &sb)) + goto err_out; + fmd->st_blksize = (size_t)sb.st_blksize; + + /* Get parent folder name. */ + if (S_ISDIR(sb.st_mode)) { + fmd->is_dir = 1; + /* Be sure that folder contain trailing '/'. */ + if ('/' != fmd->path[(fmd->path_size - 1)]) { + fmd->path[fmd->path_size] = '/'; + fmd->path_size ++; + fmd->path[fmd->path_size] = 0; + } + /* Skip last '/' for parent dir search. */ + fmd->name_offset = (fmd->path_size - 1); + } + + /* Is file system local? */ + if (0 != fmd->is_dir && + 0 != fmd->kfnm->s.mon_local_subfiles && + 0 == fstatfs(fmd->fd, &stfs)) { + fmd->is_local = is_fs_local(&stfs); + } + + /* Find parent dir path size. */ + while (0 < fmd->name_offset && '/' != fmd->path[(fmd->name_offset - 1)]) { + fmd->name_offset --; + } + + /* Dir special processing. */ + if (0 != fmd->is_dir) { + /* Read and remember dir content. */ + if (0 != kqueue_file_mon_data_readdir(fmd)) + goto err_out; + } + /* Add to kqueue. */ + EV_SET(&kev, fmd->fd, EVFILT_VNODE, (EV_ADD | EV_CLEAR), + EVFILT_VNODE_FLAGS_ALL, 0, fmd); + if (-1 == kevent(fmd->kfnm->fd, &kev, 1, NULL, 0, NULL)) + goto err_out; + if (0 != fmd->is_local) { /* Add monitor sub files/dirs, ignory errors. */ + for (i = 0; i < fmd->files_count; i ++) { + fmd->files[i].fd = openat(fmd->fd, + fmd->files[i].de.d_name, OPEN_FILE_FLAGS); + if (-1 == fmd->files[i].fd) + continue; + fmd->files[i].fmd = fmd; + EV_SET(&kev, fmd->files[i].fd, EVFILT_VNODE, + (EV_ADD | EV_CLEAR), + EVFILT_VNODE_SUB_FLAGS, 0, &fmd->files[i]); + kevent(fmd->kfnm->fd, &kev, 1, NULL, 0, NULL); + } + } + return; /* OK. */ + +err_out: + kqueue_file_mon_data_clean(fmd); +} + + +static void +kqueue_handle_changes(kqueue_fnm_p kfnm, kqueue_file_mon_data_p fmd) { + struct stat sb; + struct kevent kev; + size_t i, k, files_count; + file_info_p files; + + if (NULL == kfnm || NULL == fmd) + return; + if (0 != fstat(fmd->fd, &sb) || + 0 == sb.st_nlink) { + kqueue_file_mon_data_clean(fmd); + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_DELETED, + fmd->path, "", NULL); + return; + } + if (0 == fmd->is_dir) { + fmd->path[fmd->name_offset] = 0; + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_CHANGED, fmd->path, + (fmd->path + fmd->name_offset), NULL); + fmd->path[fmd->name_offset] = '/'; + return; + } + + /* Dir processing. */ + + /* Save prev. */ + files = fmd->files; + files_count = fmd->files_count; + fmd->files = NULL; + fmd->files_count = 0; + /* Update dir. */ + if (0 != kqueue_file_mon_data_readdir(fmd)) { + /* Restore prev state on fail. */ + fmd->files = files; + fmd->files_count = files_count; + return; + } + /* Notify removed first. */ + for (i = 0; i < files_count; i ++) { + if (0 != file_info_find_de_fileno(fmd->files, fmd->files_count, &files[i].de, &k) || + 0 != file_info_find_de_name(fmd->files, fmd->files_count, (~(size_t)0), &files[i].de, &k)) /* Deleted. */ + continue; + if (-1 != files[i].fd) { + close(files[i].fd); + } + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_DELETED, + fmd->path, files[i].de.d_name, NULL); + } + /* Notify. */ + for (i = 0; i < fmd->files_count; i ++) { + /* Is new file/folder? */ + if (0 == file_info_find_de_fileno(files, files_count, &fmd->files[i].de, &k) && + 0 == file_info_find_de_name(files, files_count, (~(size_t)0), &fmd->files[i].de, &k)) { /* Add new. */ + if (0 != fmd->is_local) { /* Add monitor sub files/dirs, ignory errors. */ + fmd->files[i].fd = openat(fmd->fd, + fmd->files[i].de.d_name, OPEN_FILE_FLAGS); + if (-1 != fmd->files[i].fd) { + fmd->files[i].fmd = fmd; + EV_SET(&kev, fmd->files[i].fd, EVFILT_VNODE, + (EV_ADD | EV_CLEAR), + EVFILT_VNODE_SUB_FLAGS, 0, + &fmd->files[i]); + kevent(kfnm->fd, &kev, 1, NULL, 0, NULL); + } + } + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_CREATED, + fmd->path, fmd->files[i].de.d_name, NULL); + continue; + } + /* Is renamed? */ + if (0 == IS_DE_NAME_EQ(&files[k].de, &fmd->files[i].de)) { + fmd->files[i].fd = files[k].fd; /* Keep file fd. */ + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_RENAMED, + fmd->path, files[k].de.d_name, fmd->files[i].de.d_name); + continue; + } + /* Is modified? */ + if (0 != memcmp(&fmd->files[i].sb, &files[k].sb, sizeof(struct stat))) { + fmd->files[i].fd = files[k].fd; /* Keep file fd. */ + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_CHANGED, + fmd->path, fmd->files[i].de.d_name, NULL); + continue; + } + /* Not changed. */ + } + + free(files); +} + +static void +kqueue_handle_rename(kqueue_fnm_p kfnm, kqueue_file_mon_data_p fmd) { + int up_dir_fd, found = 0; + readdir_data_t rdd; + struct dirent de; + struct stat sb; + char old_filename[(MAXNAMLEN + 2)]; + size_t old_filename_size; + + if (NULL == kfnm || NULL == fmd) + return; + if (0 != fstat(fmd->fd, &sb) || + 0 == sb.st_nlink) { +notify_removed: + kqueue_file_mon_data_clean(fmd); + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_DELETED, + fmd->path, "", NULL); + return; + } + /* Save old file name. */ + old_filename_size = (fmd->path_size - fmd->name_offset - (size_t)fmd->is_dir); + memcpy(old_filename, + (fmd->path + fmd->name_offset), + old_filename_size); + old_filename[old_filename_size] = 0; + + /* Get parent folder name. */ + fmd->path[fmd->name_offset] = 0; + /* Try to open. */ + up_dir_fd = open(fmd->path, (OPEN_FILE_FLAGS | O_DIRECTORY)); + /* Restore '/' after parent folder. */ + fmd->path[fmd->name_offset] = '/'; + if (-1 == up_dir_fd || + 0 != fstat(up_dir_fd, &sb) || + 0 != readdir_data_start(up_dir_fd, fmd->kfnm->tmpbuf, (size_t)sb.st_blksize, &rdd)) { + close(up_dir_fd); + return; + } + /* Find new name by inode. */ + while (0 == readdir_data_next(&rdd, &de)) { + if (de.d_fileno == sb.st_ino) { + found ++; + break; + } + } + close(up_dir_fd); + if (0 == found) + goto notify_removed; /* Not found. */ + /* Update name. */ + if (PATH_MAX <= (fmd->name_offset + de.d_namlen)) + return; /* Too long. */ + memcpy((fmd->path + fmd->name_offset), de.d_name, de.d_namlen); + fmd->path_size = (fmd->name_offset + de.d_namlen); + /* Add last '/' for dir. */ + fmd->path[fmd->path_size] = '/'; + fmd->path_size += (size_t)fmd->is_dir; + fmd->path[fmd->path_size] = 0; + /* Notify. */ + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_RENAMED, + fmd->path, old_filename, de.d_name); +} + + +static void +kqueue_fnm_delay_call_process(kqueue_fnm_p kfnm, kq_msg_cb forced_msg_cb) { + ssize_t ios; + kqueue_fnm_msg_pkt_t msg; + + for (;;) { + ios = read(kfnm->pfd[0], &msg, sizeof(msg)); + if (0 >= ios) + return; + if (sizeof(msg) != ios || + KF_MSG_PKT_MAGIC != msg.magic || + (((size_t)msg.msg_cb) ^ ((size_t)msg.fmd)) != msg.chk_sum) + continue; + if (NULL != forced_msg_cb) { + forced_msg_cb(msg.fmd); + continue; + } + if (NULL == msg.msg_cb) + continue; + msg.msg_cb(msg.fmd); + } +} + +static int +kqueue_fnm_delay_call(kqueue_fnm_p kfnm, kq_msg_cb msg_cb, + kqueue_file_mon_data_p fmd) { + kqueue_fnm_msg_pkt_t msg; + + if (NULL == kfnm || NULL == fmd) + return (EINVAL); + msg.magic = KF_MSG_PKT_MAGIC; + msg.msg_cb = msg_cb; + msg.fmd = fmd; + msg.chk_sum = (((size_t)msg.msg_cb) ^ ((size_t)msg.fmd)); + if (sizeof(msg) == write(kfnm->pfd[1], &msg, sizeof(msg))) + return (0); + return (errno); +} + + +void +kqueue_fnm_free(kqueue_fnm_p kfnm) { + + if (NULL == kfnm) + return; + close(kfnm->fd); + pthread_join(kfnm->tid, NULL); + /* Free all in delay calls queue. */ + kqueue_fnm_delay_call_process(kfnm, kqueue_file_mon_data_free); + close(kfnm->pfd[0]); + close(kfnm->pfd[1]); + free(kfnm->tmpbuf); + free(kfnm); +} + +kqueue_fnm_p +kqueue_fnm_create(kqueue_file_mon_settings_p s, kfnm_event_handler_cb cb_func) { + kqueue_fnm_p kfnm; + struct kevent kev; + + if (NULL == s || NULL == cb_func) + return (NULL); + kfnm = zalloc(sizeof(kqueue_fnm_t)); + if (NULL == kfnm) + return (NULL); + kfnm->fd = kqueue(); + if (-1 == kfnm->fd) + goto err_out; + if (-1 == pipe2(kfnm->pfd, O_NONBLOCK)) + goto err_out; + kfnm->cb_func = cb_func; + memcpy(&kfnm->s, s, sizeof(kqueue_file_mon_settings_t)); + + EV_SET(&kev, kfnm->pfd[0], EVFILT_READ, EV_ADD, 0, 0, NULL); + if (-1 == kevent(kfnm->fd, &kev, 1, NULL, 0, NULL)) + goto err_out; + if (0 != kfnm->s.use_thread) { + if (0 != pthread_create(&kfnm->tid, NULL, + kqueue_fnm_proccess_events_proc, kfnm)) + goto err_out; + } + return (kfnm); + +err_out: + kqueue_fnm_free(kfnm); + return (NULL); +} + +kqueue_file_mon_data_p +kqueue_fnm_add(kqueue_fnm_p kfnm, const char *path, void *udata) { + int error; + kqueue_file_mon_data_p fmd; + + if (NULL == kfnm || NULL == path) + return (NULL); + fmd = kqueue_file_mon_data_alloc(kfnm, path, udata); + if (NULL == fmd) + return (NULL); + /* Shedule delay call to init. */ + error = kqueue_fnm_delay_call(kfnm, kqueue_file_mon_data_init, fmd); + if (0 != error) { /* Error, do no directly init to avoid freezes. */ + kqueue_file_mon_data_free(fmd); + return (NULL); + } + return (fmd); +} + +void +kqueue_fnm_del(kqueue_fnm_p kfnm, kqueue_file_mon_data_p fmd) { + int error; + + if (NULL == kfnm || NULL == fmd) + return; + /* Cancel notifications. */ + close(fmd->fd); + fmd->fd = -1; + /* Shedule delay call to free. */ + error = kqueue_fnm_delay_call(kfnm, kqueue_file_mon_data_free, fmd); + if (0 == error) + return; + /* Error, free directly. */ + kqueue_file_mon_data_free(fmd); +} + + +static void +kqueue_fnm_proccess_event(kqueue_fnm_p kfnm, struct kevent *kev) { + kqueue_file_mon_data_p fmd; + file_info_p fi; + + if (NULL == kfnm || NULL == kev) + return; + + /* Handle delay calls. */ + if (kev->ident == (uintptr_t)kfnm->pfd[0] && + kev->filter == EVFILT_READ) { + kqueue_fnm_delay_call_process(kfnm, NULL); + return; + } + + if (0 == kev->udata) + return; /* No associated data, skip. */ + fmd = (kqueue_file_mon_data_p)kev->udata; + + /* FS notifications. */ + if (EVFILT_VNODE != kev->filter) + return; /* Unknown event, skip. */ + /* Subdir/file */ + if (EVENT_UDATA_TYPE_FILE_INFO == fmd->utype) { + fi = (file_info_p)kev->udata; + fmd = fi->fmd; + /* Update file attrs. */ + if (0 != fstat(fi->fd, &fi->sb)) { + memset(&fi->sb, 0x00, sizeof(struct stat)); + } + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_CHANGED, + fmd->path, fi->de.d_name, NULL); + return; + } + /* Monitored object. */ + if (EVENT_UDATA_TYPE_FILE_MON_DATA != fmd->utype) + return; + /* All flags from EVFILT_VNODE_FLAGS_ALL must be handled here. */ + if (EV_ERROR & kev->flags) { + kev->fflags |= NOTE_REVOKE; /* Treat error as unmount. */ + } + if (NOTE_RENAME & kev->fflags) { + kqueue_handle_rename(kfnm, fmd); + } + if ((NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_CLOSE_WRITE) & kev->fflags) { + kqueue_handle_changes(kfnm, fmd); + } + if ((NOTE_DELETE | NOTE_REVOKE) & kev->fflags) { + kqueue_file_mon_data_clean(fmd); + kfnm->cb_func(kfnm, fmd, fmd->udata, KF_EVENT_DELETED, + fmd->path, "", NULL); + } +} + +void * +kqueue_fnm_proccess_events_proc(void *data) { + struct kevent kev; + kqueue_fnm_p kfnm = data; + + if (NULL == kfnm) + return (NULL); + /* Get and proccess events, no wait. */ + while (0 < kevent(kfnm->fd, NULL, 0, &kev, 1, NULL)) { + kqueue_fnm_proccess_event(kfnm, &kev); + } + + return (NULL); +} + + +int +kqueue_fnm_get_ev_recv_fd(kqueue_fnm_p kfnm) { + + if (NULL == kfnm || NULL != kfnm->tid) + return (-1); + return (kfnm->fd); +} + +void +kqueue_fnm_proccess_events(kqueue_fnm_p kfnm) { + struct kevent kev; + struct timespec ke_timeout; + + if (NULL == kfnm || NULL != kfnm->tid) + return; + /* Get and proccess events, no wait. */ + memset(&ke_timeout, 0x00, sizeof(ke_timeout)); + while (0 < kevent(kfnm->fd, NULL, 0, &kev, 1, &ke_timeout)) { + kqueue_fnm_proccess_event(kfnm, &kev); + } +} Index: devel/glib20/files/kqueue_fnm.h.in =================================================================== --- devel/glib20/files/kqueue_fnm.h.in (nonexistent) +++ devel/glib20/files/kqueue_fnm.h.in (working copy) @@ -0,0 +1,73 @@ +/*- + * Copyright (c) 2016 - 2017 Rozhuk Ivan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Author: Rozhuk Ivan + * + */ + + + +#ifndef __KQUEUE_FILE_NOTIFY_MONITOR_H__ +#define __KQUEUE_FILE_NOTIFY_MONITOR_H__ + +#include +#include +#include + + +typedef struct kqueue_file_nonify_monitor_s *kqueue_fnm_p; +typedef struct kqueue_file_mon_data_s *kqueue_file_mon_data_p; + +typedef void (*kfnm_event_handler_cb)(kqueue_fnm_p kfnm, + kqueue_file_mon_data_p fmd, void *udata, + uint32_t event, + const char *base, + const char *filename, + const char *new_filename); +#define KF_EVENT_NOT_CHANGED 0 /* Internal use. */ +#define KF_EVENT_CREATED 1 +#define KF_EVENT_DELETED 2 +#define KF_EVENT_RENAMED 3 +#define KF_EVENT_CHANGED 4 + + +typedef struct kqueue_file_mon_settings_s { + int use_thread; + int mon_local_subfiles; /* Enable monitoring files/subdirs changes on local file systems. */ +} kqueue_file_mon_settings_t, *kqueue_file_mon_settings_p; + +kqueue_fnm_p kqueue_fnm_create(kqueue_file_mon_settings_p s, + kfnm_event_handler_cb cb_func); +void kqueue_fnm_free(kqueue_fnm_p kfnm); + +kqueue_file_mon_data_p kqueue_fnm_add(kqueue_fnm_p kfnm, + const char *path, void *udata); +void kqueue_fnm_del(kqueue_fnm_p kfnm, kqueue_file_mon_data_p fmd); + +int kqueue_fnm_get_ev_recv_fd(kqueue_fnm_p kfnm); +void kqueue_fnm_proccess_events(kqueue_fnm_p kfnm); + + +#endif /* __KQUEUE_FILE_NOTIFY_MONITOR_H__ */