From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: joakim@verona.se Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] Support for filesystem watching (inotify) Date: Sat, 04 Jun 2011 10:52:07 +0200 Message-ID: References: <201106040034.15598.ruediger@c-plusplus.de> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: quoted-printable X-Trace: dough.gmane.org 1307177568 5663 80.91.229.12 (4 Jun 2011 08:52:48 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sat, 4 Jun 2011 08:52:48 +0000 (UTC) Cc: emacs-devel@gnu.org To: =?iso-8859-1?Q?R=FCdiger?= Sonderfeld Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Jun 04 10:52:43 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 1QSmb4-0005FP-5F for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 10:52:42 +0200 Original-Received: from localhost ([::1]:49980 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSmb3-0007sW-FQ for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 04:52:41 -0400 Original-Received: from eggs.gnu.org ([140.186.70.92]:50248) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSmal-0007rS-Ei for emacs-devel@gnu.org; Sat, 04 Jun 2011 04:52:25 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QSmaj-0007Dq-23 for emacs-devel@gnu.org; Sat, 04 Jun 2011 04:52:23 -0400 Original-Received: from smtprelay-b11.telenor.se ([62.127.194.20]:56856) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSmai-0007Cv-2s for emacs-devel@gnu.org; Sat, 04 Jun 2011 04:52:20 -0400 Original-Received: from ipb4.telenor.se (ipb4.telenor.se [195.54.127.167]) by smtprelay-b11.telenor.se (Postfix) with ESMTP id 4AAEBE9C85 for ; Sat, 4 Jun 2011 10:52:11 +0200 (CEST) X-SENDER-IP: [217.174.79.66] X-LISTENER: [smtpout.euromail.se] X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: ArUHANTx6U3Zrk9CZ2dsb2JhbABFDqZGFBUVFCWIccFzDoMJgwoEkHmEURSKVw X-IronPort-AV: E=Sophos;i="4.65,319,1304287200"; d="scan'208";a="1736247776" Original-Received: from unknown (HELO brown.emailprod.vodafone.se) ([217.174.79.66]) by ipb4.telenor.se with ESMTP; 04 Jun 2011 10:52:11 +0200 Original-Received: from springfield.emailprod.vodafone.se ([192.168.106.58]) by brown.emailprod.vodafone.se with Microsoft SMTPSVC(6.0.3790.4675); Sat, 4 Jun 2011 10:52:31 +0200 Original-Received: from chopper ([79.102.113.237]) by springfield.emailprod.vodafone.se with Microsoft SMTPSVC(6.0.3790.4675); Sat, 4 Jun 2011 10:52:30 +0200 In-Reply-To: <201106040034.15598.ruediger@c-plusplus.de> (=?iso-8859-1?Q?=22R=FCdiger?= Sonderfeld"'s message of "Sat, 4 Jun 2011 00:34:15 +0200") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.0.50 (gnu/linux) X-OriginalArrivalTime: 04 Jun 2011 08:52:30.0578 (UTC) FILETIME=[BCD98D20:01CC2294] X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 62.127.194.20 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:140166 Archived-At: R=FCdiger Sonderfeld 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= =20 > (*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 stat= us=20 > 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") pat= h events)) :all) > (delete-file "foo/bar") > (file-unwatch "foo") > > So please tell me what you think of this. > > Regards, > R=FCdiger > > > [PATCH] Added basic file system watching support. > > It currently only works with inotify/Linux. It adds file-watch and file-u= nwatch 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 su= pport]) > 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) sup= port]) >=20=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=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, HA= VE_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 > --- 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 \ > - 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=20 > 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 >=20=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 > --- /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, Q= Call; > + > +#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, le= n); > + > + 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, mak= e_number(ev->cookie)))), events); > + if(ev->mask & IN_MOVED_TO) > + events =3D Fcons(Fcons(QCmove, Fcons(QCto, Fcons(name, make_= number(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=20=20=20=20=20 > + if(!NILP(events)) > + { > + call[2] =3D events; > + Ffuncall(3, call); > + } > +=20=20=20=20=20=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 occu= rs > +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 even= t. The > +first element is a flag symbol. If a directory is watched the second ele= ment is > +the name of the file that changed. If a file is moved from or to the dir= ectory > +the second element is either :from or :to and the third element is the f= ile > +name. A fourth element contains a numeric identifier (cookie) that can b= e used > +to identify matching move operations if a file is moved inside the direc= tory. > + > +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_AT= TRIB > + | 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 =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 > --- 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=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, = Qmenu; --=20 Joakim Verona