]> code.delx.au - gnu-emacs/blobdiff - src/w32notify.c
Drop FRAME_PTR typedef.
[gnu-emacs] / src / w32notify.c
index bdfdf3472a08bf8033b78516f6e62a4fae66359c..a48a83daf53090a0d43b4b7e74c5c0e18e3a0ed3 100644 (file)
@@ -1,5 +1,5 @@
 /* Filesystem notifications support for GNU Emacs on the Microsoft Windows API.
-   Copyright (C) 2012  Free Software Foundation, Inc.
+   Copyright (C) 2012-2013 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -16,15 +16,18 @@ 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 <http://www.gnu.org/licenses/>.  */
 
-/* Design overview:
+/* Written by Eli Zaretskii <eliz@gnu.org>.
+
+   Design overview:
 
    For each watch request, we launch a separate worker thread.  The
    worker thread runs the watch_worker function, which issues an
-   asynchronous call to ReadDirectoryChangesW, and then waits for that
-   call to complete in SleepEx.  Waiting in SleepEx puts the thread in
-   an alertable state, so it wakes up when either (a) the call to
-   ReadDirectoryChangesW completes, or (b) the main thread instructs
-   the worker thread to terminate by sending it an APC, see below.
+   asynchronous call to ReadDirectoryChangesW, and then waits in
+   SleepEx for that call to complete.  Waiting in SleepEx puts the
+   thread in an "alertable" state, so it wakes up when either (a) the
+   call to ReadDirectoryChangesW completes, or (b) the main thread
+   instructs the worker thread to terminate by sending it an APC, see
+   below.
 
    When the ReadDirectoryChangesW call completes, its completion
    routine watch_completion is automatically called.  watch_completion
@@ -36,7 +39,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
    return, and watch_worker then issues another call to
    ReadDirectoryChangesW.  (Except when it does not, see below.)
 
-   In a GUI session, The WM_EMACS_FILENOTIFY message, posted to the
+   In a GUI session, the WM_EMACS_FILENOTIFY message posted to the
    message queue gets dispatched to the main Emacs window procedure,
    which queues it for processing by w32_read_socket.  When
    w32_read_socket sees this message, it accesses the buffer with file
@@ -56,12 +59,13 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 
    When the FILE_NOTIFY_EVENT event is processed by keyboard.c's
    kbd_buffer_get_event, it is converted to a Lispy event that can be
-   bound to a command.  The default binding is w32notify-handle-event,
+   bound to a command.  The default binding is file-notify-handle-event,
    defined on subr.el.
 
-   After w32_read_socket or w32_console_read_socket is done processing
-   the notifications, it resets a flag signaling to all watch worker
-   threads that the notifications buffer is available for more input.
+   After w32_read_socket or w32_console_read_socket are done
+   processing the notifications, they reset a flag signaling to all
+   watch worker threads that the notifications buffer is available for
+   more input.
 
    When the watch is removed by a call to w32notify-rm-watch, the main
    thread requests that the worker thread terminates by queuing an APC
@@ -72,9 +76,10 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
    watch_completion function is called one last time with the
    ERROR_OPERATION_ABORTED status, which causes it to clean up and set
    a flag telling watch_worker to exit without issuing another
-   ReadDirectoryChangesW call.  The main thread waits for some time
-   for the worker thread to exit, and if it doesn't, terminates it
-   forcibly.  */
+   ReadDirectoryChangesW call.  Since watch_worker is the thread
+   procedure of the worker thread, exiting it causes the thread to
+   exit.  The main thread waits for some time for the worker thread to
+   exit, and if it doesn't, terminates it forcibly.  */
 
 #include <stddef.h>
 #include <errno.h>
@@ -93,6 +98,8 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "frame.h"     /* needed by termhooks.h */
 #include "termhooks.h" /* for FILE_NOTIFY_EVENT */
 
+#define DIRWATCH_SIGNATURE 0x01233210
+
 struct notification {
   BYTE *buf;           /* buffer for ReadDirectoryChangesW */
   OVERLAPPED *io_info; /* the OVERLAPPED structure for async I/O */
@@ -101,18 +108,15 @@ struct notification {
   char *watchee;       /* the file we are interested in */
   HANDLE dir;          /* handle to the watched directory */
   HANDLE thr;          /* handle to the thread that watches */
-  int terminate;       /* if non-zero, request for the thread to terminate */
+  volatile int terminate; /* if non-zero, request for the thread to terminate */
+  unsigned signature;
 };
 
-/* FIXME: this needs to be changed to support more that one request at
-   a time.  */
-static struct notification dirwatch;
-
 /* Used for communicating notifications to the main thread.  */
-int notification_buffer_in_use;
+volatile int notification_buffer_in_use;
 BYTE file_notifications[16384];
 DWORD notifications_size;
-HANDLE notifications_desc;
+void *notifications_desc;
 
 static Lisp_Object Qfile_name, Qdirectory_name, Qattributes, Qsize;
 static Lisp_Object Qlast_write_time, Qlast_access_time, Qcreation_time;
@@ -121,11 +125,11 @@ static Lisp_Object Qsecurity_desc, Qsubtree, watch_list;
 /* Signal to the main thread that we have file notifications for it to
    process.  */
 static void
-send_notifications (BYTE *info, DWORD info_size, HANDLE hdir, int *terminate)
+send_notifications (BYTE *info, DWORD info_size, void *desc,
+                   volatile int *terminate)
 {
   int done = 0;
-  FRAME_PTR f = SELECTED_FRAME ();
-
+  struct frame *f = SELECTED_FRAME ();
 
   /* A single buffer is used to communicate all notifications to the
      main thread.  Since both the main thread and several watcher
@@ -144,7 +148,7 @@ send_notifications (BYTE *info, DWORD info_size, HANDLE hdir, int *terminate)
          if (info_size)
            memcpy (file_notifications, info, info_size);
          notifications_size = info_size;
-         notifications_desc = hdir;
+         notifications_desc = desc;
          /* If PostMessage fails, the message queue is full.  If that
             happens, the last thing they will worry about is file
             notifications.  So we effectively discard the
@@ -186,9 +190,9 @@ watch_end (ULONG_PTR arg)
     }
 }
 
-/* A completion routine (a.k.a. APC function) for handling events read
-   by ReadDirectoryChangesW.  Called by the OS when the thread which
-   issued the asynchronous ReadDirectoryChangesW call is in the
+/* A completion routine (a.k.a. "APC function") for handling events
+   read by ReadDirectoryChangesW.  Called by the OS when the thread
+   which issued the asynchronous ReadDirectoryChangesW call is in the
    "alertable state", i.e. waiting inside SleepEx call.  */
 VOID CALLBACK
 watch_completion (DWORD status, DWORD bytes_ret, OVERLAPPED *io_info)
@@ -213,21 +217,19 @@ watch_completion (DWORD status, DWORD bytes_ret, OVERLAPPED *io_info)
       /* We've been called because the main thread told us to issue
         CancelIo on the directory we watch, and watch_end did so.
         The directory handle is already closed.  We should clean up
-        and exit, signalling to the thread worker routine not to
-        issue another call to ReadDirectoryChangesW.  */
-      xfree (dirwatch->buf);
-      dirwatch->buf = NULL;
-      xfree (dirwatch->io_info);
-      dirwatch->io_info = NULL;
-      xfree (dirwatch->watchee);
-      dirwatch->watchee = NULL;
+        and exit, signaling to the thread worker routine not to
+        issue another call to ReadDirectoryChangesW.  Note that we
+        don't free the dirwatch object itself nor the memory consumed
+        by its buffers; this is done by the main thread in
+        remove_watch.  Calling malloc/free from a thread other than
+        the main thread is a no-no.  */
       dirwatch->dir = NULL;
       dirwatch->terminate = 1;
     }
   else
     {
       /* Tell the main thread we have notifications for it.  */
-      send_notifications (dirwatch->buf, bytes_ret, dirwatch->dir,
+      send_notifications (dirwatch->buf, bytes_ret, dirwatch,
                          &dirwatch->terminate);
     }
 }
@@ -252,14 +254,16 @@ watch_worker (LPVOID arg)
        if (!status)
          {
            DebPrint (("watch_worker, abnormal exit: %lu\n", GetLastError ()));
-           xfree (dirwatch->buf);
-           dirwatch->buf = NULL;
-           xfree (dirwatch->io_info);
-           dirwatch->io_info = NULL;
+           /* We cannot remove the dirwatch object from watch_list,
+              because we are in a separate thread.  For the same
+              reason, we also cannot free memory consumed by the
+              buffers allocated for the dirwatch object.  So we close
+              the directory handle, but do not free the object itself
+              or its buffers.  We also don't touch the signature.
+              This way, remove_watch can still identify the object,
+              remove it, and free its memory.  */
            CloseHandle (dirwatch->dir);
            dirwatch->dir = NULL;
-           xfree (dirwatch->watchee);
-           dirwatch->watchee = NULL;
            return 1;
          }
       }
@@ -274,50 +278,51 @@ watch_worker (LPVOID arg)
 
 /* Launch a thread to watch changes to FILE in a directory open on
    handle HDIR.  */
-static int
+static struct notification *
 start_watching (const char *file, HANDLE hdir, BOOL subdirs, DWORD flags)
 {
-  dirwatch.buf = xmalloc (16384);
-  dirwatch.io_info = xzalloc (sizeof(OVERLAPPED));
+  struct notification *dirwatch = xzalloc (sizeof (struct notification));
+  HANDLE thr;
+
+  dirwatch->signature = DIRWATCH_SIGNATURE;
+  dirwatch->buf = xmalloc (16384);
+  dirwatch->io_info = xzalloc (sizeof(OVERLAPPED));
   /* Stash a pointer to dirwatch structure for use by the completion
      routine.  According to MSDN documentation of ReadDirectoryChangesW:
      "The hEvent member of the OVERLAPPED structure is not used by the
      system, so you can use it yourself." */
-  dirwatch.io_info->hEvent = &dirwatch;
-  dirwatch.subtree = subdirs;
-  dirwatch.filter = flags;
-  dirwatch.watchee = xstrdup (file);
-  dirwatch.terminate = 0;
-  dirwatch.dir = hdir;
+  dirwatch->io_info->hEvent = dirwatch;
+  dirwatch->subtree = subdirs;
+  dirwatch->filter = flags;
+  dirwatch->watchee = xstrdup (file);
+  dirwatch->terminate = 0;
+  dirwatch->dir = hdir;
 
   /* See w32proc.c where it calls CreateThread for the story behind
      the 2nd and 5th argument in the call to CreateThread.  */
-  dirwatch.thr = CreateThread (NULL, 64 * 1024, watch_worker,
-                              (void *)&dirwatch, 0x00010000, NULL);
+  dirwatch->thr = CreateThread (NULL, 64 * 1024, watch_worker, (void *)dirwatch,
+                               0x00010000, NULL);
 
-  if (!dirwatch.thr)
+  if (!dirwatch->thr)
     {
-      dirwatch.terminate = 1;
-      xfree (dirwatch.buf);
-      dirwatch.buf = NULL;
-      xfree (dirwatch.io_info);
-      dirwatch.io_info = NULL;
-      xfree (dirwatch.watchee);
-      dirwatch.watchee = NULL;
-      dirwatch.dir = NULL;
-      return -1;
+      xfree (dirwatch->buf);
+      xfree (dirwatch->io_info);
+      xfree (dirwatch->watchee);
+      xfree (dirwatch);
+      dirwatch = NULL;
     }
-  return 0;
+  return dirwatch;
 }
 
 /* Called from the main thread to start watching FILE in PARENT_DIR,
    subject to FLAGS.  If SUBDIRS is TRUE, watch the subdirectories of
-   PARENT_DIR as well.  Value is the handle on which the directory is
-   open.  */
-static HANDLE *
+   PARENT_DIR as well.  Value is a pointer to 'struct notification'
+   used by the thread that watches the changes.  */
+static struct notification *
 add_watch (const char *parent_dir, const char *file, BOOL subdirs, DWORD flags)
 {
   HANDLE hdir;
+  struct notification *dirwatch = NULL;
 
   if (!file || !*file)
     return NULL;
@@ -334,18 +339,17 @@ add_watch (const char *parent_dir, const char *file, BOOL subdirs, DWORD flags)
   if (hdir == INVALID_HANDLE_VALUE)
     return NULL;
 
-  if (start_watching (file, hdir, subdirs, flags) == 0)
-    return hdir;
+  if ((dirwatch = start_watching (file, hdir, subdirs, flags)) == NULL)
+    CloseHandle (hdir);
 
-  CloseHandle (hdir);
-  return NULL;
+  return dirwatch;
 }
 
-/* Stop watching a directory specified by its handle HDIR.  */
+/* Stop watching a directory specified by a pointer to its dirwatch object.  */
 static int
-remove_watch (HANDLE hdir)
+remove_watch (struct notification *dirwatch)
 {
-  if (hdir == dirwatch.dir)
+  if (dirwatch && dirwatch->signature == DIRWATCH_SIGNATURE)
     {
       int i;
       BOOL status;
@@ -355,19 +359,19 @@ remove_watch (HANDLE hdir)
         CancelIo on it.  (CancelIoEx is available only since Vista.)
         So we need to queue an APC for the worker thread telling it
         to terminate.  */
-      if (!QueueUserAPC (watch_end, dirwatch.thr, (ULONG_PTR)dirwatch.dir))
+      if (!QueueUserAPC (watch_end, dirwatch->thr, (ULONG_PTR)dirwatch->dir))
        DebPrint (("QueueUserAPC failed (%lu)!\n", GetLastError ()));
       /* We also set the terminate flag, for when the thread is
         waiting on the critical section that never gets acquired.
         FIXME: is there a cleaner method?  Using SleepEx there is a
         no-no, as that will lead to recursive APC invocations and
         stack overflow.  */
-      dirwatch.terminate = 1;
+      dirwatch->terminate = 1;
       /* Wait for the thread to exit.  FIXME: is there a better method
         that is not overly complex?  */
       for (i = 0; i < 50; i++)
        {
-         if (!((status = GetExitCodeThread (dirwatch.thr, &exit_code))
+         if (!((status = GetExitCodeThread (dirwatch->thr, &exit_code))
                && exit_code == STILL_ACTIVE))
            break;
          Sleep (10);
@@ -376,25 +380,29 @@ remove_watch (HANDLE hdir)
          || exit_code == STILL_ACTIVE)
        {
          if (!(status == FALSE && err == ERROR_INVALID_HANDLE))
-           TerminateThread (dirwatch.thr, 0);
+           {
+             TerminateThread (dirwatch->thr, 0);
+             if (dirwatch->dir)
+               CloseHandle (dirwatch->dir);
+           }
        }
 
       /* Clean up.  */
-      if (dirwatch.thr)
+      if (dirwatch->thr)
        {
-         CloseHandle (dirwatch.thr);
-         dirwatch.thr = NULL;
+         CloseHandle (dirwatch->thr);
+         dirwatch->thr = NULL;
        }
-      return 0;
-    }
-  else if (!dirwatch.dir)
-    {
-      DebPrint (("Directory handle already closed!\n"));
+      xfree (dirwatch->buf);
+      xfree (dirwatch->io_info);
+      xfree (dirwatch->watchee);
+      xfree (dirwatch);
+
       return 0;
     }
   else
     {
-      DebPrint (("Unknown directory handle!\n"));
+      DebPrint (("Unknown dirwatch object!\n"));
       return -1;
     }
 }
@@ -434,8 +442,8 @@ DEFUN ("w32notify-add-watch", Fw32notify_add_watch,
 This arranges for filesystem events pertaining to FILE to be reported
 to Emacs.  Use `w32notify-rm-watch' to cancel the watch.
 
-Value is a descriptor for the added watch, or nil if the file
-cannot be watched.
+Value is a descriptor for the added watch.  If the file cannot be
+watched for some reason, this function signals a `file-error' error.
 
 FILTER is a list of conditions for reporting an event.  It can include
 the following symbols:
@@ -468,15 +476,21 @@ following:
   'renamed-from' -- a file was renamed whose old name was FILE
   'renamed-to'   -- a file was renamed and its new name is FILE
 
-FILE is the name of the file whose event is being reported.  */)
+FILE is the name of the file whose event is being reported.
+
+Note that some networked filesystems, such as Samba-mounted Unix
+volumes, might not send notifications about file changes.  In these
+cases, this function will return a valid descriptor, but notifications
+will never come in.  Volumes shared from remote Windows machines do
+generate notifications correctly, though.  */)
   (Lisp_Object file, Lisp_Object filter, Lisp_Object callback)
 {
   Lisp_Object encoded_file, watch_object, watch_descriptor;
   char parent_dir[MAX_PATH], *basename;
   size_t fn_len;
-  HANDLE hdir;
   DWORD flags;
   BOOL subdirs = FALSE;
+  struct notification *dirwatch = NULL;
   Lisp_Object lisp_errstr;
   char *errstr;
 
@@ -491,10 +505,7 @@ FILE is the name of the file whose event is being reported.  */)
                         Qnil);
     }
 
-  if (dirwatch.dir)
-    error ("File watch already active");
-
-  /* We needa full absolute file name of FILE, and we need to remove
+  /* We need a full absolute file name of FILE, and we need to remove
      any trailing slashes from it, so that GetFullPathName below gets
      the basename part correctly.  */
   file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
@@ -528,8 +539,8 @@ FILE is the name of the file whose event is being reported.  */)
 
   flags = filter_list_to_flags (filter);
 
-  hdir = add_watch (parent_dir, basename, subdirs, flags);
-  if (!hdir)
+  dirwatch = add_watch (parent_dir, basename, subdirs, flags);
+  if (!dirwatch)
     {
       DWORD err = GetLastError ();
 
@@ -550,7 +561,7 @@ FILE is the name of the file whose event is being reported.  */)
        report_file_error ("Cannot watch file", Fcons (file, Qnil));
     }
   /* Store watch object in watch list. */
-  watch_descriptor = make_number (hdir);
+  watch_descriptor = XIL ((EMACS_INT)dirwatch);
   watch_object = Fcons (watch_descriptor, callback);
   watch_list = Fcons (watch_object, watch_list);
 
@@ -565,24 +576,43 @@ WATCH-DESCRIPTOR should be an object returned by `w32notify-add-watch'.  */)
      (Lisp_Object watch_descriptor)
 {
   Lisp_Object watch_object;
-  HANDLE hdir = (HANDLE)XINT (watch_descriptor);
-
-  if (remove_watch (hdir) == -1)
-    report_file_error ("Could not remove watch", Fcons (watch_descriptor,
-                                                       Qnil));
+  struct notification *dirwatch;
+  int status = -1;
 
-  /* Remove watch descriptor from watch list. */
+  /* Remove the watch object from watch list.  Do this before freeing
+     the object, do that even if we fail to free it, watch_list is
+     kept free of junk.  */
   watch_object = Fassoc (watch_descriptor, watch_list);
   if (!NILP (watch_object))
-    watch_list = Fdelete (watch_object, watch_list);
+    {
+      watch_list = Fdelete (watch_object, watch_list);
+      dirwatch = (struct notification *)XLI (watch_descriptor);
+      if (w32_valid_pointer_p (dirwatch, sizeof(struct notification)))
+       status = remove_watch (dirwatch);
+    }
+
+  if (status == -1)
+    report_file_error ("Invalid watch descriptor", Fcons (watch_descriptor,
+                                                         Qnil));
 
   return Qnil;
 }
 
 Lisp_Object
-get_watch_object (Lisp_Object desc)
+w32_get_watch_object (void *desc)
+{
+  Lisp_Object descriptor = XIL ((EMACS_INT)desc);
+
+  /* This is called from the input queue handling code, inside a
+     critical section, so we cannot possibly QUIT if watch_list is not
+     in the right condition.  */
+  return NILP (watch_list) ? Qnil : assoc_no_quit (descriptor, watch_list);
+}
+
+void
+globals_of_w32notify (void)
 {
-  return Fassoc (desc, watch_list);
+  watch_list = Qnil;
 }
 
 void