From e3354e2265bc442e4c7b84b806be482db88581a2 Mon Sep 17 00:00:00 2001 From: Michael Albinus Date: Mon, 9 Nov 2015 10:00:56 +0100 Subject: [PATCH] Add kqueue support * configure.ac (--with-file-notification): Add kqueue. (top): Remove special test for "${HAVE_NS}" and ${with_file_notification}, this is handled inside gfilenotify tests. Add kqueue tests. Use NOTIFY_CFLAGS and NOTIFY_LIBS instead of library specific variables. * src/Makefile.in: Use NOTIFY_CFLAGS and NOTIFY_LIBS. * src/emacs.c (main): Call globals_of_kqueue and syms_of_kqueue. * src/kqueue.c: New file. * src/lisp.h: Declare extern globals_of_kqueue and syms_of_kqueue. --- configure.ac | 51 +++++--- src/Makefile.in | 11 +- src/emacs.c | 16 ++- src/kqueue.c | 339 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lisp.h | 16 ++- 5 files changed, 400 insertions(+), 33 deletions(-) create mode 100644 src/kqueue.c diff --git a/configure.ac b/configure.ac index 0348c062911..dcd45ea4d61 100644 --- a/configure.ac +++ b/configure.ac @@ -355,17 +355,18 @@ OPTION_DEFAULT_ON([gnutls],[don't use -lgnutls for SSL/TLS support]) OPTION_DEFAULT_ON([zlib],[don't compile with zlib decompression support]) AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB], - [use a file notification library (LIB one of: yes, gfile, inotify, w32, no)])], + [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])], [ case "${withval}" in y | ye | yes ) val=yes ;; n | no ) val=no ;; - g | gf | gfi | gfil | gfile ) val=gfile ;; i | in | ino | inot | inoti | inotif | inotify ) val=inotify ;; + k | kq | kqu | kque | kqueu | kqueue ) val=kqueue ;; + g | gf | gfi | gfil | gfile ) val=gfile ;; w | w3 | w32 ) val=w32 ;; * ) AC_MSG_ERROR(['--with-file-notification=$withval' is invalid; -this option's value should be 'yes', 'no', 'gfile', 'inotify' or 'w32'. +this option's value should be 'yes', 'no', 'inotify', 'kqeue', 'gfile' or 'w32'. 'yes' is a synonym for 'w32' on MS-Windows, for 'no' on Nextstep, -otherwise for the first of 'inotify' or 'gfile' that is usable.]) +otherwise for the first of 'inotify', 'kqueue' or 'gfile' that is usable.]) ;; esac with_file_notification=$val @@ -2690,12 +2691,6 @@ AC_SUBST(LIBGNUTLS_CFLAGS) NOTIFY_OBJ= NOTIFY_SUMMARY=no -dnl FIXME? Don't auto-detect on NS, but do allow someone to specify -dnl a particular library. This doesn't make much sense? -if test "${HAVE_NS}" = yes && test ${with_file_notification} = yes; then - with_file_notification=no -fi - dnl MS Windows native file monitor is available for mingw32 only. case $with_file_notification,$opsys in w32,cygwin) @@ -2726,16 +2721,34 @@ case $with_file_notification,$NOTIFY_OBJ in fi ;; esac +dnl kqueue is available on BSD-like systems. +case $with_file_notification,$NOTIFY_OBJ in + kqueue,* | yes,) + EMACS_CHECK_MODULES([KQUEUE], [libkqueue]) + if test "$HAVE_KQUEUE" = "yes"; then + AC_DEFINE(HAVE_KQUEUE, 1, [Define to 1 to use kqueue.]) + CPPFLAGS="$CPPFLAGS -I/usr/include/kqueue" + NOTIFY_CFLAGS=$KQUEUE_CFLAGS + NOTIFY_LIBS=$KQUEUE_LIBS + NOTIFY_OBJ=kqueue.o + NOTIFY_SUMMARY="yes -lkqueue" + fi ;; +esac + dnl g_file_monitor exists since glib 2.18. G_FILE_MONITOR_EVENT_MOVED dnl has been added in glib 2.24. It has been tested under dnl GNU/Linux only. case $with_file_notification,$NOTIFY_OBJ in gfile,* | yes,) - EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24]) - if test "$HAVE_GFILENOTIFY" = "yes"; then - AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.]) - NOTIFY_OBJ=gfilenotify.o - NOTIFY_SUMMARY="yes -lgio (gfile)" + if test "${HAVE_NS}" != yes; then + EMACS_CHECK_MODULES([GFILENOTIFY], [gio-2.0 >= 2.24]) + if test "$HAVE_GFILENOTIFY" = "yes"; then + AC_DEFINE(HAVE_GFILENOTIFY, 1, [Define to 1 if using GFile.]) + NOTIFY_CFLAGS=$GFILENOTIFY_CFLAGS + NOTIFY_LIBS=$GFILENOTIFY_LIBS + NOTIFY_OBJ=gfilenotify.o + NOTIFY_SUMMARY="yes -lgio (gfile)" + fi fi ;; esac @@ -2747,9 +2760,9 @@ esac if test -n "$NOTIFY_OBJ"; then AC_DEFINE(USE_FILE_NOTIFY, 1, [Define to 1 if using file notifications.]) fi +AC_SUBST(NOTIFY_CFLAGS) +AC_SUBST(NOTIFY_LIBS) AC_SUBST(NOTIFY_OBJ) -AC_SUBST(GFILENOTIFY_CFLAGS) -AC_SUBST(GFILENOTIFY_LIBS) dnl Do not put whitespace before the #include statements below. dnl Older compilers (eg sunos4 cc) choke on it. @@ -4066,8 +4079,8 @@ OLDCFLAGS="$CFLAGS" OLDLIBS="$LIBS" CFLAGS="$CFLAGS $GTK_CFLAGS $RSVG_CFLAGS $DBUS_CFLAGS $SETTINGS_CFLAGS" LIBS="$LIBS $GTK_LIBS $RSVG_LIBS $DBUS_LIBS $SETTINGS_LIBS" -CFLAGS="$CFLAGS $GFILENOTIFY_CFLAGS $CAIRO_CFLAGS" -LIBS="$LIBS $GFILENOTIFY_LIBS $CAIRO_LIBS" +CFLAGS="$CFLAGS $NOTIFY_CFLAGS $CAIRO_CFLAGS" +LIBS="$LIBS $NOTIFY_LIBS $CAIRO_LIBS" AC_MSG_CHECKING([whether GLib is linked in]) AC_LINK_IFELSE([AC_LANG_PROGRAM( [[#include diff --git a/src/Makefile.in b/src/Makefile.in index d667c55ee33..d7ad3954579 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -160,12 +160,13 @@ SETTINGS_LIBS = @SETTINGS_LIBS@ ## gtkutil.o if USE_GTK, else empty. GTK_OBJ=@GTK_OBJ@ -## gfilenotify.o if HAVE_GFILENOTIFY. ## inotify.o if HAVE_INOTIFY. +## kqueue.o if HAVE_KQUEUE. +## gfilenotify.o if HAVE_GFILENOTIFY. ## w32notify.o if HAVE_W32NOTIFY. NOTIFY_OBJ = @NOTIFY_OBJ@ -GFILENOTIFY_CFLAGS = @GFILENOTIFY_CFLAGS@ -GFILENOTIFY_LIBS = @GFILENOTIFY_LIBS@ +NOTIFY_CFLAGS = @NOTIFY_CFLAGS@ +NOTIFY_LIBS = @NOTIFY_LIBS@ ## -ltermcap, or -lncurses, or -lcurses, or "". LIBS_TERMCAP=@LIBS_TERMCAP@ @@ -355,7 +356,7 @@ ALL_CFLAGS=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \ $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) \ $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \ - $(LIBGNUTLS_CFLAGS) $(GFILENOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ + $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ $(WARN_CFLAGS) $(WERROR_CFLAGS) $(CFLAGS) ALL_OBJC_CFLAGS=$(ALL_CFLAGS) $(GNU_OBJC_CFLAGS) @@ -468,7 +469,7 @@ LIBES = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \ $(LIBS_TERMCAP) $(GETLOADAVG_LIBS) $(SETTINGS_LIBS) $(LIBSELINUX_LIBS) \ $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) \ - $(GFILENOTIFY_LIBS) $(LIB_MATH) $(LIBZ) + $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(leimdir)/leim-list.el: bootstrap-emacs$(EXEEXT) $(MAKE) -C ../leim leim-list.el EMACS="$(bootstrap_exe)" diff --git a/src/emacs.c b/src/emacs.c index b4052b851d7..2e9f950851a 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1350,6 +1350,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem tzset (); #endif /* MSDOS */ +#ifdef HAVE_KQUEUE + globals_of_kqueue (); +#endif + #ifdef HAVE_GFILENOTIFY globals_of_gfilenotify (); #endif @@ -1520,14 +1524,18 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem syms_of_gnutls (); -#ifdef HAVE_GFILENOTIFY - syms_of_gfilenotify (); -#endif /* HAVE_GFILENOTIFY */ - #ifdef HAVE_INOTIFY syms_of_inotify (); #endif /* HAVE_INOTIFY */ +#ifdef HAVE_KQUEUE + syms_of_kqueue (); +#endif /* HAVE_KQUEUE */ + +#ifdef HAVE_GFILENOTIFY + syms_of_gfilenotify (); +#endif /* HAVE_GFILENOTIFY */ + #ifdef HAVE_DBUS syms_of_dbusbind (); #endif /* HAVE_DBUS */ diff --git a/src/kqueue.c b/src/kqueue.c new file mode 100644 index 00000000000..69bf5f61080 --- /dev/null +++ b/src/kqueue.c @@ -0,0 +1,339 @@ +/* Filesystem notifications support with glib API. + Copyright (C) 2013-2015 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +#include + +#ifdef HAVE_KQUEUE +#include +#include +#include "lisp.h" +#include "coding.h" +#include "termhooks.h" +#include "keyboard.h" + + +/* File handle for kqueue. */ +static int kqueuefd = -1; + +/* This is a list, elements are triples (DESCRIPTOR FILE FLAGS CALLBACK) */ +static Lisp_Object watch_list; + +#if 0 +/* This is the callback function for arriving signals from + g_file_monitor. It shall create a Lisp event, and put it into + Emacs input queue. */ +static gboolean +dir_monitor_callback (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + Lisp_Object symbol, monitor_object, watch_object, flags; + char *name = g_file_get_parse_name (file); + char *oname = other_file ? g_file_get_parse_name (other_file) : NULL; + + /* Determine event symbol. */ + switch (event_type) + { + case G_FILE_MONITOR_EVENT_CHANGED: + symbol = Qchanged; + break; + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + symbol = Qchanges_done_hint; + break; + case G_FILE_MONITOR_EVENT_DELETED: + symbol = Qdeleted; + break; + case G_FILE_MONITOR_EVENT_CREATED: + symbol = Qcreated; + break; + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + symbol = Qattribute_changed; + break; + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + symbol = Qpre_unmount; + break; + case G_FILE_MONITOR_EVENT_UNMOUNTED: + symbol = Qunmounted; + break; + case G_FILE_MONITOR_EVENT_MOVED: + symbol = Qmoved; + break; + default: + goto cleanup; + } + + /* Determine callback function. */ + monitor_object = make_pointer_integer (monitor); + eassert (INTEGERP (monitor_object)); + watch_object = assq_no_quit (monitor_object, watch_list); + + if (CONSP (watch_object)) + { + struct input_event event; + Lisp_Object otail = oname ? list1 (build_string (oname)) : Qnil; + + /* Check, whether event_type is expected. */ + flags = XCAR (XCDR (XCDR (watch_object))); + if ((!NILP (Fmember (Qchange, flags)) && + !NILP (Fmember (symbol, list5 (Qchanged, Qchanges_done_hint, + Qdeleted, Qcreated, Qmoved)))) || + (!NILP (Fmember (Qattribute_change, flags)) && + ((EQ (symbol, Qattribute_changed))))) + { + /* Construct an event. */ + EVENT_INIT (event); + event.kind = FILE_NOTIFY_EVENT; + event.frame_or_window = Qnil; + event.arg = list2 (Fcons (monitor_object, + Fcons (symbol, + Fcons (build_string (name), + otail))), + XCAR (XCDR (XCDR (XCDR (watch_object))))); + + /* Store it into the input event queue. */ + kbd_buffer_store_event (&event); + // XD_DEBUG_MESSAGE ("%s", XD_OBJECT_TO_STRING (event.arg)); + } + + /* Cancel monitor if file or directory is deleted. */ + if (!NILP (Fmember (symbol, list2 (Qdeleted, Qmoved))) && + (strcmp (name, SSDATA (XCAR (XCDR (watch_object)))) == 0) && + !g_file_monitor_is_cancelled (monitor)) + g_file_monitor_cancel (monitor); + } + + /* Cleanup. */ + cleanup: + g_free (name); + g_free (oname); + + return TRUE; +} +#endif /* 0 */ + +DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0, + doc: /* Add a watch for filesystem events pertaining to FILE. + +This arranges for filesystem events pertaining to FILE to be reported +to Emacs. Use `gfile-rm-watch' to cancel the watch. + +Value is a descriptor for the added watch. If the file cannot be +watched for some reason, this function signals a `file-notify-error' error. + +FLAGS is a list of conditions to set what will be watched for. It can +include the following symbols: + + `change' -- watch for file changes + `attribute-change' -- watch for file attributes changes, like + permissions or modification time + `watch-mounts' -- watch for mount events + `send-moved' -- pair `deleted' and `created' events caused by + file renames and send a single `renamed' event + instead + +When any event happens, Emacs will call the CALLBACK function passing +it a single argument EVENT, which is of the form + + (DESCRIPTOR ACTION FILE [FILE1]) + +DESCRIPTOR is the same object as the one returned by this function. +ACTION is the description of the event. It could be any one of the +following: + + `changed' -- FILE has changed + `changes-done-hint' -- a hint that this was probably the last change + in a set of changes + `deleted' -- FILE was deleted + `created' -- FILE was created + `attribute-changed' -- a FILE attribute was changed + `pre-unmount' -- the FILE location will soon be unmounted + `unmounted' -- the FILE location was unmounted + `moved' -- FILE was moved to FILE1 + +FILE is the name of the file whose event is being reported. FILE1 +will be reported only in case of the `moved' event. */) + (Lisp_Object file, Lisp_Object flags, Lisp_Object callback) +{ + Lisp_Object watch_object; + GFile *gfile; + GFileMonitor *monitor; + GFileMonitorFlags gflags = G_FILE_MONITOR_NONE; + GError *gerror = NULL; + + /* Check parameters. */ + CHECK_STRING (file); + file = Fdirectory_file_name (Fexpand_file_name (file, Qnil)); + if (NILP (Ffile_exists_p (file))) + report_file_error ("File does not exist", file); + + CHECK_LIST (flags); + + if (!FUNCTIONP (callback)) + wrong_type_argument (Qinvalid_function, callback); + + /* Create GFile name. */ + // gfile = g_file_new_for_path (SSDATA (ENCODE_FILE (file))); + + /* Assemble flags. */ + // if (!NILP (Fmember (Qwatch_mounts, flags))) + // gflags |= G_FILE_MONITOR_WATCH_MOUNTS; + // if (!NILP (Fmember (Qsend_moved, flags))) + // gflags |= G_FILE_MONITOR_SEND_MOVED; + + if (kqueuefd < 0) + { + kqueuefd = kqueue (); + if (kqueuefd < 0) + report_file_notify_error ("File watching is not available", Qnil); + watch_list = Qnil; + // add_read_fd (inotifyfd, &inotify_callback, NULL); + } + + +} +#if 0 + + mask = aspect_to_inotifymask (aspect); + encoded_file_name = ENCODE_FILE (file_name); + watchdesc = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask); + if (watchdesc == -1) + report_file_notify_error ("Could not add watch for file", file_name); + + /* Enable watch. */ + monitor = g_file_monitor (gfile, gflags, NULL, &gerror); + g_object_unref (gfile); + if (gerror) + { + char msg[1024]; + strcpy (msg, gerror->message); + g_error_free (gerror); + xsignal1 (Qfile_notify_error, build_string (msg)); + } + if (! monitor) + xsignal2 (Qfile_notify_error, build_string ("Cannot watch file"), file); + + Lisp_Object watch_descriptor = make_pointer_integer (monitor); + + /* Check the dicey assumption that make_pointer_integer is safe. */ + if (! INTEGERP (watch_descriptor)) + { + g_object_unref (monitor); + xsignal2 (Qfile_notify_error, build_string ("Unsupported file watcher"), + file); + } + + /* The default rate limit is 800 msec. We adapt this. */ + g_file_monitor_set_rate_limit (monitor, 100); + + /* Subscribe to the "changed" signal. */ + g_signal_connect (monitor, "changed", + (GCallback) dir_monitor_callback, NULL); + + /* Store watch object in watch list. */ + watch_object = list4 (watch_descriptor, file, flags, callback); + watch_list = Fcons (watch_object, watch_list); + + return watch_descriptor; +} + +DEFUN ("gfile-rm-watch", Fgfile_rm_watch, Sgfile_rm_watch, 1, 1, 0, + doc: /* Remove an existing WATCH-DESCRIPTOR. + +WATCH-DESCRIPTOR should be an object returned by `gfile-add-watch'. */) + (Lisp_Object watch_descriptor) +{ + Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list); + + if (! CONSP (watch_object)) + xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"), + watch_descriptor); + + eassert (INTEGERP (watch_descriptor)); + GFileMonitor *monitor = XINTPTR (watch_descriptor); + if (!g_file_monitor_is_cancelled (monitor) && + !g_file_monitor_cancel (monitor)) + xsignal2 (Qfile_notify_error, build_string ("Could not rm watch"), + watch_descriptor); + + /* Remove watch descriptor from watch list. */ + watch_list = Fdelq (watch_object, watch_list); + + /* Cleanup. */ + g_object_unref (monitor); + + return Qt; +} + +DEFUN ("gfile-valid-p", Fgfile_valid_p, Sgfile_valid_p, 1, 1, 0, + doc: /* "Check a watch specified by its WATCH-DESCRIPTOR. + +WATCH-DESCRIPTOR should be an object returned by `gfile-add-watch'. + +A watch can become invalid if the file or directory it watches is +deleted, or if the watcher thread exits abnormally for any other +reason. Removing the watch by calling `gfile-rm-watch' also makes it +invalid. */) + (Lisp_Object watch_descriptor) +{ + Lisp_Object watch_object = Fassoc (watch_descriptor, watch_list); + if (NILP (watch_object)) + return Qnil; + else + { + GFileMonitor *monitor = XINTPTR (watch_descriptor); + return g_file_monitor_is_cancelled (monitor) ? Qnil : Qt; + } +} +#endif /* 0 */ + + +void +globals_of_kqueue (void) +{ + watch_list = Qnil; +} + +void +syms_of_kqueue (void) +{ + defsubr (&Skqueue_add_watch); + // defsubr (&Skqueue_rm_watch); + // defsubr (&Skqueue_valid_p); + + /* Filter objects. */ + DEFSYM (Qchange, "change"); + DEFSYM (Qattribute_change, "attribute-change"); + DEFSYM (Qwatch_mounts, "watch-mounts"); /* G_FILE_MONITOR_WATCH_MOUNTS */ + DEFSYM (Qsend_moved, "send-moved"); /* G_FILE_MONITOR_SEND_MOVED */ + + /* Event types. */ + DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */ + DEFSYM (Qwrite, "write"); /* NOTE_WRITE */ + DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */ + DEFSYM (Qattrib, "attrib"); /* NOTE_ATTRIB */ + DEFSYM (Qlink, "link"); /* NOTE_LINK */ + DEFSYM (Qrename, "rename"); /* NOTE_RENAME */ + + staticpro (&watch_list); + + Fprovide (intern_c_string ("kqueue"), Qnil); +} + +#endif /* HAVE_KQUEUE */ diff --git a/src/lisp.h b/src/lisp.h index 3efa492e0e8..426b6c949e9 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4257,17 +4257,23 @@ extern void init_font (void); extern void syms_of_fontset (void); #endif +/* Defined in inotify.c */ +#ifdef HAVE_INOTIFY +extern void syms_of_inotify (void); +#endif + +/* Defined in kqueue.c */ +#ifdef HAVE_KQUEUE +extern void globals_of_kqueue (void); +extern void syms_of_kqueue (void); +#endif + /* Defined in gfilenotify.c */ #ifdef HAVE_GFILENOTIFY extern void globals_of_gfilenotify (void); extern void syms_of_gfilenotify (void); #endif -/* Defined in inotify.c */ -#ifdef HAVE_INOTIFY -extern void syms_of_inotify (void); -#endif - #ifdef HAVE_W32NOTIFY /* Defined on w32notify.c. */ extern void syms_of_w32notify (void);