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 update3] Support for filesystem watching (inotify) Date: Sun, 5 Jun 2011 11:48:07 +0200 Message-ID: <201106051148.07908.ruediger@c-plusplus.de> References: <201106040034.15598.ruediger@c-plusplus.de> <201106050136.35939.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 1307270453 31258 80.91.229.12 (5 Jun 2011 10:40:53 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sun, 5 Jun 2011 10:40:53 +0000 (UTC) Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Jun 05 12:40:48 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 1QTAlD-0003Uc-2b for ged-emacs-devel@m.gmane.org; Sun, 05 Jun 2011 12:40:47 +0200 Original-Received: from localhost ([::1]:52256 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QTAlB-0007hZ-S7 for ged-emacs-devel@m.gmane.org; Sun, 05 Jun 2011 06:40:45 -0400 Original-Received: from eggs.gnu.org ([140.186.70.92]:56930) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QT9wS-0002ju-0l for emacs-devel@gnu.org; Sun, 05 Jun 2011 05:48:22 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QT9wP-0000T5-TU for emacs-devel@gnu.org; Sun, 05 Jun 2011 05:48:19 -0400 Original-Received: from pseudoterminal.org ([213.239.220.147]:57780) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QT9wJ-0000RH-W0; Sun, 05 Jun 2011 05:48:12 -0400 Original-Received: from liny.localnet (93-82-8-45.adsl.highway.telekom.at [93.82.8.45]) by pseudoterminal.org (Postfix) with ESMTPSA id BEA108BC0CA; Sun, 5 Jun 2011 11:48:09 +0200 (CEST) User-Agent: KMail/1.13.6 (Linux/2.6.38-8-generic; KDE/4.6.2; x86_64; ; ) In-Reply-To: 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: Sun, 05 Jun 2011 06:40:25 -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:140191 Archived-At: On Sunday 05 June 2011 07:45:58 Eli Zaretskii wrote: > > From: R=C3=BCdiger Sonderfeld > > Date: Sun, 5 Jun 2011 01:36:35 +0200 > >=20 > > > 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. > >=20 > > Thanks for your advise. I changed the code to use events. >=20 > 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-unw= atch functions. =2D-- 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 =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/lisp/filewatch.el b/lisp/filewatch.el new file mode 100644 index 0000000..2c97589 =2D-- /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=C3=BCdiger Sonderfeld +;; 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 . + +;;; 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) (>=3D (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 =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..56b79b7 =2D-- /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 . */ + +#include + +#ifdef HAVE_FILEWATCH +#include /* 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_a= spect; + +#ifdef HAVE_INOTIFY +#include +#include + +enum { uninitialized =3D -100 }; +/* File handle for inotify. */ +static int inotifyfd =3D 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 =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) + { + xfree (buffer); + report_file_error ("Error while trying to read file system events", + Qnil); + } + + EVENT_INIT (event); + event.kind =3D FILEWATCH_EVENT; + event.arg =3D 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, event_info; + + /* 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 (Qmodify, name), events); + if (ev->mask & IN_MOVE_SELF) + events =3D Fcons (Fcons (Qmove, name), events); + if (ev->mask & IN_MOVED_FROM) + events =3D Fcons (Fcons (Qmove, + Fcons (Qfrom, + Fcons (name, + make_number (ev->cookie))= )), + events); + if (ev->mask & IN_MOVED_TO) + events =3D Fcons (Fcons (Qmove, + Fcons (Qto, + Fcons (name, + make_number (ev->cookie))= )), + events); + if (ev->mask & IN_ATTRIB) + events =3D Fcons (Fcons (Qattrib, name), events); + if (ev->mask & (IN_DELETE|IN_DELETE_SELF) ) + events =3D Fcons (Fcons (Qdelete, name), events); + + if (!NILP (events)) + { + Lisp_Object args[2], event_info; + args[0] =3D Fcdr (callback); + args[1] =3D Fcons (events, Qnil); + event_info =3D Fcons (Fappend (2, args), Qnil); + if (NILP (event.arg)) + event.arg =3D event_info; + else + { + args[0] =3D event.arg; + args[1] =3D event_info; + event.arg =3D 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 =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; + } + + 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 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" 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 =3D=3D uninitialized) + { + inotifyfd =3D inotify_init1 (IN_NONBLOCK|IN_CLOEXEC); + if (inotifyfd =3D=3D -1) + { + inotifyfd =3D uninitialized; + report_file_error ("File watching feature (inotify) is not avail= able", + Qnil); + } + watch_list =3D Qnil; + add_read_fd (inotifyfd, &inotify_callback, &watch_list); + } + + /* Convert ASPECT into the inotify event mask. */ + if (CONSP (aspect)) + { + Lisp_Object x =3D aspect; + mask =3D 0; + while (!NILP(x)) + { + mask |=3D symbol_to_inotifymask (Fcar (x)); + x =3D Fcdr(x); + } + } + else + mask =3D symbol_to_inotifymask(aspect); + + decoded_file_name =3D ENCODE_FILE (file_name); + watchdesc =3D inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), = mask); + if (watchdesc =3D=3D -1) + report_file_error ("Could not watch file", Fcons (file_name, Qnil)); + /* TODO: check if file is already in the watch_list. */ + watch_list =3D 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 =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, Qnil)); + + /* 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) +{ + 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 =2D-- 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; =20 /* Lisp_Object Qmouse_movement; - also an event header */ @@ -4017,6 +4020,13 @@ kbd_buffer_get_event (KBOARD **kbp, kbd_fetch_ptr =3D event + 1; } #endif +#ifdef HAVE_FILEWATCH + else if (event->kind =3D=3D FILEWATCH_EVENT) + { + obj =3D make_lispy_event (event); + kbd_fetch_ptr =3D event + 1; + } +#endif else if (event->kind =3D=3D CONFIG_CHANGED_EVENT) { obj =3D make_lispy_event (event); @@ -5913,6 +5923,13 @@ make_lispy_event (struct input_event *event) } #endif /* HAVE_DBUS */ =20 +#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 =20 +#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 =20 +#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 =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; diff --git a/src/termhooks.h b/src/termhooks.h index 6a58517..9db2019 100644 =2D-- a/src/termhooks.h +++ b/src/termhooks.h @@ -206,6 +206,11 @@ enum event_kind , NS_NONKEY_EVENT #endif =20 +#ifdef HAVE_FILEWATCH + /* File or directory was changed. */ + , FILEWATCH_EVENT +#endif + }; =20 /* If a struct input_event has a kind which is SELECTION_REQUEST_EVENT =2D-=20 1.7.5.2