[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH update3] Support for filesystem watching (inotify)
From: |
Rüdiger Sonderfeld |
Subject: |
Re: [PATCH update3] Support for filesystem watching (inotify) |
Date: |
Sun, 5 Jun 2011 11:48:07 +0200 |
User-agent: |
KMail/1.13.6 (Linux/2.6.38-8-generic; KDE/4.6.2; x86_64; ; ) |
On Sunday 05 June 2011 07:45:58 Eli Zaretskii wrote:
> > From: Rüdiger Sonderfeld <address@hidden>
> > Date: Sun, 5 Jun 2011 01:36:35 +0200
> >
> > > In general, you should not call Lisp from the read fd callback.
> > > Recursive calls into Lisp may fail. Instead post a Lisp event and
> > > handle it in keyboard.c and some Lisp code.
> >
> > Thanks for your advise. I changed the code to use events.
>
> You did? Did you send the correct patch?
Oh. This should be the correct patch.
Subject: [PATCH] Added basic file system watching support.
It currently only works with inotify/Linux. It adds file-watch and file-unwatch
functions.
---
configure.in | 14 +++
lisp/filewatch.el | 54 ++++++++++
src/Makefile.in | 2 +-
src/emacs.c | 4 +
src/filewatch.c | 305 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/keyboard.c | 28 +++++
src/lisp.h | 5 +
src/termhooks.h | 5 +
8 files changed, 416 insertions(+), 1 deletions(-)
create mode 100644 lisp/filewatch.el
create mode 100644 src/filewatch.c
diff --git a/configure.in b/configure.in
index 06880ea..5696fa2 100644
--- a/configure.in
+++ b/configure.in
@@ -169,6 +169,7 @@ OPTION_DEFAULT_ON([dbus],[don't compile with D-Bus support])
OPTION_DEFAULT_ON([gconf],[don't compile with GConf support])
OPTION_DEFAULT_ON([selinux],[don't compile with SELinux support])
OPTION_DEFAULT_ON([gnutls],[don't use -lgnutls for SSL/TLS support])
+OPTION_DEFAULT_ON([inotify],[don't compile with inotify (file-watch) support])
## For the times when you want to build Emacs but don't have
## a suitable makeinfo, and can live without the manuals.
@@ -1981,6 +1982,19 @@ fi
AC_SUBST(LIBGNUTLS_LIBS)
AC_SUBST(LIBGNUTLS_CFLAGS)
+dnl inotify is only available on GNU/Linux.
+HAVE_INOTIFY=no
+if test "${with_inotify}" = "yes"; then
+ AC_CHECK_HEADERS(sys/inotify.h)
+ if test "$ac_cv_header_sys_inotify_h" = yes ; then
+ AC_CHECK_FUNCS(inotify_init1 inotify_add_watch inotify_rm_watch,
HAVE_INOTIFY=yes)
+ fi
+fi
+if test "${HAVE_INOTIFY}" = "yes"; then
+ AC_DEFINE(HAVE_INOTIFY, [1], [Define to 1 to use inotify])
+ AC_DEFINE(HAVE_FILEWATCH, [1], [Define to 1 to support filewatch])
+fi
+
dnl Do not put whitespace before the #include statements below.
dnl Older compilers (eg sunos4 cc) choke on it.
HAVE_XAW3D=no
diff --git a/lisp/filewatch.el b/lisp/filewatch.el
new file mode 100644
index 0000000..2c97589
--- /dev/null
+++ b/lisp/filewatch.el
@@ -0,0 +1,54 @@
+;;; filewatch.el --- Elisp support for watching filesystem events.
+
+;; Copyright (C) 2011 Free Software Foundation, Inc.
+
+;; Author: Rüdiger Sonderfeld <address@hidden>
+;; Keywords: files
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file contains the elisp part of the filewatch API.
+
+;;; Code:
+
+(eval-when-compile
+ (require 'cl))
+
+(defun filewatch-event-p (event)
+ "Check if EVENT is a filewatch event."
+ (and (listp event)
+ (eq (car event) 'filewatch-event)))
+
+;;;###autoload
+(defun filewatch-handle-event (event)
+ "Handle file system monitoring event.
+If EVENT is a filewatch event then the callback is called. If EVENT is
+not a filewatch event then a `filewatch-error' is signaled."
+ (interactive "e")
+
+ (unless (filewatch-event-p event)
+ (signal 'filewatch-error (cons "Not a valid filewatch event" event)))
+
+ (loop for ev in (cdr event)
+ unless (and (listp ev) (>= (length ev) 3))
+ do (signal 'filewatch-error (cons "Not a valid filewatch event" event))
+ do (funcall (cadr ev) (car ev) (caddr ev))))
+
+(provide 'filewatch)
+
+;;; filewatch.el ends here
diff --git a/src/Makefile.in b/src/Makefile.in
index c4250b9..9135e7d 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -334,7 +334,7 @@ base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o
$(XMENU_OBJ) window.o \
syntax.o $(UNEXEC_OBJ) bytecode.o \
process.o gnutls.o callproc.o \
region-cache.o sound.o atimer.o \
- doprnt.o intervals.o textprop.o composite.o xml.o \
+ doprnt.o intervals.o textprop.o composite.o xml.o filewatch.o \
$(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ)
obj = $(base_obj) $(NS_OBJC_OBJ)
diff --git a/src/emacs.c b/src/emacs.c
index 3a7c5c0..fb734e5 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1558,6 +1558,10 @@ main (int argc, char **argv)
syms_of_gnutls ();
#endif
+#ifdef HAVE_FILEWATCH
+ syms_of_filewatch ();
+#endif /* HAVE_FILEWATCH */
+
#ifdef HAVE_DBUS
syms_of_dbusbind ();
#endif /* HAVE_DBUS */
diff --git a/src/filewatch.c b/src/filewatch.c
new file mode 100644
index 0000000..56b79b7
--- /dev/null
+++ b/src/filewatch.c
@@ -0,0 +1,305 @@
+/* Watching file system changes.
+
+Copyright (C) 2011
+ 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#ifdef HAVE_FILEWATCH
+#include <setjmp.h> /* Required for lisp.h. */
+#include "lisp.h"
+#include "coding.h"
+#include "process.h"
+#include "keyboard.h"
+#include "character.h"
+#include "frame.h" /* Required for termhooks.h. */
+#include "termhooks.h"
+
+static Lisp_Object Qmodify, Qmove, Qattrib, Qdelete, Qfrom, Qto,
Qunkown_aspect;
+
+#ifdef HAVE_INOTIFY
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+
+enum { uninitialized = -100 };
+/* File handle for inotify. */
+static int inotifyfd = uninitialized;
+
+/* Assoc list of files being watched. */
+static Lisp_Object watch_list;
+
+/* This callback is called when the FD is available FOR_READ. The inotify
+ events are read from FD and converted into input_events. */
+
+static void
+inotify_callback (int fd, void *_, int for_read)
+{
+ struct input_event event;
+ int to_read;
+ char *buffer;
+ ssize_t n;
+ size_t i;
+
+ if (!for_read)
+ return;
+
+ to_read = 0;
+ if (ioctl (fd, FIONREAD, &to_read) == -1)
+ report_file_error ("Error while trying to retrieve file system events",
+ Qnil);
+ buffer = xmalloc (to_read);
+ n = read (fd, buffer, to_read);
+ if (n < 0)
+ {
+ xfree (buffer);
+ report_file_error ("Error while trying to read file system events",
+ Qnil);
+ }
+
+ EVENT_INIT (event);
+ event.kind = FILEWATCH_EVENT;
+ event.arg = Qnil;
+
+ i = 0;
+ while (i < (size_t)n)
+ {
+ struct inotify_event *ev = (struct inotify_event*)&buffer[i];
+
+ Lisp_Object callback = Fassoc (make_number (ev->wd), watch_list);
+ if (!NILP (callback))
+ {
+ size_t len;
+ Lisp_Object name, events, event_info;
+
+ /* If a directory is watched name contains the name
+ of the file that was changed. */
+ len = strlen (ev->name);
+ name = make_unibyte_string (ev->name, len);
+ name = DECODE_FILE (name);
+
+ events = Qnil;
+ if (ev->mask & (IN_MODIFY|IN_CREATE) )
+ events = Fcons (Fcons (Qmodify, name), events);
+ if (ev->mask & IN_MOVE_SELF)
+ events = Fcons (Fcons (Qmove, name), events);
+ if (ev->mask & IN_MOVED_FROM)
+ events = Fcons (Fcons (Qmove,
+ Fcons (Qfrom,
+ Fcons (name,
+ make_number (ev->cookie)))),
+ events);
+ if (ev->mask & IN_MOVED_TO)
+ events = Fcons (Fcons (Qmove,
+ Fcons (Qto,
+ Fcons (name,
+ make_number (ev->cookie)))),
+ events);
+ if (ev->mask & IN_ATTRIB)
+ events = Fcons (Fcons (Qattrib, name), events);
+ if (ev->mask & (IN_DELETE|IN_DELETE_SELF) )
+ events = Fcons (Fcons (Qdelete, name), events);
+
+ if (!NILP (events))
+ {
+ Lisp_Object args[2], event_info;
+ args[0] = Fcdr (callback);
+ args[1] = Fcons (events, Qnil);
+ event_info = Fcons (Fappend (2, args), Qnil);
+ if (NILP (event.arg))
+ event.arg = event_info;
+ else
+ {
+ args[0] = event.arg;
+ args[1] = event_info;
+ event.arg = Fappend (2, args);
+ }
+ }
+
+ if (ev->mask & IN_IGNORED)
+ {
+ /* Event was removed automatically: Drop it from data list. */
+ add_to_log ("File-watch: \"%s\" will be ignored", name, Qnil);
+ watch_list = Fdelete (callback, watch_list);
+ }
+ if (ev->mask & IN_Q_OVERFLOW)
+ add_to_log ("File watch: Inotify Queue Overflow!", Qnil, Qnil);
+ }
+
+ i += sizeof (*ev) + ev->len;
+ }
+
+ if (!NILP (event.arg))
+ kbd_buffer_store_event (&event);
+
+ xfree (buffer);
+}
+
+static uint32_t
+symbol_to_inotifymask (Lisp_Object symb)
+{
+ if (EQ (symb, Qmodify))
+ return IN_MODIFY | IN_CREATE;
+ else if (EQ (symb, Qmove))
+ return IN_MOVE_SELF | IN_MOVE;
+ else if (EQ (symb, Qattrib))
+ return IN_ATTRIB;
+ else if (EQ (symb, Qdelete))
+ return IN_DELETE_SELF | IN_DELETE;
+ else if (EQ (symb, Qt))
+ return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB
+ | IN_DELETE_SELF | IN_DELETE;
+ else
+ Fsignal (Qunkown_aspect, Fcons (symb, Qnil));
+}
+
+DEFUN ("file-watch", Ffile_watch, Sfile_watch, 3, 3, 0,
+ doc: /* Arrange to call FUNC if ASPECT of FILENAME changes.
+
+ASPECT might be one of the following symbols or a list of those symbols:
+
+modify -- notify when a file is modified or created.
+
+move -- notify when a file/directory is moved.
+
+attrib -- notify when attributes change.
+
+delete -- notify when a file/directory is deleted.
+
+t -- notify for all of the above.
+
+Watching a directory is not recursive. CALLBACK receives the events as a list
+with each list element being a list containing information about an event. The
+first element is a flag symbol. If a directory is watched the second element
is
+the name of the file that changed. If a file is moved from or to the directory
+the second element is either 'from or 'to and the third element is the file
+name. A fourth element contains a numeric identifier (cookie) that can be used
+to identify matching move operations if a file is moved inside the directory.
+
+Example:
+(file-watch "foo" t #'(lambda (file event) (message "%s %s" file event)))
+
+Use `file-unwatch' to stop watching.
+
+usage: (file-watch FILENAME ASPECT CALLBACK) */)
+ (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback)
+{
+ uint32_t mask;
+ size_t i;
+ int watchdesc;
+ Lisp_Object decoded_file_name;
+
+ CHECK_STRING (file_name);
+
+ if (inotifyfd == uninitialized)
+ {
+ inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
+ if (inotifyfd == -1)
+ {
+ inotifyfd = uninitialized;
+ report_file_error ("File watching feature (inotify) is not
available",
+ Qnil);
+ }
+ watch_list = Qnil;
+ add_read_fd (inotifyfd, &inotify_callback, &watch_list);
+ }
+
+ /* Convert ASPECT into the inotify event mask. */
+ if (CONSP (aspect))
+ {
+ Lisp_Object x = aspect;
+ mask = 0;
+ while (!NILP(x))
+ {
+ mask |= symbol_to_inotifymask (Fcar (x));
+ x = Fcdr(x);
+ }
+ }
+ else
+ mask = symbol_to_inotifymask(aspect);
+
+ decoded_file_name = ENCODE_FILE (file_name);
+ watchdesc = inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask);
+ if (watchdesc == -1)
+ report_file_error ("Could not watch file", Fcons (file_name, Qnil));
+ /* TODO: check if file is already in the watch_list. */
+ watch_list = Fcons (Fcons (make_number (watchdesc),
+ Fcons (file_name, Fcons (callback, Qnil))),
+ watch_list);
+ return Qt;
+}
+
+DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0,
+ doc: /* Stop watching a file or directory. */)
+ (Lisp_Object file_name)
+{
+ Lisp_Object cell, file_name_data;
+ Lisp_Object x = watch_list;
+
+ if (inotifyfd == uninitialized)
+ return Qnil;
+ CHECK_STRING (file_name);
+
+ while (!NILP (x))
+ {
+ cell = Fcar (x);
+ x = Fcdr (x);
+ file_name_data = Fcdr (cell);
+ if (!NILP (file_name_data) && STRINGP (Fcar (file_name_data))
+ && Fstring_equal (Fcar (file_name_data), file_name)
+ && NUMBERP (Fcar (cell)))
+ {
+ int const magicno = XINT (Fcar (cell));
+ watch_list = Fdelete (cell, watch_list);
+ if (inotify_rm_watch (inotifyfd, magicno) == -1)
+ report_file_error ("Could not unwatch file",
+ Fcons (file_name, Qnil));
+
+ /* Cleanup if watch_list is empty. */
+ if (NILP (watch_list))
+ {
+ close (inotifyfd);
+ delete_read_fd (inotifyfd);
+ inotifyfd = uninitialized;
+ }
+ return Qt;
+ }
+ }
+ return Qnil;
+}
+
+#else /* HAVE_INOTIFY */
+#error "Filewatch defined but no watch mechanism (inotify) available"
+#endif /* HAVE_INOTIFY */
+
+void
+syms_of_filewatch (void)
+{
+ DEFSYM (Qmodify, "modify");
+ DEFSYM (Qmove, "move");
+ DEFSYM (Qattrib, "attrib");
+ DEFSYM (Qdelete, "delete");
+ DEFSYM (Qfrom, "from");
+ DEFSYM (Qto, "to");
+
+ DEFSYM (Qunkown_aspect, "unkown-aspect");
+
+ defsubr (&Sfile_watch);
+ defsubr (&Sfile_unwatch);
+}
+
+#endif /* HAVE_FILEWATCH */
diff --git a/src/keyboard.c b/src/keyboard.c
index 7bc406a..ccf25f7 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -329,6 +329,9 @@ static Lisp_Object Qsave_session;
#ifdef HAVE_DBUS
static Lisp_Object Qdbus_event;
#endif
+#ifdef HAVE_FILEWATCH
+static Lisp_Object Qfilewatch_event;
+#endif /* HAVE_FILEWATCH */
static Lisp_Object Qconfig_changed_event;
/* Lisp_Object Qmouse_movement; - also an event header */
@@ -4017,6 +4020,13 @@ kbd_buffer_get_event (KBOARD **kbp,
kbd_fetch_ptr = event + 1;
}
#endif
+#ifdef HAVE_FILEWATCH
+ else if (event->kind == FILEWATCH_EVENT)
+ {
+ obj = make_lispy_event (event);
+ kbd_fetch_ptr = event + 1;
+ }
+#endif
else if (event->kind == CONFIG_CHANGED_EVENT)
{
obj = make_lispy_event (event);
@@ -5913,6 +5923,13 @@ make_lispy_event (struct input_event *event)
}
#endif /* HAVE_DBUS */
+#ifdef HAVE_FILEWATCH
+ case FILEWATCH_EVENT:
+ {
+ return Fcons (Qfilewatch_event, event->arg);
+ }
+#endif /* HAVE_FILEWATCH */
+
case CONFIG_CHANGED_EVENT:
return Fcons (Qconfig_changed_event,
Fcons (event->arg,
@@ -11524,6 +11541,10 @@ syms_of_keyboard (void)
DEFSYM (Qdbus_event, "dbus-event");
#endif
+#ifdef HAVE_FILEWATCH
+ DEFSYM (Qfilewatch_event, "filewatch-event");
+#endif /* HAVE_FILEWATCH */
+
DEFSYM (QCenable, ":enable");
DEFSYM (QCvisible, ":visible");
DEFSYM (QChelp, ":help");
@@ -12274,6 +12295,13 @@ keys_of_keyboard (void)
"dbus-handle-event");
#endif
+#ifdef HAVE_FILEWATCH
+ /* Define a special event which is raised for filewatch callback
+ functions. */
+ initial_define_lispy_key (Vspecial_event_map, "filewatch-event",
+ "filewatch-handle-event");
+#endif /* HAVE_FILEWATCH */
+
initial_define_lispy_key (Vspecial_event_map, "config-changed-event",
"ignore");
}
diff --git a/src/lisp.h b/src/lisp.h
index 8a504e8..8b21e0e 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -3430,6 +3430,11 @@ EXFUN (Fxw_display_color_p, 1);
EXFUN (Fx_focus_frame, 1);
#endif
+/* Defined in filewatch.c */
+#ifdef HAVE_FILEWATCH
+extern void syms_of_filewatch (void);
+#endif
+
/* Defined in xfaces.c */
extern Lisp_Object Qdefault, Qtool_bar, Qfringe;
extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor;
diff --git a/src/termhooks.h b/src/termhooks.h
index 6a58517..9db2019 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -206,6 +206,11 @@ enum event_kind
, NS_NONKEY_EVENT
#endif
+#ifdef HAVE_FILEWATCH
+ /* File or directory was changed. */
+ , FILEWATCH_EVENT
+#endif
+
};
/* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT
--
1.7.5.2
- [PATCH] Support for filesystem watching (inotify), Rüdiger Sonderfeld, 2011/06/04
- Re: [PATCH] Support for filesystem watching (inotify), joakim, 2011/06/04
- Re: [PATCH] Support for filesystem watching (inotify), Jan Djärv, 2011/06/04
- Re: [PATCH update2] Support for filesystem watching (inotify), Rüdiger Sonderfeld, 2011/06/04
- Re: [PATCH update2] Support for filesystem watching (inotify), Jan Djärv, 2011/06/05
- Re: [PATCH update2] Support for filesystem watching (inotify), Andreas Schwab, 2011/06/05
- Re: [PATCH update2] Support for filesystem watching (inotify), Rüdiger Sonderfeld, 2011/06/05
- Re: [PATCH update2] Support for filesystem watching (inotify), John Yates, 2011/06/05
- Re: [PATCH update2] Support for filesystem watching (inotify), Rüdiger Sonderfeld, 2011/06/05
- Re: [PATCH update2] Support for filesystem watching (inotify), Eli Zaretskii, 2011/06/05
- Re: [PATCH update2] Support for filesystem watching (inotify), Rüdiger Sonderfeld, 2011/06/07
- Re: [PATCH update2] Support for filesystem watching (inotify), Eli Zaretskii, 2011/06/06
Re: [PATCH] Support for filesystem watching (inotify), Eli Zaretskii, 2011/06/04