* [PATCH] Support for filesystem watching (inotify) @ 2011-06-03 22:34 Rüdiger Sonderfeld 2011-06-04 8:52 ` joakim ` (3 more replies) 0 siblings, 4 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-03 22:34 UTC (permalink / raw) To: emacs-devel Hello, I wrote a patch to support watching for file system events. It currently only works with inotify (Linux) but it could be extended to support kqueue (*BSD, OS X) or Win32. But I wanted to get some feedback first. Watching for filesystem events would be very useful for, e.g., dired or magit's status view. Here is a quick example (expects a directory foo containing a file bar): (file-watch "foo" #'(lambda (path events) (message "FS-Event: %s %s") path events)) :all) (delete-file "foo/bar") (file-unwatch "foo") So please tell me what you think of this. Regards, Rüdiger [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 | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lisp.h | 5 + 5 files changed, 266 insertions(+), 1 deletions(-) create mode 100644 src/filewatch.c diff --git a/configure.in b/configure.in index 77deef8..3263876 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. @@ -1978,6 +1979,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 e119596..0cd6d6e 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -354,7 +354,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 6bdd255..5ca5cda 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1550,6 +1550,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..5b7c130 --- /dev/null +++ b/src/filewatch.c @@ -0,0 +1,242 @@ +/* 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 "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 data; + +static void +inotify_callback(int fd, void *_, int for_read) { + eassert(for_read); + eassert(data); + + int to_read = 0; + if(ioctl(fd, FIONREAD, &to_read) == -1) + report_file_error("ioctl(2) on inotify", Qnil); + char *const buffer = xmalloc(to_read); + ssize_t const n = read(fd, buffer, to_read); + if(n < 0) + report_file_error("read from inotify", Qnil); + else if(n < to_read) + { + // TODO + message1("n < to_read"); + } + + size_t i = 0; + while(i < (size_t)n) + { + struct inotify_event *ev = (struct inotify_event*)&buffer[i]; + + Lisp_Object callback = Fassoc(make_number(ev->wd), data); + if(!NILP(callback)) + { + Lisp_Object call[3]; + call[0] = Fcar(Fcdr(Fcdr(callback))); /* callback */ + call[1] = Fcar(Fcdr(callback)); /* path */ + + /* TODO check how to handle UTF-8 in file names: + make_string_from_bytes NCHARS vs. NBYTES */ + /* ev->len seems to be an arbitrary large number + and not the exact length of ev->name */ + size_t const len = strlen(ev->name); + /* if a directory is watched name contains the name + of the file that was changed */ + Lisp_Object name = make_string_from_bytes (ev->name, len, len); + + Lisp_Object 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. */ + message("File-watch: \"%s\" will be ignored", SSDATA(call[1])); + data = Fdelete(callback, data); + } + if(ev->mask & IN_Q_OVERFLOW) + message1("File watch: Inotify Queue Overflow!"); + } + + i += sizeof(*ev) + ev->len; + } + + free(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 PATH. If a change occurs +CALLBACK is called with PATH 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. + +Use `file-unwatch' to stop watching. + +usage: (file-watch PATH CALLBACK &rest FLAGS) */) + (size_t nargs, Lisp_Object *args) +{ + 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("Initializing file watching", Qnil); + data = Qnil; + add_read_fd(inotifyfd, &inotify_callback, &data); + } + uint32_t mask = 0; + int i; + 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))); + } + int watchdesc = inotify_add_watch(inotifyfd, SSDATA(args[0]), mask); + if(watchdesc == -1) { + report_file_error("Watching file", Qnil); + } + data = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), data); + return Qt; +} + +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0, + doc: /* Stop watching a file or directory. */) + (Lisp_Object path) +{ + if(inotifyfd == uninitialized) + return Qnil; + CHECK_STRING(path); + + Lisp_Object x = data; + Lisp_Object cell, path_data; + while(!NILP(x)) + { + cell = Fcar(x); + x = Fcdr(x); + path_data = Fcdr(cell); + if(!NILP(path_data) && STRINGP(Fcar(path_data)) + && Fstring_equal(Fcar(path_data), path) + && NUMBERP(Fcar(cell))) + { + int const magicno = XINT(Fcar(cell)); + data = Fdelete(cell, data); + if(inotify_rm_watch(inotifyfd, magicno) == -1) + report_file_error("Unwatch path", Qnil); + 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 85838d1..3ed5281 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -3405,6 +3405,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, Qregion, Qfringe; extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor, Qborder, Qmouse, Qmenu; -- 1.7.5.2 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2011-06-03 22:34 [PATCH] Support for filesystem watching (inotify) Rüdiger Sonderfeld @ 2011-06-04 8:52 ` joakim 2011-06-04 16:40 ` Rüdiger Sonderfeld 2011-06-04 10:43 ` Jan Djärv ` (2 subsequent siblings) 3 siblings, 1 reply; 125+ messages in thread From: joakim @ 2011-06-04 8:52 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel Rüdiger Sonderfeld <ruediger@c-plusplus.de> writes: > Hello, > I wrote a patch to support watching for file system events. It currently only works with inotify (Linux) but it could be extended to support kqueue > (*BSD, OS X) or Win32. But I wanted to get some feedback first. Watching for filesystem events would be very useful for, e.g., dired or magit's status > view. Very interesting! Have you signed copyright papers and so on? > > Here is a quick example (expects a directory foo containing a file bar): > > (file-watch "foo" #'(lambda (path events) (message "FS-Event: %s %s") path events)) :all) > (delete-file "foo/bar") > (file-unwatch "foo") > > So please tell me what you think of this. > > Regards, > Rüdiger > > > [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 | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/lisp.h | 5 + > 5 files changed, 266 insertions(+), 1 deletions(-) > create mode 100644 src/filewatch.c > > diff --git a/configure.in b/configure.in > index 77deef8..3263876 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. > @@ -1978,6 +1979,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 e119596..0cd6d6e 100644 > --- a/src/Makefile.in > +++ b/src/Makefile.in > @@ -354,7 +354,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 6bdd255..5ca5cda 100644 > --- a/src/emacs.c > +++ b/src/emacs.c > @@ -1550,6 +1550,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..5b7c130 > --- /dev/null > +++ b/src/filewatch.c > @@ -0,0 +1,242 @@ > +/* 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 "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 data; > + > +static void > +inotify_callback(int fd, void *_, int for_read) { > + eassert(for_read); > + eassert(data); > + > + int to_read = 0; > + if(ioctl(fd, FIONREAD, &to_read) == -1) > + report_file_error("ioctl(2) on inotify", Qnil); > + char *const buffer = xmalloc(to_read); > + ssize_t const n = read(fd, buffer, to_read); > + if(n < 0) > + report_file_error("read from inotify", Qnil); > + else if(n < to_read) > + { > + // TODO > + message1("n < to_read"); > + } > + > + size_t i = 0; > + while(i < (size_t)n) > + { > + struct inotify_event *ev = (struct inotify_event*)&buffer[i]; > + > + Lisp_Object callback = Fassoc(make_number(ev->wd), data); > + if(!NILP(callback)) > + { > + Lisp_Object call[3]; > + call[0] = Fcar(Fcdr(Fcdr(callback))); /* callback */ > + call[1] = Fcar(Fcdr(callback)); /* path */ > + > + /* TODO check how to handle UTF-8 in file names: > + make_string_from_bytes NCHARS vs. NBYTES */ > + /* ev->len seems to be an arbitrary large number > + and not the exact length of ev->name */ > + size_t const len = strlen(ev->name); > + /* if a directory is watched name contains the name > + of the file that was changed */ > + Lisp_Object name = make_string_from_bytes (ev->name, len, len); > + > + Lisp_Object 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. */ > + message("File-watch: \"%s\" will be ignored", SSDATA(call[1])); > + data = Fdelete(callback, data); > + } > + if(ev->mask & IN_Q_OVERFLOW) > + message1("File watch: Inotify Queue Overflow!"); > + } > + > + i += sizeof(*ev) + ev->len; > + } > + > + free(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 PATH. If a change occurs > +CALLBACK is called with PATH 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. > + > +Use `file-unwatch' to stop watching. > + > +usage: (file-watch PATH CALLBACK &rest FLAGS) */) > + (size_t nargs, Lisp_Object *args) > +{ > + 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("Initializing file watching", Qnil); > + data = Qnil; > + add_read_fd(inotifyfd, &inotify_callback, &data); > + } > + uint32_t mask = 0; > + int i; > + 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))); > + } > + int watchdesc = inotify_add_watch(inotifyfd, SSDATA(args[0]), mask); > + if(watchdesc == -1) { > + report_file_error("Watching file", Qnil); > + } > + data = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), data); > + return Qt; > +} > + > +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0, > + doc: /* Stop watching a file or directory. */) > + (Lisp_Object path) > +{ > + if(inotifyfd == uninitialized) > + return Qnil; > + CHECK_STRING(path); > + > + Lisp_Object x = data; > + Lisp_Object cell, path_data; > + while(!NILP(x)) > + { > + cell = Fcar(x); > + x = Fcdr(x); > + path_data = Fcdr(cell); > + if(!NILP(path_data) && STRINGP(Fcar(path_data)) > + && Fstring_equal(Fcar(path_data), path) > + && NUMBERP(Fcar(cell))) > + { > + int const magicno = XINT(Fcar(cell)); > + data = Fdelete(cell, data); > + if(inotify_rm_watch(inotifyfd, magicno) == -1) > + report_file_error("Unwatch path", Qnil); > + 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 85838d1..3ed5281 100644 > --- a/src/lisp.h > +++ b/src/lisp.h > @@ -3405,6 +3405,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, Qregion, Qfringe; > extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor, Qborder, Qmouse, Qmenu; -- Joakim Verona ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2011-06-04 8:52 ` joakim @ 2011-06-04 16:40 ` Rüdiger Sonderfeld 0 siblings, 0 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-04 16:40 UTC (permalink / raw) To: joakim; +Cc: emacs-devel On Saturday 04 June 2011 10:52:07 joakim@verona.se wrote: > Rüdiger Sonderfeld <ruediger@c-plusplus.de> writes: > > Hello, > > I wrote a patch to support watching for file system events. It currently > > only works with inotify (Linux) but it could be extended to support > > kqueue (*BSD, OS X) or Win32. But I wanted to get some feedback first. > > Watching for filesystem events would be very useful for, e.g., dired or > > magit's status view. > > Very interesting! Have you signed copyright papers and so on? Yes. Since 25 May 2011. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2011-06-03 22:34 [PATCH] Support for filesystem watching (inotify) Rüdiger Sonderfeld 2011-06-04 8:52 ` joakim @ 2011-06-04 10:43 ` Jan Djärv 2011-06-04 23:36 ` [PATCH update2] " Rüdiger Sonderfeld 2011-06-04 11:30 ` [PATCH] " Eli Zaretskii 2012-09-18 11:50 ` [PATCH] " Leo 3 siblings, 1 reply; 125+ messages in thread From: Jan Djärv @ 2011-06-04 10:43 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel@gnu.org Hi. 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. Jan D. 4 jun 2011 kl. 00:34 skrev Rüdiger Sonderfeld <ruediger@c-plusplus.de>: > Hello, > I wrote a patch to support watching for file system events. It currently only works with inotify (Linux) but it could be extended to support kqueue > (*BSD, OS X) or Win32. But I wanted to get some feedback first. Watching for filesystem events would be very useful for, e.g., dired or magit's status > view. > > Here is a quick example (expects a directory foo containing a file bar): > > (file-watch "foo" #'(lambda (path events) (message "FS-Event: %s %s") path events)) :all) > (delete-file "foo/bar") > (file-unwatch "foo") > > So please tell me what you think of this. > > Regards, > Rüdiger > > > [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 | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/lisp.h | 5 + > 5 files changed, 266 insertions(+), 1 deletions(-) > create mode 100644 src/filewatch.c > > diff --git a/configure.in b/configure.in > index 77deef8..3263876 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. > @@ -1978,6 +1979,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 e119596..0cd6d6e 100644 > --- a/src/Makefile.in > +++ b/src/Makefile.in > @@ -354,7 +354,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 6bdd255..5ca5cda 100644 > --- a/src/emacs.c > +++ b/src/emacs.c > @@ -1550,6 +1550,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..5b7c130 > --- /dev/null > +++ b/src/filewatch.c > @@ -0,0 +1,242 @@ > +/* 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 "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 data; > + > +static void > +inotify_callback(int fd, void *_, int for_read) { > + eassert(for_read); > + eassert(data); > + > + int to_read = 0; > + if(ioctl(fd, FIONREAD, &to_read) == -1) > + report_file_error("ioctl(2) on inotify", Qnil); > + char *const buffer = xmalloc(to_read); > + ssize_t const n = read(fd, buffer, to_read); > + if(n < 0) > + report_file_error("read from inotify", Qnil); > + else if(n < to_read) > + { > + // TODO > + message1("n < to_read"); > + } > + > + size_t i = 0; > + while(i < (size_t)n) > + { > + struct inotify_event *ev = (struct inotify_event*)&buffer[i]; > + > + Lisp_Object callback = Fassoc(make_number(ev->wd), data); > + if(!NILP(callback)) > + { > + Lisp_Object call[3]; > + call[0] = Fcar(Fcdr(Fcdr(callback))); /* callback */ > + call[1] = Fcar(Fcdr(callback)); /* path */ > + > + /* TODO check how to handle UTF-8 in file names: > + make_string_from_bytes NCHARS vs. NBYTES */ > + /* ev->len seems to be an arbitrary large number > + and not the exact length of ev->name */ > + size_t const len = strlen(ev->name); > + /* if a directory is watched name contains the name > + of the file that was changed */ > + Lisp_Object name = make_string_from_bytes (ev->name, len, len); > + > + Lisp_Object 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. */ > + message("File-watch: \"%s\" will be ignored", SSDATA(call[1])); > + data = Fdelete(callback, data); > + } > + if(ev->mask & IN_Q_OVERFLOW) > + message1("File watch: Inotify Queue Overflow!"); > + } > + > + i += sizeof(*ev) + ev->len; > + } > + > + free(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 PATH. If a change occurs > +CALLBACK is called with PATH 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. > + > +Use `file-unwatch' to stop watching. > + > +usage: (file-watch PATH CALLBACK &rest FLAGS) */) > + (size_t nargs, Lisp_Object *args) > +{ > + 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("Initializing file watching", Qnil); > + data = Qnil; > + add_read_fd(inotifyfd, &inotify_callback, &data); > + } > + uint32_t mask = 0; > + int i; > + 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))); > + } > + int watchdesc = inotify_add_watch(inotifyfd, SSDATA(args[0]), mask); > + if(watchdesc == -1) { > + report_file_error("Watching file", Qnil); > + } > + data = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), data); > + return Qt; > +} > + > +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0, > + doc: /* Stop watching a file or directory. */) > + (Lisp_Object path) > +{ > + if(inotifyfd == uninitialized) > + return Qnil; > + CHECK_STRING(path); > + > + Lisp_Object x = data; > + Lisp_Object cell, path_data; > + while(!NILP(x)) > + { > + cell = Fcar(x); > + x = Fcdr(x); > + path_data = Fcdr(cell); > + if(!NILP(path_data) && STRINGP(Fcar(path_data)) > + && Fstring_equal(Fcar(path_data), path) > + && NUMBERP(Fcar(cell))) > + { > + int const magicno = XINT(Fcar(cell)); > + data = Fdelete(cell, data); > + if(inotify_rm_watch(inotifyfd, magicno) == -1) > + report_file_error("Unwatch path", Qnil); > + 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 85838d1..3ed5281 100644 > --- a/src/lisp.h > +++ b/src/lisp.h > @@ -3405,6 +3405,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, Qregion, Qfringe; > extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor, Qborder, Qmouse, Qmenu; > -- > 1.7.5.2 > ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-04 10:43 ` Jan Djärv @ 2011-06-04 23:36 ` Rüdiger Sonderfeld 2011-06-05 5:45 ` Eli Zaretskii ` (2 more replies) 0 siblings, 3 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-04 23:36 UTC (permalink / raw) To: emacs-devel@gnu.org Hi, On Saturday 04 June 2011 12:43:37 you wrote: > 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. I imitated the behaviour of the dbus subsystem. I hope this is what you had in mind. 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 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-04 23:36 ` [PATCH update2] " Rüdiger Sonderfeld @ 2011-06-05 5:45 ` Eli Zaretskii 2011-06-05 9:48 ` [PATCH update3] " Rüdiger Sonderfeld 2011-06-05 7:48 ` [PATCH update2] " Jan Djärv 2011-06-05 7:54 ` Andreas Schwab 2 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2011-06-05 5:45 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > 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? ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update3] Support for filesystem watching (inotify) 2011-06-05 5:45 ` Eli Zaretskii @ 2011-06-05 9:48 ` Rüdiger Sonderfeld 0 siblings, 0 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-05 9:48 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel On Sunday 05 June 2011 07:45:58 Eli Zaretskii wrote: > > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > > 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 <ruediger@c-plusplus.de> +;; 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 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-04 23:36 ` [PATCH update2] " Rüdiger Sonderfeld 2011-06-05 5:45 ` Eli Zaretskii @ 2011-06-05 7:48 ` Jan Djärv 2011-06-05 7:54 ` Andreas Schwab 2 siblings, 0 replies; 125+ messages in thread From: Jan Djärv @ 2011-06-05 7:48 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel@gnu.org Rüdiger Sonderfeld skrev 2011-06-05 01.36: > Hi, > > On Saturday 04 June 2011 12:43:37 you wrote: >> 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. I imitated the > behaviour of the dbus subsystem. I hope this is what you had in mind. As Eli already said, it doesn't appear in the patch you sent. Jan D. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-04 23:36 ` [PATCH update2] " Rüdiger Sonderfeld 2011-06-05 5:45 ` Eli Zaretskii 2011-06-05 7:48 ` [PATCH update2] " Jan Djärv @ 2011-06-05 7:54 ` Andreas Schwab 2011-06-05 9:49 ` Rüdiger Sonderfeld 2 siblings, 1 reply; 125+ messages in thread From: Andreas Schwab @ 2011-06-05 7:54 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel@gnu.org What happens if inotify is not available and you call file-watch and then file-unwatch? Andreas. -- Andreas Schwab, schwab@linux-m68k.org GPG Key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-05 7:54 ` Andreas Schwab @ 2011-06-05 9:49 ` Rüdiger Sonderfeld 2011-06-05 15:59 ` John Yates 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-05 9:49 UTC (permalink / raw) To: Andreas Schwab; +Cc: emacs-devel On Sunday 05 June 2011 09:54:20 Andreas Schwab wrote: > What happens if inotify is not available and you call file-watch and > then file-unwatch? > > Andreas. Oh you are right. I should reset inotifyfd to uninitialized in case of error. I changed this in Version 3 of the patch. Thanks! ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-05 9:49 ` Rüdiger Sonderfeld @ 2011-06-05 15:59 ` John Yates 2011-06-05 16:14 ` Rüdiger Sonderfeld 0 siblings, 1 reply; 125+ messages in thread From: John Yates @ 2011-06-05 15:59 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 252 bytes --] Is there a good reason not to provide access to the full functionality of the inotify API? - Why conflate IN_MODIFY and IN_CREATE? - Why conflate IN_DELETE and IN_DELETE_SELF? - Why omit IN_OPEN, IN_ACCESS, IN_CLOSE_WRITE and IN_CLOSE_NOWRITE? /john [-- Attachment #2: Type: text/html, Size: 1384 bytes --] ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-05 15:59 ` John Yates @ 2011-06-05 16:14 ` Rüdiger Sonderfeld 2011-06-05 16:58 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-05 16:14 UTC (permalink / raw) To: John Yates; +Cc: emacs-devel Hi, On Sunday 05 June 2011 17:59:03 John Yates wrote: > Is there a good reason not to provide access to the full functionality of > the inotify API? Yes, the reason is portability. I want the same API to be available on Linux, *BSD, OS X and Windows. inotify is the most advanced API and therefore I can not support every inotify feature if I want to stay compatible. Porting to kqueue (*BSD, OS X) will be especially troublesome because they don't seem to tell you which file actually changed if you watch a directory. Regards, Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-05 16:14 ` Rüdiger Sonderfeld @ 2011-06-05 16:58 ` Eli Zaretskii 2011-06-06 18:56 ` Rüdiger Sonderfeld 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2011-06-05 16:58 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel, john > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Sun, 5 Jun 2011 18:14:33 +0200 > Cc: emacs-devel@gnu.org > > On Sunday 05 June 2011 17:59:03 John Yates wrote: > > Is there a good reason not to provide access to the full functionality of > > the inotify API? > > Yes, the reason is portability. I want the same API to be available on Linux, > *BSD, OS X and Windows. inotify is the most advanced API and therefore I can > not support every inotify feature if I want to stay compatible. Being compatible does not mean providing only the least common denominator. We have already several features that provide different levels of support depending on the underlying facilities. One example is process-attributes (which is the main primitive on which Proced is based). All you need is provide a superset of all the supported attributes, and document which ones are supported on which platform. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-05 16:58 ` Eli Zaretskii @ 2011-06-06 18:56 ` Rüdiger Sonderfeld 2011-06-06 19:57 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-06 18:56 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel, john On Sunday 05 June 2011 18:58:07 Eli Zaretskii wrote: > > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > > Date: Sun, 5 Jun 2011 18:14:33 +0200 > > Cc: emacs-devel@gnu.org > > > > On Sunday 05 June 2011 17:59:03 John Yates wrote: > > > Is there a good reason not to provide access to the full functionality > > > of the inotify API? > > > > Yes, the reason is portability. I want the same API to be available on > > Linux, *BSD, OS X and Windows. inotify is the most advanced API and > > therefore I can not support every inotify feature if I want to stay > > compatible. > > Being compatible does not mean providing only the least common > denominator. We have already several features that provide different > levels of support depending on the underlying facilities. One example > is process-attributes (which is the main primitive on which Proced is > based). All you need is provide a superset of all the supported > attributes, and document which ones are supported on which platform. I could add the IN_OPEN, IN_ACCESS, IN_CLOSE_WRITE and IN_CLOSE_NOWRITE feature. But having separate IN_MODIFY, IN_CREATE and IN_DELETE, IN_DELETE_SELF would be problematic because they can't be separated for kqueue. Maybe I should just export the basic inotify and kqueue API and implement a portable layer on top of it in elisp. Regards, Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH update2] Support for filesystem watching (inotify) 2011-06-06 18:56 ` Rüdiger Sonderfeld @ 2011-06-06 19:57 ` Eli Zaretskii 0 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2011-06-06 19:57 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel, john > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Mon, 6 Jun 2011 20:56:10 +0200 > Cc: john@yates-sheets.org, > emacs-devel@gnu.org > > I could add the IN_OPEN, IN_ACCESS, IN_CLOSE_WRITE and IN_CLOSE_NOWRITE > feature. But having separate IN_MODIFY, IN_CREATE and IN_DELETE, > IN_DELETE_SELF would be problematic because they can't be separated for > kqueue. Maybe I should just export the basic inotify and kqueue API and > implement a portable layer on top of it in elisp. I trust you to make the right decision. I just wanted you to know you shouldn't confine yourself to a subset for portability's sake, in this case. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2011-06-03 22:34 [PATCH] Support for filesystem watching (inotify) Rüdiger Sonderfeld 2011-06-04 8:52 ` joakim 2011-06-04 10:43 ` Jan Djärv @ 2011-06-04 11:30 ` Eli Zaretskii 2011-06-04 17:13 ` [PATCH updated] " Rüdiger Sonderfeld 2012-09-18 11:50 ` [PATCH] " Leo 3 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2011-06-04 11:30 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Sat, 4 Jun 2011 00:34:15 +0200 > > I wrote a patch to support watching for file system events. It currently only works with inotify (Linux) but it could be extended to support kqueue > (*BSD, OS X) or Win32. But I wanted to get some feedback first. Watching for filesystem events would be very useful for, e.g., dired or magit's status > view. Thanks. A few comments below. > +#include <setjmp.h> Why do you need this header? > +// Assoc list of files being watched. We don't use C++-style comments, because we don't require C9x compiler to build Emacs. > +static Lisp_Object data; The name "data" is too generic. Use something more specific and descriptive, like inotify_watch_alist. > +static void > +inotify_callback(int fd, void *_, int for_read) { What's that _ in the argument list? Also, the style of braces is not according to GNU coding standards. > + if(ioctl(fd, FIONREAD, &to_read) == -1) > + report_file_error("ioctl(2) on inotify", Qnil); Error messages that are shown to the user should not be cryptic. Please use some text that would make sense to a user who is not an expert on inotify. I would think something like this would be appropriate: File watching feature (inotify) is not available > + char *const buffer = xmalloc(to_read); > + ssize_t const n = read(fd, buffer, to_read); Can't declare locals in the middle of code: this isn't C++. > + if(n < 0) > + report_file_error("read from inotify", Qnil); The text of this message also needs work, to make it understandable by mere mortals. > + size_t i = 0; C++/C9x again. > + 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. > + call[1] = Fcar(Fcdr(callback)); /* path */ GNU coding standards frown on using "path" for anything except PATH-style lists of directories separated by colons. Use "file name" instead. > + /* TODO check how to handle UTF-8 in file names: > + make_string_from_bytes NCHARS vs. NBYTES */ Not make_string_from_bytes, but DECODE_FILE. > + size_t const len = strlen(ev->name); C9x again. > + /* if a directory is watched name contains the name > + of the file that was changed */ Comments should begin with a capital letter and end with a period and 2 blanks. > + 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. > + free(buffer); xfree, not free. > +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. Please leave two blanks after a period that ends a sentence. Also, I think it's best to show the forms of the non-primitive arguments to CALLBACK, rather than trying to describe them: a picture is worth a thousand words. Finally, the lines are too long. > 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? > + add_read_fd(inotifyfd, &inotify_callback, &data); I don't see any code that does the corresponding delete_read_fd. > + uint32_t mask = 0; Why not `unsigned'? uint32_t is not part of C90, so it's less portable. > + int watchdesc = inotify_add_watch(inotifyfd, SSDATA(args[0]), mask); Declaration in the middle of code. More importantly, you cannot pass to inotify_add_watch a string in its internal Emacs representation. You need to run it through ENCODE_FILE first. > + if(watchdesc == -1) { > + report_file_error("Watching file", Qnil); Again, this error message text is not helpful. It should also mention the file's name. > + (Lisp_Object path) > +{ > + if(inotifyfd == uninitialized) > + return Qnil; > + CHECK_STRING(path); > + > + Lisp_Object x = data; > + Lisp_Object cell, path_data; `path' and `path_data' again go against the GNU coding standards wrt using "path". > + 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.? > + if(!NILP(path_data) && STRINGP(Fcar(path_data)) > + && Fstring_equal(Fcar(path_data), path) > + && NUMBERP(Fcar(cell))) > + { > + int const magicno = XINT(Fcar(cell)); > + data = Fdelete(cell, data); > + if(inotify_rm_watch(inotifyfd, magicno) == -1) > + report_file_error("Unwatch path", Qnil); > + return Qt; > + } > + } I think this should call delete_read_fd when `data' becomes empty. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 11:30 ` [PATCH] " Eli Zaretskii @ 2011-06-04 17:13 ` Rüdiger Sonderfeld 2011-06-04 19:15 ` Eli Zaretskii ` (2 more replies) 0 siblings, 3 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-04 17:13 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel 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 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 17:13 ` [PATCH updated] " Rüdiger Sonderfeld @ 2011-06-04 19:15 ` Eli Zaretskii 2011-06-04 20:10 ` Thien-Thi Nguyen 2011-06-06 15:14 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2011-06-04 19:15 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Sat, 4 Jun 2011 19:13:08 +0200 > Cc: emacs-devel@gnu.org > > Thank you for your comments! I updated the patch and I hope I fixed everything > except the things noted below. Style of braces is still incorrect, at least in this place: > + if(watchdesc == -1) { > + report_file_error("Could not watch file", Fcons(args[0], Qnil)); > + } The doc string still uses lines that are too long. See also Jan's important comment about how to expose these events to the rest of Emacs. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 17:13 ` [PATCH updated] " Rüdiger Sonderfeld 2011-06-04 19:15 ` Eli Zaretskii @ 2011-06-04 20:10 ` Thien-Thi Nguyen 2011-06-04 23:43 ` Rüdiger Sonderfeld 2011-06-06 15:21 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2011-06-06 15:14 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2 siblings, 2 replies; 125+ messages in thread From: Thien-Thi Nguyen @ 2011-06-04 20:10 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel () Rüdiger Sonderfeld <ruediger@c-plusplus.de> () Sat, 4 Jun 2011 19:13:08 +0200 +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. I think FLAGS should be symbols, not keywords. Furthermore, "flags" is too generic; maybe something like "aspect" or even "what" would be (slightly) better. Instead of ‘:all’ (or ‘all’), consider using ‘t’. Also, it's cool if the first line names all the args. How about: "Arrange to call FUNC if ASPECT of FILENAME changes." ? +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) For upward-compatability, it is better to not use rest-args for these aspects. You should specify one arg ASPECT, document that it can be "either a symbol, one of: ..., or a list of of these symbols". Style preference: I'd prefer CALLBACK to be last so that long lambda expressions need not be (visually) scanned to determine the aspects watched. Consider: (file-watch "foo" t (lambda (name event) ;; LONG ;; COMPLICATED ;; DRAWN-OUT ;; COMPUTATION )) vs (file-watch "foo" (lambda (name event) ;; LONG ;; COMPLICATED ;; DRAWN-OUT ;; COMPUTATION ) t) No big deal, just sympathy for the lone t. + CHECK_STRING(args[0]); Please insert a space between a function (or macro) and its arg list. Generally, (info "(standards) Formatting"). + else /* TODO: should this be an error? */ Yes. + /* TODO: check if file is already in the watch_list. */ Moreover, you need to decide what to do given, for example: (progn (file-watch "foo" 'modify 'notify-modify) (file-watch "foo" 'move 'notify-move)) Is this OK, is this an error, is this "close to" an error? I think a simple "file is already in" check is insufficient. + watch_list = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), watch_list); You can use ‘acons’: (acons K V ALIST) ≡ (cons (cons K V) ALIST). ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 20:10 ` Thien-Thi Nguyen @ 2011-06-04 23:43 ` Rüdiger Sonderfeld 2011-06-05 2:15 ` Thien-Thi Nguyen 2011-06-06 15:21 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 1 sibling, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-04 23:43 UTC (permalink / raw) To: Thien-Thi Nguyen; +Cc: emacs-devel Hi, thank your for your comments. I changed the arguments and changed an unkown aspect into an error. See my reply to Jan Djärv for the version of the patch containing the changes. On Saturday 04 June 2011 22:10:45 Thien-Thi Nguyen wrote: > + /* TODO: check if file is already in the watch_list. */ > > Moreover, you need to decide what to do given, for example: > (progn (file-watch "foo" 'modify 'notify-modify) > (file-watch "foo" 'move 'notify-move)) > Is this OK, is this an error, is this "close to" an error? > I think a simple "file is already in" check is insufficient. Yes, this is a bit complicated. The inotify API itself does not create a separate event number if you try to watch the same file. But this could of course be done in the implementation by checking which callback corresponds to which event. But I'm not sure if this is the right way to handle it. > + watch_list = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), > watch_list); > > You can use ‘acons’: (acons K V ALIST) ≡ (cons (cons K V) ALIST). Sadly acons is not available from the C API. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 23:43 ` Rüdiger Sonderfeld @ 2011-06-05 2:15 ` Thien-Thi Nguyen 2011-06-05 9:22 ` Štěpán Němec 0 siblings, 1 reply; 125+ messages in thread From: Thien-Thi Nguyen @ 2011-06-05 2:15 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel () Rüdiger Sonderfeld <ruediger@c-plusplus.de> () Sun, 5 Jun 2011 01:43:16 +0200 Yes, this is a bit complicated. The inotify API itself does not create a separate event number if you try to watch the same file. But this could of course be done in the implementation by checking which callback corresponds to which event. But I'm not sure if this is the right way to handle it. [...] Sadly acons is not available from the C API. Perhaps the book-keeping parts should be moved to Lisp. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-05 2:15 ` Thien-Thi Nguyen @ 2011-06-05 9:22 ` Štěpán Němec 2011-06-15 20:53 ` Johan Bockgård 0 siblings, 1 reply; 125+ messages in thread From: Štěpán Němec @ 2011-06-05 9:22 UTC (permalink / raw) To: Thien-Thi Nguyen; +Cc: Rüdiger Sonderfeld, emacs-devel Thien-Thi Nguyen <ttn@gnuvola.org> writes: > () Rüdiger Sonderfeld <ruediger@c-plusplus.de> > () Sun, 5 Jun 2011 01:43:16 +0200 [...] > Sadly acons is not available from the C API. > > Perhaps the book-keeping parts should be moved to Lisp. It's not really available from Lisp either. It's a cl.el function, i.e. still a forbidden territory for GNU Emacs core code. That alone doesn't necessarily affect validity of your Lisp-moving suggestion, of course. Štěpán ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-05 9:22 ` Štěpán Němec @ 2011-06-15 20:53 ` Johan Bockgård 2011-06-16 8:52 ` Inlined cl functions -- how to learn about them Štěpán Němec 0 siblings, 1 reply; 125+ messages in thread From: Johan Bockgård @ 2011-06-15 20:53 UTC (permalink / raw) To: emacs-devel Štěpán Němec <stepnem@gmail.com> writes: > Thien-Thi Nguyen <ttn@gnuvola.org> writes: > >> () Rüdiger Sonderfeld <ruediger@c-plusplus.de> >> () Sun, 5 Jun 2011 01:43:16 +0200 > > [...] > >> Sadly acons is not available from the C API. >> >> Perhaps the book-keeping parts should be moved to Lisp. > > It's not really available from Lisp either. It's a cl.el function, i.e. > still a forbidden territory for GNU Emacs core code. FWIW, cl arranges for acons to be inlined at compile time, so it's enough to (eval-when-compile (require 'cl)) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Inlined cl functions -- how to learn about them 2011-06-15 20:53 ` Johan Bockgård @ 2011-06-16 8:52 ` Štěpán Němec 0 siblings, 0 replies; 125+ messages in thread From: Štěpán Němec @ 2011-06-16 8:52 UTC (permalink / raw) To: emacs-devel Johan Bockgård <bojohan@gnu.org> writes: > FWIW, cl arranges for acons to be inlined at compile time, so it's > enough to > > (eval-when-compile (require 'cl)) Interesting, thank you! Is there a list of such exceptions somewhere? Inspecting the symbol's plist or trying to byte-compile such functions and see if the warning pops up or not hardly seems practical even for true GNU-fearing folk. Štěpán ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 20:10 ` Thien-Thi Nguyen 2011-06-04 23:43 ` Rüdiger Sonderfeld @ 2011-06-06 15:21 ` Stefan Monnier 2011-06-06 16:25 ` Rüdiger Sonderfeld 1 sibling, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-06-06 15:21 UTC (permalink / raw) To: Thien-Thi Nguyen; +Cc: Rüdiger Sonderfeld, emacs-devel Thanks for your review, I generally agree with your comments. > Moreover, you need to decide what to do given, for example: > (progn (file-watch "foo" 'modify 'notify-modify) > (file-watch "foo" 'move 'notify-move)) > Is this OK, is this an error, is this "close to" an error? I think dired shouldn't need to know if some other package decided to watch the same directory, so having several watchers for the same file should be accepted and work correctly, i.e. both callbacks should be run when needed. > I think a simple "file is already in" check is insufficient. > + watch_list = Fcons(Fcons(make_number(watchdesc), Flist(2, args)), watch_list); > You can use ‘acons’: (acons K V ALIST) ≡ (cons (cons K V) ALIST). No, Fcons is the right thing to use there. We could #define ACONS(a,b,c) Fcons(Fcons(a,b),c) but that's orthogonal to this patch. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-06 15:21 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier @ 2011-06-06 16:25 ` Rüdiger Sonderfeld 2011-06-06 17:11 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-06 16:25 UTC (permalink / raw) To: Stefan Monnier; +Cc: Thien-Thi Nguyen, emacs-devel On Monday 06 June 2011 17:21:35 Stefan Monnier wrote: > Thanks for your review, I generally agree with your comments. > > > Moreover, you need to decide what to do given, for example: > > (progn (file-watch "foo" 'modify 'notify-modify) > > > > (file-watch "foo" 'move 'notify-move)) > > > > Is this OK, is this an error, is this "close to" an error? > > I think dired shouldn't need to know if some other package decided to > watch the same directory, so having several watchers for the same file > should be accepted and work correctly, i.e. both callbacks should be run > when needed. The problem is: How to implement the file-unwatch then? I need a way to identify each separate file-watch request. What would be the best way to do that? Regards, Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-06 16:25 ` Rüdiger Sonderfeld @ 2011-06-06 17:11 ` Stefan Monnier 2011-06-06 20:16 ` Ted Zlatanov 2011-06-24 0:50 ` Rüdiger Sonderfeld 0 siblings, 2 replies; 125+ messages in thread From: Stefan Monnier @ 2011-06-06 17:11 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Thien-Thi Nguyen, emacs-devel > The problem is: How to implement the file-unwatch then? I need a way > to identify each separate file-watch request. What would be the best > way to do that? How about letting file-watch return a "file-watcher" which you then need to pass to file-unwatch? This "file-watcher" could be any kind of Elisp data you find convenient for this. You may decide to provide no other operation than file-unwatch, but you could also decide to provided additional operations such as changing the callback (I'm not saying that would necessarily be a good idea, tho, but maybe other operations would be handy). Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-06 17:11 ` Stefan Monnier @ 2011-06-06 20:16 ` Ted Zlatanov 2011-06-07 14:42 ` Stefan Monnier 2011-06-24 0:50 ` Rüdiger Sonderfeld 1 sibling, 1 reply; 125+ messages in thread From: Ted Zlatanov @ 2011-06-06 20:16 UTC (permalink / raw) To: emacs-devel On Mon, 06 Jun 2011 14:11:12 -0300 Stefan Monnier <monnier@iro.umontreal.ca> wrote: >> The problem is: How to implement the file-unwatch then? I need a way >> to identify each separate file-watch request. What would be the best >> way to do that? SM> How about letting file-watch return a "file-watcher" which you then need SM> to pass to file-unwatch? This "file-watcher" could be any kind of Elisp SM> data you find convenient for this. You may decide to provide no other SM> operation than file-unwatch, but you could also decide to provided SM> additional operations such as changing the callback (I'm not saying SM> that would necessarily be a good idea, tho, but maybe other operations SM> would be handy). (background: url-future.el is really a generic futures library, but the only expected use for it currently is in url-*.el) The "file-watcher" could derive from url-future, with the nice error-catching and accessor functions that come with it, plus it's protected from double-invocation. Ted ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-06 20:16 ` Ted Zlatanov @ 2011-06-07 14:42 ` Stefan Monnier 2011-06-07 16:46 ` Ted Zlatanov 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-06-07 14:42 UTC (permalink / raw) To: emacs-devel >>> The problem is: How to implement the file-unwatch then? I need a way >>> to identify each separate file-watch request. What would be the best >>> way to do that? SM> How about letting file-watch return a "file-watcher" which you then need SM> to pass to file-unwatch? This "file-watcher" could be any kind of Elisp SM> data you find convenient for this. You may decide to provide no other SM> operation than file-unwatch, but you could also decide to provided SM> additional operations such as changing the callback (I'm not saying SM> that would necessarily be a good idea, tho, but maybe other operations SM> would be handy). > (background: url-future.el is really a generic futures library, but the > only expected use for it currently is in url-*.el) > The "file-watcher" could derive from url-future, with the nice > error-catching and accessor functions that come with it, plus it's > protected from double-invocation. That doesn't sound right to me. At least I'm having trouble figuring out how to bend my mind such that this file-watcher matches (even coarsely) the concept of a "future". Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-07 14:42 ` Stefan Monnier @ 2011-06-07 16:46 ` Ted Zlatanov 2011-06-07 18:06 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Ted Zlatanov @ 2011-06-07 16:46 UTC (permalink / raw) To: emacs-devel On Tue, 07 Jun 2011 11:42:40 -0300 Stefan Monnier <monnier@iro.umontreal.ca> wrote: >>>> The problem is: How to implement the file-unwatch then? I need a way >>>> to identify each separate file-watch request. What would be the best >>>> way to do that? SM> How about letting file-watch return a "file-watcher" which you then need SM> to pass to file-unwatch? This "file-watcher" could be any kind of Elisp SM> data you find convenient for this. You may decide to provide no other SM> operation than file-unwatch, but you could also decide to provided SM> additional operations such as changing the callback (I'm not saying SM> that would necessarily be a good idea, tho, but maybe other operations SM> would be handy). >> (background: url-future.el is really a generic futures library, but the >> only expected use for it currently is in url-*.el) >> The "file-watcher" could derive from url-future, with the nice >> error-catching and accessor functions that come with it, plus it's >> protected from double-invocation. SM> That doesn't sound right to me. At least I'm having trouble figuring SM> out how to bend my mind such that this file-watcher matches (even SM> coarsely) the concept of a "future". In this case it's just a place to stash the callback, with protection against double invocation and error catching. Each instance holds a separate `file-watch' request. `file-unwatch' simply invokes the lambda to get the value to deregister. So `file-watch' would return (make-url-future :value (lambda () (unwatch file here)) :errorback (lambda (&rest d) (handle unwatch errors)) :callback (lambda (f) (arbitrary callback))) Which, in turn, can be used (say it's saved in `f'): (url-future-call f) ; funcall :value (url-future-value good) ; whatever (unwatch file here) returned (url-future-done-p f) ; true (url-future-completed-p f) ; true if no errors, plus :callback was called (url-future-errored-p f) ; true if errors, plus :errorback was called (url-future-call f) ; throws an error I think that's more convenient than a new special "file-watcher" type, especially since the "file-watcher" can be derived from "url-future" and inherit all the functions above under its own namespace, plus of course any fields it cares to define on top. The built-in support for callbacks and error callbacks is pretty nice too. Ted ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-07 16:46 ` Ted Zlatanov @ 2011-06-07 18:06 ` Stefan Monnier 2011-06-07 18:26 ` Ted Zlatanov 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-06-07 18:06 UTC (permalink / raw) To: emacs-devel SM> That doesn't sound right to me. At least I'm having trouble figuring SM> out how to bend my mind such that this file-watcher matches (even SM> coarsely) the concept of a "future". [...] > So `file-watch' would return > (make-url-future :value (lambda () (unwatch file here)) > :errorback (lambda (&rest d) (handle unwatch errors)) > :callback (lambda (f) (arbitrary callback))) I'm not saying you can't twist the code so that it works, but it makes no sense at the conceptual level. The file-watcher callback may be called several times; unwatching a file has nothing to do with "get the resulting value"; ... It's just a completely wrong analogy. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-07 18:06 ` Stefan Monnier @ 2011-06-07 18:26 ` Ted Zlatanov 0 siblings, 0 replies; 125+ messages in thread From: Ted Zlatanov @ 2011-06-07 18:26 UTC (permalink / raw) To: emacs-devel On Tue, 07 Jun 2011 15:06:51 -0300 Stefan Monnier <monnier@iro.umontreal.ca> wrote: SM> That doesn't sound right to me. At least I'm having trouble figuring SM> out how to bend my mind such that this file-watcher matches (even SM> coarsely) the concept of a "future". SM> [...] >> So `file-watch' would return >> (make-url-future :value (lambda () (unwatch file here)) >> :errorback (lambda (&rest d) (handle unwatch errors)) >> :callback (lambda (f) (arbitrary callback))) SM> I'm not saying you can't twist the code so that it works, but it SM> makes no sense at the conceptual level [...] It's just a completely SM> wrong analogy. So you're cautiously in favor, then? ;) Ted ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-06 17:11 ` Stefan Monnier 2011-06-06 20:16 ` Ted Zlatanov @ 2011-06-24 0:50 ` Rüdiger Sonderfeld 2011-06-24 10:19 ` Ted Zlatanov ` (2 more replies) 1 sibling, 3 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-24 0:50 UTC (permalink / raw) To: Stefan Monnier; +Cc: Eli Zaretskii, Thien-Thi Nguyen, emacs-devel On Monday 06 June 2011 19:11:12 Stefan Monnier wrote: > > The problem is: How to implement the file-unwatch then? I need a way > > to identify each separate file-watch request. What would be the best > > way to do that? > > How about letting file-watch return a "file-watcher" which you then need > to pass to file-unwatch? This "file-watcher" could be any kind of Elisp > data you find convenient for this. You may decide to provide no other > operation than file-unwatch, but you could also decide to provided > additional operations such as changing the callback (I'm not saying > that would necessarily be a good idea, tho, but maybe other operations > would be handy). I updated the patch to return a "file-watcher". file-watch can now be called several times for the same file with different flags and callbacks. This however required large changes to the patch. So it would be great if someone could take a look at it again. I added an automated test. But it doesn't test the actually file-watching yet. This is a bit tricky to get right in a unit test. But I'll add it. The code requires some heavy testing. Regards, Rüdiger From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= <ruediger@c-plusplus.de> Date: Fri, 3 Jun 2011 23:58:12 +0200 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 | 485 +++++++++++++++++++++++++++++++++++++ src/keyboard.c | 28 +++ src/lisp.h | 5 + src/termhooks.h | 5 + test/automated/filewatch-tests.el | 50 ++++ 9 files changed, 646 insertions(+), 1 deletions(-) create mode 100644 lisp/filewatch.el create mode 100644 src/filewatch.c create mode 100644 test/automated/filewatch-tests.el diff --git a/configure.in b/configure.in index 0135b9f..4a816ec 100644 --- a/configure.in +++ b/configure.in @@ -174,6 +174,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. @@ -1999,6 +2000,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 <ruediger@c-plusplus.de> +;; 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 d14acd6..db897d5 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1563,6 +1563,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem 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..2833544 --- /dev/null +++ b/src/filewatch.c @@ -0,0 +1,485 @@ +/* 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, Qall_modify; +static Lisp_Object Qunknown_aspect, Qfile_watch; +static Lisp_Object Qaccess, Qclose_write, Qclose_nowrite, Qopen, Qall; + +#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. + Format: + (id . (filename ((subid callback flags) (subid callbacks flags) ...))) */ +static Lisp_Object watch_list; + +#define CADDR(x) Fcar (Fcdr (Fcdr (x))) +#define CADR(x) Fcar (Fcdr (x)) + +static Lisp_Object +append2 (Lisp_Object s1, Lisp_Object s2) +{ + Lisp_Object args[2]; + args[0] = s1; + args[1] = s2; + return Fappend (2, args); +} + +/* Returns a list with all the elements from EVENTS the `watch_list' CELL + is interested in. */ +static Lisp_Object +match_aspects (Lisp_Object cell, Lisp_Object events) +{ + Lisp_Object ret = Qnil; + Lisp_Object aspects = CADDR (cell); + Lisp_Object i; + if (EQ (aspects, Qt) || EQ (aspects, Qall)) + return events; + else if (EQ (aspects, Qall_modify)) + aspects = list4 (Qmodify, Qmove, Qattrib, Qdelete); + + i = Fcar (aspects); + while (!NILP (i)) + { + Lisp_Object e = Fcar (events); + while (!NILP (e)) + { + if (EQ (Fcar (e), i)) + ret = Fcons (e, ret); + events = Fcdr (events); + e = Fcar (events); + } + aspects = Fcdr (aspects); + i = Fcar (aspects); + } + return ret; +} + +static Lisp_Object +inotifyevent_to_aspects (Lisp_Object name, struct inotify_event *ev) +{ + Lisp_Object 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 (ev->mask & IN_ACCESS) + events = Fcons (Fcons (Qaccess, name), events); + if (ev->mask & IN_CLOSE_WRITE) + events = Fcons (Fcons (Qclose_write, name), events); + if (ev->mask & IN_CLOSE_NOWRITE) + events = Fcons (Fcons (Qclose_nowrite, name), events); + if (ev->mask & IN_OPEN) + events = Fcons (Fcons (Qopen, name), events); + return events; +} + +/* 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 watch_data = Fassoc (make_number (ev->wd), watch_list); + if (!NILP (watch_data)) + { + Lisp_Object name, events; + + /* If a directory is watched name contains the name + of the file that was changed. */ + if (ev->len > 0) + { + size_t const len = strlen (ev->name); + name = make_unibyte_string (ev->name, min (len, ev->len)); + name = DECODE_FILE (name); + } + else + { + name = CADR (watch_data); + } + + events = inotifyevent_to_aspects (name, ev); + + if (!NILP (events)) + { + Lisp_Object x = CADDR (watch_data); + while (!NILP (x)) + { + Lisp_Object cell = Fcar (x); + Lisp_Object aspects = match_aspects (cell, events); + if (!NILP (aspects)) + { + Lisp_Object id = list3 (Qfile_watch, make_number (ev->wd), + Fcar (cell)); + Lisp_Object event_info = list1 (list3 (id, CADR (cell), + aspects)); + + if (NILP (event.arg)) + event.arg = event_info; + else + event.arg = append2 (event.arg, event_info); + } + x = Fcdr (x); + } + } + + 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 (watch_data, 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, int in_list) +{ + 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 (!in_list && EQ (symb, Qall_modify)) + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB + | IN_DELETE_SELF | IN_DELETE; + else if (EQ (symb, Qaccess)) + return IN_ACCESS; + else if (EQ (symb, Qclose_write)) + return IN_CLOSE_WRITE; + else if (EQ (symb, Qclose_nowrite)) + return IN_CLOSE_NOWRITE; + else if (EQ (symb, Qopen)) + return IN_OPEN; + else if (!in_list && (EQ (symb, Qt) || EQ (symb, Qall))) + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB + | IN_DELETE_SELF | IN_DELETE | IN_ACCESS | IN_CLOSE_WRITE + | IN_CLOSE_NOWRITE | IN_OPEN; + else + Fsignal (Qunknown_aspect, Fcons (symb, Qnil)); +} + +static uint32_t +aspect_to_inotifymask (Lisp_Object aspect) +{ + if (CONSP (aspect)) + { + Lisp_Object x = aspect; + uint32_t mask = 0; + while (!NILP (x)) + { + mask |= symbol_to_inotifymask (Fcar (x), 1); + x = Fcdr (x); + } + return mask; + } + else + return symbol_to_inotifymask (aspect, 0); +} + +static Lisp_Object +get_watch_data_by_file_name (Lisp_Object file_name) +{ + Lisp_Object cell, file_name_data; + Lisp_Object x = watch_list; + 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)) + && !NILP (Fstring_equal (Fcar (file_name_data), file_name)) + && NUMBERP (Fcar (cell))) + return cell; + } + return 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 (except +for all-modify and t): + +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-modify -- notify for all of the above (all modifying changes). + +access -- notify when file was accessed/read. + +close-write -- notify when a file opened for writing was closed. + +close-nowrite -- notify when a file not opened for writing was closed. + +open -- notify when a file was opened. + +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 (id event) (message "%s %s" id event))) + +Use `file-unwatch' to stop watching. + +usage: (file-watch FILENAME ASPECT CALLBACK) */) + (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback) +{ + static int intern_counter = 0; /* assign local ids */ + uint32_t mask; + int watchdesc; + Lisp_Object decoded_file_name; + Lisp_Object data; + Lisp_Object info; + + 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); + } + + mask = aspect_to_inotifymask (aspect); + data = get_watch_data_by_file_name (file_name); + if (!NILP (data)) + mask |= IN_MASK_ADD; + + 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)); + + info = list3 (make_number (intern_counter), callback, aspect); + ++intern_counter; + + if (!NILP (data)) + Fsetcdr (Fcdr (data), Fcons (append2 (CADDR (data), Fcons (info, Qnil)), + Qnil)); + else + watch_list = Fcons (list3 (make_number (watchdesc), + file_name, Fcons (info, Qnil)), + watch_list); + + return list3 (Qfile_watch, make_number (watchdesc), Fcar (info)); +} + +static int +file_watch_objectp (Lisp_Object obj) +{ + return CONSP (obj) && XINT (Flength (obj)) == 3 + && EQ (Fcar (obj), Qfile_watch); +} + +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0, + doc: /* Stop watching a file or directory. */) + (Lisp_Object watch_object) +{ + Lisp_Object watch_data; + Lisp_Object info; + + if (!file_watch_objectp (watch_object)) + wrong_type_argument (Qfile_watch, watch_object); + + if (inotifyfd == uninitialized) + return Qnil; + + watch_data = Fassoc ( CADR (watch_object), watch_list ); + if (NILP (watch_data)) + return Qnil; + + info = Fassoc (CADDR (watch_object), CADDR (watch_data)); + if (NILP (info)) + return Qnil; + + Fsetcdr (Fcdr (watch_data), Fcons (Fdelete (info, CADDR (watch_data)), Qnil)); + + if (NILP (CADDR (watch_data))) + { + int const magicno = XINT (CADR (watch_object)); + watch_list = Fdelete (watch_data, watch_list); + + if (inotify_rm_watch (inotifyfd, magicno) == -1) + report_file_error ("Could not unwatch file", + Fcons (Fcar (watch_data), Qnil)); + + /* Cleanup if watch_list is empty. */ + if (NILP (watch_list)) + { + close (inotifyfd); + delete_read_fd (inotifyfd); + inotifyfd = uninitialized; + } + } + else + { + Lisp_Object decoded_file_name = ENCODE_FILE (CADR (watch_data)); + Lisp_Object x = CADDR (watch_data); + uint32_t mask = 0; + while (!NILP (x)) + { + Lisp_Object cell = Fcar (x); + mask |= aspect_to_inotifymask (CADDR (cell)); + x = Fcdr (x); + } + /* Reset watch mask */ + if (inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask) == -1) + report_file_error ("Could not unwatch file", + Fcons (Fcar (watch_data), Qnil)); + } + + return Qt; +} + +#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 (Qall_modify, "all-modify"); + + DEFSYM (Qaccess, "access"); + DEFSYM (Qclose_write, "close-write"); + DEFSYM (Qclose_nowrite, "close-nowrite"); + DEFSYM (Qopen, "open"); + DEFSYM (Qall, "all"); + + DEFSYM (Qunknown_aspect, "unknown-aspect"); + DEFSYM (Qfile_watch, "file-watch"); + + Fput (Qunknown_aspect, Qerror_conditions, + list2 (Qunknown_aspect, Qerror)); + Fput (Qunknown_aspect, Qerror_message, + make_pure_c_string ("Unknown Aspect")); + + defsubr (&Sfile_watch); + defsubr (&Sfile_unwatch); + + staticpro (&watch_list); + + Fprovide (intern_c_string ("filewatch"), Qnil); +} + +#endif /* HAVE_FILEWATCH */ diff --git a/src/keyboard.c b/src/keyboard.c index e7a0598..673cb6e 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 1e30363..a8419d1 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -3494,6 +3494,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 diff --git a/test/automated/filewatch-tests.el b/test/automated/filewatch-tests.el new file mode 100644 index 0000000..494d704 --- /dev/null +++ b/test/automated/filewatch-tests.el @@ -0,0 +1,50 @@ +;;; filewatch-tests.el --- Test suite for filewatch. + +;; Copyright (C) 2011 Free Software Foundation, Inc. + +;; Author: Rüdiger Sonderfeld <ruediger@c-plusplus.de> +;; Keywords: internal +;; Human-Keywords: internal + +;; 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/>. + +;;; Code: + +(require 'ert) + +(when (featurep 'filewatch) + + (ert-deftest filewatch-file-unwatch-type-check () + "Test whether `file-unwatch' does proper type checking." + (should-error (file-unwatch "path") :type 'wrong-type-argument) + (should-error (file-unwatch '(file 1 2)) :type 'wrong-type-argument)) + + (ert-deftest filewatch-file-watch-aspects-check () + "Test whether `file-watch' properly checks the aspects." + (let ((temp-file (make-temp-file "filewatch-aspects"))) + (should (stringp temp-file)) + (should-error (file-watch temp-file 'wrong nil) + :type 'unknown-aspect) + (should-error (file-watch temp-file '(modify t) nil) + :type 'unknown-aspect) + (should-error (file-watch temp-file '(modify Qall-modify) nil) + :type 'unknown-aspect) + (should-error (file-watch temp-file '(access wrong modify) nil) + :type 'unknown-aspect))) +) + +(provide 'filewatch-tests) +;;; filewatch-tests.el ends here. -- 1.7.5.4 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-24 0:50 ` Rüdiger Sonderfeld @ 2011-06-24 10:19 ` Ted Zlatanov 2011-06-24 12:18 ` Ted Zlatanov 2011-07-06 13:36 ` Stefan Monnier 2011-07-07 19:43 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2 siblings, 1 reply; 125+ messages in thread From: Ted Zlatanov @ 2011-06-24 10:19 UTC (permalink / raw) To: emacs-devel On Fri, 24 Jun 2011 02:50:00 +0200 Rüdiger Sonderfeld <ruediger@c-plusplus.de> wrote: RS> +static Lisp_Object Qmodify, Qmove, Qattrib, Qdelete, Qfrom, Qto, Qall_modify; ... RS> + if (ev->mask & (IN_MODIFY|IN_CREATE) ) RS> + events = Fcons (Fcons (Qmodify, name), events); ... RS> + if (EQ (symb, Qmodify)) RS> + return IN_MODIFY | IN_CREATE; You have several symbols like Qmodify, and I wanted to suggest that you could use a name prefix like for example gnutls.c: #+begin_src c Qgnutls_bootprop_verify_hostname_error = intern_c_string (":verify-error"); staticpro (&Qgnutls_bootprop_verify_error); #+end_src and that you assign a numeric value directly to the symbol when it's initialized instead of statically doing case statements in your code. That way your code will be shorter and simpler because you'll work with the integer values directly. This could be a problem if the IN_* constants overflow the Emacs integer size, but I don't think they will, after checking inotify.h. Only the special flags are large (2147483648 = IN_ONESHOT = 0x80000000) and you won't use those. Ted ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-24 10:19 ` Ted Zlatanov @ 2011-06-24 12:18 ` Ted Zlatanov 0 siblings, 0 replies; 125+ messages in thread From: Ted Zlatanov @ 2011-06-24 12:18 UTC (permalink / raw) To: emacs-devel On Fri, 24 Jun 2011 05:19:20 -0500 Ted Zlatanov <tzz@lifelogs.com> wrote: TZ> You have several symbols like Qmodify, and I wanted to suggest that you TZ> could use a name prefix like for example gnutls.c: TZ> and that you assign a numeric value directly to the symbol when it's TZ> initialized instead of statically doing case statements in your TZ> code. I noticed you were using the DEFSYM macro, I didn't know about it. I put it into gnutls.c. #+begin_src c DEFSYM(Qgnutls_e_interrupted, "gnutls-e-interrupted"); Fput (Qgnutls_e_interrupted, Qgnutls_code, make_number (GNUTLS_E_INTERRUPTED)); #+end_src ...and I wanted to mention how I put a numeric value in the symbol's property list instead of setting it as the value. HTH. Ted ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-24 0:50 ` Rüdiger Sonderfeld 2011-06-24 10:19 ` Ted Zlatanov @ 2011-07-06 13:36 ` Stefan Monnier 2011-07-06 15:54 ` Paul Eggert 2011-07-07 19:43 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-07-06 13:36 UTC (permalink / raw) To: emacs-devel Do you guys think this patch is good enough to make it into Emacs-24.1? My preference would be to wait for 24.2 since we're in feature freeze, but it's the kind of feature which benefits from being available early so other packages can start using it. WDYT? Stefan >> > The problem is: How to implement the file-unwatch then? I need a way >> > to identify each separate file-watch request. What would be the best >> > way to do that? >> >> How about letting file-watch return a "file-watcher" which you then need >> to pass to file-unwatch? This "file-watcher" could be any kind of Elisp >> data you find convenient for this. You may decide to provide no other >> operation than file-unwatch, but you could also decide to provided >> additional operations such as changing the callback (I'm not saying >> that would necessarily be a good idea, tho, but maybe other operations >> would be handy). > I updated the patch to return a "file-watcher". file-watch can now be called > several times for the same file with different flags and callbacks. This > however required large changes to the patch. So it would be great if someone > could take a look at it again. I added an automated test. But it doesn't test the > actually file-watching yet. This is a bit tricky to get right in a unit test. > But I'll add it. The code requires some heavy testing. > Regards, > Rüdiger > From: =?UTF-8?q?R=C3=BCdiger=20Sonderfeld?= <ruediger@c-plusplus.de> > Date: Fri, 3 Jun 2011 23:58:12 +0200 > 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 | 485 +++++++++++++++++++++++++++++++++++++ > src/keyboard.c | 28 +++ > src/lisp.h | 5 + > src/termhooks.h | 5 + > test/automated/filewatch-tests.el | 50 ++++ > 9 files changed, 646 insertions(+), 1 deletions(-) > create mode 100644 lisp/filewatch.el > create mode 100644 src/filewatch.c > create mode 100644 test/automated/filewatch-tests.el > diff --git a/configure.in b/configure.in > index 0135b9f..4a816ec 100644 > --- a/configure.in > +++ b/configure.in > @@ -174,6 +174,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. > @@ -1999,6 +2000,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 <ruediger@c-plusplus.de> > +;; 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 d14acd6..db897d5 100644 > --- a/src/emacs.c > +++ b/src/emacs.c > @@ -1563,6 +1563,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem > 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..2833544 > --- /dev/null > +++ b/src/filewatch.c > @@ -0,0 +1,485 @@ > +/* 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, Qall_modify; > +static Lisp_Object Qunknown_aspect, Qfile_watch; > +static Lisp_Object Qaccess, Qclose_write, Qclose_nowrite, Qopen, Qall; > + > +#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. > + Format: > + (id . (filename ((subid callback flags) (subid callbacks flags) ...))) */ > +static Lisp_Object watch_list; > + > +#define CADDR(x) Fcar (Fcdr (Fcdr (x))) > +#define CADR(x) Fcar (Fcdr (x)) > + > +static Lisp_Object > +append2 (Lisp_Object s1, Lisp_Object s2) > +{ > + Lisp_Object args[2]; > + args[0] = s1; > + args[1] = s2; > + return Fappend (2, args); > +} > + > +/* Returns a list with all the elements from EVENTS the `watch_list' CELL > + is interested in. */ > +static Lisp_Object > +match_aspects (Lisp_Object cell, Lisp_Object events) > +{ > + Lisp_Object ret = Qnil; > + Lisp_Object aspects = CADDR (cell); > + Lisp_Object i; > + if (EQ (aspects, Qt) || EQ (aspects, Qall)) > + return events; > + else if (EQ (aspects, Qall_modify)) > + aspects = list4 (Qmodify, Qmove, Qattrib, Qdelete); > + > + i = Fcar (aspects); > + while (!NILP (i)) > + { > + Lisp_Object e = Fcar (events); > + while (!NILP (e)) > + { > + if (EQ (Fcar (e), i)) > + ret = Fcons (e, ret); > + events = Fcdr (events); > + e = Fcar (events); > + } > + aspects = Fcdr (aspects); > + i = Fcar (aspects); > + } > + return ret; > +} > + > +static Lisp_Object > +inotifyevent_to_aspects (Lisp_Object name, struct inotify_event *ev) > +{ > + Lisp_Object 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 (ev->mask & IN_ACCESS) > + events = Fcons (Fcons (Qaccess, name), events); > + if (ev->mask & IN_CLOSE_WRITE) > + events = Fcons (Fcons (Qclose_write, name), events); > + if (ev->mask & IN_CLOSE_NOWRITE) > + events = Fcons (Fcons (Qclose_nowrite, name), events); > + if (ev->mask & IN_OPEN) > + events = Fcons (Fcons (Qopen, name), events); > + return events; > +} > + > +/* 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 watch_data = Fassoc (make_number (ev->wd), watch_list); > + if (!NILP (watch_data)) > + { > + Lisp_Object name, events; > + > + /* If a directory is watched name contains the name > + of the file that was changed. */ > + if (ev->len > 0) > + { > + size_t const len = strlen (ev->name); > + name = make_unibyte_string (ev->name, min (len, ev->len)); > + name = DECODE_FILE (name); > + } > + else > + { > + name = CADR (watch_data); > + } > + > + events = inotifyevent_to_aspects (name, ev); > + > + if (!NILP (events)) > + { > + Lisp_Object x = CADDR (watch_data); > + while (!NILP (x)) > + { > + Lisp_Object cell = Fcar (x); > + Lisp_Object aspects = match_aspects (cell, events); > + if (!NILP (aspects)) > + { > + Lisp_Object id = list3 (Qfile_watch, make_number (ev->wd), > + Fcar (cell)); > + Lisp_Object event_info = list1 (list3 (id, CADR (cell), > + aspects)); > + > + if (NILP (event.arg)) > + event.arg = event_info; > + else > + event.arg = append2 (event.arg, event_info); > + } > + x = Fcdr (x); > + } > + } > + > + 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 (watch_data, 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, int in_list) > +{ > + 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 (!in_list && EQ (symb, Qall_modify)) > + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB > + | IN_DELETE_SELF | IN_DELETE; > + else if (EQ (symb, Qaccess)) > + return IN_ACCESS; > + else if (EQ (symb, Qclose_write)) > + return IN_CLOSE_WRITE; > + else if (EQ (symb, Qclose_nowrite)) > + return IN_CLOSE_NOWRITE; > + else if (EQ (symb, Qopen)) > + return IN_OPEN; > + else if (!in_list && (EQ (symb, Qt) || EQ (symb, Qall))) > + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB > + | IN_DELETE_SELF | IN_DELETE | IN_ACCESS | IN_CLOSE_WRITE > + | IN_CLOSE_NOWRITE | IN_OPEN; > + else > + Fsignal (Qunknown_aspect, Fcons (symb, Qnil)); > +} > + > +static uint32_t > +aspect_to_inotifymask (Lisp_Object aspect) > +{ > + if (CONSP (aspect)) > + { > + Lisp_Object x = aspect; > + uint32_t mask = 0; > + while (!NILP (x)) > + { > + mask |= symbol_to_inotifymask (Fcar (x), 1); > + x = Fcdr (x); > + } > + return mask; > + } > + else > + return symbol_to_inotifymask (aspect, 0); > +} > + > +static Lisp_Object > +get_watch_data_by_file_name (Lisp_Object file_name) > +{ > + Lisp_Object cell, file_name_data; > + Lisp_Object x = watch_list; > + 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)) > + && !NILP (Fstring_equal (Fcar (file_name_data), file_name)) > + && NUMBERP (Fcar (cell))) > + return cell; > + } > + return 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 (except > +for all-modify and t): > + > +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-modify -- notify for all of the above (all modifying changes). > + > +access -- notify when file was accessed/read. > + > +close-write -- notify when a file opened for writing was closed. > + > +close-nowrite -- notify when a file not opened for writing was closed. > + > +open -- notify when a file was opened. > + > +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 (id event) (message "%s %s" id event))) > + > +Use `file-unwatch' to stop watching. > + > +usage: (file-watch FILENAME ASPECT CALLBACK) */) > + (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback) > +{ > + static int intern_counter = 0; /* assign local ids */ > + uint32_t mask; > + int watchdesc; > + Lisp_Object decoded_file_name; > + Lisp_Object data; > + Lisp_Object info; > + > + 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); > + } > + > + mask = aspect_to_inotifymask (aspect); > + data = get_watch_data_by_file_name (file_name); > + if (!NILP (data)) > + mask |= IN_MASK_ADD; > + > + 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)); > + > + info = list3 (make_number (intern_counter), callback, aspect); > + ++intern_counter; > + > + if (!NILP (data)) > + Fsetcdr (Fcdr (data), Fcons (append2 (CADDR (data), Fcons (info, Qnil)), > + Qnil)); > + else > + watch_list = Fcons (list3 (make_number (watchdesc), > + file_name, Fcons (info, Qnil)), > + watch_list); > + > + return list3 (Qfile_watch, make_number (watchdesc), Fcar (info)); > +} > + > +static int > +file_watch_objectp (Lisp_Object obj) > +{ > + return CONSP (obj) && XINT (Flength (obj)) == 3 > + && EQ (Fcar (obj), Qfile_watch); > +} > + > +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0, > + doc: /* Stop watching a file or directory. */) > + (Lisp_Object watch_object) > +{ > + Lisp_Object watch_data; > + Lisp_Object info; > + > + if (!file_watch_objectp (watch_object)) > + wrong_type_argument (Qfile_watch, watch_object); > + > + if (inotifyfd == uninitialized) > + return Qnil; > + > + watch_data = Fassoc ( CADR (watch_object), watch_list ); > + if (NILP (watch_data)) > + return Qnil; > + > + info = Fassoc (CADDR (watch_object), CADDR (watch_data)); > + if (NILP (info)) > + return Qnil; > + > + Fsetcdr (Fcdr (watch_data), Fcons (Fdelete (info, CADDR (watch_data)), Qnil)); > + > + if (NILP (CADDR (watch_data))) > + { > + int const magicno = XINT (CADR (watch_object)); > + watch_list = Fdelete (watch_data, watch_list); > + > + if (inotify_rm_watch (inotifyfd, magicno) == -1) > + report_file_error ("Could not unwatch file", > + Fcons (Fcar (watch_data), Qnil)); > + > + /* Cleanup if watch_list is empty. */ > + if (NILP (watch_list)) > + { > + close (inotifyfd); > + delete_read_fd (inotifyfd); > + inotifyfd = uninitialized; > + } > + } > + else > + { > + Lisp_Object decoded_file_name = ENCODE_FILE (CADR (watch_data)); > + Lisp_Object x = CADDR (watch_data); > + uint32_t mask = 0; > + while (!NILP (x)) > + { > + Lisp_Object cell = Fcar (x); > + mask |= aspect_to_inotifymask (CADDR (cell)); > + x = Fcdr (x); > + } > + /* Reset watch mask */ > + if (inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask) == -1) > + report_file_error ("Could not unwatch file", > + Fcons (Fcar (watch_data), Qnil)); > + } > + > + return Qt; > +} > + > +#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 (Qall_modify, "all-modify"); > + > + DEFSYM (Qaccess, "access"); > + DEFSYM (Qclose_write, "close-write"); > + DEFSYM (Qclose_nowrite, "close-nowrite"); > + DEFSYM (Qopen, "open"); > + DEFSYM (Qall, "all"); > + > + DEFSYM (Qunknown_aspect, "unknown-aspect"); > + DEFSYM (Qfile_watch, "file-watch"); > + > + Fput (Qunknown_aspect, Qerror_conditions, > + list2 (Qunknown_aspect, Qerror)); > + Fput (Qunknown_aspect, Qerror_message, > + make_pure_c_string ("Unknown Aspect")); > + > + defsubr (&Sfile_watch); > + defsubr (&Sfile_unwatch); > + > + staticpro (&watch_list); > + > + Fprovide (intern_c_string ("filewatch"), Qnil); > +} > + > +#endif /* HAVE_FILEWATCH */ > diff --git a/src/keyboard.c b/src/keyboard.c > index e7a0598..673cb6e 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 1e30363..a8419d1 100644 > --- a/src/lisp.h > +++ b/src/lisp.h > @@ -3494,6 +3494,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 > diff --git a/test/automated/filewatch-tests.el b/test/automated/filewatch-tests.el > new file mode 100644 > index 0000000..494d704 > --- /dev/null > +++ b/test/automated/filewatch-tests.el > @@ -0,0 +1,50 @@ > +;;; filewatch-tests.el --- Test suite for filewatch. > + > +;; Copyright (C) 2011 Free Software Foundation, Inc. > + > +;; Author: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > +;; Keywords: internal > +;; Human-Keywords: internal > + > +;; 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/>. > + > +;;; Code: > + > +(require 'ert) > + > +(when (featurep 'filewatch) > + > + (ert-deftest filewatch-file-unwatch-type-check () > + "Test whether `file-unwatch' does proper type checking." > + (should-error (file-unwatch "path") :type 'wrong-type-argument) > + (should-error (file-unwatch '(file 1 2)) :type 'wrong-type-argument)) > + > + (ert-deftest filewatch-file-watch-aspects-check () > + "Test whether `file-watch' properly checks the aspects." > + (let ((temp-file (make-temp-file "filewatch-aspects"))) > + (should (stringp temp-file)) > + (should-error (file-watch temp-file 'wrong nil) > + :type 'unknown-aspect) > + (should-error (file-watch temp-file '(modify t) nil) > + :type 'unknown-aspect) > + (should-error (file-watch temp-file '(modify Qall-modify) nil) > + :type 'unknown-aspect) > + (should-error (file-watch temp-file '(access wrong modify) nil) > + :type 'unknown-aspect))) > +) > + > +(provide 'filewatch-tests) > +;;; filewatch-tests.el ends here. > -- > 1.7.5.4 ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-07-06 13:36 ` Stefan Monnier @ 2011-07-06 15:54 ` Paul Eggert 2011-07-06 18:30 ` Stefan Monnier 2011-07-06 19:14 ` wide-int crash [was Re: [PATCH updated] Support for filesystem watching (inotify)] Glenn Morris 0 siblings, 2 replies; 125+ messages in thread From: Paul Eggert @ 2011-07-06 15:54 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel On 07/06/11 06:36, Stefan Monnier wrote: > My preference would be to wait for 24.2 since we're in feature freeze, > but it's the kind of feature which benefits from being available early > so other packages can start using it. WDYT? I briefly looked at it for integer-overflow issues, and found one: it assumes that inotify cookies fit into an Emacs fixnum. This assumption isn't true on 32-bit hosts, unless Emacs is configured with --with-wide-int. As you know I'm a fan of wide integers, and my preferred solution would be to make --with-wide-int the default, which would solve the problem. As that is also being considered for 24.2, perhaps the inotify feature should wait for 24.2 as well. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-07-06 15:54 ` Paul Eggert @ 2011-07-06 18:30 ` Stefan Monnier 2011-07-06 20:39 ` Paul Eggert 2011-07-06 19:14 ` wide-int crash [was Re: [PATCH updated] Support for filesystem watching (inotify)] Glenn Morris 1 sibling, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-07-06 18:30 UTC (permalink / raw) To: Paul Eggert; +Cc: emacs-devel >> My preference would be to wait for 24.2 since we're in feature freeze, >> but it's the kind of feature which benefits from being available early >> so other packages can start using it. WDYT? > I briefly looked at it for integer-overflow issues, and found one: it > assumes that inotify cookies fit into an Emacs fixnum. Thanks. This needs to be fixed, indeed. > This assumption isn't true on 32-bit hosts, unless Emacs is configured > with --with-wide-int. As you know I'm a fan of wide integers, and my > preferred solution would be to make --with-wide-int the default, which > would solve the problem. No, that would only solve the problem if the --with-wide-int option is removed on 32bit systems and imposed as the only choice. Even if we may change the default for 24.2 we're definitely not going to drop support for "narrow int" operation so soon. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-07-06 18:30 ` Stefan Monnier @ 2011-07-06 20:39 ` Paul Eggert 0 siblings, 0 replies; 125+ messages in thread From: Paul Eggert @ 2011-07-06 20:39 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel On 07/06/11 11:30, Stefan Monnier wrote: > that would only solve the problem if the --with-wide-int option is > removed on 32bit systems and imposed as the only choice. Another possibility would be to enable inotify only if --with-wide-int is also in effect. Every platform that has inotify also has wide ints, so in 24.2 this would work except for small number of people who configure --without-wide-int. Come to think of it, we can do that now. I.e., we can put in the inotify stuff now, but have it available only for the small number of people who configure --with-wide-int. This might help early adopters try out inotify without it being "mainstream" in 24. ^ permalink raw reply [flat|nested] 125+ messages in thread
* wide-int crash [was Re: [PATCH updated] Support for filesystem watching (inotify)] 2011-07-06 15:54 ` Paul Eggert 2011-07-06 18:30 ` Stefan Monnier @ 2011-07-06 19:14 ` Glenn Morris 2011-07-06 22:31 ` Paul Eggert 1 sibling, 1 reply; 125+ messages in thread From: Glenn Morris @ 2011-07-06 19:14 UTC (permalink / raw) To: Paul Eggert; +Cc: emacs-devel Paul Eggert wrote: > As you know I'm a fan of wide integers, and my preferred solution > would be to make --with-wide-int the default, which would solve the > problem. Could you look at this report sometime? http://debbugs.gnu.org/cgi/bugreport.cgi?bug=8884 ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: wide-int crash [was Re: [PATCH updated] Support for filesystem watching (inotify)] 2011-07-06 19:14 ` wide-int crash [was Re: [PATCH updated] Support for filesystem watching (inotify)] Glenn Morris @ 2011-07-06 22:31 ` Paul Eggert 0 siblings, 0 replies; 125+ messages in thread From: Paul Eggert @ 2011-07-06 22:31 UTC (permalink / raw) To: Glenn Morris; +Cc: 8884, Peter Dyballa, emacs-devel On 07/06/11 12:14, Glenn Morris wrote: > http://debbugs.gnu.org/cgi/bugreport.cgi?bug=8884 Thanks for the heads-up. Although the nearby code has a portability bug regardless of whether --with-wide-int is used, --with-wide-int is more likely to tickle the bug. The bug could well explain the symptoms Peter observed. I committed the following patch into the trunk. Peter, can you please check whether it solves your problem? If not, can you please compile with -g and without -O, and use GDB to report the following values at the point of the crash: buffer_local_flags, idx, offset, sizeof (struct buffer). Thanks. === modified file 'src/ChangeLog' --- src/ChangeLog 2011-07-05 09:51:56 +0000 +++ src/ChangeLog 2011-07-06 22:22:32 +0000 @@ -1,3 +1,15 @@ +2011-07-06 Paul Eggert <eggert@cs.ucla.edu> + + Remove unportable assumption about struct layout (Bug#8884). + * alloc.c (mark_buffer): + * buffer.c (reset_buffer_local_variables, Fbuffer_local_variables) + (clone_per_buffer_values): Don't assume that + sizeof (struct buffer) is a multiple of sizeof (Lisp_Object). + This isn't true in general, and it's particularly not true + if Emacs is configured with --with-wide-int. + * buffer.h (FIRST_FIELD_PER_BUFFER, LAST_FIELD_PER_BUFFER): + New macros, used in the buffer.c change. + 2011-07-05 Jan Djärv <jan.h.d@swipnet.se> * xsettings.c: Use both GConf and GSettings if both are available. === modified file 'src/alloc.c' --- src/alloc.c 2011-06-24 21:25:22 +0000 +++ src/alloc.c 2011-07-06 22:22:32 +0000 @@ -5619,7 +5619,8 @@ /* buffer-local Lisp variables start at `undo_list', tho only the ones from `name' on are GC'd normally. */ for (ptr = &buffer->BUFFER_INTERNAL_FIELD (name); - (char *)ptr < (char *)buffer + sizeof (struct buffer); + ptr <= &PER_BUFFER_VALUE (buffer, + PER_BUFFER_VAR_OFFSET (LAST_FIELD_PER_BUFFER)); ptr++) mark_object (*ptr); === modified file 'src/buffer.c' --- src/buffer.c 2011-07-04 15:32:22 +0000 +++ src/buffer.c 2011-07-06 22:22:32 +0000 @@ -471,8 +471,8 @@ /* buffer-local Lisp variables start at `undo_list', tho only the ones from `name' on are GC'd normally. */ - for (offset = PER_BUFFER_VAR_OFFSET (undo_list); - offset < sizeof *to; + for (offset = PER_BUFFER_VAR_OFFSET (FIRST_FIELD_PER_BUFFER); + offset <= PER_BUFFER_VAR_OFFSET (LAST_FIELD_PER_BUFFER); offset += sizeof (Lisp_Object)) { Lisp_Object obj; @@ -830,8 +830,8 @@ /* buffer-local Lisp variables start at `undo_list', tho only the ones from `name' on are GC'd normally. */ - for (offset = PER_BUFFER_VAR_OFFSET (undo_list); - offset < sizeof *b; + for (offset = PER_BUFFER_VAR_OFFSET (FIRST_FIELD_PER_BUFFER); + offset <= PER_BUFFER_VAR_OFFSET (LAST_FIELD_PER_BUFFER); offset += sizeof (Lisp_Object)) { int idx = PER_BUFFER_IDX (offset); @@ -1055,8 +1055,8 @@ /* buffer-local Lisp variables start at `undo_list', tho only the ones from `name' on are GC'd normally. */ - for (offset = PER_BUFFER_VAR_OFFSET (undo_list); - offset < sizeof (struct buffer); + for (offset = PER_BUFFER_VAR_OFFSET (FIRST_FIELD_PER_BUFFER); + offset <= PER_BUFFER_VAR_OFFSET (LAST_FIELD_PER_BUFFER); /* sizeof EMACS_INT == sizeof Lisp_Object */ offset += (sizeof (EMACS_INT))) { === modified file 'src/buffer.h' --- src/buffer.h 2011-06-21 21:32:10 +0000 +++ src/buffer.h 2011-07-06 21:53:56 +0000 @@ -612,6 +612,7 @@ /* Everything from here down must be a Lisp_Object. */ /* buffer-local Lisp variables start at `undo_list', tho only the ones from `name' on are GC'd normally. */ + #define FIRST_FIELD_PER_BUFFER undo_list /* Changes in the buffer are recorded here for undo. t means don't record anything. @@ -846,6 +847,9 @@ t means to use hollow box cursor. See `cursor-type' for other values. */ Lisp_Object BUFFER_INTERNAL_FIELD (cursor_in_non_selected_windows); + + /* This must be the last field in the above list. */ + #define LAST_FIELD_PER_BUFFER cursor_in_non_selected_windows }; \f ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-24 0:50 ` Rüdiger Sonderfeld 2011-06-24 10:19 ` Ted Zlatanov 2011-07-06 13:36 ` Stefan Monnier @ 2011-07-07 19:43 ` Stefan Monnier 2012-09-28 13:06 ` [PATCH] Added basic file system watching support Rüdiger Sonderfeld 2 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-07-07 19:43 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Eli Zaretskii, Thien-Thi Nguyen, emacs-devel > I updated the patch to return a "file-watcher". file-watch can now be > called several times for the same file with different flags and > callbacks. This however required large changes to the patch. So it > would be great if someone could take a look at it again. I added an > automated test. But it doesn't test the actually file-watching > yet. This is a bit tricky to get right in a unit test. But I'll add > it. The code requires some heavy testing. Here are some comments, only based on a cursory read. I do not know anything about the inotify API and have not tried your code. > --- /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 <ruediger@c-plusplus.de> > +;; 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) Unless we expect many more functions in this file, we could move those functions to subr.el (tho after removing the CL dependency since subr.el can't use CL for bootstrapping reasons). > +/* Assoc list of files being watched. > + Format: > + (id . (filename ((subid callback flags) (subid callbacks flags) ...))) */ > +static Lisp_Object watch_list; > + > +#define CADDR(x) Fcar (Fcdr (Fcdr (x))) > +#define CADR(x) Fcar (Fcdr (x)) Most C code should use XCAR/XCDR rather than Fcr/Fcdr. > + while (!NILP (i)) > + { > + Lisp_Object e = Fcar (events); E.g. here, better test CONSP rather than !NILP, which lets you then use XCAR/XCDR. > + while (!NILP (e)) > + { > + if (EQ (Fcar (e), i)) > + ret = Fcons (e, ret); > + events = Fcdr (events); > + e = Fcar (events); > + } > + aspects = Fcdr (aspects); > + i = Fcar (aspects); > + } I'd have used an outside loop on events so that the inner loop on aspects can use Fmemq. > + return ret; > +} > + > +static Lisp_Object > +inotifyevent_to_aspects (Lisp_Object name, struct inotify_event *ev) > +{ It doesn't just return aspects but events (using a Lips_Object representation), so the name is somewhat misleading. > + Lisp_Object 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, The structure of those events is a bit odd. It seems that each event is either of form (SYMBOL . FILENAME) where FILENAME is the watched object, or of the form (SYMBOL from NAME . COOKIE) where NAME is the file added/removed from the watched object (a directory, presumably), but this object's FILENAME is not present. Any particular reason for this inconsistency? > + make_number (ev->cookie)))), > + events); > + if (ev->mask & IN_MOVED_TO) > + events = Fcons (Fcons (Qmove, > + Fcons (Qto, > + Fcons (name, > + make_number (ev->cookie)))), IIUC, these cookies are part of the patch to which Paul objected. And IIUC their content has no importance, they're only checked for equality, right? So they don't have to be represented as numbers, but could be represented by some other special object, as long as (eq cookie1 cookie2) returns the proper boolean value when comparing those special objects. I guess that several `ev' objects can have the same `ev->cookie' value, right? > +/* 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) > +{ AFAICT, the void* argument will be a pointer to watch_list. I think that either you should use it here, or you should pass NULL to add_read_fd (rather than passing &watch_list). > + Lisp_Object watch_data = Fassoc (make_number (ev->wd), watch_list); > + if (!NILP (watch_data)) > + { > + Lisp_Object name, events; > + > + /* If a directory is watched name contains the name > + of the file that was changed. */ > + if (ev->len > 0) > + { > + size_t const len = strlen (ev->name); > + name = make_unibyte_string (ev->name, min (len, ev->len)); > + name = DECODE_FILE (name); > + } > + else > + { > + name = CADR (watch_data); > + } > + > + events = inotifyevent_to_aspects (name, ev); > + > + if (!NILP (events)) Wouldn't it be a bug for events to be nil here (that would mean you receive inotify events for files you do not know you're watching, or something like that)? > + { > + Lisp_Object x = CADDR (watch_data); > + while (!NILP (x)) > + { > + Lisp_Object cell = Fcar (x); > + Lisp_Object aspects = match_aspects (cell, events); > + if (!NILP (aspects)) > + { > + Lisp_Object id = list3 (Qfile_watch, make_number (ev->wd), > + Fcar (cell)); I don't think you need ev->wd in your file-watch objects. You could use Fcons (Qfile_watch, cell) instead, IIUC. It would be good, because it would let you use a SAVE_VALUE object for ev->wd (since it wouldn't escape to Lisp any more), which would solve the problem of losing a few bits of data in make_number. > + > +static uint32_t > +symbol_to_inotifymask (Lisp_Object symb, int in_list) > +{ > + 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 (!in_list && EQ (symb, Qall_modify)) > + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB > + | IN_DELETE_SELF | IN_DELETE; > + else if (EQ (symb, Qaccess)) > + return IN_ACCESS; > + else if (EQ (symb, Qclose_write)) > + return IN_CLOSE_WRITE; > + else if (EQ (symb, Qclose_nowrite)) > + return IN_CLOSE_NOWRITE; > + else if (EQ (symb, Qopen)) > + return IN_OPEN; > + else if (!in_list && (EQ (symb, Qt) || EQ (symb, Qall))) > + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB > + | IN_DELETE_SELF | IN_DELETE | IN_ACCESS | IN_CLOSE_WRITE > + | IN_CLOSE_NOWRITE | IN_OPEN; > + else > + Fsignal (Qunknown_aspect, Fcons (symb, Qnil)); Just make it an `error'. > +static Lisp_Object > +get_watch_data_by_file_name (Lisp_Object file_name) > +{ > + Lisp_Object cell, file_name_data; > + Lisp_Object x = watch_list; > + 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)) > + && !NILP (Fstring_equal (Fcar (file_name_data), file_name)) > + && NUMBERP (Fcar (cell))) Why do you need NUMBERP? Shouldn't it be an "eassert (NUMBERP (...))" instead? > +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. We typically try to avoid such "third element" style and use forms like: "the argument takes the form (OP . FILENAME) or (OP DIRECTION NAME COOKIE) where OP is blabl, etc...". Also please say how many arguments are passed to CALLBACK and what means each one of them. > + mask = aspect_to_inotifymask (aspect); > + data = get_watch_data_by_file_name (file_name); > + if (!NILP (data)) > + mask |= IN_MASK_ADD; Would it hurt to always add IN_MASK_ADD? > + decoded_file_name = ENCODE_FILE (file_name); > + watchdesc = inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask); Are we sure that when data is nil, watchdesc is different from all other watchdesc we currently have, and that when data is non-nil, watchdesc is equal to the watchdesc stored in data? If so, let's says so via asserts, and if not, we should check and decide what to do. IIUC inotify operates on inodes whereas our watch_list is indexed by file names, so get_watch_data_by_file_name may think we're changing an existing watcher but in reality it may operate on a new inode and hence return a new watchdesc. Inversely, two different filenames can refer to the same inode, so I suspect that there are also cases where get_watch_data_by_file_name thinks this is a new watcher but the watchdesc returned will be one that already exists. IOW, I think we should always say IN_MASK_ADD (just in case) and we should not use get_watch_data_by_file_name before calling inotify_add_watch, but instead use get_watch_data_by_watchdesc afterwards. > + return list3 (Qfile_watch, make_number (watchdesc), Fcar (info)); See comment earlier about eliding watchdesc and including `info' instead. > + if (inotifyfd == uninitialized) > + return Qnil; Shouldn't this signal an error instead? Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2011-07-07 19:43 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier @ 2012-09-28 13:06 ` Rüdiger Sonderfeld 2012-09-28 14:13 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2012-09-28 13:06 UTC (permalink / raw) To: emacs-devel; +Cc: Stefan Monnier, Leo Hello, this is an updated version of my "file system watching" patch (full thread: http://thread.gmane.org/gmane.emacs.devel/140158 ) I don't think the patch will be ready for 24.3. It is still not complete and needs some heavy testing. I addressed some of the issues mentioned by Stefan Monnier here http://article.gmane.org/gmane.emacs.devel/141741 > It seems that each event is either of form (SYMBOL . FILENAME) where > FILENAME is the watched object, or of the form (SYMBOL from NAME . COOKIE) > where NAME is the file added/removed from the watched object (a > directory, presumably), but this object's FILENAME is not present. > Any particular reason for this inconsistency? FILENAME is not always the watched object. If a directory is watched then it is the file name of the file inside the directory. This should really not be used as identification for the watched object! That's why CALLBACK also gets the ID. > IIUC, these cookies are part of the patch to which Paul objected. > And IIUC their content has no importance, they're only checked for > equality, right? > > So they don't have to be represented as numbers, but could be > represented by some other special object, as long as (eq cookie1 > cookie2) returns the proper boolean value when comparing those > special objects. > > I guess that several `ev' objects can have the same `ev->cookie' > value, right? Yes exactly. The value is unimportant as long as it is unique to an event and can be compared with eq. What would you propose as an alternative object here? > Wouldn't it be a bug for events to be nil here (that would mean you > receive inotify events for files you do not know you're watching, or > something like that)? The problem is that even after removal of a watch descriptor there can still be issues in the queue. Therefore I think it is best to ignore any events for files we do not watch. There could also be event types we do not know about when inotify gets expanded. That's why I think unknown event types should be ignored instead of reporting an error. > I don't think you need ev->wd in your file-watch objects. You could use > Fcons (Qfile_watch, cell) instead, IIUC. It would be good, because it > would let you use a SAVE_VALUE object for ev->wd (since it wouldn't > escape to Lisp any more), which would solve the problem of losing a few > bits of data in make_number. I don't need to make the watchdescriptor (wd) available to elisp. But I need a way to get the descriptor from the watch handle. How could this be implemented with SAVE_VALUE? > Shouldn't this signal an error instead? This could be the result of calling file-unwatch too often. Currently the behaviour is to return nil in that case. But this could also be seen as an error of course. Regards, Rüdiger -- >8 -- The current patch only works with inotify (Linux). It adds two elisp functions file-watch and file-unwatch. Example (let ((fh (file-watch "foo" t #'(lambda (id event) (message "%s %s" id event))))) (delete-file "foo/bar") (file-unwatch fh)) (Expects a directory foo containing a file bar) This watches for any kind of changes in a directory foo and then deletes a file foo/bar and finally removes the watch. Signed-off-by: Rüdiger Sonderfeld <ruediger@c-plusplus.de> --- configure.ac | 14 ++ lisp/subr.el | 23 ++ src/Makefile.in | 2 +- src/emacs.c | 4 + src/filewatch.c | 457 ++++++++++++++++++++++++++++++++++++++ src/keyboard.c | 28 +++ src/lisp.h | 5 + src/termhooks.h | 5 + test/automated/filewatch-tests.el | 50 +++++ 9 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 src/filewatch.c create mode 100644 test/automated/filewatch-tests.el diff --git a/configure.ac b/configure.ac index 5a3aea7..8b8054b 100644 --- a/configure.ac +++ b/configure.ac @@ -184,6 +184,7 @@ OPTION_DEFAULT_ON([gconf],[don't compile with GConf support]) OPTION_DEFAULT_ON([gsettings],[don't compile with GSettings 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. @@ -2101,6 +2102,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/subr.el b/lisp/subr.el index 8dfe78d..f47d4fb 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -4159,6 +4159,29 @@ convenience wrapper around `make-progress-reporter' and friends. nil ,@(cdr (cdr spec))))) \f +;;;; Support for watching filesystem events. + +(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))) + + (dolist (ev (cdr event)) + (unless (and (listp ev) (>= (length ev) 3)) + (signal 'filewatch-error (cons "Not a valid filewatch event" event))) + (funcall (car (cdr ev)) (car ev) (car (cdr (cdr ev)))))) + +\f ;;;; Comparing version strings. (defconst version-separator "." diff --git a/src/Makefile.in b/src/Makefile.in index e43f83e..1f17830 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -338,7 +338,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 \ profiler.o \ $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \ $(WINDOW_SYSTEM_OBJ) diff --git a/src/emacs.c b/src/emacs.c index 05affee..7c306ee 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1411,6 +1411,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem 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..c29e8b1 --- /dev/null +++ b/src/filewatch.c @@ -0,0 +1,457 @@ +/* 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 "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, Qall_modify; +static Lisp_Object Qfile_watch; +static Lisp_Object Qaccess, Qclose_write, Qclose_nowrite, Qopen, Qall; + +#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. + Format: + (id . (filename ((subid callback flags) (subid callbacks flags) ...))) */ +static Lisp_Object watch_list; + +static Lisp_Object +get_watch_data_by_watchdesc (int watchdesc) +{ + return Fassq (make_number (watchdesc), watch_list); +} + +#define CADDR(x) XCAR (XCDR (XCDR (x))) +#define CADR(x) XCAR (XCDR (x)) + +static Lisp_Object +append2 (Lisp_Object s1, Lisp_Object s2) +{ + Lisp_Object args[2]; + args[0] = s1; + args[1] = s2; + return Fappend (2, args); +} + +/* Returns a list with all the elements from EVENTS the `watch_list' CELL + is interested in. */ +static Lisp_Object +match_aspects (Lisp_Object cell, Lisp_Object events) +{ + Lisp_Object e; + Lisp_Object ret = Qnil; + Lisp_Object aspects = CADDR (cell); + if (EQ (aspects, Qt) || EQ (aspects, Qall)) + return events; + else if (EQ (aspects, Qall_modify)) + aspects = list4 (Qmodify, Qmove, Qattrib, Qdelete); + else if (! CONSP (aspects)) + aspects = list1 (aspects); + + while (CONSP (events)) + { + e = XCAR (events); + if (! NILP (Fmemq (XCAR (e), aspects))) + ret = Fcons (e, ret); + events = XCDR (events); + } + return ret; +} + +static Lisp_Object +inotifyevent_to_events (Lisp_Object name, struct inotify_event *ev) +{ + Lisp_Object 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 (ev->mask & IN_ACCESS) + events = Fcons (Fcons (Qaccess, name), events); + if (ev->mask & IN_CLOSE_WRITE) + events = Fcons (Fcons (Qclose_write, name), events); + if (ev->mask & IN_CLOSE_NOWRITE) + events = Fcons (Fcons (Qclose_nowrite, name), events); + if (ev->mask & IN_OPEN) + events = Fcons (Fcons (Qopen, name), events); + return events; +} + +/* 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 *_) +{ + struct input_event event; + int to_read; + char *buffer; + ssize_t n; + size_t i; + + 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 watch_data = get_watch_data_by_watchdesc (ev->wd); + if (!NILP (watch_data)) + { + Lisp_Object name, events; + + /* If a directory is watched name contains the name + of the file that was changed. */ + if (ev->len > 0) + { + size_t const len = strlen (ev->name); + name = make_unibyte_string (ev->name, min (len, ev->len)); + name = DECODE_FILE (name); + } + else + { + name = CADR (watch_data); + } + + events = inotifyevent_to_events (name, ev); + + if (!NILP (events)) + { + Lisp_Object x = CADDR (watch_data); + while (CONSP (x)) + { + Lisp_Object cell = XCAR (x); + Lisp_Object aspects = match_aspects (cell, events); + if (!NILP (aspects)) + { + Lisp_Object id = list3 (Qfile_watch, make_number (ev->wd), + XCAR (cell)); + Lisp_Object event_info = list1 (list3 (id, CADR (cell), + aspects)); + + if (NILP (event.arg)) + event.arg = event_info; + else + event.arg = append2 (event.arg, event_info); + } + x = XCDR (x); + } + } + + 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 (watch_data, 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, int in_list) +{ + 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 (!in_list && EQ (symb, Qall_modify)) + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB + | IN_DELETE_SELF | IN_DELETE; + else if (EQ (symb, Qaccess)) + return IN_ACCESS; + else if (EQ (symb, Qclose_write)) + return IN_CLOSE_WRITE; + else if (EQ (symb, Qclose_nowrite)) + return IN_CLOSE_NOWRITE; + else if (EQ (symb, Qopen)) + return IN_OPEN; + else if (!in_list && (EQ (symb, Qt) || EQ (symb, Qall))) + return IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTRIB + | IN_DELETE_SELF | IN_DELETE | IN_ACCESS | IN_CLOSE_WRITE + | IN_CLOSE_NOWRITE | IN_OPEN; + else + signal_error ("Unknown aspect", symb); +} + +static uint32_t +aspect_to_inotifymask (Lisp_Object aspect) +{ + if (CONSP (aspect)) + { + Lisp_Object x = aspect; + uint32_t mask = 0; + while (CONSP (x)) + { + mask |= symbol_to_inotifymask (XCAR (x), 1); + x = XCDR (x); + } + return mask; + } + else + return symbol_to_inotifymask (aspect, 0); +} + +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 (except +for all-modify and t): + +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-modify -- notify for all of the above (all modifying changes). + +access -- notify when file was accessed/read. + +close-write -- notify when a file opened for writing was closed. + +close-nowrite -- notify when a file not opened for writing was closed. + +open -- notify when a file was opened. + +t -- notify for all of the above. + +Watching a directory is not recursive. CALLBACK gets passed two +arguments ID and EVENT. The ID argument is the id returned by +file-watch. The EVENT argument is a list of events taking the form +(OP . FILENAME) or (OP DIRECTION NAME COOKIE). OP is the operation +that caused the event and FILENAME is the name of the file the +operation applied to. If the even belongs to a move operation inside +a directory then the (OP DIRECTION NAME COOKIE) form is used and +DIRECTION is either 'from or 'to indicating the direction of the move +operation. NAME is the corresponding file name. COOKIE is an +unspecified object that can be compared using `eq' to identify the +matching from and to move operation. + +Example: +(file-watch "foo" t #'(lambda (id event) (message "%s %s" id event))) + +Use `file-unwatch' to stop watching. + +usage: (file-watch FILENAME ASPECT CALLBACK) */) + (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback) +{ + static int intern_counter = 0; /* assign local ids */ + uint32_t mask; + int watchdesc; + Lisp_Object decoded_file_name; + Lisp_Object data; + Lisp_Object info; + + 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, NULL); + } + + mask = aspect_to_inotifymask (aspect) | IN_MASK_ADD; + 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)); + + info = list3 (make_number (intern_counter), callback, aspect); + ++intern_counter; + + data = get_watch_data_by_watchdesc (watchdesc); + if (CONSP (data)) + XSETCDR (XCDR (data), Fcons (append2 (CADDR (data), Fcons (info, Qnil)), + Qnil)); + else + watch_list = Fcons (list3 (make_number (watchdesc), + file_name, Fcons (info, Qnil)), + watch_list); + + return list3 (Qfile_watch, make_number (watchdesc), XCAR (info)); +} + +static int +file_watch_objectp (Lisp_Object obj) +{ + return CONSP (obj) && XINT (Flength (obj)) == 3 + && EQ (XCAR (obj), Qfile_watch); +} + +DEFUN ("file-unwatch", Ffile_unwatch, Sfile_unwatch, 1, 1, 0, + doc: /* Stop watching a file or directory. */) + (Lisp_Object watch_object) +{ + Lisp_Object watch_data; + Lisp_Object info; + + if (!file_watch_objectp (watch_object)) + wrong_type_argument (Qfile_watch, watch_object); + + if (inotifyfd == uninitialized) + return Qnil; + + watch_data = Fassoc ( CADR (watch_object), watch_list ); + if (NILP (watch_data)) + return Qnil; + + info = Fassoc (CADDR (watch_object), CADDR (watch_data)); + if (NILP (info)) + return Qnil; + + XSETCDR (XCDR (watch_data), Fcons (Fdelete (info, CADDR (watch_data)), Qnil)); + + if (NILP (CADDR (watch_data))) + { + int const magicno = XINT (CADR (watch_object)); + watch_list = Fdelete (watch_data, watch_list); + + if (inotify_rm_watch (inotifyfd, magicno) == -1) + report_file_error ("Could not unwatch file", + Fcons (XCAR (watch_data), Qnil)); + + /* Cleanup if watch_list is empty. */ + if (NILP (watch_list)) + { + close (inotifyfd); + delete_read_fd (inotifyfd); + inotifyfd = uninitialized; + } + } + else + { + Lisp_Object decoded_file_name = ENCODE_FILE (CADR (watch_data)); + Lisp_Object x = CADDR (watch_data); + uint32_t mask = 0; + while (CONSP (x)) + { + Lisp_Object cell = XCAR (x); + mask |= aspect_to_inotifymask (CADDR (cell)); + x = XCDR (x); + } + /* Reset watch mask */ + if (inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask) == -1) + report_file_error ("Could not unwatch file", + Fcons (XCAR (watch_data), Qnil)); + } + + return Qt; +} + +#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 (Qall_modify, "all-modify"); + + DEFSYM (Qaccess, "access"); + DEFSYM (Qclose_write, "close-write"); + DEFSYM (Qclose_nowrite, "close-nowrite"); + DEFSYM (Qopen, "open"); + DEFSYM (Qall, "all"); + + DEFSYM (Qfile_watch, "file-watch"); + + defsubr (&Sfile_watch); + defsubr (&Sfile_unwatch); + + staticpro (&watch_list); + + Fprovide (intern_c_string ("filewatch"), Qnil); +} + +#endif /* HAVE_FILEWATCH */ diff --git a/src/keyboard.c b/src/keyboard.c index f3d7df5..f694b13 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -321,6 +321,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 */ @@ -4021,6 +4024,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); @@ -5938,6 +5948,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, @@ -11408,6 +11425,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"); @@ -12168,6 +12189,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"); #if defined (WINDOWSNT) diff --git a/src/lisp.h b/src/lisp.h index 21ac55c..2f3e8b7 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -3497,6 +3497,11 @@ extern void syms_of_fontset (void); extern Lisp_Object Qfont_param; #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 f35bd92..ccb5887 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -211,6 +211,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 diff --git a/test/automated/filewatch-tests.el b/test/automated/filewatch-tests.el new file mode 100644 index 0000000..494d704 --- /dev/null +++ b/test/automated/filewatch-tests.el @@ -0,0 +1,50 @@ +;;; filewatch-tests.el --- Test suite for filewatch. + +;; Copyright (C) 2011 Free Software Foundation, Inc. + +;; Author: Rüdiger Sonderfeld <ruediger@c-plusplus.de> +;; Keywords: internal +;; Human-Keywords: internal + +;; 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/>. + +;;; Code: + +(require 'ert) + +(when (featurep 'filewatch) + + (ert-deftest filewatch-file-unwatch-type-check () + "Test whether `file-unwatch' does proper type checking." + (should-error (file-unwatch "path") :type 'wrong-type-argument) + (should-error (file-unwatch '(file 1 2)) :type 'wrong-type-argument)) + + (ert-deftest filewatch-file-watch-aspects-check () + "Test whether `file-watch' properly checks the aspects." + (let ((temp-file (make-temp-file "filewatch-aspects"))) + (should (stringp temp-file)) + (should-error (file-watch temp-file 'wrong nil) + :type 'unknown-aspect) + (should-error (file-watch temp-file '(modify t) nil) + :type 'unknown-aspect) + (should-error (file-watch temp-file '(modify Qall-modify) nil) + :type 'unknown-aspect) + (should-error (file-watch temp-file '(access wrong modify) nil) + :type 'unknown-aspect))) +) + +(provide 'filewatch-tests) +;;; filewatch-tests.el ends here. -- 1.7.11.3 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-09-28 13:06 ` [PATCH] Added basic file system watching support Rüdiger Sonderfeld @ 2012-09-28 14:13 ` Stefan Monnier 2012-09-28 16:27 ` Rüdiger Sonderfeld 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-09-28 14:13 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Leo, emacs-devel > I don't think the patch will be ready for 24.3. It is still not complete and > needs some heavy testing. I'm not very familiar with inotify and other file-system watching facilities, so just as a guideline for others's reviews: I'm OK with installing in 24.3 a non-complete version of the code, and I'm OK to install before the freeze a code that needs more testing, *but* only if those missing functionalities and testing won't require a redesign of the API. >> It seems that each event is either of form (SYMBOL . FILENAME) where >> FILENAME is the watched object, or of the form (SYMBOL from NAME . COOKIE) >> where NAME is the file added/removed from the watched object (a >> directory, presumably), but this object's FILENAME is not present. >> Any particular reason for this inconsistency? > FILENAME is not always the watched object. If a directory is watched then > it is the file name of the file inside the directory. This should really not > be used as identification for the watched object! That's why CALLBACK > also gets the ID. I understand that NAME refers to the added/removed file, but I'm wondering why we don't also add FILENAME (the name of the directory) to the event. If inotify doesn't provide it, we can provide it ourselves, right? Or is it rarely/never needed? Or is it because that directory may change name without us knowing? >> I guess that several `ev' objects can have the same `ev->cookie' >> value, right? > Yes exactly. The value is unimportant as long as it is unique to an event > and can be compared with eq. What would you propose as an alternative > object here? I can't remember enough of the context, but objects that are "unique" are plentiful. A cons cell would do, regardless of its value, for example (now, as to whether it's better than a number, that depends on what it's use for, e.g. whether you can put the cons's fields to good use). > The problem is that even after removal of a watch descriptor there can > still be issues in the queue. Therefore I think it is best to ignore any > events for files we do not watch. There could also be event types we do not > know about when inotify gets expanded. That's why I think unknown > event types should be ignored instead of reporting an error. Sounds fair. Please mention it in the comments. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-09-28 14:13 ` Stefan Monnier @ 2012-09-28 16:27 ` Rüdiger Sonderfeld 2012-10-01 4:38 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2012-09-28 16:27 UTC (permalink / raw) To: Stefan Monnier; +Cc: Leo, emacs-devel Thank you for your reply! On Friday 28 September 2012 10:13:52 you wrote: > I'm not very familiar with inotify and other file-system watching > facilities, so just as a guideline for others's reviews: I'm OK with > installing in 24.3 a non-complete version of the code, and I'm OK to > install before the freeze a code that needs more testing, *but* only if > those missing functionalities and testing won't require a redesign of > the API. It could require a minor redesign of the API. At least I want to gather some experience using the API first. And maybe porting it to other systems will require API adjustments. Both BSD's and Windows' filesystem watch APIs are not as powerful as inotify. > I understand that NAME refers to the added/removed file, but I'm > wondering why we don't also add FILENAME (the name of the directory) to > the event. If inotify doesn't provide it, we can provide it > ourselves, right? > Or is it rarely/never needed? > Or is it because that directory may change name without us knowing? The name might change. This would require file-watch to register MOVE_SELF events for every watched file and then update the name. This would make the code quite complicated and worst of all leave the user wondering why the name suddenly changed unless we force him to always accept MOVE_SELF events. Which would then again impose something on the user he might not need. Therefore I think it is better if the user keeps track of any ID to FILENAME mapping himself. Maybe there could be some user defined storage in the ID to handle it. > I can't remember enough of the context, but objects that are "unique" > are plentiful. A cons cell would do, regardless of its value, for > example (now, as to whether it's better than a number, that depends on > what it's use for, e.g. whether you can put the cons's fields to good > use). Currently the only risk is that cookie is bigger than MOST_POSITIVE_FIXNUM and then gets mapped to a value that is already taken by another cookie. This is only a problem on 32bit systems without the use of wide-int because cookie is uint32_t. There is a similar problem with the watch descriptor. It is currently also stored in the ID with make_number. Inotify uses an incremental system for watch descriptors and should be run out of descriptors by the time MOST_POSITIVE_FIXNUM is reached. But there is no guarantee for it. Therefore something like SAVE_VALUE should probably be used. Regards Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-09-28 16:27 ` Rüdiger Sonderfeld @ 2012-10-01 4:38 ` Stefan Monnier 2012-10-01 7:24 ` Eli Zaretskii ` (2 more replies) 0 siblings, 3 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-01 4:38 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Leo, emacs-devel >> I'm not very familiar with inotify and other file-system watching >> facilities, so just as a guideline for others's reviews: I'm OK with >> installing in 24.3 a non-complete version of the code, and I'm OK to >> install before the freeze a code that needs more testing, *but* only if >> those missing functionalities and testing won't require a redesign of >> the API. > It could require a minor redesign of the API. I know of "minor changes" (where backward compatibility is easy to preserve) and "redesigns" but I don't know what a "minor redesign" would look like. > At least I want to gather some experience using the API first. > And maybe porting it to other systems will require API adjustments. > Both BSD's and Windows' filesystem watch APIs are not as powerful > as inotify. [ I'm probably rehashing something already discussed at the time. ] If the API is sufficiently general that it can be reused for other systems, that's great. If there's a good chance this won't work without breaking compatibility, maybe a better option is to provide a low-level API that maps very closely to inotify and then an Elisp layer on top which abstracts away differences between different systems. In that case we can install the inotify support right away while we're still experimenting with the higher-level abstraction. >> I understand that NAME refers to the added/removed file, but I'm >> wondering why we don't also add FILENAME (the name of the directory) to >> the event. If inotify doesn't provide it, we can provide it >> ourselves, right? >> Or is it rarely/never needed? >> Or is it because that directory may change name without us knowing? > The name might change. OK, makes sense. >> I can't remember enough of the context, but objects that are "unique" >> are plentiful. A cons cell would do, regardless of its value, for >> example (now, as to whether it's better than a number, that depends on >> what it's use for, e.g. whether you can put the cons's fields to good >> use). > Currently the only risk is that cookie is bigger than > MOST_POSITIVE_FIXNUM and then gets mapped to a value that is already > taken by another cookie. This is only a problem on 32bit systems > without the use of wide-int because cookie is uint32_t. Ah, I remember what this is now. Rather than `cookie', I'd rather name it `identifier' or `identity'. So IIUC Emacs's code doesn't never needs to pass this cookie back to the inotify code, right? So the only issue with the current "make_number (ev->cookie)" is that it might incorrectly lead to matching two events that are really unrelated, in case they have the same lower 30bits. I don't know how important those last 2bits are in practice. But if they're unlikely to be important in practice, then I guess the current solution might be acceptable. OTOH we should stipulate that COOKIES should be compared with `equal', not `eq', so we could later use something like Fcons (make_number (ev->cookie / 65536), make_number (ev->cookie % 65536)). > There is a similar problem with the watch descriptor. This is slightly different in that here, the descriptor is later passed back to inotify, so it's indispensable that it be preserved correctly. Also, descriptors aren't usually compared for equality: it might be OK to have two watch-descriptors that refer to the same watch object but which aren't `eq'. The docstring of file-watch doesn't really make it clear what file-watch returns (it only says it indirectly with "The ID argument is the id returned by file-watch") so that should be fixed. Also this ID should be called WD for "watch descriptor", WH for "watch handle", or FW for "file-watcher". > Inotify uses an incremental system for watch descriptors and should > be run out of descriptors by the time MOST_POSITIVE_FIXNUM is > reached. But there is no guarantee for it. Therefore something like > SAVE_VALUE should probably be used. If watch descriptors are documented to be "small integers", like file descriptors, it might be OK to use fixnums, but it's a bit ugly. I think the cleaner option is to define a new object type for it. It could be either a new Lisp_Misc type, so you can make them print as something like "#<file-watcher NNN>" (take a look at "enum Lisp_Misc_Type" and "union Lisp_Misc" in src/lisp.h for starters; adding a new type will require adding corresponding branches to the switch statements in alloc.c and in print.c). Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-10-01 4:38 ` Stefan Monnier @ 2012-10-01 7:24 ` Eli Zaretskii 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld 2012-12-11 7:54 ` [PATCH] Added basic file system watching support Michael Albinus 2 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-10-01 7:24 UTC (permalink / raw) To: Stefan Monnier; +Cc: ruediger, sdl.web, emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Mon, 01 Oct 2012 00:38:09 -0400 > Cc: Leo <sdl.web@gmail.com>, emacs-devel@gnu.org > > >> I'm not very familiar with inotify and other file-system watching > >> facilities, so just as a guideline for others's reviews: I'm OK with > >> installing in 24.3 a non-complete version of the code, and I'm OK to > >> install before the freeze a code that needs more testing, *but* only if > >> those missing functionalities and testing won't require a redesign of > >> the API. > > It could require a minor redesign of the API. > > I know of "minor changes" (where backward compatibility is easy to > preserve) and "redesigns" but I don't know what a "minor redesign" would > look like. > > > At least I want to gather some experience using the API first. > > And maybe porting it to other systems will require API adjustments. > > Both BSD's and Windows' filesystem watch APIs are not as powerful > > as inotify. > > [ I'm probably rehashing something already discussed at the time. ] > If the API is sufficiently general that it can be reused for other > systems, that's great. Perhaps the API could be spelled out here, then this question could be answered for systems without inotify. ^ permalink raw reply [flat|nested] 125+ messages in thread
* [PATCH] Added inotify support. 2012-10-01 4:38 ` Stefan Monnier 2012-10-01 7:24 ` Eli Zaretskii @ 2012-10-01 14:09 ` Rüdiger Sonderfeld 2012-10-01 16:27 ` Stefan Monnier ` (3 more replies) 2012-12-11 7:54 ` [PATCH] Added basic file system watching support Michael Albinus 2 siblings, 4 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2012-10-01 14:09 UTC (permalink / raw) To: Stefan Monnier; +Cc: Leo, emacs-devel On Monday 01 October 2012 00:38:09 Stefan Monnier wrote: > If there's a good chance this won't work without breaking compatibility, > maybe a better option is to provide a low-level API that maps very > closely to inotify and then an Elisp layer on top which abstracts away > differences between different systems. In that case we can install the > inotify support right away while we're still experimenting with the > higher-level abstraction. That's probably the best approach here. I changed the patch to provide a low level inotify interface. However I did not provide an inotify_init(2) like function and instead initialization and closing of the inotify handle is done internally. I don't think that this should be exposed to elisp even in the low level interface. > But if they're unlikely to be important in practice, then > I guess the current solution might be acceptable. I think we are safe. I added that `equal' should be used to compare cookies. So we can easily change it without breaking the API. > I think the cleaner option is to define a new object type for it. > It could be either a new Lisp_Misc type, so you can make them print as > something like "#<file-watcher NNN>" (take a look at "enum > Lisp_Misc_Type" and "union Lisp_Misc" in src/lisp.h for starters; adding > a new type will require adding corresponding branches to the switch > statements in alloc.c and in print.c). That sounds like the best option. I haven't implemented it yet. Is it possible to make the Lisp_Misc_Type comparable with `equal'? Because the watch-descriptor has to be comparable. Regards, Rüdiger -- >8 -- Inotify is a Linux kernel API to monitor file system events. This patch adds a basic inotify based API to Emacs: `inotify-add-watch' is based on inotify_add_watch(2). But uses callbacks and accepts lisp objects instead of a bit mask. `inotify-rm-watch' is based on inotify_rm_watch(2). The creation and destruction of an inotify fd with inotify_init1(2) is handled internally. Example: (let ((wd (inotify-add-watch "foo.txt" t (lambda (ev) (message "FS Event %s" ev))))) ;; ... (inotify-rm-watch wd)) Signed-off-by: Rüdiger Sonderfeld <ruediger@c-plusplus.de> --- configure.ac | 13 ++ lisp/subr.el | 21 ++ src/Makefile.in | 2 +- src/emacs.c | 4 + src/inotify.c | 431 +++++++++++++++++++++++++++++++++++++++++ src/keyboard.c | 28 +++ src/lisp.h | 5 + src/termhooks.h | 5 + test/automated/inotify-test.el | 60 ++++++ 9 files changed, 568 insertions(+), 1 deletion(-) create mode 100644 src/inotify.c create mode 100644 test/automated/inotify-test.el diff --git a/configure.ac b/configure.ac index 5a3aea7..c1ce23f 100644 --- a/configure.ac +++ b/configure.ac @@ -184,6 +184,7 @@ OPTION_DEFAULT_ON([gconf],[don't compile with GConf support]) OPTION_DEFAULT_ON([gsettings],[don't compile with GSettings 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. @@ -2101,6 +2102,18 @@ 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]) +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/subr.el b/lisp/subr.el index 8dfe78d..134a1dc 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -4159,6 +4159,27 @@ convenience wrapper around `make-progress-reporter' and friends. nil ,@(cdr (cdr spec))))) \f +;;;; Support for watching filesystem events. + +(defun inotify-event-p (event) + "Check if EVENT is an inotify event." + (and (listp event) + (>= (length event) 3) + (eq (car event) 'inotify-event))) + +;;;###autoload +(defun inotify-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 (inotify-event-p event) + (signal 'inotify-error (cons "Not a valid inotify event" event))) + + (funcall (nth 2 event) (nth 1 event))) + +\f ;;;; Comparing version strings. (defconst version-separator "." diff --git a/src/Makefile.in b/src/Makefile.in index f8da009..1492dc6 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -339,7 +339,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 inotify.o \ profiler.o \ $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \ $(WINDOW_SYSTEM_OBJ) diff --git a/src/emacs.c b/src/emacs.c index 05affee..e43fa0e 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1411,6 +1411,10 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem syms_of_gnutls (); #endif +#ifdef HAVE_INOTIFY + syms_of_inotify (); +#endif /* HAVE_INOTIFY */ + #ifdef HAVE_DBUS syms_of_dbusbind (); #endif /* HAVE_DBUS */ diff --git a/src/inotify.c b/src/inotify.c new file mode 100644 index 0000000..49a45cb --- /dev/null +++ b/src/inotify.c @@ -0,0 +1,431 @@ +/* Inotify support for Emacs + +Copyright (C) 2012 + 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_INOTIFY + +#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 Qaccess; /* IN_ACCESS */ +static Lisp_Object Qattrib; /* IN_ATTRIB */ +static Lisp_Object Qclose_write; /* IN_CLOSE_WRITE */ +static Lisp_Object Qclose_nowrite; /* IN_CLOSE_NOWRITE */ +static Lisp_Object Qcreate; /* IN_CREATE */ +static Lisp_Object Qdelete; /* IN_DELETE */ +static Lisp_Object Qdelete_self; /* IN_DELETE_SELF */ +static Lisp_Object Qmodify; /* IN_MODIFY */ +static Lisp_Object Qmove_self; /* IN_MOVE_SELF */ +static Lisp_Object Qmoved_from; /* IN_MOVED_FROM */ +static Lisp_Object Qmoved_to; /* IN_MOVED_TO */ +static Lisp_Object Qopen; /* IN_OPEN */ + +static Lisp_Object Qall_events; /* IN_ALL_EVENTS */ +static Lisp_Object Qmove; /* IN_MOVE */ +static Lisp_Object Qclose; /* IN_CLOSE */ + +static Lisp_Object Qdont_follow; /* IN_DONT_FOLLOW */ +static Lisp_Object Qexcl_unlink; /* IN_EXCL_UNLINK */ +static Lisp_Object Qmask_add; /* IN_MASK_ADD */ +static Lisp_Object Qoneshot; /* IN_ONESHOT */ +static Lisp_Object Qonlydir; /* IN_ONLYDIR */ + +static Lisp_Object Qignored; /* IN_IGNORED */ +static Lisp_Object Qisdir; /* IN_ISDIR */ +static Lisp_Object Qq_overflow; /* IN_Q_OVERFLOW */ +static Lisp_Object Qunmount; /* IN_UNMOUNT */ + +static Lisp_Object Qinotify_event; + +#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. + Format: + (watch-descriptor . callback) + */ +static Lisp_Object watch_list; + +static Lisp_Object +make_watch_descriptor (int wd) +{ + /* TODO replace this with a Misc Object! */ + return make_number (wd); +} + +static Lisp_Object +mask_to_aspects (uint32_t mask) { + Lisp_Object aspects = Qnil; + if (mask & IN_ACCESS) + aspects = Fcons (Qaccess, aspects); + if (mask & IN_ATTRIB) + aspects = Fcons (Qattrib, aspects); + if (mask & IN_CLOSE_WRITE) + aspects = Fcons (Qclose_write, aspects); + if (mask & IN_CLOSE_NOWRITE) + aspects = Fcons (Qclose_nowrite, aspects); + if (mask & IN_CREATE) + aspects = Fcons (Qcreate, aspects); + if (mask & IN_DELETE) + aspects = Fcons (Qdelete, aspects); + if (mask & IN_DELETE_SELF) + aspects = Fcons (Qdelete_self, aspects); + if (mask & IN_MODIFY) + aspects = Fcons (Qmodify, aspects); + if (mask & IN_MOVE_SELF) + aspects = Fcons (Qmove_self, aspects); + if (mask & IN_MOVED_FROM) + aspects = Fcons (Qmoved_from, aspects); + if (mask & IN_MOVED_TO) + aspects = Fcons (Qmoved_to, aspects); + if (mask & IN_OPEN) + aspects = Fcons (Qopen, aspects); + if (mask & IN_IGNORED) + aspects = Fcons (Qignored, aspects); + if (mask & IN_ISDIR) + aspects = Fcons (Qisdir, aspects); + if (mask & IN_Q_OVERFLOW) + aspects = Fcons (Qq_overflow, aspects); + if (mask & IN_UNMOUNT) + aspects = Fcons (Qunmount, aspects); + return aspects; +} + +static Lisp_Object +inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev) +{ + Lisp_Object name = Qnil; + if (ev->len > 0) + { + size_t const len = strlen (ev->name); + name = make_unibyte_string (ev->name, min (len, ev->len)); + name = DECODE_FILE (name); + } + + return list2 (list4 (make_watch_descriptor (ev->wd), + mask_to_aspects (ev->mask), + make_number (ev->cookie), + name), + XCDR (watch_object)); +} + +/* 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 *_) +{ + struct input_event event; + Lisp_Object watch_object; + int to_read; + char *buffer; + ssize_t n; + size_t i; + + 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 = INOTIFY_EVENT; + event.arg = Qnil; + + i = 0; + while (i < (size_t)n) + { + struct inotify_event *ev = (struct inotify_event*)&buffer[i]; + + watch_object = Fassoc (make_watch_descriptor (ev->wd), watch_list); + if (!NILP (watch_object)) + { + event.arg = inotifyevent_to_event (watch_object, ev); + + /* If event was removed automatically: Drop it from watch list. */ + if (ev->mask & IN_IGNORED) + watch_list = Fdelete (watch_object, watch_list); + } + + 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, Qaccess)) + return IN_ACCESS; + else if (EQ (symb, Qattrib)) + return IN_ATTRIB; + else if (EQ (symb, Qclose_write)) + return IN_CLOSE_WRITE; + else if (EQ (symb, Qclose_nowrite)) + return IN_CLOSE_NOWRITE; + else if (EQ (symb, Qcreate)) + return IN_CREATE; + else if (EQ (symb, Qdelete)) + return IN_DELETE; + else if (EQ (symb, Qdelete_self)) + return IN_DELETE_SELF; + else if (EQ (symb, Qmodify)) + return IN_MODIFY; + else if (EQ (symb, Qmove_self)) + return IN_MOVE_SELF; + else if (EQ (symb, Qmoved_from)) + return IN_MOVED_FROM; + else if (EQ (symb, Qmoved_to)) + return IN_MOVED_TO; + else if (EQ (symb, Qopen)) + return IN_OPEN; + else if (EQ (symb, Qmove)) + return IN_MOVE; + else if (EQ (symb, Qclose)) + return IN_CLOSE; + + else if (EQ (symb, Qdont_follow)) + return IN_DONT_FOLLOW; + else if (EQ (symb, Qexcl_unlink)) + return IN_EXCL_UNLINK; + else if (EQ (symb, Qmask_add)) + return IN_MASK_ADD; + else if (EQ (symb, Qoneshot)) + return IN_ONESHOT; + else if (EQ (symb, Qonlydir)) + return IN_ONLYDIR; + + else if (EQ (symb, Qt) || EQ (symb, Qall_events)) + return IN_ALL_EVENTS; + else + signal_error ("Unknown aspect", symb); +} + +static uint32_t +aspect_to_inotifymask (Lisp_Object aspect) +{ + if (CONSP (aspect)) + { + Lisp_Object x = aspect; + uint32_t mask = 0; + while (CONSP (x)) + { + mask |= symbol_to_inotifymask (XCAR (x)); + x = XCDR (x); + } + return mask; + } + else + return symbol_to_inotifymask (aspect); +} + +DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0, + doc: /* Add a watch for FILE-NAME to inotify. + +A WATCH-DESCRIPTOR is returned on success. ASPECT might be one of the following +symbols or a list of those symbols: + +access +attrib +close-write +close-nowrite +create +delete +delete-self +modify +move-self +moved-from +moved-to +open + +all-events or t +move +close + +The following symbols can also be added to a list of aspects + +dont-follow +excl-unlink +mask-add +oneshot +onlydir + +Watching a directory is not recursive. CALLBACK gets called in case of an +event. It gets passed a single argument EVENT which contains an event structure +of the format + +(WATCH-DESCRIPTOR ASPECTS COOKIE NAME) + +WATCH-DESCRIPTOR is the same object that was returned by this function. It can +be tested for equality using `equal'. ASPECTS describes the event. It is a +list of ASPECT symbols described above and can also contain one of the following +symbols + +ignored +isdir +q-overflow +unmount + +COOKIE is an object that can be compared using `equal' to identify two matching +renames (moved-from and moved-to). + +If a directory is watched then NAME is the name of file that caused the event. + +See inotify(7) and inotify_add_watch(2) for further information. The inotify fd +is managed internally and there is no corresponding inotify_init. Use +`inotify-rm-watch' to remove a watch. + */) + (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback) +{ + uint32_t mask; + Lisp_Object watch_object; + Lisp_Object decoded_file_name; + Lisp_Object watch_descriptor; + int watchdesc = -1; + + 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, NULL); + } + + mask = aspect_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 add watch for file", Fcons (file_name, Qnil)); + + watch_descriptor = make_watch_descriptor (watchdesc); + + /* Delete existing watch object. */ + watch_object = Fassoc (watch_descriptor, watch_list); + if (!NILP (watch_object)) + watch_list = Fdelete (watch_object, watch_list); + + /* Store watch object in watch list. */ + watch_object = Fcons (watch_descriptor, callback); + watch_list = Fcons (watch_object, watch_list); + + return watch_descriptor; +} + +DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0, + doc: /* Remove an existing WATCH-DESCRIPTOR. + +WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'. + +See inotify_rm_watch(2) for more information. + */) + (Lisp_Object watch_descriptor) +{ + Lisp_Object watch_object; + int wd = XINT (watch_descriptor); + + if (inotify_rm_watch (inotifyfd, wd) == -1) + report_file_error ("Could not rm watch", Fcons (watch_descriptor, + Qnil)); + + /* Remove watch descriptor from watch list. */ + watch_object = Fassoc (watch_descriptor, watch_list); + if (!NILP (watch_object)) + watch_list = Fdelete (watch_object, watch_list); + + /* Cleanup if no more files are watched. */ + if (NILP (watch_list)) + { + close (inotifyfd); + delete_read_fd (inotifyfd); + inotifyfd = uninitialized; + } + + return Qt; +} + +void +syms_of_inotify (void) +{ + DEFSYM (Qaccess, "access"); + DEFSYM (Qattrib, "attrib"); + DEFSYM (Qclose_write, "close-write"); + DEFSYM (Qclose_nowrite, "close-nowrite"); + DEFSYM (Qcreate, "create"); + DEFSYM (Qdelete, "delete"); + DEFSYM (Qdelete_self, "delete-self"); + DEFSYM (Qmodify, "modify"); + DEFSYM (Qmove_self, "move-self"); + DEFSYM (Qmoved_from, "moved-from"); + DEFSYM (Qmoved_to, "moved-to"); + DEFSYM (Qopen, "open"); + + DEFSYM (Qall_events, "all-events"); + DEFSYM (Qmove, "move"); + DEFSYM (Qclose, "close"); + + DEFSYM (Qdont_follow, "dont-follow"); + DEFSYM (Qexcl_unlink, "excl-unlink"); + DEFSYM (Qmask_add, "mask-add"); + DEFSYM (Qoneshot, "oneshot"); + DEFSYM (Qonlydir, "onlydir"); + + DEFSYM (Qignored, "ignored"); + DEFSYM (Qisdir, "isdir"); + DEFSYM (Qq_overflow, "q-overflow"); + DEFSYM (Qunmount, "unmount"); + + DEFSYM (Qinotify_event, "inotify-event"); + + defsubr (&Sinotify_add_watch); + defsubr (&Sinotify_rm_watch); + + staticpro (&watch_list); + + Fprovide (intern_c_string ("inotify"), Qnil); +} + +#endif /* HAVE_INOTIFY */ diff --git a/src/keyboard.c b/src/keyboard.c index f3d7df5..02e1473 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -321,6 +321,9 @@ static Lisp_Object Qsave_session; #ifdef HAVE_DBUS static Lisp_Object Qdbus_event; #endif +#ifdef HAVE_INOTIFY +static Lisp_Object Qinotify_event; +#endif /* HAVE_INOTIFY */ static Lisp_Object Qconfig_changed_event; /* Lisp_Object Qmouse_movement; - also an event header */ @@ -4021,6 +4024,13 @@ kbd_buffer_get_event (KBOARD **kbp, kbd_fetch_ptr = event + 1; } #endif +#ifdef HAVE_INOTIFY + else if (event->kind == INOTIFY_EVENT) + { + obj = make_lispy_event (event); + kbd_fetch_ptr = event + 1; + } +#endif else if (event->kind == CONFIG_CHANGED_EVENT) { obj = make_lispy_event (event); @@ -5938,6 +5948,13 @@ make_lispy_event (struct input_event *event) } #endif /* HAVE_DBUS */ +#ifdef HAVE_INOTIFY + case INOTIFY_EVENT: + { + return Fcons (Qinotify_event, event->arg); + } +#endif /* HAVE_INOTIFY */ + case CONFIG_CHANGED_EVENT: return Fcons (Qconfig_changed_event, Fcons (event->arg, @@ -11408,6 +11425,10 @@ syms_of_keyboard (void) DEFSYM (Qdbus_event, "dbus-event"); #endif +#ifdef HAVE_INOTIFY + DEFSYM (Qinotify_event, "inotify-event"); +#endif /* HAVE_INOTIFY */ + DEFSYM (QCenable, ":enable"); DEFSYM (QCvisible, ":visible"); DEFSYM (QChelp, ":help"); @@ -12168,6 +12189,13 @@ keys_of_keyboard (void) "dbus-handle-event"); #endif +#ifdef HAVE_INOTIFY + /* Define a special event which is raised for inotify callback + functions. */ + initial_define_lispy_key (Vspecial_event_map, "inotify-event", + "inotify-handle-event"); +#endif /* HAVE_INOTIFY */ + initial_define_lispy_key (Vspecial_event_map, "config-changed-event", "ignore"); #if defined (WINDOWSNT) diff --git a/src/lisp.h b/src/lisp.h index c3cabe0..02bfa08 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -3497,6 +3497,11 @@ extern void syms_of_fontset (void); extern Lisp_Object Qfont_param; #endif +/* Defined in inotify.c */ +#ifdef HAVE_INOTIFY +extern void syms_of_inotify (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 f35bd92..5890d10 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -211,6 +211,11 @@ enum event_kind , NS_NONKEY_EVENT #endif +#ifdef HAVE_INOTIFY + /* File or directory was changed. */ + , INOTIFY_EVENT +#endif + }; /* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT diff --git a/test/automated/inotify-test.el b/test/automated/inotify-test.el new file mode 100644 index 0000000..edda7ef --- /dev/null +++ b/test/automated/inotify-test.el @@ -0,0 +1,60 @@ +;;; inotify-tests.el --- Test suite for inotify. -*- lexical-binding: t -*- + +;; Copyright (C) 2012 Free Software Foundation, Inc. + +;; Author: Rüdiger Sonderfeld <ruediger@c-plusplus.de> +;; Keywords: internal +;; Human-Keywords: internal + +;; 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/>. + +;;; Code: + +(require 'ert) + +(when (featurep 'inotify) + + ;; (ert-deftest filewatch-file-watch-aspects-check () + ;; "Test whether `file-watch' properly checks the aspects." + ;; (let ((temp-file (make-temp-file "filewatch-aspects"))) + ;; (should (stringp temp-file)) + ;; (should-error (file-watch temp-file 'wrong nil) + ;; :type 'error) + ;; (should-error (file-watch temp-file '(modify t) nil) + ;; :type 'error) + ;; (should-error (file-watch temp-file '(modify all-modify) nil) + ;; :type 'error) + ;; (should-error (file-watch temp-file '(access wrong modify) nil) + ;; :type 'error))) + + (ert-deftest inotify-file-watch-simple () + "Test if watching a normal file works." + (let ((temp-file (make-temp-file "inotify-simple")) + (events 0)) + (let ((wd + (inotify-add-watch temp-file t (lambda (ev) + (setq events (1+ events)))))) + (unwind-protect + (progn + (with-temp-file temp-file + (insert "Foo\n")) + (sit-for 5) ;; Hacky. Wait for 5s until events are processed + (should (> events 0))) + (inotify-rm-watch wd))))) +) + +(provide 'inotify-tests) +;;; inotify-tests.el ends here. -- 1.7.11.3 ^ permalink raw reply related [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld @ 2012-10-01 16:27 ` Stefan Monnier 2012-10-02 21:28 ` Eli Zaretskii ` (2 subsequent siblings) 3 siblings, 0 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-01 16:27 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Leo, emacs-devel >> I think the cleaner option is to define a new object type for it. >> It could be either a new Lisp_Misc type, so you can make them print as >> something like "#<file-watcher NNN>" (take a look at "enum >> Lisp_Misc_Type" and "union Lisp_Misc" in src/lisp.h for starters; adding >> a new type will require adding corresponding branches to the switch >> statements in alloc.c and in print.c). > That sounds like the best option. I haven't implemented it yet. Is it > possible to make the Lisp_Misc_Type comparable with `equal'? It could be done (fairly easily), but I'd rather not. > Because the watch-descriptor has to be comparable. It's comparable with `eq'. Why would you need two different #<file-watcher...> objects for the same C-level `wd'? Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld 2012-10-01 16:27 ` Stefan Monnier @ 2012-10-02 21:28 ` Eli Zaretskii 2012-10-02 23:46 ` Óscar Fuentes 2012-12-02 20:08 ` Eli Zaretskii 2012-12-10 11:52 ` Eli Zaretskii 3 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-10-02 21:28 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel, monnier, sdl.web > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Mon, 01 Oct 2012 16:09:55 +0200 > Cc: Leo <sdl.web@gmail.com>, emacs-devel@gnu.org > > On Monday 01 October 2012 00:38:09 Stefan Monnier wrote: > > If there's a good chance this won't work without breaking compatibility, > > maybe a better option is to provide a low-level API that maps very > > closely to inotify and then an Elisp layer on top which abstracts away > > differences between different systems. In that case we can install the > > inotify support right away while we're still experimenting with the > > higher-level abstraction. > > That's probably the best approach here. I changed the patch to provide a low > level inotify interface. However I did not provide an inotify_init(2) like > function and instead initialization and closing of the inotify handle is done > internally. I don't think that this should be exposed to elisp even in the > low level interface. Btw, what are Emacs use cases for using this kind of feature? Watching a directory or even a single file can easily flood Emacs with many events, and in some extreme cases (like watching /tmp, especially using IN_ACCESS) so many that it will probably make Emacs unusable. (The equivalent Windows APIs give you an even longer rope, in that you can watch a directory and all its subdirectories, recursively, with a single watch handle.) Also, a typical file operation can be presented as a series of notifications that are not easy to make sense of, unless you are a filesystem expert and know exactly what to expect. Maybe we should think a little more, before we expose all the guts of inotify to the Lisp level. It's not like anyone will write an OS in Emacs Lisp any time soon, is it? ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-02 21:28 ` Eli Zaretskii @ 2012-10-02 23:46 ` Óscar Fuentes 2012-10-03 2:10 ` Stefan Monnier 2012-10-03 3:57 ` Eli Zaretskii 0 siblings, 2 replies; 125+ messages in thread From: Óscar Fuentes @ 2012-10-02 23:46 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > Btw, what are Emacs use cases for using this kind of feature? Apart from those mentioned on the original message (dired or magit's (or vc-dir) status view) I'll like to mention auto-revert-mode. Currently auto-revert is a bit of a hack, in the sense that Emacs implements a poor man's method for being aware of file changes. Checking the timestamps every N seconds is cumbersome and has annoying side-effects compared to the Right Thing. > Watching a directory or even a single file can easily flood Emacs with > many events, There are users that turn on auto-revert-mode on all file buffers. When you accumulated quite a few buffers, checking all the associated timestamps can be a bit lengthy and disruptive, so auto-revert-stop-on-user-input was introduced (another hack). Plus, waiting 5 seconds for checking for changes is, sometimes, annoyingly long, and decreasing that value may end on too much workload for Emacs. > and in some extreme cases (like watching /tmp, especially > using IN_ACCESS) so many that it will probably make Emacs unusable. > (The equivalent Windows APIs give you an even longer rope, in that you > can watch a directory and all its subdirectories, recursively, with a > single watch handle.) Also, a typical file operation can be presented > as a series of notifications that are not easy to make sense of, > unless you are a filesystem expert and know exactly what to expect. > > Maybe we should think a little more, before we expose all the guts of > inotify to the Lisp level. It's not like anyone will write an OS in > Emacs Lisp any time soon, is it? You are leaving this case to extremes. Emacs already allows you to do very silly things. Let's not throw out useful features simply because they might be abused. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-02 23:46 ` Óscar Fuentes @ 2012-10-03 2:10 ` Stefan Monnier 2012-10-03 3:54 ` Eli Zaretskii 2012-10-05 16:55 ` Nix 2012-10-03 3:57 ` Eli Zaretskii 1 sibling, 2 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-03 2:10 UTC (permalink / raw) To: Óscar Fuentes; +Cc: emacs-devel >> Btw, what are Emacs use cases for using this kind of feature? > Apart from those mentioned on the original message (dired or magit's (or > vc-dir) status view) I'll like to mention auto-revert-mode. Yes, for my own use auto-revert-mode is the main one. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 2:10 ` Stefan Monnier @ 2012-10-03 3:54 ` Eli Zaretskii 2012-10-03 12:46 ` Stefan Monnier 2012-10-05 16:55 ` Nix 1 sibling, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-10-03 3:54 UTC (permalink / raw) To: Stefan Monnier; +Cc: ofv, emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Tue, 02 Oct 2012 22:10:41 -0400 > Cc: emacs-devel@gnu.org > > >> Btw, what are Emacs use cases for using this kind of feature? > > Apart from those mentioned on the original message (dired or magit's (or > > vc-dir) status view) I'll like to mention auto-revert-mode. > > Yes, for my own use auto-revert-mode is the main one. AFAICS, all these use cases don't need support for every possible notification inotify lets you define. How about a simpler interface? ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 3:54 ` Eli Zaretskii @ 2012-10-03 12:46 ` Stefan Monnier 2012-10-03 18:34 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-10-03 12:46 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ofv, emacs-devel >> >> Btw, what are Emacs use cases for using this kind of feature? >> > Apart from those mentioned on the original message (dired or magit's (or >> > vc-dir) status view) I'll like to mention auto-revert-mode. >> Yes, for my own use auto-revert-mode is the main one. > AFAICS, all these use cases don't need support for every possible > notification inotify lets you define. How about a simpler interface? The patch he suggests is "as simple as it gets" because it just exposes the C-level API. And yes, we'll want a simpler interface on top. Both so as to make it easier to use but also so it can work with other mechanisms than inotify. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 12:46 ` Stefan Monnier @ 2012-10-03 18:34 ` Eli Zaretskii 2012-10-03 18:47 ` Stefan Monnier 2012-10-03 19:33 ` Óscar Fuentes 0 siblings, 2 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-10-03 18:34 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Cc: ofv@wanadoo.es, emacs-devel@gnu.org > Date: Wed, 03 Oct 2012 08:46:39 -0400 > > >> >> Btw, what are Emacs use cases for using this kind of feature? > >> > Apart from those mentioned on the original message (dired or magit's (or > >> > vc-dir) status view) I'll like to mention auto-revert-mode. > >> Yes, for my own use auto-revert-mode is the main one. > > AFAICS, all these use cases don't need support for every possible > > notification inotify lets you define. How about a simpler interface? > > The patch he suggests is "as simple as it gets" because it just exposes > the C-level API. And yes, we'll want a simpler interface on top. > Both so as to make it easier to use but also so it can work with other > mechanisms than inotify. If providing a higher-level interface is the plan, then I think inotify.el should be renamed to something like file-notifications.el, or some other neutral name, because before long it will be home to other back ends and to those higher-level APIs. That said, when will this simpler interface be added? before or after Emacs 24.3 is locked for changes? If before, then you can skip everything I write below (or save it for a future discussion ;-). That's assuming the inotify support _will_ be in Emacs 24.3. But if Emacs 24.3 is supposed to be released without this simpler interface, then I think we will do a disservice to Lisp programmers. E.g., consider the Dired use case. This wants to know about any and all changes to the files in the directory, because it will need to refresh the listing, right? But as things are, whoever will want to code this will have to carefully read the inotify(7) man page and figure out which of the IN_* flags she wants, because there's no 'just-what-dired-needs' flag in the API that is being proposed. Or consider the autorevert use case. Which flag(s) to use for that? My first guess was IN_ATTRIB, but the man page says IN_ATTRIB Metadata changed, e.g., permissions, timestamps, extended attributes, link count (since Linux 2.6.25), UID, GID, etc. Hmmm... does this include file size? I don't know. If it doesn't, then do I need to catch the series of IN_OPEN, IN_WRITE, IN_CLOSE? Are you confused yet? Because I am. Another concern is how to design the Lisp code that gets run by notifications. Since we are converting notifications into Lisp events, the most natural way of using them would be to bind a function to the 'inotify-event' event. (Btw, I'd drop the "event" part from the symbol name, it's redundant.) But since meaningful file-level events are likely to produce multiple notifications (see below), such a function will need to implement a non-trivial state machine to DTRT. For example, if you type at the shell prompt "echo foo > bar" with 'bar' an existing non-empty file, how many notifications do you see? On Windows, I get 2 or 3, depending on the details: 2 for size change (first one when the file is truncated, second one when "foo" is written into it), and sometimes also an attribute or "security" change (e.g., if the original file was owned by someone else). Unless inotify magically merges all these into a single meaningful notification, the Lisp code that receives this series of notifications will have hard time doing TRT with them, even if the exact expected series are known in advance, especially if other notifications are interspersed with these. As yet another example, many applications update a file by first writing a temporary file, then deleting the old file and renaming the new one. That's another series of notifications someone will have to figure out in Lisp, when the function bound to 'inotify-event' will get called several times, because from the user POV, the file got updated, not overwritten by moving another file over it. This is why I think we _must_ have a higher-level API in place for this feature to be useful in practice, even if just inotify back end is supported. And we should talk _today_ about that API, because some of the processing needed to produce higher-level abstractions of events are much easier done in C than in Lisp. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 18:34 ` Eli Zaretskii @ 2012-10-03 18:47 ` Stefan Monnier 2012-10-03 19:08 ` Eli Zaretskii 2012-10-03 19:33 ` Óscar Fuentes 1 sibling, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-10-03 18:47 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel > That said, when will this simpler interface be added? before or after > Emacs 24.3 is locked for changes? After: there is no time to design such an API before the freeze. > That's assuming the inotify support _will_ be in Emacs 24.3. I think it would be good for it to be in 24.3, mostly to get the ball rolling. > Hmmm... does this include file size? I don't know. If it doesn't, > then do I need to catch the series of IN_OPEN, IN_WRITE, IN_CLOSE? Yes, someone will have to figure that out. Leaving inotify outside of 24.3 won't save this work, tho. > For example, if you type at the shell prompt "echo foo > bar" with > 'bar' an existing non-empty file, how many notifications do you see? > On Windows, I get 2 or 3, depending on the details: 2 for size change > (first one when the file is truncated, second one when "foo" is > written into it), and sometimes also an attribute or "security" change > (e.g., if the original file was owned by someone else). Unless > inotify magically merges all these into a single meaningful > notification, the Lisp code that receives this series of notifications > will have hard time doing TRT with them, even if the exact expected > series are known in advance, especially if other notifications are > interspersed with these. Yes, but I don't see what alternative approach would save us from doing this work. > And we should talk _today_ about that API, because some of the > processing needed to produce higher-level abstractions of events are > much easier done in C than in Lisp. Ah, so that's what it's about. I don't see why that should be the case, but if it is so, then indeed we might be better off postponing the merge. Or to only include it if Emacs is compiled with some "experimental-inotify" flag. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 18:47 ` Stefan Monnier @ 2012-10-03 19:08 ` Eli Zaretskii 2012-10-03 20:59 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-10-03 19:08 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Wed, 03 Oct 2012 14:47:44 -0400 > Cc: emacs-devel@gnu.org > > Yes, someone will have to figure that out. Leaving inotify outside of > 24.3 won't save this work, tho. I'm not lobbying for leaving it out. > > And we should talk _today_ about that API, because some of the > > processing needed to produce higher-level abstractions of events are > > much easier done in C than in Lisp. > > Ah, so that's what it's about. I don't see why that should be the case Let me put it this way: we won't be able to see whether or not it is the case, unless we discuss how to overcome these difficulties. When we find good solution(s), we can then see where they are implemented best. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 19:08 ` Eli Zaretskii @ 2012-10-03 20:59 ` Stefan Monnier 2012-10-12 13:54 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-10-03 20:59 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel >> Ah, so that's what it's about. I don't see why that should be the case > Let me put it this way: we won't be able to see whether or not it is > the case, unless we discuss how to overcome these difficulties. When > we find good solution(s), we can then see where they are implemented > best. I think we'll only see it with experience. And (based on our previous experiences with code on non-trunk branches) as long as there's no code in trunk for it, we won't see too many people experiment with it. So I think we should include it in 24.3 but label it as experimental. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 20:59 ` Stefan Monnier @ 2012-10-12 13:54 ` Eli Zaretskii 2012-10-14 14:55 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-10-12 13:54 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel > From: Stefan Monnier <monnier@iro.umontreal.ca> > Cc: emacs-devel@gnu.org > Date: Wed, 03 Oct 2012 16:59:43 -0400 > > So I think we should include it in 24.3 but label it as experimental. Is this still the intention? If so, why isn't inotify.c and stuff in the repository yet? With Óscar's help, I will soon have a w32 implementation for this. But if the inotify stuff is not going to be committed, it doesn't make sense to commit the w32 implementation, either. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-12 13:54 ` Eli Zaretskii @ 2012-10-14 14:55 ` Eli Zaretskii 0 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-10-14 14:55 UTC (permalink / raw) To: monnier; +Cc: emacs-devel > Date: Fri, 12 Oct 2012 15:54:34 +0200 > From: Eli Zaretskii <eliz@gnu.org> > Cc: emacs-devel@gnu.org > > > From: Stefan Monnier <monnier@iro.umontreal.ca> > > Cc: emacs-devel@gnu.org > > Date: Wed, 03 Oct 2012 16:59:43 -0400 > > > > So I think we should include it in 24.3 but label it as experimental. > > Is this still the intention? If so, why isn't inotify.c and stuff in > the repository yet? Any word on this? Anything at all? ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 18:34 ` Eli Zaretskii 2012-10-03 18:47 ` Stefan Monnier @ 2012-10-03 19:33 ` Óscar Fuentes 2012-10-05 7:40 ` Eli Zaretskii 1 sibling, 1 reply; 125+ messages in thread From: Óscar Fuentes @ 2012-10-03 19:33 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: [snip] > This is why I think we _must_ have a higher-level API in place for > this feature to be useful in practice, even if just inotify back end > is supported. Agreed. For starters, IMHO we need just two events: file-contents-changed, which comprises write, truncate, delete, and rename operations and fulfills the requirements of autorevert-like features, and file-metadata-changed, which is what dired-like features need. BTW, I would take care of the MSWindows implementation, if nobody beats me to it. [snip] ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 19:33 ` Óscar Fuentes @ 2012-10-05 7:40 ` Eli Zaretskii 2012-10-05 13:07 ` Óscar Fuentes 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-10-05 7:40 UTC (permalink / raw) To: Óscar Fuentes; +Cc: emacs-devel > From: Óscar Fuentes <ofv@wanadoo.es> > Date: Wed, 03 Oct 2012 21:33:08 +0200 > > BTW, I would take care of the MSWindows implementation, if nobody beats > me to it. Thank you. I started working on that, too, and will probably have some working code, still outside of Emacs, by tomorrow. I could give the code to you then, or we could work together. There's a design issue with how to signal these events to the Emacs input channels; if you got that figured out, maybe you would like to start from that end. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 7:40 ` Eli Zaretskii @ 2012-10-05 13:07 ` Óscar Fuentes 2012-10-05 15:19 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Óscar Fuentes @ 2012-10-05 13:07 UTC (permalink / raw) To: emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> BTW, I would take care of the MSWindows implementation, if nobody beats >> me to it. > > Thank you. I started working on that, too, and will probably have > some working code, still outside of Emacs, by tomorrow. I could give > the code to you then, or we could work together. There's a design > issue with how to signal these events to the Emacs input channels; if > you got that figured out, maybe you would like to start from that end. You can publish your branch somewhere or send a patch to me, explaining what's missing or broken, and I'll look at it this weekend. (Note: I'm at home with the MSWindows API, but not even a novice on Emacs's C<->Elisp interface, so adjust your expectations on accordance :-) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 13:07 ` Óscar Fuentes @ 2012-10-05 15:19 ` Eli Zaretskii 0 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-10-05 15:19 UTC (permalink / raw) To: Óscar Fuentes; +Cc: emacs-devel > From: Óscar Fuentes <ofv@wanadoo.es> > Date: Fri, 05 Oct 2012 15:07:19 +0200 > > Eli Zaretskii <eliz@gnu.org> writes: > > >> BTW, I would take care of the MSWindows implementation, if nobody beats > >> me to it. > > > > Thank you. I started working on that, too, and will probably have > > some working code, still outside of Emacs, by tomorrow. I could give > > the code to you then, or we could work together. There's a design > > issue with how to signal these events to the Emacs input channels; if > > you got that figured out, maybe you would like to start from that end. > > You can publish your branch somewhere or send a patch to me, explaining > what's missing or broken, and I'll look at it this weekend. I cannot send anything directly to you, because your mail server rejects my mails, has been doing that for months if not years: Your message cannot be delivered to the following recipients: Recipient address: ofv@wanadoo.es Reason: Rejection greeting returned by server. Diagnostic code: smtp;550 Your domain is blacklisted mtaout20.012.net.il [80.179.55.166]. Remote system: dns;inw.wanadoo.es (TCP|80.179.55.166|62212|62.36.20.20|25) (Your domain is blacklisted mtaout20.012.net.il [80.179.55.166].) If you can whitelist me, fine; otherwise I will post here. > (Note: I'm at home with the MSWindows API, but not even a novice on > Emacs's C<->Elisp interface, so adjust your expectations on accordance > :-) In that case, I will stop working on the API part (what I have already supports a single watched file), and instead work on incorporating this into Emacs. You can then extend the API interaction, or even redesign it, if you think it's not up to speed. Thanks. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-03 2:10 ` Stefan Monnier 2012-10-03 3:54 ` Eli Zaretskii @ 2012-10-05 16:55 ` Nix 2012-10-05 17:15 ` Eli Zaretskii ` (2 more replies) 1 sibling, 3 replies; 125+ messages in thread From: Nix @ 2012-10-05 16:55 UTC (permalink / raw) To: Stefan Monnier; +Cc: Óscar Fuentes, emacs-devel On 3 Oct 2012, Stefan Monnier spake thusly: >>> Btw, what are Emacs use cases for using this kind of feature? >> Apart from those mentioned on the original message (dired or magit's (or >> vc-dir) status view) I'll like to mention auto-revert-mode. > > Yes, for my own use auto-revert-mode is the main one. Of course you can't rely on inotify rather than polling, because inotify simply silently omits all changes that come from other hosts when an fs is mounted or exported over the network. inotify and friends are only spying on local VFS traffic, which in my experience makes them less than useful for most applications. -- NULL && (void) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 16:55 ` Nix @ 2012-10-05 17:15 ` Eli Zaretskii 2012-10-05 17:46 ` Nix 2012-10-05 18:22 ` Stefan Monnier 2012-10-06 7:04 ` Stephen J. Turnbull 2 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-10-05 17:15 UTC (permalink / raw) To: Nix; +Cc: ofv, monnier, emacs-devel > From: Nix <nix@esperi.org.uk> > Emacs: Lovecraft was an optimist. > Date: Fri, 05 Oct 2012 17:55:34 +0100 > Cc: Óscar Fuentes <ofv@wanadoo.es>, emacs-devel@gnu.org > > Of course you can't rely on inotify rather than polling, because inotify > simply silently omits all changes that come from other hosts when an fs > is mounted or exported over the network. inotify and friends are only > spying on local VFS traffic If this is true (I don't see this limitation in the inotify(7) man page), it is specific to inotify. The MS-Windows equivalents work for exported filesystems as well. Nevertheless, the general observation is correct: these notifications are not 100% reliable. Even for local filesystems, if the event queue overflows, events are thrown away without being reported. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 17:15 ` Eli Zaretskii @ 2012-10-05 17:46 ` Nix 0 siblings, 0 replies; 125+ messages in thread From: Nix @ 2012-10-05 17:46 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ofv, monnier, emacs-devel On 5 Oct 2012, Eli Zaretskii stated: >> Of course you can't rely on inotify rather than polling, because inotify >> simply silently omits all changes that come from other hosts when an fs >> is mounted or exported over the network. inotify and friends are only >> spying on local VFS traffic > > If this is true (I don't see this limitation in the inotify(7) man > page), it is specific to inotify. The MS-Windows equivalents work for > exported filesystems as well. Yeah, it's specific to inotify/dnotify and anything built atop the fsnotify Linux kernel subsystem. It is really annoying for those of us with $HOME on NFS :( I periodically wish I had the time to write something better, but it's quite hard. -- NULL && (void) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 16:55 ` Nix 2012-10-05 17:15 ` Eli Zaretskii @ 2012-10-05 18:22 ` Stefan Monnier 2012-10-05 18:30 ` Óscar Fuentes 2012-10-06 16:39 ` Nix 2012-10-06 7:04 ` Stephen J. Turnbull 2 siblings, 2 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-05 18:22 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, emacs-devel > Of course you can't rely on inotify rather than polling, because inotify > simply silently omits all changes that come from other hosts when an fs > is mounted or exported over the network. Of course, it also doesn't help when Emacs is built without inotify. The main questions would then be: - how often does it work? I think this is likely to be "often" since it should work for most/all the "home desktop" as well as laptop use cases. - can we reliably determine whether it will work? IIUC inotify works reliably for local file-systems (even if they're exported because the other hosts's actions will be locally performed by the nfsd) but not for file-systems mounted from a remote host. Can inotify inform Emacs that its notices won't be reliable (so Emacs can decide to use polling instead)? > inotify and friends are only spying on local VFS traffic, which in my > experience makes them less than useful for most applications. Aren't they used by most GUI file managers? Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 18:22 ` Stefan Monnier @ 2012-10-05 18:30 ` Óscar Fuentes 2012-10-06 16:39 ` Nix 1 sibling, 0 replies; 125+ messages in thread From: Óscar Fuentes @ 2012-10-05 18:30 UTC (permalink / raw) To: emacs-devel Stefan Monnier <monnier@IRO.UMontreal.CA> writes: >> inotify and friends are only spying on local VFS traffic, which in my >> experience makes them less than useful for most applications. > > Aren't they used by most GUI file managers? For ages, on my Kubuntu machines the usual file managers (Dolphin & Konqueror) usually fail to notice changes on directories (most annoyingly, file creation). Dunno if this is a KDE problem or something about the underlying mechanism. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 18:22 ` Stefan Monnier 2012-10-05 18:30 ` Óscar Fuentes @ 2012-10-06 16:39 ` Nix 2012-10-06 17:01 ` Stefan Monnier 1 sibling, 1 reply; 125+ messages in thread From: Nix @ 2012-10-06 16:39 UTC (permalink / raw) To: Stefan Monnier; +Cc: Óscar Fuentes, emacs-devel On 5 Oct 2012, Stefan Monnier verbalised: >> Of course you can't rely on inotify rather than polling, because inotify >> simply silently omits all changes that come from other hosts when an fs >> is mounted or exported over the network. > > Of course, it also doesn't help when Emacs is built without inotify. > The main questions would then be: > - how often does it work? I think this is likely to be "often" since it > should work for most/all the "home desktop" as well as laptop use > cases. Most, but certainly not all: I have a home desktop, but it so happens that I have my home directory on a separate low-power home server so that I can shut the desktop down when not in use. If I run Emacs on the desktop machine, it's not going to see inotify watches on my home directory -- or, rather, it'll see those performed by the desktop, but not those performed by the server (e.g. email delivery). > - can we reliably determine whether it will work? No :( you get that subset of events that happened on the local machine only, but even I ran Emacs on the server, it would see a local filesystem but would *still* miss events -- those happening on the desktop. > IIUC inotify works reliably for local file-systems (even if they're > exported because the other hosts's actions will be locally performed > by the nfsd) Those don't always appear :( the nfsd doesn't do all its actions at a level that inotify can see, alas, and certainly won't necessarily generate the expected events for them (a touch shows up as an attrib event locally, but an open if it's something the nfsd has done on behalf of a remote client (!).) > but not for file-systems mounted from a remote host. > Can inotify inform Emacs that its notices won't be reliable (so Emacs > can decide to use polling instead)? I don't think so. The answer in any case is 'inotify is never reliable': even if the FS is not exported across the network and never becomes exportted across the network, the queue can fill up and lose events and the like. I really really wish notify worked over the network :( >> inotify and friends are only spying on local VFS traffic, which in my >> experience makes them less than useful for most applications. > > Aren't they used by most GUI file managers? Yes. If you have an NFS-mounted home directory, you get used to hitting refresh in GUI file managers :( inotify basically sucks for these reasons and others, but the kernel people say that they don't care if it doesn't work over the network and that it can't be made to work anyway, and the desktop people who make use of inotify say that nobody uses NFS and everyone just has a single laptop and your use case is out of scope, go away. Meanwhile, Windows does file notification over the net perfectly well and has for years. :( -- NULL && (void) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 16:39 ` Nix @ 2012-10-06 17:01 ` Stefan Monnier 2012-10-06 18:51 ` Nix 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-10-06 17:01 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, emacs-devel >> - can we reliably determine whether it will work? > No :( you get that subset of events that happened on the local machine > only, but even I ran Emacs on the server, it would see a local > filesystem but would *still* miss events -- those happening on the > desktop. That's a misfeature that should be easy to fix (in the kernel): any FS should be able to indicate whether it will reliably send inotify notices, so that client code can be told when it requests inotify events for a particular object whether it will work reliably. >> IIUC inotify works reliably for local file-systems (even if they're >> exported because the other hosts's actions will be locally performed >> by the nfsd) > Those don't always appear :( the nfsd doesn't do all its actions at a > level that inotify can see, alas, If nfsd applies a modification and it's not reflected with some inotify event, I think that's a bug that the kernel developers would want to fix. > and certainly won't necessarily generate the expected events for them > (a touch shows up as an attrib event locally, but an open if it's > something the nfsd has done on behalf of a remote client (!).) Slight semantic differences are probably unavoidable, so I think this would likely be considered as something you need to live with. >> Can inotify inform Emacs that its notices won't be reliable (so Emacs >> can decide to use polling instead)? > I don't think so. The answer in any case is 'inotify is never reliable': > even if the FS is not exported across the network and never becomes > exportted across the network, the queue can fill up and lose events and > the like. Can't that be fixed by making it possible to "compress" the queue, e.g. replace a bunch of events on FILE with a single "something happened to FILE", or in the worst case replace all the events with a single "queue filled up, all the files might have been modified in arbitrary ways". This would let it be reliable even in the face of lost events. >> Aren't they used by most GUI file managers? > Yes. If you have an NFS-mounted home directory, you get used to hitting > refresh in GUI file managers :( So there's nothing for Emacs to worry about, because Emacs won't fix those issues. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 17:01 ` Stefan Monnier @ 2012-10-06 18:51 ` Nix 2012-10-06 21:26 ` Stefan Monnier 2012-10-14 15:52 ` Jim Meyering 0 siblings, 2 replies; 125+ messages in thread From: Nix @ 2012-10-06 18:51 UTC (permalink / raw) To: Stefan Monnier; +Cc: Óscar Fuentes, emacs-devel On 6 Oct 2012, Stefan Monnier stated: >>> - can we reliably determine whether it will work? >> No :( you get that subset of events that happened on the local machine >> only, but even I ran Emacs on the server, it would see a local >> filesystem but would *still* miss events -- those happening on the >> desktop. > > That's a misfeature that should be easy to fix (in the kernel): any FS > should be able to indicate whether it will reliably send > inotify notices, so that client code can be told when it requests > inotify events for a particular object whether it will work reliably. Unfortunately not, because e.g. a perfectly normal local ext4 filesystem can be unreliable if it is NFS-exported -- worse, it can be *partially* unreliable if only a subtree is exported, and it can flip between reliable and potentially unreliable on the fly as you export or unexport filesystems. >>> IIUC inotify works reliably for local file-systems (even if they're >>> exported because the other hosts's actions will be locally performed >>> by the nfsd) >> Those don't always appear :( the nfsd doesn't do all its actions at a >> level that inotify can see, alas, > > If nfsd applies a modification and it's not reflected with some inotify > event, I think that's a bug that the kernel developers would want to fix. They don't care. If an event doesn't come through the normal VFS layers, inotify won't see it, and that's that. >>> Can inotify inform Emacs that its notices won't be reliable (so Emacs >>> can decide to use polling instead)? >> I don't think so. The answer in any case is 'inotify is never reliable': >> even if the FS is not exported across the network and never becomes >> exportted across the network, the queue can fill up and lose events and >> the like. > > Can't that be fixed by making it possible to "compress" the queue, > e.g. replace a bunch of events on FILE with a single "something happened > to FILE", or in the worst case replace all the events with a single > "queue filled up, all the files might have been modified in arbitrary > ways". Theoretically, yes, only that would require you to look at previously queued events, which soon gets you into locking horrors. (As a first approximation, *anything* to do with the filesystem soon gets you into locking horrors.) > This would let it be reliable even in the face of lost events. > >>> Aren't they used by most GUI file managers? >> Yes. If you have an NFS-mounted home directory, you get used to hitting >> refresh in GUI file managers :( > > So there's nothing for Emacs to worry about, because Emacs won't fix > those issues. Oh yes, indeed. I'm just arguing against ripping out polling and replacing it with inotify, really: we need at least a customization knob for people who know their filesystems are NFS-exported or otherwise network-affected to tweak to say 'poll, please'. -- NULL && (void) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 18:51 ` Nix @ 2012-10-06 21:26 ` Stefan Monnier 2012-10-06 21:28 ` Nix 2012-10-14 15:52 ` Jim Meyering 1 sibling, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-10-06 21:26 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, emacs-devel >> If nfsd applies a modification and it's not reflected with some inotify >> event, I think that's a bug that the kernel developers would want to fix. > They don't care. If an event doesn't come through the normal VFS layers, > inotify won't see it, and that's that. But that's a bug in nfsd, still. > Oh yes, indeed. I'm just arguing against ripping out polling and > replacing it with inotify, really: I have no intention to rip out polling. Rather I hope that the higher-level API we come up with can be implemented by any of Windows's low-level API, inotify, MacOSX's equvalent, or polling. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 21:26 ` Stefan Monnier @ 2012-10-06 21:28 ` Nix 2012-10-07 7:38 ` Achim Gratz 2012-10-07 8:24 ` Stephen J. Turnbull 0 siblings, 2 replies; 125+ messages in thread From: Nix @ 2012-10-06 21:28 UTC (permalink / raw) To: Stefan Monnier; +Cc: Óscar Fuentes, emacs-devel On 6 Oct 2012, Stefan Monnier uttered the following: >>> If nfsd applies a modification and it's not reflected with some inotify >>> event, I think that's a bug that the kernel developers would want to fix. >> They don't care. If an event doesn't come through the normal VFS layers, >> inotify won't see it, and that's that. > > But that's a bug in nfsd, still. Well, yes, I think so, and you think so, but the Linux kernel hackers do not think so :( from userspace via the VFS: other activity might >> Oh yes, indeed. I'm just arguing against ripping out polling and >> replacing it with inotify, really: > > I have no intention to rip out polling. Rather I hope that the > higher-level API we come up with can be implemented by any of Windows's > low-level API, inotify, MacOSX's equvalent, or polling. ... which is what I hoped to hear. I've seen several projects rip out polling because inotify can do anything, and end up with something that worked worse for NFS users than what they had before. (NFSv4 could in theory implement something like inotify, but for earlier versions, polling is really all you could hope to do. Well, that or have some parallel daemon inotifying on the clients and informing the server of inotify activity, and vice versa...) -- NULL && (void) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 21:28 ` Nix @ 2012-10-07 7:38 ` Achim Gratz 2012-10-07 13:58 ` Stefan Monnier 2012-10-07 8:24 ` Stephen J. Turnbull 1 sibling, 1 reply; 125+ messages in thread From: Achim Gratz @ 2012-10-07 7:38 UTC (permalink / raw) To: emacs-devel Nix writes: >> But that's a bug in nfsd, still. > > Well, yes, I think so, and you think so, but the Linux kernel hackers do > not think so :( This behaviour doesn't have anything to do with nfsd (other than the stateless nature of NFS and the way it implements that) and you can trigger it quite easily without nfsd involved. Regards, Achim. -- +<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+ SD adaptation for Waldorf rackAttack V1.04R1: http://Synth.Stromeko.net/Downloads.html#WaldorfSDada ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 7:38 ` Achim Gratz @ 2012-10-07 13:58 ` Stefan Monnier 2012-10-07 14:54 ` Achim Gratz 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-10-07 13:58 UTC (permalink / raw) To: Achim Gratz; +Cc: emacs-devel >>> But that's a bug in nfsd, still. >> Well, yes, I think so, and you think so, but the Linux kernel hackers do >> not think so :( > This behaviour doesn't have anything to do with nfsd (other than the > stateless nature of NFS and the way it implements that) and you can > trigger it quite easily without nfsd involved. Which behavior? Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 13:58 ` Stefan Monnier @ 2012-10-07 14:54 ` Achim Gratz 0 siblings, 0 replies; 125+ messages in thread From: Achim Gratz @ 2012-10-07 14:54 UTC (permalink / raw) To: emacs-devel Stefan Monnier writes: >>>> But that's a bug in nfsd, still. >>> Well, yes, I think so, and you think so, but the Linux kernel hackers do >>> not think so :( >> This behaviour doesn't have anything to do with nfsd (other than the >> stateless nature of NFS and the way it implements that) and you can >> trigger it quite easily without nfsd involved. > > Which behavior? The nfsd is made stateless by using hidden hardlinks to the files it uses and then uses their inode numbers to manipulate these files (all IIRC). Inotify watches files based on their names and maps that to inodes using a cache, which can be out of sync or even drop the association between inode and filename. Any further operation on that file will then not be seen by inotify unless you re-create the watch. It works somewhat more reliably if you monitor individual files rather than directories, but that isn't foolproof either. Regards Achim. -- +<[Q+ Matrix-12 WAVE#46+305 Neuron microQkb Andromeda XTk Blofeld]>+ Wavetables for the Terratec KOMPLEXER: http://Synth.Stromeko.net/Downloads.html#KomplexerWaves ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 21:28 ` Nix 2012-10-07 7:38 ` Achim Gratz @ 2012-10-07 8:24 ` Stephen J. Turnbull 2012-10-07 14:00 ` Stefan Monnier 1 sibling, 1 reply; 125+ messages in thread From: Stephen J. Turnbull @ 2012-10-07 8:24 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, Stefan Monnier, emacs-devel Nix writes: > On 6 Oct 2012, Stefan Monnier uttered the following: > > I have no intention to rip out polling. Rather I hope that the > > higher-level API we come up with can be implemented by any of Windows's > > low-level API, inotify, MacOSX's equvalent, or polling. > > ... which is what I hoped to hear. Plus combinations of the above, as needed. Right? ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 8:24 ` Stephen J. Turnbull @ 2012-10-07 14:00 ` Stefan Monnier 2012-10-07 14:28 ` Óscar Fuentes 2012-10-08 7:07 ` Stephen J. Turnbull 0 siblings, 2 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-07 14:00 UTC (permalink / raw) To: Stephen J. Turnbull; +Cc: Nix, Óscar Fuentes, emacs-devel >> > I have no intention to rip out polling. Rather I hope that the >> > higher-level API we come up with can be implemented by any of Windows's >> > low-level API, inotify, MacOSX's equvalent, or polling. >> ... which is what I hoped to hear. > Plus combinations of the above, as needed. Right? If someone bothers to implement it, why not, tho I'm not sure how important that would be (unless that patch can automatically figure out when to use which). Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 14:00 ` Stefan Monnier @ 2012-10-07 14:28 ` Óscar Fuentes 2012-10-07 14:38 ` Stefan Monnier 2012-10-08 7:07 ` Stephen J. Turnbull 1 sibling, 1 reply; 125+ messages in thread From: Óscar Fuentes @ 2012-10-07 14:28 UTC (permalink / raw) To: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >>> > I have no intention to rip out polling. Rather I hope that the >>> > higher-level API we come up with can be implemented by any of Windows's >>> > low-level API, inotify, MacOSX's equvalent, or polling. >>> ... which is what I hoped to hear. >> Plus combinations of the above, as needed. Right? > > If someone bothers to implement it, why not, tho I'm not sure how > important that would be (unless that patch can automatically figure out > when to use which). In theory, it is possible to automatically figure out when to use polling and when to use the OS notification features. But if inotify doesn't work on some filesystems, we need a method for detecting those filesystems. Does inotify report "I don't work on this" when it is used on those unsupported filesystems? Eli and I discussed the high level Lisp API for filesystem notifications. I think it is compatible with polling, in the sense that it can transparently fall back to polling, once the issue mentioned on the previous paragraph is addressed. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 14:28 ` Óscar Fuentes @ 2012-10-07 14:38 ` Stefan Monnier 0 siblings, 0 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-07 14:38 UTC (permalink / raw) To: Óscar Fuentes; +Cc: emacs-devel > Eli and I discussed the high level Lisp API for filesystem > notifications. I think it is compatible with polling, in the sense that > it can transparently fall back to polling, once the issue mentioned on > the previous paragraph is addressed. AFAIK inotify does not provide the needed info, so for now the choice should be based on a global variable. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 14:00 ` Stefan Monnier 2012-10-07 14:28 ` Óscar Fuentes @ 2012-10-08 7:07 ` Stephen J. Turnbull 2012-10-08 8:06 ` Eli Zaretskii 1 sibling, 1 reply; 125+ messages in thread From: Stephen J. Turnbull @ 2012-10-08 7:07 UTC (permalink / raw) To: Stefan Monnier; +Cc: Nix, Óscar Fuentes, emacs-devel Stefan Monnier writes: > >> > I have no intention to rip out polling. Rather I hope that the > >> > higher-level API we come up with can be implemented by any of Windows's > >> > low-level API, inotify, MacOSX's equvalent, or polling. > >> ... which is what I hoped to hear. > > Plus combinations of the above, as needed. Right? > > If someone bothers to implement it, why not, tho I'm not sure how > important that would be According to nix, inotify is unreliable, end of discussion. If so, it may make sense to back up inotify with polling, although at a greatly reduced rate. Also, files may get moved across filesystems which support different mechanisms, etc, etc. So allowing different files to get notifications in different ways, and perhaps even changing backends on the fly may be useful/necessary for maximum reliability. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-08 7:07 ` Stephen J. Turnbull @ 2012-10-08 8:06 ` Eli Zaretskii 0 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-10-08 8:06 UTC (permalink / raw) To: Stephen J. Turnbull; +Cc: nix, ofv, monnier, emacs-devel > From: "Stephen J. Turnbull" <stephen@xemacs.org> > Date: Mon, 08 Oct 2012 16:07:18 +0900 > Cc: Nix <nix@esperi.org.uk>, Óscar Fuentes <ofv@wanadoo.es>, > emacs-devel@gnu.org > > According to nix, inotify is unreliable, end of discussion. > > If so, it may make sense to back up inotify with polling, although at > a greatly reduced rate. Also, files may get moved across filesystems > which support different mechanisms, etc, etc. So allowing different > files to get notifications in different ways, and perhaps even > changing backends on the fly may be useful/necessary for maximum > reliability. If one wants a 100% reliability, polling cannot be done at slower rates without degrading response times. Some applications might not like the long response times. And of course, you won't know when the notifications are less reliable than you would like to, so selectively increasing the polling rate in those problematic cases seems impossible. Moreover, both inotify and the equivalent Windows APIs are documented to lose notifications on a busy filesystem, so even a perfectly local file/directory cannot be watched with 100% reliability. And that is even before we think about the effects of the internal Emacs mechanisms of handling these events. E.g., if there's a lot of input from the keyboard and the window system, and if some heavy Lisp is running, it is quite possible to have the file-notification event be stuck in limbo for some time, before Emacs examines it and takes action. IOW, this will work well "most of the time", but that's about it. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 18:51 ` Nix 2012-10-06 21:26 ` Stefan Monnier @ 2012-10-14 15:52 ` Jim Meyering 1 sibling, 0 replies; 125+ messages in thread From: Jim Meyering @ 2012-10-14 15:52 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, Stefan Monnier, emacs-devel Nix wrote: ... > Oh yes, indeed. I'm just arguing against ripping out polling and > replacing it with inotify, really: we need at least a customization knob > for people who know their filesystems are NFS-exported or otherwise > network-affected to tweak to say 'poll, please'. GNU tail's -f support has addressed precisely this problem by distinguishing between file system types for which inotify is known to work and those for which it does not work reliably. It uses inotify only for a file system on which it is known to work, polls for others, and warns about if the file system type is not known. To see the list, look at coreutils/src/stat.c's S_MAGIC_* case statements. The ones with "local" in the comment work with inotify, the ones with admittedly poorly named "remote" in the comment require that tail -f support use polling. The only problem is that the list is a moving target. New file system types arise regularly. To give you an idea of the pace, there have been two additions since the latest release, which was less than two months ago. Here's the NEWS entry for those: stat and tail know about ZFS, VZFS and VMHGFS. stat -f --format=%T now reports the file system type, and tail -f now uses inotify for files on ZFS and VZFS file systems, rather than the default (for unknown file system types) of issuing a warning and reverting to polling. tail -f still uses polling for files on VMHGFS file systems. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-05 16:55 ` Nix 2012-10-05 17:15 ` Eli Zaretskii 2012-10-05 18:22 ` Stefan Monnier @ 2012-10-06 7:04 ` Stephen J. Turnbull 2012-10-06 7:23 ` Andreas Schwab 2 siblings, 1 reply; 125+ messages in thread From: Stephen J. Turnbull @ 2012-10-06 7:04 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, Stefan Monnier, emacs-devel Nix writes: > [Only capable of] spying on local VFS traffic, which in my > experience makes them less than useful for most applications. These days, though (as I am very tired of hearing but seems to be true) most people who use Emacs don't mount remote filesystems. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 7:04 ` Stephen J. Turnbull @ 2012-10-06 7:23 ` Andreas Schwab 2012-10-06 16:41 ` Nix 0 siblings, 1 reply; 125+ messages in thread From: Andreas Schwab @ 2012-10-06 7:23 UTC (permalink / raw) To: Stephen J. Turnbull; +Cc: Nix, Óscar Fuentes, Stefan Monnier, emacs-devel "Stephen J. Turnbull" <stephen@xemacs.org> writes: > These days, though (as I am very tired of hearing but seems to be > true) most people who use Emacs don't mount remote filesystems. I think NAS boxes are still common. Andreas. -- Andreas Schwab, schwab@linux-m68k.org GPG Key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 7:23 ` Andreas Schwab @ 2012-10-06 16:41 ` Nix 2012-10-07 8:09 ` Stephen J. Turnbull 0 siblings, 1 reply; 125+ messages in thread From: Nix @ 2012-10-06 16:41 UTC (permalink / raw) To: Andreas Schwab Cc: Óscar Fuentes, Stephen J. Turnbull, Stefan Monnier, emacs-devel On 6 Oct 2012, Andreas Schwab verbalised: > "Stephen J. Turnbull" <stephen@xemacs.org> writes: > >> These days, though (as I am very tired of hearing but seems to be >> true) most people who use Emacs don't mount remote filesystems. > > I think NAS boxes are still common. If anything they are more common than they used to be in the large parts of the world where the cost of power is shooting up, not going down: NASes often use quite a lot less power than a desktop with the same number of disks would, and much less than a headless server with the same number of disks in addition to the desktop. (Though I actually do have a headless server with a disk farm, but I know that is pretty unusual for a home user.) -- NULL && (void) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-06 16:41 ` Nix @ 2012-10-07 8:09 ` Stephen J. Turnbull 2012-10-07 14:08 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Stephen J. Turnbull @ 2012-10-07 8:09 UTC (permalink / raw) To: Nix; +Cc: Óscar Fuentes, Andreas Schwab, Stefan Monnier, emacs-devel Nix writes: > If anything they are more common than they used to be in the large > parts of the world where the cost of power is shooting up, not > going down: Makes sense. Unlike the IT and energy behavior of my host country, unfortunately. :-P Sorry for spreading disinformation (I got it from the same people you did, anyway. ;-) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-07 8:09 ` Stephen J. Turnbull @ 2012-10-07 14:08 ` Stefan Monnier 0 siblings, 0 replies; 125+ messages in thread From: Stefan Monnier @ 2012-10-07 14:08 UTC (permalink / raw) To: Stephen J. Turnbull; +Cc: Nix, Óscar Fuentes, Andreas Schwab, emacs-devel >> If anything they are more common than they used to be in the large >> parts of the world where the cost of power is shooting up, not >> going down: > Makes sense. Unlike the IT and energy behavior of my host country, > unfortunately. :-P Sorry for spreading disinformation (I got it from > the same people you did, anyway. ;-) BTW, I do have a low-power home-server with a large disk, so that my desktop can sleep to save energy. But I don't really access it via NFS. Basically, I stopped sharing my home directory via NFS years ago, replacing it with a VCS with a remote repository. So other than collections of large files (typically multimedia), the increase in disk sizes has made NFS-sharing much less important for me. OTOH, if you know of a good file-system that can provide a model half-way between NFS and "VCS + remote repository", so that I get the best of both worlds (i.e. the transparent synchronization of NFS, along with the reliability and disconnected operation of VCS), I'd love to know about it. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-02 23:46 ` Óscar Fuentes 2012-10-03 2:10 ` Stefan Monnier @ 2012-10-03 3:57 ` Eli Zaretskii 1 sibling, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-10-03 3:57 UTC (permalink / raw) To: Óscar Fuentes; +Cc: emacs-devel > From: Óscar Fuentes <ofv@wanadoo.es> > Date: Wed, 03 Oct 2012 01:46:33 +0200 > > Eli Zaretskii <eliz@gnu.org> writes: > > > Btw, what are Emacs use cases for using this kind of feature? > > Apart from those mentioned on the original message (dired or magit's (or > vc-dir) status view) I'll like to mention auto-revert-mode. All those need either a single file to be watched or don't need any details about what exactly changed (dired). That's a far cry from what's being proposed. > You are leaving this case to extremes. Emacs already allows you to do > very silly things. Let's not throw out useful features simply because > they might be abused. I didn't suggest to throw away anything. I suggested to think about the API we provide, so that it could be simpler and safer. Abuse has nothing to do with naivette. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld 2012-10-01 16:27 ` Stefan Monnier 2012-10-02 21:28 ` Eli Zaretskii @ 2012-12-02 20:08 ` Eli Zaretskii 2012-12-03 17:18 ` Stefan Monnier 2012-12-10 11:52 ` Eli Zaretskii 3 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-12-02 20:08 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel, monnier, sdl.web > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Mon, 01 Oct 2012 16:09:55 +0200 > Cc: Leo <sdl.web@gmail.com>, emacs-devel@gnu.org > > On Monday 01 October 2012 00:38:09 Stefan Monnier wrote: > > If there's a good chance this won't work without breaking compatibility, > > maybe a better option is to provide a low-level API that maps very > > closely to inotify and then an Elisp layer on top which abstracts away > > differences between different systems. In that case we can install the > > inotify support right away while we're still experimenting with the > > higher-level abstraction. > > That's probably the best approach here. I changed the patch to provide a low > level inotify interface. However I did not provide an inotify_init(2) like > function and instead initialization and closing of the inotify handle is done > internally. I don't think that this should be exposed to elisp even in the > low level interface. > > > But if they're unlikely to be important in practice, then > > I guess the current solution might be acceptable. > > I think we are safe. I added that `equal' should be used to compare cookies. > So we can easily change it without breaking the API. > > > I think the cleaner option is to define a new object type for it. > > It could be either a new Lisp_Misc type, so you can make them print as > > something like "#<file-watcher NNN>" (take a look at "enum > > Lisp_Misc_Type" and "union Lisp_Misc" in src/lisp.h for starters; adding > > a new type will require adding corresponding branches to the switch > > statements in alloc.c and in print.c). > > That sounds like the best option. I haven't implemented it yet. Is it > possible to make the Lisp_Misc_Type comparable with `equal'? Because the > watch-descriptor has to be comparable. Any news on this? The corresponding w32 implementation collects dust in my local branch, waiting for the inotify based implementation to be committed. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-02 20:08 ` Eli Zaretskii @ 2012-12-03 17:18 ` Stefan Monnier 2012-12-10 14:16 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2012-12-03 17:18 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Rüdiger Sonderfeld, sdl.web, emacs-devel > Any news on this? The corresponding w32 implementation collects dust > in my local branch, waiting for the inotify based implementation to be > committed. Please go ahead and install both in trunk, Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-03 17:18 ` Stefan Monnier @ 2012-12-10 14:16 ` Eli Zaretskii 2012-12-10 14:47 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-12-10 14:16 UTC (permalink / raw) To: Stefan Monnier; +Cc: ruediger, sdl.web, emacs-devel > From: Stefan Monnier <monnier@IRO.UMontreal.CA> > Cc: Rüdiger Sonderfeld <ruediger@c-plusplus.de>, > emacs-devel@gnu.org, sdl.web@gmail.com > Date: Mon, 03 Dec 2012 12:18:50 -0500 > > > Any news on this? The corresponding w32 implementation collects dust > > in my local branch, waiting for the inotify based implementation to be > > committed. > > Please go ahead and install both in trunk, Done. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-10 14:16 ` Eli Zaretskii @ 2012-12-10 14:47 ` Stefan Monnier 0 siblings, 0 replies; 125+ messages in thread From: Stefan Monnier @ 2012-12-10 14:47 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, sdl.web, emacs-devel >> > Any news on this? The corresponding w32 implementation collects dust >> > in my local branch, waiting for the inotify based implementation to be >> > committed. >> Please go ahead and install both in trunk, Thank you very much, Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld ` (2 preceding siblings ...) 2012-12-02 20:08 ` Eli Zaretskii @ 2012-12-10 11:52 ` Eli Zaretskii 2012-12-10 12:11 ` Rüdiger Sonderfeld 2012-12-11 7:43 ` Michael Albinus 3 siblings, 2 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-12-10 11:52 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: emacs-devel, monnier, sdl.web > From: Rüdiger Sonderfeld <ruediger@c-plusplus.de> > Date: Mon, 01 Oct 2012 16:09:55 +0200 > Cc: Leo <sdl.web@gmail.com>, emacs-devel@gnu.org > > On Monday 01 October 2012 00:38:09 Stefan Monnier wrote: > > If there's a good chance this won't work without breaking compatibility, > > maybe a better option is to provide a low-level API that maps very > > closely to inotify and then an Elisp layer on top which abstracts away > > differences between different systems. In that case we can install the > > inotify support right away while we're still experimenting with the > > higher-level abstraction. > > That's probably the best approach here. I changed the patch to provide a low > level inotify interface. However I did not provide an inotify_init(2) like > function and instead initialization and closing of the inotify handle is done > internally. I don't think that this should be exposed to elisp even in the > low level interface. > > > But if they're unlikely to be important in practice, then > > I guess the current solution might be acceptable. > > I think we are safe. I added that `equal' should be used to compare cookies. > So we can easily change it without breaking the API. Committed (with necessary fixes and additions, like ChangeLog entries and NEWS) as trunk revision 111171. Thanks. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-10 11:52 ` Eli Zaretskii @ 2012-12-10 12:11 ` Rüdiger Sonderfeld 2012-12-11 7:43 ` Michael Albinus 1 sibling, 0 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2012-12-10 12:11 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel, monnier, sdl.web Hello, On Monday 10 December 2012 13:52:48 Eli Zaretskii wrote: > Committed (with necessary fixes and additions, like ChangeLog entries > and NEWS) as trunk revision 111171. > > Thanks. Great news! Thanks. I'm currently very busy. But I'll try to find some time between Christmas and New Year to look into the existing issues. Regards, Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-10 11:52 ` Eli Zaretskii 2012-12-10 12:11 ` Rüdiger Sonderfeld @ 2012-12-11 7:43 ` Michael Albinus 2012-12-11 8:20 ` Eli Zaretskii 1 sibling, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 7:43 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Rüdiger Sonderfeld, sdl.web, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > Committed (with necessary fixes and additions, like ChangeLog entries > and NEWS) as trunk revision 111171. I've played a little bit with this. Looks, like not all events are handled by the callback. Testcase: In a shell, a have applied $ inotifywait -mq /tmp/123 In another shell, I have started Emacs $ emacs --eval "(inotify-add-watch \"/tmp/123\" t (lambda (&rest rest) (message \"callback %s\" rest)))" Then I have modified&saved /tmp/123. In the first shell, I see /tmp/123 MODIFY /tmp/123 OPEN /tmp/123 MODIFY /tmp/123 CLOSE_WRITE,CLOSE But in Emacs' *Messages* buffer, there's only callback ((1 (modify) 0 nil)) callback ((1 (close-write) 0 nil)) What do I miss? > Thanks. Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-11 7:43 ` Michael Albinus @ 2012-12-11 8:20 ` Eli Zaretskii 2012-12-11 16:36 ` Michael Albinus 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 8:20 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, sdl.web, monnier, emacs-devel > From: Michael Albinus <michael.albinus@gmx.de> > Cc: Rüdiger Sonderfeld <ruediger@c-plusplus.de>, > emacs-devel@gnu.org, monnier@iro.umontreal.ca, sdl.web@gmail.com > Date: Tue, 11 Dec 2012 08:43:35 +0100 > > Eli Zaretskii <eliz@gnu.org> writes: > > > Committed (with necessary fixes and additions, like ChangeLog entries > > and NEWS) as trunk revision 111171. > > I've played a little bit with this. Looks, like not all events are > handled by the callback. Testcase: > > In a shell, a have applied > > $ inotifywait -mq /tmp/123 > > In another shell, I have started Emacs > > $ emacs --eval "(inotify-add-watch \"/tmp/123\" t (lambda (&rest rest) (message \"callback %s\" rest)))" > > Then I have modified&saved /tmp/123. In the first shell, I see > > /tmp/123 MODIFY > /tmp/123 OPEN > /tmp/123 MODIFY > /tmp/123 CLOSE_WRITE,CLOSE > > But in Emacs' *Messages* buffer, there's only > > callback ((1 (modify) 0 nil)) > callback ((1 (close-write) 0 nil)) > > What do I miss? No idea. Perhaps Rüdiger could help. Or step with a debugger through the code and see what's going on. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-11 8:20 ` Eli Zaretskii @ 2012-12-11 16:36 ` Michael Albinus 2012-12-11 16:46 ` Rüdiger Sonderfeld 0 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 16:36 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, emacs-devel, sdl.web, monnier Eli Zaretskii <eliz@gnu.org> writes: >> I've played a little bit with this. Looks, like not all events are >> handled by the callback. Testcase: >> >> In a shell, a have applied >> >> $ inotifywait -mq /tmp/123 >> >> In another shell, I have started Emacs >> >> $ emacs --eval "(inotify-add-watch \"/tmp/123\" t (lambda (&rest >> rest) (message \"callback %s\" rest)))" >> >> Then I have modified&saved /tmp/123. In the first shell, I see >> >> /tmp/123 MODIFY >> /tmp/123 OPEN >> /tmp/123 MODIFY >> /tmp/123 CLOSE_WRITE,CLOSE >> >> But in Emacs' *Messages* buffer, there's only >> >> callback ((1 (modify) 0 nil)) >> callback ((1 (close-write) 0 nil)) >> >> What do I miss? > > No idea. Perhaps Rüdiger could help. Or step with a debugger through > the code and see what's going on. I've fixed this in the trunk. There is still something not clear to me: An inotify event /tmp/123 CLOSE_WRITE,CLOSE is transformed into an Emacs event (file-notify (1 (close-write) 0 nil) callback) Is there a reason, that CLOSE (and also MOVE) are not handled in mask_to_aspects? Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-11 16:36 ` Michael Albinus @ 2012-12-11 16:46 ` Rüdiger Sonderfeld 2012-12-11 20:17 ` Michael Albinus 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2012-12-11 16:46 UTC (permalink / raw) To: Michael Albinus; +Cc: Eli Zaretskii, emacs-devel, sdl.web, monnier On Tuesday 11 December 2012 17:36:41 Michael Albinus wrote: > I've fixed this in the trunk. Thanks! > There is still something not clear to me: An inotify event > > /tmp/123 CLOSE_WRITE,CLOSE > > is transformed into an Emacs event > > (file-notify (1 (close-write) 0 nil) callback) > > Is there a reason, that CLOSE (and also MOVE) are not handled in > mask_to_aspects? IN_CLOSE and IN_MOVE are not separate events but convenience macros. From the notify(7) manpage Two additional convenience macros are IN_MOVE, which equates to IN_MOVED_FROM|IN_MOVED_TO, and IN_CLOSE, which equates to IN_CLOSE_WRITE| IN_CLOSE_NOWRITE. Regards, Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added inotify support. 2012-12-11 16:46 ` Rüdiger Sonderfeld @ 2012-12-11 20:17 ` Michael Albinus 0 siblings, 0 replies; 125+ messages in thread From: Michael Albinus @ 2012-12-11 20:17 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Eli Zaretskii, emacs-devel, sdl.web, monnier Rüdiger Sonderfeld <ruediger@c-plusplus.de> writes: >> Is there a reason, that CLOSE (and also MOVE) are not handled in >> mask_to_aspects? > > IN_CLOSE and IN_MOVE are not separate events but convenience macros. > > From the notify(7) manpage > > Two additional convenience macros are IN_MOVE, which equates to > IN_MOVED_FROM|IN_MOVED_TO, and IN_CLOSE, which equates to IN_CLOSE_WRITE| > IN_CLOSE_NOWRITE. I see. I will filter them out in the Trammp handler as well. Maybe you could precise it in the docstring of inotify-add-watch. Close, move and all-events are separated from the other aspects, but there is no reasoning why. And midterm it might be useful to describe the inotify mecahnism in the Elisp manual in more detail. > Regards, > Rüdiger Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-10-01 4:38 ` Stefan Monnier 2012-10-01 7:24 ` Eli Zaretskii 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld @ 2012-12-11 7:54 ` Michael Albinus 2012-12-11 8:12 ` Eli Zaretskii 2 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 7:54 UTC (permalink / raw) To: Stefan Monnier; +Cc: Rüdiger Sonderfeld, Leo, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > If there's a good chance this won't work without breaking compatibility, > maybe a better option is to provide a low-level API that maps very > closely to inotify and then an Elisp layer on top which abstracts away > differences between different systems. In that case we can install the > inotify support right away while we're still experimenting with the > higher-level abstraction. This has inspired me to check, whether we could extend inotify support for remote files, via Tramp. I know it might have performance issues, but I'm curious :-) A first shot to write a file handler for `inotify-add-watch' was quite easy. It works pretty well, and would need only some polishing before being installed. For `inotify-rm-watch' that's not possible right now. It takes as argument WATCH-DESCRIPTOR, which is not a file name, and which does not contain a file name. Could we extend the interface of `inotify-rm-watch' to add a file name? It would be sufficient already, if WATCH-DESCRIPTOR would contain a file name as first element of its structure. > Stefan Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 7:54 ` [PATCH] Added basic file system watching support Michael Albinus @ 2012-12-11 8:12 ` Eli Zaretskii 2012-12-11 8:19 ` Michael Albinus 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 8:12 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, emacs-devel, monnier, sdl.web > From: Michael Albinus <michael.albinus@gmx.de> > Date: Tue, 11 Dec 2012 08:54:54 +0100 > Cc: Rüdiger Sonderfeld <ruediger@c-plusplus.de>, > Leo <sdl.web@gmail.com>, emacs-devel@gnu.org > > For `inotify-rm-watch' that's not possible right now. It takes as > argument WATCH-DESCRIPTOR, which is not a file name, and which does not > contain a file name. > > Could we extend the interface of `inotify-rm-watch' to add a file name? Why do you need an extension? This is Lisp: we could pass the file name _as_ the descriptor. Since the remote file handler will take care of the call anyway, it will assign the correct meaning to the descriptor. Am I missing something? ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 8:12 ` Eli Zaretskii @ 2012-12-11 8:19 ` Michael Albinus 2012-12-11 8:29 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 8:19 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, emacs-devel, monnier, sdl.web Eli Zaretskii <eliz@gnu.org> writes: >> For `inotify-rm-watch' that's not possible right now. It takes as >> argument WATCH-DESCRIPTOR, which is not a file name, and which does not >> contain a file name. >> >> Could we extend the interface of `inotify-rm-watch' to add a file name? > > Why do you need an extension? This is Lisp: we could pass the file > name _as_ the descriptor. Since the remote file handler will take > care of the call anyway, it will assign the correct meaning to the > descriptor. Sure, that would be sufficient for Tramp. I had the impression, that a more complex structure as descriptor was chosen, because there could be several `inotify-add-watch' calls for the *same* file, being different in the aspect or the callback function. Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 8:19 ` Michael Albinus @ 2012-12-11 8:29 ` Eli Zaretskii 2012-12-11 8:44 ` Michael Albinus 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 8:29 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, emacs-devel, monnier, sdl.web > From: Michael Albinus <michael.albinus@gmx.de> > Cc: monnier@iro.umontreal.ca, ruediger@c-plusplus.de, sdl.web@gmail.com, emacs-devel@gnu.org > Date: Tue, 11 Dec 2012 09:19:10 +0100 > > >> Could we extend the interface of `inotify-rm-watch' to add a file name? > > > > Why do you need an extension? This is Lisp: we could pass the file > > name _as_ the descriptor. Since the remote file handler will take > > care of the call anyway, it will assign the correct meaning to the > > descriptor. > > Sure, that would be sufficient for Tramp. I had the impression, that a > more complex structure as descriptor was chosen, because there could be > several `inotify-add-watch' calls for the *same* file, being different > in the aspect or the callback function. Then Tramp could maintain its own data structure for watched files, and give each watch a unique identifier, like (FILENAME . NUMBER), or just NUMBER, or whatever. IOW, the DESCRIPTOR is an opaque data type, which the implementation back-end, and the back-end alone, can and should interpret. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 8:29 ` Eli Zaretskii @ 2012-12-11 8:44 ` Michael Albinus 2012-12-11 9:39 ` Eli Zaretskii 0 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 8:44 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, emacs-devel, monnier, sdl.web Eli Zaretskii <eliz@gnu.org> writes: > Then Tramp could maintain its own data structure for watched files, > and give each watch a unique identifier, like (FILENAME . NUMBER), or > just NUMBER, or whatever. That I do already in my inotify-add-watch handler. Of course. > IOW, the DESCRIPTOR is an opaque data type, which the implementation > back-end, and the back-end alone, can and should interpret. The point is, that something must trigger Tramp. In inotify-add-watch, I have added the following code (shortened, there's more): --8<---------------cut here---------------start------------->8--- /* If the file name has special constructs in it, call the corresponding file handler. */ handler = Ffind_file_name_handler (file_name, Qinotify_add_watch); if (!NILP (handler)) { return call4 (handler, Qinotify_add_watch, file_name, aspect, callback); } --8<---------------cut here---------------end--------------->8--- In inotify-rm-watch I couldn't add similar lines, because file_name is unknown. My proposal is either to add file_name as first argument of inotify-rm-watch, or to declare WATCH-DESCRIPTOR as a cons cell, which car is always the file name. Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 8:44 ` Michael Albinus @ 2012-12-11 9:39 ` Eli Zaretskii 2012-12-11 10:15 ` Eli Zaretskii ` (2 more replies) 0 siblings, 3 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 9:39 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, emacs-devel, monnier, sdl.web > From: Michael Albinus <michael.albinus@gmx.de> > Cc: monnier@iro.umontreal.ca, ruediger@c-plusplus.de, sdl.web@gmail.com, emacs-devel@gnu.org > Date: Tue, 11 Dec 2012 09:44:55 +0100 > > Eli Zaretskii <eliz@gnu.org> writes: > > > Then Tramp could maintain its own data structure for watched files, > > and give each watch a unique identifier, like (FILENAME . NUMBER), or > > just NUMBER, or whatever. > > That I do already in my inotify-add-watch handler. Of course. > > > IOW, the DESCRIPTOR is an opaque data type, which the implementation > > back-end, and the back-end alone, can and should interpret. > > The point is, that something must trigger Tramp. In inotify-add-watch, I > have added the following code (shortened, there's more): Sorry, I misunderstood the issue. See below. > --8<---------------cut here---------------start------------->8--- > /* If the file name has special constructs in it, > call the corresponding file handler. */ > handler = Ffind_file_name_handler (file_name, Qinotify_add_watch); > if (!NILP (handler)) > { > return call4 (handler, Qinotify_add_watch, file_name, aspect, > callback); > } > --8<---------------cut here---------------end--------------->8--- > > In inotify-rm-watch I couldn't add similar lines, because file_name is > unknown. My proposal is either to add file_name as first argument of > inotify-rm-watch, or to declare WATCH-DESCRIPTOR as a cons cell, which > car is always the file name. IMO, this design is wrong. Tramp is just one more back-end for this feature, in addition to two others: inotify and w32notify. So I think Tramp handlers should be called from a higher-level code, one that calls whichever back-end is appropriate. Otherwise, we will need to implement the Tramp support twice, in 2 different sets of primitives. Which, of course, goes back to the kind of design discussion I suggested to have at the time, where we were supposed to consider various alternatives and eventually agree on some higher-level APIs. Jumping to coding right away is IMO not the right way. E.g., currently there are subtle but very real differences between the 2 back-ends: w32notify doesn't accept t or a lone symbol as the 2nd argument (it insists on getting a list); the list of supported watch types is entirely different; and the w32 back-ends actually watches the entire directory of the file, not just that file. IOW, this feature is not really ready for Tramp-ization, or for user-land in general. Stefan wanted people to experiment with this and gather experience, before we know enough to discuss how to make it user- and Lisp-friendly. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 9:39 ` Eli Zaretskii @ 2012-12-11 10:15 ` Eli Zaretskii 2012-12-11 10:38 ` Michael Albinus 2012-12-11 10:24 ` Michael Albinus 2013-01-07 11:33 ` Michael Albinus 2 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 10:15 UTC (permalink / raw) To: michael.albinus; +Cc: ruediger, sdl.web, monnier, emacs-devel > Date: Tue, 11 Dec 2012 11:39:15 +0200 > From: Eli Zaretskii <eliz@gnu.org> > Cc: ruediger@c-plusplus.de, emacs-devel@gnu.org, monnier@iro.umontreal.ca, > sdl.web@gmail.com > > > --8<---------------cut here---------------start------------->8--- > > /* If the file name has special constructs in it, > > call the corresponding file handler. */ > > handler = Ffind_file_name_handler (file_name, Qinotify_add_watch); > > if (!NILP (handler)) > > { > > return call4 (handler, Qinotify_add_watch, file_name, aspect, > > callback); > > } > > --8<---------------cut here---------------end--------------->8--- > > > > In inotify-rm-watch I couldn't add similar lines, because file_name is > > unknown. My proposal is either to add file_name as first argument of > > inotify-rm-watch, or to declare WATCH-DESCRIPTOR as a cons cell, which > > car is always the file name. > > IMO, this design is wrong. Tramp is just one more back-end for this > feature, in addition to two others: inotify and w32notify. So I think > Tramp handlers should be called from a higher-level code, one that > calls whichever back-end is appropriate. Otherwise, we will need to > implement the Tramp support twice, in 2 different sets of primitives. Moreover, Tramp shouldn't use Qinotify_* symbols, but some (currently non-existent) platform-independent symbols, e.g. Qfile_notify_*. Otherwise, a Windows user will not be able to use this feature in conjunction with remote files residing on Posix hosts, because Qinotify_* are only defined when Emacs is built with local inotify support, which is impossible on Windows. Again, this calls for some design discussions. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 10:15 ` Eli Zaretskii @ 2012-12-11 10:38 ` Michael Albinus 0 siblings, 0 replies; 125+ messages in thread From: Michael Albinus @ 2012-12-11 10:38 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, sdl.web, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > Moreover, Tramp shouldn't use Qinotify_* symbols, but some (currently > non-existent) platform-independent symbols, e.g. Qfile_notify_*. > Otherwise, a Windows user will not be able to use this feature in > conjunction with remote files residing on Posix hosts, because > Qinotify_* are only defined when Emacs is built with local inotify > support, which is impossible on Windows. Sure. It's just a matter of the primitive function, Tramp is called from. Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 9:39 ` Eli Zaretskii 2012-12-11 10:15 ` Eli Zaretskii @ 2012-12-11 10:24 ` Michael Albinus 2012-12-11 12:51 ` Eli Zaretskii 2013-01-07 11:33 ` Michael Albinus 2 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 10:24 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, emacs-devel, monnier, sdl.web Eli Zaretskii <eliz@gnu.org> writes: >> In inotify-rm-watch I couldn't add similar lines, because file_name is >> unknown. My proposal is either to add file_name as first argument of >> inotify-rm-watch, or to declare WATCH-DESCRIPTOR as a cons cell, which >> car is always the file name. > > IMO, this design is wrong. Tramp is just one more back-end for this > feature, in addition to two others: inotify and w32notify. So I think > Tramp handlers should be called from a higher-level code, one that > calls whichever back-end is appropriate. Otherwise, we will need to > implement the Tramp support twice, in 2 different sets of primitives. Maybe. But this would require a common understanding of the API between all involved backends. Honestly, I don't plan Tramp handlers for w32notify-add-watch and w32notify-rm-watch. I wouldn't know how to implement, neither if the local host runs MS Windows, nor if the remote host runs MS Windows. At least this thread does not exist :-) For me, it would be sufficient to regard Tramp handlers as an extension to inotify-add-watch and inotify-rm-watch, and not as a third backend in parallel to two others. > IOW, this feature is not really ready for Tramp-ization, or for > user-land in general. Stefan wanted people to experiment with this > and gather experience, before we know enough to discuss how to make it > user- and Lisp-friendly. It was never planned for Tramp; as I said I'm curious. And we got what Stefan has asked for: "people to experiment with this and gather experience". Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 10:24 ` Michael Albinus @ 2012-12-11 12:51 ` Eli Zaretskii 2012-12-11 13:17 ` Michael Albinus 2012-12-12 1:09 ` Leo 0 siblings, 2 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 12:51 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, sdl.web, monnier, emacs-devel > From: Michael Albinus <michael.albinus@gmx.de> > Date: Tue, 11 Dec 2012 11:24:38 +0100 > Cc: ruediger@c-plusplus.de, emacs-devel@gnu.org, monnier@iro.umontreal.ca, > sdl.web@gmail.com > > Honestly, I don't plan Tramp handlers for w32notify-add-watch and > w32notify-rm-watch. I wouldn't know how to implement, neither if the > local host runs MS Windows, nor if the remote host runs MS Windows. At > least this thread does not exist :-) That was not my intent. The intent is to let Windows users use this feature for remote files residing on GNU/Linux hosts. > For me, it would be sufficient to regard Tramp handlers as an extension > to inotify-add-watch and inotify-rm-watch, and not as a third backend in > parallel to two others. But then inotify-*-watch primitives will have to be able to be compiled on platforms where HAVE_INOTIFY is not defined. Currently, this is impossible, so what you regard as sufficient would limit this feature to GNU/Linux users and remote files on other GNU/Linux systems. I don't think such a limitation is desirable. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 12:51 ` Eli Zaretskii @ 2012-12-11 13:17 ` Michael Albinus 2012-12-11 13:23 ` Eli Zaretskii 2012-12-12 1:09 ` Leo 1 sibling, 1 reply; 125+ messages in thread From: Michael Albinus @ 2012-12-11 13:17 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, sdl.web, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> For me, it would be sufficient to regard Tramp handlers as an extension >> to inotify-add-watch and inotify-rm-watch, and not as a third backend in >> parallel to two others. > > But then inotify-*-watch primitives will have to be able to be > compiled on platforms where HAVE_INOTIFY is not defined. Currently, > this is impossible, so what you regard as sufficient would limit this > feature to GNU/Linux users and remote files on other GNU/Linux > systems. I don't think such a limitation is desirable. I've implemented a quick shot what's possible just now. If we have a more general solution, I'm open. Your proposal with a unified API would do it, but it means we need to unify. Now we have seen in practice first problems. Since I'm not an inotify expert, I would let the proposal for such an API to somebody else. I would participate if it comes to practical details for Tramp implementation. If support for remote files is desired. Bes regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 13:17 ` Michael Albinus @ 2012-12-11 13:23 ` Eli Zaretskii 0 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-12-11 13:23 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, emacs-devel, sdl.web, monnier > From: Michael Albinus <michael.albinus@gmx.de> > Date: Tue, 11 Dec 2012 14:17:28 +0100 > Cc: ruediger@c-plusplus.de, sdl.web@gmail.com, monnier@iro.umontreal.ca, > emacs-devel@gnu.org > > Eli Zaretskii <eliz@gnu.org> writes: > > >> For me, it would be sufficient to regard Tramp handlers as an extension > >> to inotify-add-watch and inotify-rm-watch, and not as a third backend in > >> parallel to two others. > > > > But then inotify-*-watch primitives will have to be able to be > > compiled on platforms where HAVE_INOTIFY is not defined. Currently, > > this is impossible, so what you regard as sufficient would limit this > > feature to GNU/Linux users and remote files on other GNU/Linux > > systems. I don't think such a limitation is desirable. > > I've implemented a quick shot what's possible just now. I understand, and thank you for doing this. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 12:51 ` Eli Zaretskii 2012-12-11 13:17 ` Michael Albinus @ 2012-12-12 1:09 ` Leo 2012-12-12 3:57 ` Eli Zaretskii 1 sibling, 1 reply; 125+ messages in thread From: Leo @ 2012-12-12 1:09 UTC (permalink / raw) To: emacs-devel On 2012-12-11 20:51 +0800, Eli Zaretskii wrote: > That was not my intent. The intent is to let Windows users use this > feature for remote files residing on GNU/Linux hosts. Sorry to jump in here. I seem to remember the original patch made use of kqueue and worked on BSD systems too. What is happening there? Thanks. Leo ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-12 1:09 ` Leo @ 2012-12-12 3:57 ` Eli Zaretskii 0 siblings, 0 replies; 125+ messages in thread From: Eli Zaretskii @ 2012-12-12 3:57 UTC (permalink / raw) To: Leo; +Cc: emacs-devel > From: Leo <sdl.web@gmail.com> > Date: Wed, 12 Dec 2012 09:09:26 +0800 > > On 2012-12-11 20:51 +0800, Eli Zaretskii wrote: > > That was not my intent. The intent is to let Windows users use this > > feature for remote files residing on GNU/Linux hosts. > > Sorry to jump in here. I seem to remember the original patch made use of > kqueue and worked on BSD systems too. What is happening there? Thanks. You are talking about another patch and another contributor, IIRC. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2012-12-11 9:39 ` Eli Zaretskii 2012-12-11 10:15 ` Eli Zaretskii 2012-12-11 10:24 ` Michael Albinus @ 2013-01-07 11:33 ` Michael Albinus 2013-01-07 15:57 ` Eli Zaretskii 2 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2013-01-07 11:33 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, sdl.web, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > IMO, this design is wrong. Tramp is just one more back-end for this > feature, in addition to two others: inotify and w32notify. So I think > Tramp handlers should be called from a higher-level code, one that > calls whichever back-end is appropriate. Otherwise, we will need to > implement the Tramp support twice, in 2 different sets of primitives. > > Which, of course, goes back to the kind of design discussion I > suggested to have at the time, where we were supposed to consider > various alternatives and eventually agree on some higher-level APIs. > Jumping to coding right away is IMO not the right way. E.g., > currently there are subtle but very real differences between the 2 > back-ends: w32notify doesn't accept t or a lone symbol as the 2nd > argument (it insists on getting a list); the list of supported watch > types is entirely different; and the w32 back-ends actually watches > the entire directory of the file, not just that file. > > IOW, this feature is not really ready for Tramp-ization, or for > user-land in general. Stefan wanted people to experiment with this > and gather experience, before we know enough to discuss how to make it > user- and Lisp-friendly. Is there any progress on this? Any attempt of a unified interface? Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2013-01-07 11:33 ` Michael Albinus @ 2013-01-07 15:57 ` Eli Zaretskii 2013-01-07 16:00 ` Michael Albinus 0 siblings, 1 reply; 125+ messages in thread From: Eli Zaretskii @ 2013-01-07 15:57 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, sdl.web, monnier, emacs-devel > From: Michael Albinus <michael.albinus@gmx.de> > Cc: ruediger@c-plusplus.de, emacs-devel@gnu.org, monnier@iro.umontreal.ca, sdl.web@gmail.com > Date: Mon, 07 Jan 2013 12:33:26 +0100 > > Eli Zaretskii <eliz@gnu.org> writes: > > > IMO, this design is wrong. Tramp is just one more back-end for this > > feature, in addition to two others: inotify and w32notify. So I think > > Tramp handlers should be called from a higher-level code, one that > > calls whichever back-end is appropriate. Otherwise, we will need to > > implement the Tramp support twice, in 2 different sets of primitives. > > > > Which, of course, goes back to the kind of design discussion I > > suggested to have at the time, where we were supposed to consider > > various alternatives and eventually agree on some higher-level APIs. > > Jumping to coding right away is IMO not the right way. E.g., > > currently there are subtle but very real differences between the 2 > > back-ends: w32notify doesn't accept t or a lone symbol as the 2nd > > argument (it insists on getting a list); the list of supported watch > > types is entirely different; and the w32 back-ends actually watches > > the entire directory of the file, not just that file. > > > > IOW, this feature is not really ready for Tramp-ization, or for > > user-land in general. Stefan wanted people to experiment with this > > and gather experience, before we know enough to discuss how to make it > > user- and Lisp-friendly. > > Is there any progress on this? Any attempt of a unified interface? Not that I'm aware of, no. OTOH, I haven't seen any attempts to use the feature in any Lisp package, either. Maybe we just don't need it ;-) ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2013-01-07 15:57 ` Eli Zaretskii @ 2013-01-07 16:00 ` Michael Albinus 2013-01-07 20:26 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Michael Albinus @ 2013-01-07 16:00 UTC (permalink / raw) To: Eli Zaretskii; +Cc: ruediger, sdl.web, monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> Is there any progress on this? Any attempt of a unified interface? > > Not that I'm aware of, no. OTOH, I haven't seen any attempts to use > the feature in any Lisp package, either. Maybe we just don't need it ;-) It might be too early judging it. And people might wait for a stable API ... Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2013-01-07 16:00 ` Michael Albinus @ 2013-01-07 20:26 ` Stefan Monnier 2013-01-08 7:44 ` Michael Albinus 0 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2013-01-07 20:26 UTC (permalink / raw) To: Michael Albinus; +Cc: ruediger, Eli Zaretskii, sdl.web, emacs-devel >>> Is there any progress on this? Any attempt of a unified interface? >> Not that I'm aware of, no. OTOH, I haven't seen any attempts to use >> the feature in any Lisp package, either. Maybe we just don't need it ;-) > It might be too early judging it. And people might wait for a stable API ... I don't see how we can develop a good API without first seeing the feature in use. So someone should try and use the existing code for auto-revert and dired, to get a good idea of how it plays out. And then we can abstract out the differences into a sane API. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Added basic file system watching support. 2013-01-07 20:26 ` Stefan Monnier @ 2013-01-08 7:44 ` Michael Albinus 0 siblings, 0 replies; 125+ messages in thread From: Michael Albinus @ 2013-01-08 7:44 UTC (permalink / raw) To: Stefan Monnier; +Cc: ruediger, Eli Zaretskii, sdl.web, emacs-devel Stefan Monnier <monnier@IRO.UMontreal.CA> writes: >>>> Is there any progress on this? Any attempt of a unified interface? >>> Not that I'm aware of, no. OTOH, I haven't seen any attempts to use >>> the feature in any Lisp package, either. Maybe we just don't need it ;-) >> It might be too early judging it. And people might wait for a stable API ... > > I don't see how we can develop a good API without first seeing the > feature in use. Maybe we shall mark this change as experimental in NEWS then? And it could also be useful to install Tramp's `inotify-add-watch' handler? > So someone should try and use the existing code for auto-revert and > dired, to get a good idea of how it plays out. And then we can abstract > out the differences into a sane API. Hmm. If time permits, I'll check what I could do. But likely I'm not able to implement something for `w32notify-*'; I don't run MS Windows locally. > Stefan Best regards, Michael. ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-04 17:13 ` [PATCH updated] " Rüdiger Sonderfeld 2011-06-04 19:15 ` Eli Zaretskii 2011-06-04 20:10 ` Thien-Thi Nguyen @ 2011-06-06 15:14 ` Stefan Monnier 2011-06-06 16:21 ` Rüdiger Sonderfeld 2 siblings, 1 reply; 125+ messages in thread From: Stefan Monnier @ 2011-06-06 15:14 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Eli Zaretskii, emacs-devel > Thank you for your comments! I updated the patch and I hope I fixed > everything except the things noted below. Eli's comments took care of most nitpicks, but there's still one I care about: every open paren should be preceded by a space. BTW, thanks very much for your contribution. Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH updated] Support for filesystem watching (inotify) 2011-06-06 15:14 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier @ 2011-06-06 16:21 ` Rüdiger Sonderfeld 0 siblings, 0 replies; 125+ messages in thread From: Rüdiger Sonderfeld @ 2011-06-06 16:21 UTC (permalink / raw) To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel On Monday 06 June 2011 17:14:53 Stefan Monnier wrote: > > Thank you for your comments! I updated the patch and I hope I fixed > > everything except the things noted below. > > Eli's comments took care of most nitpicks, but there's still one I care > about: every open paren should be preceded by a space. This should be fixed in Version 3 of the patch http://lists.gnu.org/archive/html/emacs-devel/2011-06/msg00178.html Regards, Rüdiger ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2011-06-03 22:34 [PATCH] Support for filesystem watching (inotify) Rüdiger Sonderfeld ` (2 preceding siblings ...) 2011-06-04 11:30 ` [PATCH] " Eli Zaretskii @ 2012-09-18 11:50 ` Leo 2012-09-26 12:15 ` Rüdiger Sonderfeld 3 siblings, 1 reply; 125+ messages in thread From: Leo @ 2012-09-18 11:50 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Chong Yidong, emacs-devel On 2011-06-04 06:34 +0800, Rüdiger Sonderfeld wrote: > I wrote a patch to support watching for file system events. It currently only works with inotify (Linux) but it could be extended to support kqueue > (*BSD, OS X) or Win32. But I wanted to get some feedback first. Watching for filesystem events would be very useful for, e.g., dired or magit's status > view. (whole thread: http://thread.gmane.org/gmane.emacs.devel/140158) Is this patch ready for 24.3? Leo ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2012-09-18 11:50 ` [PATCH] " Leo @ 2012-09-26 12:15 ` Rüdiger Sonderfeld 2012-09-26 17:52 ` Stefan Monnier 0 siblings, 1 reply; 125+ messages in thread From: Rüdiger Sonderfeld @ 2012-09-26 12:15 UTC (permalink / raw) To: Leo; +Cc: Chong Yidong, emacs-devel Hello, sorry the Patch is not ready, yet. I haven't added the suggestions made by Stefan Monnier: <jwv39ii6jmw.fsf-monnier+emacs@gnu.org> What is the time window for 24.3? Regards, Rüdiger On Tuesday 18 September 2012 19:50:49 Leo wrote: > On 2011-06-04 06:34 +0800, Rüdiger Sonderfeld wrote: > > I wrote a patch to support watching for file system events. It currently > > only works with inotify (Linux) but it could be extended to support > > kqueue (*BSD, OS X) or Win32. But I wanted to get some feedback first. > > Watching for filesystem events would be very useful for, e.g., dired or > > magit's status view. > > (whole thread: http://thread.gmane.org/gmane.emacs.devel/140158) > > Is this patch ready for 24.3? > > Leo ^ permalink raw reply [flat|nested] 125+ messages in thread
* Re: [PATCH] Support for filesystem watching (inotify) 2012-09-26 12:15 ` Rüdiger Sonderfeld @ 2012-09-26 17:52 ` Stefan Monnier 0 siblings, 0 replies; 125+ messages in thread From: Stefan Monnier @ 2012-09-26 17:52 UTC (permalink / raw) To: Rüdiger Sonderfeld; +Cc: Chong Yidong, Leo, emacs-devel > sorry the Patch is not ready, yet. I haven't added the suggestions > made by Stefan Monnier: <jwv39ii6jmw.fsf-monnier+emacs@gnu.org> > What is the time window for 24.3? The freeze is planned for Oct 1 (i.e. Real Soon Now(tm)). Stefan ^ permalink raw reply [flat|nested] 125+ messages in thread
end of thread, other threads:[~2013-01-08 7:44 UTC | newest] Thread overview: 125+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-06-03 22:34 [PATCH] Support for filesystem watching (inotify) Rüdiger Sonderfeld 2011-06-04 8:52 ` joakim 2011-06-04 16:40 ` Rüdiger Sonderfeld 2011-06-04 10:43 ` Jan Djärv 2011-06-04 23:36 ` [PATCH update2] " Rüdiger Sonderfeld 2011-06-05 5:45 ` Eli Zaretskii 2011-06-05 9:48 ` [PATCH update3] " Rüdiger Sonderfeld 2011-06-05 7:48 ` [PATCH update2] " Jan Djärv 2011-06-05 7:54 ` Andreas Schwab 2011-06-05 9:49 ` Rüdiger Sonderfeld 2011-06-05 15:59 ` John Yates 2011-06-05 16:14 ` Rüdiger Sonderfeld 2011-06-05 16:58 ` Eli Zaretskii 2011-06-06 18:56 ` Rüdiger Sonderfeld 2011-06-06 19:57 ` Eli Zaretskii 2011-06-04 11:30 ` [PATCH] " Eli Zaretskii 2011-06-04 17:13 ` [PATCH updated] " Rüdiger Sonderfeld 2011-06-04 19:15 ` Eli Zaretskii 2011-06-04 20:10 ` Thien-Thi Nguyen 2011-06-04 23:43 ` Rüdiger Sonderfeld 2011-06-05 2:15 ` Thien-Thi Nguyen 2011-06-05 9:22 ` Štěpán Němec 2011-06-15 20:53 ` Johan Bockgård 2011-06-16 8:52 ` Inlined cl functions -- how to learn about them Štěpán Němec 2011-06-06 15:21 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2011-06-06 16:25 ` Rüdiger Sonderfeld 2011-06-06 17:11 ` Stefan Monnier 2011-06-06 20:16 ` Ted Zlatanov 2011-06-07 14:42 ` Stefan Monnier 2011-06-07 16:46 ` Ted Zlatanov 2011-06-07 18:06 ` Stefan Monnier 2011-06-07 18:26 ` Ted Zlatanov 2011-06-24 0:50 ` Rüdiger Sonderfeld 2011-06-24 10:19 ` Ted Zlatanov 2011-06-24 12:18 ` Ted Zlatanov 2011-07-06 13:36 ` Stefan Monnier 2011-07-06 15:54 ` Paul Eggert 2011-07-06 18:30 ` Stefan Monnier 2011-07-06 20:39 ` Paul Eggert 2011-07-06 19:14 ` wide-int crash [was Re: [PATCH updated] Support for filesystem watching (inotify)] Glenn Morris 2011-07-06 22:31 ` Paul Eggert 2011-07-07 19:43 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2012-09-28 13:06 ` [PATCH] Added basic file system watching support Rüdiger Sonderfeld 2012-09-28 14:13 ` Stefan Monnier 2012-09-28 16:27 ` Rüdiger Sonderfeld 2012-10-01 4:38 ` Stefan Monnier 2012-10-01 7:24 ` Eli Zaretskii 2012-10-01 14:09 ` [PATCH] Added inotify support Rüdiger Sonderfeld 2012-10-01 16:27 ` Stefan Monnier 2012-10-02 21:28 ` Eli Zaretskii 2012-10-02 23:46 ` Óscar Fuentes 2012-10-03 2:10 ` Stefan Monnier 2012-10-03 3:54 ` Eli Zaretskii 2012-10-03 12:46 ` Stefan Monnier 2012-10-03 18:34 ` Eli Zaretskii 2012-10-03 18:47 ` Stefan Monnier 2012-10-03 19:08 ` Eli Zaretskii 2012-10-03 20:59 ` Stefan Monnier 2012-10-12 13:54 ` Eli Zaretskii 2012-10-14 14:55 ` Eli Zaretskii 2012-10-03 19:33 ` Óscar Fuentes 2012-10-05 7:40 ` Eli Zaretskii 2012-10-05 13:07 ` Óscar Fuentes 2012-10-05 15:19 ` Eli Zaretskii 2012-10-05 16:55 ` Nix 2012-10-05 17:15 ` Eli Zaretskii 2012-10-05 17:46 ` Nix 2012-10-05 18:22 ` Stefan Monnier 2012-10-05 18:30 ` Óscar Fuentes 2012-10-06 16:39 ` Nix 2012-10-06 17:01 ` Stefan Monnier 2012-10-06 18:51 ` Nix 2012-10-06 21:26 ` Stefan Monnier 2012-10-06 21:28 ` Nix 2012-10-07 7:38 ` Achim Gratz 2012-10-07 13:58 ` Stefan Monnier 2012-10-07 14:54 ` Achim Gratz 2012-10-07 8:24 ` Stephen J. Turnbull 2012-10-07 14:00 ` Stefan Monnier 2012-10-07 14:28 ` Óscar Fuentes 2012-10-07 14:38 ` Stefan Monnier 2012-10-08 7:07 ` Stephen J. Turnbull 2012-10-08 8:06 ` Eli Zaretskii 2012-10-14 15:52 ` Jim Meyering 2012-10-06 7:04 ` Stephen J. Turnbull 2012-10-06 7:23 ` Andreas Schwab 2012-10-06 16:41 ` Nix 2012-10-07 8:09 ` Stephen J. Turnbull 2012-10-07 14:08 ` Stefan Monnier 2012-10-03 3:57 ` Eli Zaretskii 2012-12-02 20:08 ` Eli Zaretskii 2012-12-03 17:18 ` Stefan Monnier 2012-12-10 14:16 ` Eli Zaretskii 2012-12-10 14:47 ` Stefan Monnier 2012-12-10 11:52 ` Eli Zaretskii 2012-12-10 12:11 ` Rüdiger Sonderfeld 2012-12-11 7:43 ` Michael Albinus 2012-12-11 8:20 ` Eli Zaretskii 2012-12-11 16:36 ` Michael Albinus 2012-12-11 16:46 ` Rüdiger Sonderfeld 2012-12-11 20:17 ` Michael Albinus 2012-12-11 7:54 ` [PATCH] Added basic file system watching support Michael Albinus 2012-12-11 8:12 ` Eli Zaretskii 2012-12-11 8:19 ` Michael Albinus 2012-12-11 8:29 ` Eli Zaretskii 2012-12-11 8:44 ` Michael Albinus 2012-12-11 9:39 ` Eli Zaretskii 2012-12-11 10:15 ` Eli Zaretskii 2012-12-11 10:38 ` Michael Albinus 2012-12-11 10:24 ` Michael Albinus 2012-12-11 12:51 ` Eli Zaretskii 2012-12-11 13:17 ` Michael Albinus 2012-12-11 13:23 ` Eli Zaretskii 2012-12-12 1:09 ` Leo 2012-12-12 3:57 ` Eli Zaretskii 2013-01-07 11:33 ` Michael Albinus 2013-01-07 15:57 ` Eli Zaretskii 2013-01-07 16:00 ` Michael Albinus 2013-01-07 20:26 ` Stefan Monnier 2013-01-08 7:44 ` Michael Albinus 2011-06-06 15:14 ` [PATCH updated] Support for filesystem watching (inotify) Stefan Monnier 2011-06-06 16:21 ` Rüdiger Sonderfeld 2012-09-18 11:50 ` [PATCH] " Leo 2012-09-26 12:15 ` Rüdiger Sonderfeld 2012-09-26 17:52 ` Stefan Monnier
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).