From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: =?utf-8?q?R=C3=BCdiger_Sonderfeld?= Newsgroups: gmane.emacs.devel Subject: [PATCH] Support for filesystem watching (inotify) Date: Sat, 4 Jun 2011 00:34:15 +0200 Message-ID: <201106040034.15598.ruediger@c-plusplus.de> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: Text/Plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Trace: dough.gmane.org 1307167571 26399 80.91.229.12 (4 Jun 2011 06:06:11 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sat, 4 Jun 2011 06:06:11 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Jun 04 08:06:07 2011 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([140.186.70.17]) by lo.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1QSjzq-0003GF-Aa for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 08:06:06 +0200 Original-Received: from localhost ([::1]:41571 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSjzp-0006Uu-4b for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 02:06:05 -0400 Original-Received: from eggs.gnu.org ([140.186.70.92]:46369) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QScwj-0005FX-4r for emacs-devel@gnu.org; Fri, 03 Jun 2011 18:34:27 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QScwf-0001eQ-Bv for emacs-devel@gnu.org; Fri, 03 Jun 2011 18:34:24 -0400 Original-Received: from pseudoterminal.org ([213.239.220.147]:52173) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QScwe-0001dt-8V for emacs-devel@gnu.org; Fri, 03 Jun 2011 18:34:20 -0400 Original-Received: from liny.localnet (93-82-9-23.adsl.highway.telekom.at [93.82.9.23]) by pseudoterminal.org (Postfix) with ESMTPSA id 48D718BCD62; Sat, 4 Jun 2011 00:34:16 +0200 (CEST) User-Agent: KMail/1.13.6 (Linux/2.6.38-8-generic; KDE/4.6.2; x86_64; ; ) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 2) X-Received-From: 213.239.220.147 X-Mailman-Approved-At: Sat, 04 Jun 2011 02:05:53 -0400 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:140158 Archived-At: Hello, I wrote a patch to support watching for file system events. It currently on= ly works with inotify (Linux) but it could be extended to support kqueue=20 (*BSD, OS X) or Win32. But I wanted to get some feedback first. Watching fo= r filesystem events would be very useful for, e.g., dired or magit's status= =20 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=C3=BCdiger [PATCH] Added basic file system watching support. It currently only works with inotify/Linux. It adds file-watch and file-unw= atch functions. =2D-- 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 =2D-- a/configure.in +++ b/configure.in @@ -169,6 +169,7 @@ OPTION_DEFAULT_ON([dbus],[don't compile with D-Bus supp= ort]) 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) suppo= rt]) =20 ## 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) =20 +dnl inotify is only available on GNU/Linux. +HAVE_INOTIFY=3Dno +if test "${with_inotify}" =3D "yes"; then + AC_CHECK_HEADERS(sys/inotify.h) + if test "$ac_cv_header_sys_inotify_h" =3D yes ; then + AC_CHECK_FUNCS(inotify_init1 inotify_add_watch inotify_rm_watch, HAVE= _INOTIFY=3Dyes) + fi +fi +if test "${HAVE_INOTIFY}" =3D "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=3Dno diff --git a/src/Makefile.in b/src/Makefile.in index e119596..0cd6d6e 100644 =2D-- a/src/Makefile.in +++ b/src/Makefile.in @@ -354,7 +354,7 @@ base_obj =3D 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 \ =2D 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 =3D $(base_obj) $(NS_OBJC_OBJ) =20 diff --git a/src/emacs.c b/src/emacs.c index 6bdd255..5ca5cda 100644 =2D-- a/src/emacs.c +++ b/src/emacs.c @@ -1550,6 +1550,10 @@ main (int argc, char **argv) syms_of_gnutls (); #endif =20 +#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 =2D-- /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 . */ + +#include + +#ifdef HAVE_FILEWATCH + +#include + +#include "lisp.h" +#include "process.h" + +static Lisp_Object QCmodify, QCmove, QCattrib, QCdelete, QCfrom, QCto, QCa= ll; + +#ifdef HAVE_INOTIFY +#include +#include + +enum { uninitialized =3D -100 }; +static int inotifyfd =3D uninitialized; + +// Assoc list of files being watched. +static Lisp_Object data; + +static void=20 +inotify_callback(int fd, void *_, int for_read) { + eassert(for_read); + eassert(data); + + int to_read =3D 0; + if(ioctl(fd, FIONREAD, &to_read) =3D=3D -1) + report_file_error("ioctl(2) on inotify", Qnil); + char *const buffer =3D xmalloc(to_read); + ssize_t const n =3D 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 =3D 0; + while(i < (size_t)n) + { + struct inotify_event *ev =3D (struct inotify_event*)&buffer[i]; + + Lisp_Object callback =3D Fassoc(make_number(ev->wd), data); + if(!NILP(callback)) + { + Lisp_Object call[3]; + call[0] =3D Fcar(Fcdr(Fcdr(callback))); /* callback */ + call[1] =3D 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 =3D strlen(ev->name); + /* if a directory is watched name contains the name + of the file that was changed */ + Lisp_Object name =3D make_string_from_bytes (ev->name, len, len); + + Lisp_Object events =3D Qnil; + if(ev->mask & (IN_MODIFY|IN_CREATE) ) + events =3D Fcons(Fcons(QCmodify, name), events); + if(ev->mask & IN_MOVE_SELF) + events =3D Fcons(Fcons(QCmove, name), events); + if(ev->mask & IN_MOVED_FROM) + events =3D Fcons(Fcons(QCmove, Fcons(QCfrom, Fcons(name, make_= number(ev->cookie)))), events); + if(ev->mask & IN_MOVED_TO) + events =3D Fcons(Fcons(QCmove, Fcons(QCto, Fcons(name, make_nu= mber(ev->cookie)))), events); + if(ev->mask & IN_ATTRIB) + events =3D Fcons(Fcons(QCattrib, name), events); + if(ev->mask & (IN_DELETE|IN_DELETE_SELF) ) + events =3D Fcons(Fcons(QCdelete, name), events); + =20 + if(!NILP(events)) + { + call[2] =3D events; + Ffuncall(3, call); + } + =20 + 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 =3D Fdelete(callback, data); + } + if(ev->mask & IN_Q_OVERFLOW) + message1("File watch: Inotify Queue Overflow!"); + } + + i +=3D 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 se= cond +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 l= ist +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 eleme= nt is +the name of the file that changed. If a file is moved from or to the direc= tory +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 directo= ry. + +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 =3D=3D uninitialized) + { + inotifyfd =3D inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if(inotifyfd =3D=3D -1) + report_file_error("Initializing file watching", Qnil); + data =3D Qnil; + add_read_fd(inotifyfd, &inotify_callback, &data); + } + uint32_t mask =3D 0; + int i; + for(i =3D 2; i < nargs; ++i) + { + if(EQ(args[i], QCmodify)) + mask |=3D IN_MODIFY | IN_CREATE; + else if(EQ(args[i], QCmove)) + mask |=3D IN_MOVE_SELF | IN_MOVE; + else if(EQ(args[i], QCattrib)) + mask |=3D IN_ATTRIB; + else if(EQ(args[i], QCdelete)) + mask |=3D IN_DELETE_SELF | IN_DELETE; + else if(EQ(args[i], QCall)) + mask |=3D IN_MODIFY | IN_CREATE | IN_MOVE_SELF | IN_MOVE | IN_ATTR= IB + | IN_DELETE_SELF | IN_DELETE; + else /* TODO: should this be an error? */ + message("Unkown parameter %s (ignored)", SSDATA(Fprin1_to_string(a= rgs[i], Qnil))); + } + int watchdesc =3D inotify_add_watch(inotifyfd, SSDATA(args[0]), mask); + if(watchdesc =3D=3D -1) { + report_file_error("Watching file", Qnil); + } + data =3D 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 =3D=3D uninitialized) + return Qnil; + CHECK_STRING(path); + + Lisp_Object x =3D data; + Lisp_Object cell, path_data; + while(!NILP(x)) + { + cell =3D Fcar(x); + x =3D Fcdr(x); + path_data =3D Fcdr(cell); + if(!NILP(path_data) && STRINGP(Fcar(path_data)) + && Fstring_equal(Fcar(path_data), path) + && NUMBERP(Fcar(cell))) + { + int const magicno =3D XINT(Fcar(cell)); + data =3D Fdelete(cell, data); + if(inotify_rm_watch(inotifyfd, magicno) =3D=3D -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 =3D intern_c_string (":modify"); + staticpro (&QCmodify); + QCmove =3D intern_c_string (":move"); + staticpro (&QCmove); + QCattrib =3D intern_c_string (":attrib"); + staticpro (&QCattrib); + QCdelete =3D intern_c_string (":delete"); + staticpro (&QCdelete); + QCfrom =3D intern_c_string (":from"); + staticpro (&QCfrom); + QCto =3D intern_c_string (":to"); + staticpro (&QCto); + QCall =3D 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 =2D-- a/src/lisp.h +++ b/src/lisp.h @@ -3405,6 +3405,11 @@ EXFUN (Fxw_display_color_p, 1); EXFUN (Fx_focus_frame, 1); #endif =20 +/* 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, Qm= enu; =2D-=20 1.7.5.2