emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH updated] Support for filesystem watching (inotify)


From: Rüdiger Sonderfeld
Subject: Re: [PATCH updated] Support for filesystem watching (inotify)
Date: Sat, 4 Jun 2011 19:13:08 +0200
User-agent: KMail/1.13.6 (Linux/2.6.38-8-generic; KDE/4.6.2; x86_64; ; )

On Saturday 04 June 2011 13:30:42 Eli Zaretskii wrote:

> Thanks.  A few comments below.

Thank you for your comments! I updated the patch and I hope I fixed everything
except the things noted below.

> > +#include <setjmp.h>
> 
> Why do you need this header?

"lisp.h" requires it. (lisp.h:2034:3: error: expected specifier-qualifier-list
before ‘jmp_buf’)

> > +static void
> > +inotify_callback(int fd, void *_, int for_read) {
> 
> What's that _ in the argument list?

I ignore this argument.

> > +  while(i < (size_t)n)
> 
> I think this cast, in addition to being ugly, is also unneeded,
> because you already verified above that n is positive.  So `i' can be
> ssize_t as well, and the need for the cast is gone.

I think changing i to ssize_t is the wrong way around, because i should never
be negative.

> > +          if(ev->mask & IN_IGNORED)
> > +            {
> > +              /* Event was removed automatically: Drop it from data
> > list. */ +              message("File-watch: \"%s\" will be ignored",
> > SSDATA(call[1])); +              data = Fdelete(callback, data);
> > +            }
> 
> Do we really need this message?  Consider outputting it only to the
> *Messages* buffer.
> > +          if(ev->mask & IN_Q_OVERFLOW)
> > +            message1("File watch: Inotify Queue Overflow!");
> 
> Same here.

I changes it to use add_to_log. I'm not sure if there is a better way to do
it.

> >     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.
> 
> This part is extremely unclear.  How about an example?


> > +  uint32_t mask = 0;
> 
> Why not `unsigned'?  uint32_t is not part of C90, so it's less
> portable.

It's the type used in the inotify structure.

> > +  while(!NILP(x))
> > +    {
> > +      cell = Fcar(x);
> > +      x = Fcdr(x);
> > +      path_data = Fcdr(cell);
> 
> ??? Isn't `data' an alist?  If so, why not use Fassq etc.?

Because I have to query for the file name and the structure of data is
((magicno . (filename callback)) ...)



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 +++
 src/Makefile.in |    2 +-
 src/emacs.c     |    4 +
 src/filewatch.c |  268 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/lisp.h      |    5 +
 5 files changed, 292 insertions(+), 1 deletions(-)
 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/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..55da25f
--- /dev/null
+++ b/src/filewatch.c
@@ -0,0 +1,268 @@
+/* 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>
+#include "lisp.h"
+#include "coding.h"
+#include "process.h"
+
+static Lisp_Object QCmodify, QCmove, QCattrib, QCdelete, QCfrom, QCto, QCall;
+
+#ifdef HAVE_INOTIFY
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+
+enum { uninitialized = -100 };
+static int inotifyfd = uninitialized;
+
+/* Assoc list of files being watched.  */
+static Lisp_Object watch_list;
+
+static void
+inotify_callback(int fd, void *_, int for_read)
+{
+  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)
+    report_file_error("Error while trying to read file system events",
+                      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;
+          Lisp_Object call[3];
+          call[0] = Fcar(Fcdr(Fcdr(callback))); /* callback */
+          call[1] = Fcar(Fcdr(callback));       /* file name */
+
+          /* 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(QCmodify, name), events);
+          if(ev->mask & IN_MOVE_SELF)
+            events = Fcons(Fcons(QCmove, name), events);
+          if(ev->mask & IN_MOVED_FROM)
+            events = Fcons(Fcons(QCmove,
+                                 Fcons(QCfrom,
+                                       Fcons(name, make_number(ev->cookie)))),
+                           events);
+          if(ev->mask & IN_MOVED_TO)
+            events = Fcons(Fcons(QCmove,
+                                 Fcons(QCto,
+                                       Fcons(name, make_number(ev->cookie)))),
+                           events);
+          if(ev->mask & IN_ATTRIB)
+            events = Fcons(Fcons(QCattrib, name), events);
+          if(ev->mask & (IN_DELETE|IN_DELETE_SELF) )
+            events = Fcons(Fcons(QCdelete, name), events);
+
+          if(!NILP(events))
+            {
+              call[2] = events;
+              Ffuncall(3, call);
+            }
+
+          if(ev->mask & IN_IGNORED)
+            {
+              /* Event was removed automatically: Drop it from data list.  */
+              add_to_log("File-watch: \"%s\" will be ignored", call[1], 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;
+    }
+
+  xfree(buffer);
+}
+
+DEFUN ("file-watch", Ffile_watch, Sfile_watch, 3, MANY, 0,
+       doc: /* Watch a file or directory.
+
+file-watch watches the file or directory given in FILENAME.  If a change occurs
+CALLBACK is called with FILENAME as first argument and a list of changes as 
second
+argument.  FLAGS can be
+
+: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.
+
+:all -- 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" #'(lambda (file event) (message "%s %s" file event)) :all)
+
+Use `file-unwatch' to stop watching.
+
+usage: (file-watch FILENAME CALLBACK &rest FLAGS) */)
+  (size_t nargs, Lisp_Object *args)
+{
+  uint32_t mask;
+  size_t i;
+  int watchdesc;
+  Lisp_Object decoded_file_name;
+
+  if(nargs < 3)
+    return Qnil;
+
+  CHECK_STRING(args[0]);
+
+  if(inotifyfd == uninitialized)
+    {
+      inotifyfd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+      if(inotifyfd == -1)
+        report_file_error("File watching feature (inotify) is not available",
+                          Qnil);
+      watch_list = Qnil;
+      add_read_fd(inotifyfd, &inotify_callback, &watch_list);
+    }
+  mask = 0;
+  for(i = 2; i < nargs; ++i)
+    {
+      if(EQ(args[i], QCmodify))
+        mask |= IN_MODIFY | IN_CREATE;
+      else if(EQ(args[i], QCmove))
+        mask |= IN_MOVE_SELF | IN_MOVE;
+      else if(EQ(args[i], QCattrib))
+        mask |= IN_ATTRIB;
+      else if(EQ(args[i], QCdelete))
+        mask |= IN_DELETE_SELF | IN_DELETE;
+      else if(EQ(args[i], QCall))
+        mask |= IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB
+          | IN_DELETE_SELF | IN_DELETE;
+      else /* TODO: should this be an error? */
+        message("Unkown parameter %s (ignored)",
+                SSDATA(Fprin1_to_string(args[i], Qnil)));
+    }
+  decoded_file_name = ENCODE_FILE(args[0]);
+  watchdesc = inotify_add_watch(inotifyfd, SSDATA(decoded_file_name), mask);
+  if(watchdesc == -1) {
+    report_file_error("Could not watch file", Fcons(args[0], Qnil));
+  }
+  /* TODO: check if file is already in the watch_list.  */
+  watch_list = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), 
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)
+{
+  QCmodify = intern_c_string (":modify");
+  staticpro (&QCmodify);
+  QCmove = intern_c_string (":move");
+  staticpro (&QCmove);
+  QCattrib = intern_c_string (":attrib");
+  staticpro (&QCattrib);
+  QCdelete = intern_c_string (":delete");
+  staticpro (&QCdelete);
+  QCfrom = intern_c_string (":from");
+  staticpro (&QCfrom);
+  QCto = intern_c_string (":to");
+  staticpro (&QCto);
+  QCall = intern_c_string (":all");
+  staticpro (&QCall);
+
+  defsubr (&Sfile_watch);
+  defsubr (&Sfile_unwatch);
+}
+
+
+#endif /* HAVE_FILEWATCH */
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;
-- 
1.7.5.2





reply via email to

[Prev in Thread] Current Thread [Next in Thread]