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: Re: [PATCH updated] Support for filesystem watching (inotify) Date: Sat, 4 Jun 2011 19:13:08 +0200 Message-ID: <201106041913.09203.ruediger@c-plusplus.de> References: <201106040034.15598.ruediger@c-plusplus.de> <831uz9esv1.fsf@gnu.org> 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 1307212904 16722 80.91.229.12 (4 Jun 2011 18:41:44 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sat, 4 Jun 2011 18:41:44 +0000 (UTC) Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sat Jun 04 20:41:39 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 1QSvn1-0007GZ-3X for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 20:41:39 +0200 Original-Received: from localhost ([::1]:59797 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSvn0-0002Fq-6R for ged-emacs-devel@m.gmane.org; Sat, 04 Jun 2011 14:41:38 -0400 Original-Received: from eggs.gnu.org ([140.186.70.92]:42437) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSuPU-0004ZF-U8 for emacs-devel@gnu.org; Sat, 04 Jun 2011 13:13:19 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QSuPS-0002lj-VY for emacs-devel@gnu.org; Sat, 04 Jun 2011 13:13:16 -0400 Original-Received: from pseudoterminal.org ([213.239.220.147]:52758) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QSuPP-0002lF-Ti; Sat, 04 Jun 2011 13:13:12 -0400 Original-Received: from liny.localnet (188-23-77-124.adsl.highway.telekom.at [188.23.77.124]) by pseudoterminal.org (Postfix) with ESMTPSA id 27CD68BC0CA; Sat, 4 Jun 2011 19:13:10 +0200 (CEST) User-Agent: KMail/1.13.6 (Linux/2.6.38-8-generic; KDE/4.6.2; x86_64; ; ) In-Reply-To: <831uz9esv1.fsf@gnu.org> 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 14:41:26 -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:140179 Archived-At: 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 everyth= ing except the things noted below. > > +#include >=20 > Why do you need this header? "lisp.h" requires it. (lisp.h:2034:3: error: expected specifier-qualifier-l= ist before =E2=80=98jmp_buf=E2=80=99) > > +static void > > +inotify_callback(int fd, void *_, int for_read) { >=20 > What's that _ in the argument list? I ignore this argument. > > + while(i < (size_t)n) >=20 > 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 nev= er 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 =3D Fdelete(callback, data); > > + } >=20 > 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!"); >=20 > 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 > >=20 > > +to identify matching move operations if a file is moved inside the > > directory. >=20 > This part is extremely unclear. How about an example? > > + uint32_t mask =3D 0; >=20 > 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 =3D Fcar(x); > > + x =3D Fcdr(x); > > + path_data =3D Fcdr(cell); >=20 > ??? 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-unw= atch functions. =2D-- 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 =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. @@ -1981,6 +1982,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 c4250b9..9135e7d 100644 =2D-- a/src/Makefile.in +++ b/src/Makefile.in @@ -334,7 +334,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 3a7c5c0..fb734e5 100644 =2D-- a/src/emacs.c +++ b/src/emacs.c @@ -1558,6 +1558,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..55da25f =2D-- /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 . */ + +#include + +#ifdef HAVE_FILEWATCH +#include +#include "lisp.h" +#include "coding.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 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 =3D 0; + if(ioctl(fd, FIONREAD, &to_read) =3D=3D -1) + report_file_error("Error while trying to retrieve file system events", + Qnil); + buffer =3D xmalloc(to_read); + n =3D read(fd, buffer, to_read); + if(n < 0) + report_file_error("Error while trying to read file system events", + Qnil); + + 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), watch_list); + if(!NILP(callback)) + { + size_t len; + Lisp_Object name, events; + Lisp_Object call[3]; + call[0] =3D Fcar(Fcdr(Fcdr(callback))); /* callback */ + call[1] =3D Fcar(Fcdr(callback)); /* file name */ + + /* If a directory is watched name contains the name + of the file that was changed. */ + len =3D strlen(ev->name); + name =3D make_unibyte_string (ev->name, len); + name =3D DECODE_FILE(name); + + 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_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); + + if(!NILP(events)) + { + call[2] =3D 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], Qn= il); + watch_list =3D Fdelete(callback, watch_list); + } + if(ev->mask & IN_Q_OVERFLOW) + add_to_log("File watch: Inotify Queue Overflow!", Qnil, Qnil); + } + + i +=3D 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 o= ccurs +CALLBACK is called with FILENAME as first argument and a list of changes a= s 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 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 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. + +Example: +(file-watch "foo" #'(lambda (file event) (message "%s %s" file event)) :al= l) + +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 =3D=3D uninitialized) + { + inotifyfd =3D inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if(inotifyfd =3D=3D -1) + report_file_error("File watching feature (inotify) is not availabl= e", + Qnil); + watch_list =3D Qnil; + add_read_fd(inotifyfd, &inotify_callback, &watch_list); + } + mask =3D 0; + 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(args[i], Qnil))); + } + decoded_file_name =3D ENCODE_FILE(args[0]); + watchdesc =3D inotify_add_watch(inotifyfd, SSDATA(decoded_file_name), ma= sk); + if(watchdesc =3D=3D -1) { + report_file_error("Could not watch file", Fcons(args[0], Qnil)); + } + /* TODO: check if file is already in the watch_list. */ + watch_list =3D Fcons(Fcons(make_number(watchdesc), Flist(2, args)), watc= h_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 =3D watch_list; + + if(inotifyfd =3D=3D uninitialized) + return Qnil; + CHECK_STRING(file_name); + + while(!NILP(x)) + { + cell =3D Fcar(x); + x =3D Fcdr(x); + file_name_data =3D 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 =3D XINT(Fcar(cell)); + watch_list =3D Fdelete(cell, watch_list); + if(inotify_rm_watch(inotifyfd, magicno) =3D=3D -1) + report_file_error("Could not unwatch file", Fcons(file_name, Q= nil)); + /* Cleanup if watch_list is empty. */ + if(NILP(watch_list)) + { + close(inotifyfd); + delete_read_fd(inotifyfd); + inotifyfd =3D 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 =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 8a504e8..8b21e0e 100644 =2D-- a/src/lisp.h +++ b/src/lisp.h @@ -3430,6 +3430,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, Qfringe; extern Lisp_Object Qheader_line, Qscroll_bar, Qcursor; =2D-=20 1.7.5.2