From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: =?utf-8?Q?Jan_Dj=C3=A4rv?= Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] Support for filesystem watching (inotify) Date: Sat, 4 Jun 2011 12:43:37 +0200 Message-ID: References: <201106040034.15598.ruediger@c-plusplus.de> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 (iPhone Mail 8J2) Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Trace: dough.gmane.org 1307184253 5838 80.91.229.12 (4 Jun 2011 10:44:13 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sat, 4 Jun 2011 10:44:13 +0000 (UTC) Cc: "emacs-devel@gnu.org" To: =?utf-8?Q?R=C3=BCdiger_Sonderfeld?= Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Jun 04 12:44:08 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 1QSoKt-0004Cf-Gd for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 12:44:07 +0200 Original-Received: from localhost ([::1]:55621 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSoKs-0007qz-JZ for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 06:44:06 -0400 Original-Received: from eggs.gnu.org ([140.186.70.92]:47659) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSoKZ-0007qJ-Nm for emacs-devel@gnu.org; Sat, 04 Jun 2011 06:43:49 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QSoKX-00048p-JD for emacs-devel@gnu.org; Sat, 04 Jun 2011 06:43:47 -0400 Original-Received: from smtprelay-b11.telenor.se ([62.127.194.20]:46397) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSoKX-00048i-0J for emacs-devel@gnu.org; Sat, 04 Jun 2011 06:43:45 -0400 Original-Received: from ipb2.telenor.se (ipb2.telenor.se [195.54.127.165]) by smtprelay-b11.telenor.se (Postfix) with ESMTP id 97D95E8296 for ; Sat, 4 Jun 2011 12:43:42 +0200 (CEST) X-SENDER-IP: [85.225.45.100] X-LISTENER: [smtp.bredband.net] X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AqpAAJ4L6k1V4S1kPGdsb2JhbABEDoRKoRBrCwEBAQE3MohxsGqQO4ErgWyCAIEKBJVBinQ X-IronPort-AV: E=Sophos;i="4.65,319,1304287200"; d="scan'208";a="194686943" Original-Received: from c-642de155.25-1-64736c10.cust.bredbandsbolaget.se (HELO coolsville.localdomain) ([85.225.45.100]) by ipb2.telenor.se with ESMTP; 04 Jun 2011 12:43:42 +0200 Original-Received: from [172.20.199.248] (janiphone [172.20.199.248]) by coolsville.localdomain (Postfix) with ESMTPSA id 795D57FA05A; Sat, 4 Jun 2011 12:43:41 +0200 (CEST) In-Reply-To: <201106040034.15598.ruediger@c-plusplus.de> X-Mailer: iPhone Mail (8J2) 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:140168 Archived-At: Hi.=20 In general, you should not call Lisp from the read fd callback. Recursive ca= lls 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=C3=BCdiger Sonderfeld := > Hello, > I wrote a patch to support watching for file system events. It currently o= nly 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 f= or filesystem events would be very useful for, e.g., dired or magit's status= =20 > view. >=20 > Here is a quick example (expects a directory foo containing a file bar): >=20 > (file-watch "foo" #'(lambda (path events) (message "FS-Event: %s %s") path= events)) :all) > (delete-file "foo/bar") > (file-unwatch "foo") >=20 > So please tell me what you think of this. >=20 > Regards, > R=C3=BCdiger >=20 >=20 > [PATCH] Added basic file system watching support. >=20 > It currently only works with inotify/Linux. It adds file-watch and file-un= watch 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 >=20 > 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 sup= port]) > 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) supp= ort]) >=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, HAV= E_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 > 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 > +#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, QC= all; > + > +#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_n= umber(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 occur= s > +CALLBACK is called with PATH as first argument and a list of changes as s= econd > +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 elem= ent is > +the name of the file that changed. If a file is moved from or to the dire= ctory > +the second element is either :from or :to and the third element is the fi= le > +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 direct= ory. > + > +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_ATT= RIB > + | 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 > +/* 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; > --=20 > 1.7.5.2 >=20