]> code.delx.au - gnu-emacs/commitdiff
Merge from trunk after addition of w32common.h.
authorEli Zaretskii <eliz@gnu.org>
Mon, 8 Oct 2012 13:36:06 +0000 (15:36 +0200)
committerEli Zaretskii <eliz@gnu.org>
Mon, 8 Oct 2012 13:36:06 +0000 (15:36 +0200)
1  2 
src/ChangeLog
src/emacs.c
src/makefile.w32-in
src/w32fns.c
src/w32notify.c

diff --cc src/ChangeLog
Simple merge
diff --cc src/emacs.c
Simple merge
index 5fc5b6ece464bee09c755f540ccc2f6743af65ac,66127d5ba6a4f17f1b1b8b31c3a886a0101751bc..29b7925f1b9ba62d2d09bbcb1158769d44da1c52
@@@ -1680,17 -1684,6 +1685,17 @@@ $(BLD)/w32uniscribe.$(O) : 
        $(W32FONT_H) \
        $(W32TERM_H)
  
-       $(SRC)/w32heap.h \
 +$(BLD)/w32notify.$(O) : \
 +      $(SRC)/w32notify.c \
++      $(SRC)/w32common.h \
 +      $(CODING_H) \
 +      $(CONFIG_H) \
 +      $(FRAME_H) \
 +      $(KEYBOARD_H) \
 +      $(LISP_H) \
 +      $(TERMHOOKS_H) \
 +      $(W32TERM_H)
 +
  # Each object file depends on stamp_BLD, because in parallel builds we must
  # make sure $(BLD) exists before starting compilations.
  #
diff --cc src/w32fns.c
Simple merge
diff --cc src/w32notify.c
index 1de4f4a7d8c18d0705c0c49aa82c97a8595b587a,0000000000000000000000000000000000000000..05c918d8ac8f866e7c3bbf031cbca3aea5c93b9f
mode 100644,000000..100644
--- /dev/null
@@@ -1,590 -1,0 +1,590 @@@
- #include "w32heap.h"  /* for OS version data */
 +/* Filesystem notifications support for GNU Emacs on the Microsoft Windows API.
 +   Copyright (C) 2012  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 <http://www.gnu.org/licenses/>.  */
 +
 +/* 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.
 +
 +   When the ReadDirectoryChangesW call completes, its completion
 +   routine watch_completion is automatically called.  watch_completion
 +   stashes the received file events in a buffer used to communicate
 +   them to the main thread (using a critical section, so that several
 +   threads could use the same buffer), posts a special message,
 +   WM_EMACS_FILENOTIFY, to the Emacs's message queue, and returns.
 +   That causes the SleepEx function call inside watch_worker to
 +   return, and watch_worker then issues another call to
 +   ReadDirectoryChangesW.  (Except when it does not, see below.)
 +
 +   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 notifications (using a
 +   critical section), extracts the information, converts it to a
 +   series of FILE_NOTIFY_EVENT events, and stuffs them into the input
 +   event queue to be processed by keyboard.c input machinery
 +   (read_char via a call to kbd_buffer_get_event).  When the
 +   FILE_NOTIFY_EVENT event is processed by 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, defined on subr.el.
 +
 +   After w32_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.
 +
 +   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
 +   for the worker thread.  The APC specifies the watch_end function to
 +   be called.  watch_end calls CancelIo on the outstanding
 +   ReadDirectoryChangesW call and closes the handle on which the
 +   watched directory was open.  When watch_end returns, the
 +   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.  */
 +
 +#include <stddef.h>
 +#include <errno.h>
 +
 +/* must include CRT headers *before* config.h */
 +#include <config.h>
 +
 +#include <windows.h>
 +
 +#include "lisp.h"
 +#include "w32term.h"  /* for enter_crit/leave_crit and WM_EMACS_FILENOTIFY */
++#include "w32common.h"        /* for OS version data */
 +#include "w32.h"      /* for w32_strerror */
 +#include "coding.h"
 +#include "keyboard.h"
 +#include "frame.h"    /* needed by termhooks.h */
 +#include "termhooks.h"        /* for FILE_NOTIFY_EVENT */
 +
 +struct notification {
 +  BYTE *buf;          /* buffer for ReadDirectoryChangesW */
 +  OVERLAPPED *io_info;        /* the OVERLAPPED structure for async I/O */
 +  BOOL subtree;               /* whether to watch subdirectories */
 +  DWORD filter;               /* bit mask for events to watch */
 +  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 */
 +};
 +
 +/* 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;
 +BYTE file_notifications[16384];
 +DWORD notifications_size;
 +HANDLE notifications_desc;
 +
 +static Lisp_Object Qfile_name, Qdirectory_name, Qattributes, Qsize;
 +static Lisp_Object Qlast_write_time, Qlast_access_time, Qcreation_time;
 +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)
 +{
 +  int done = 0;
 +  FRAME_PTR f = SELECTED_FRAME ();
 +
 +  /* Too bad, but PostMessage will not work in non-GUI sessions.
 +     FIXME.  */
 +  if (!FRAME_W32_P (f))
 +    return;
 +
 +  /* A single buffer is used to communicate all notifications to the
 +     main thread.  Since both the main thread and several watcher
 +     threads could be active at the same time, we use a critical area
 +     and an "in-use" flag to synchronize them.  A watcher thread can
 +     only put its notifications in the buffer if it acquires the
 +     critical area and finds the "in-use" flag reset.  The main thread
 +     resets the flag after it is done processing notifications.
 +
 +     FIXME: is there a better way of dealing with this?  */
 +  while (!done && !*terminate)
 +    {
 +      enter_crit ();
 +      if (!notification_buffer_in_use)
 +      {
 +        if (info_size)
 +          memcpy (file_notifications, info, info_size);
 +        notifications_size = info_size;
 +        notifications_desc = hdir;
 +        /* 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
 +           notification in that case.  */
 +        if (PostMessage (FRAME_W32_WINDOW (f), WM_EMACS_FILENOTIFY, 0, 0))
 +          notification_buffer_in_use = 1;
 +        done = 1;
 +      }
 +      leave_crit ();
 +      if (!done)
 +      Sleep (5);
 +    }
 +}
 +
 +/* An APC routine to cancel outstanding directory watch.  Invoked by
 +   the main thread via QueueUserAPC.  This is needed because only the
 +   thread that issued the ReadDirectoryChangesW call can call CancelIo
 +   to cancel that.  (CancelIoEx is only available since Vista, so we
 +   cannot use it on XP.)  */
 +VOID CALLBACK
 +watch_end (ULONG_PTR arg)
 +{
 +  HANDLE hdir = (HANDLE)arg;
 +
 +  if (hdir && hdir != INVALID_HANDLE_VALUE)
 +    {
 +      CancelIo (hdir);
 +      CloseHandle (hdir);
 +    }
 +}
 +
 +/* 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)
 +{
 +  struct notification *dirwatch;
 +
 +  /* Who knows what happened?  Perhaps the OVERLAPPED structure was
 +     freed by someone already?  In any case, we cannot do anything
 +     with this request, so just punt and skip it.  FIXME: should we
 +     raise the 'terminate' flag in this case?  */
 +  if (!io_info)
 +    return;
 +
 +  /* We have a pointer to our dirwatch structure conveniently stashed
 +     away in the hEvent member of the OVERLAPPED struct.  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 = (struct notification *)io_info->hEvent;
 +  if (status == ERROR_OPERATION_ABORTED)
 +    {
 +      /* 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;
 +      dirwatch->dir = NULL;
 +      dirwatch->terminate = 1;
 +    }
 +  else
 +    {
 +      /* Tell the main thread we have notifications for it.  */
 +      send_notifications (dirwatch->buf, bytes_ret, dirwatch->dir,
 +                        &dirwatch->terminate);
 +    }
 +}
 +
 +/* Worker routine for the watch thread.  */
 +static DWORD WINAPI
 +watch_worker (LPVOID arg)
 +{
 +  struct notification *dirwatch = (struct notification *)arg;
 +
 +  do {
 +    BOOL status;
 +    DWORD sleep_result;
 +    DWORD bytes_ret = 0;
 +
 +    if (dirwatch->dir)
 +      {
 +      status = ReadDirectoryChangesW (dirwatch->dir, dirwatch->buf, 16384,
 +                                      dirwatch->subtree, dirwatch->filter,
 +                                      &bytes_ret,
 +                                      dirwatch->io_info, watch_completion);
 +      if (!status)
 +        {
 +          DebPrint (("watch_worker, abnormal exit: %lu\n", GetLastError ()));
 +          xfree (dirwatch->buf);
 +          dirwatch->buf = NULL;
 +          xfree (dirwatch->io_info);
 +          dirwatch->io_info = NULL;
 +          CloseHandle (dirwatch->dir);
 +          dirwatch->dir = NULL;
 +          xfree (dirwatch->watchee);
 +          dirwatch->watchee = NULL;
 +          return 1;
 +        }
 +      }
 +    /* Sleep indefinitely until awoken by the I/O completion, which
 +       could be either a change notification or a cancellation of the
 +       watch.  */
 +    sleep_result = SleepEx (INFINITE, TRUE);
 +  } while (!dirwatch->terminate);
 +
 +  return 0;
 +}
 +
 +/* Launch a thread to watch changes to FILE in a directory open on
 +   handle HDIR.  */
 +static int
 +start_watching (const char *file, HANDLE hdir, BOOL subdirs, DWORD flags)
 +{
 +  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;
 +
 +  /* 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);
 +
 +  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;
 +    }
 +  return 0;
 +}
 +
 +/* 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 *
 +add_watch (const char *parent_dir, const char *file, BOOL subdirs, DWORD flags)
 +{
 +  HANDLE hdir;
 +
 +  if (!file || !*file)
 +    return NULL;
 +
 +  hdir = CreateFile (parent_dir,
 +                   FILE_LIST_DIRECTORY,
 +                   /* FILE_SHARE_DELETE doesn't preclude other
 +                      processes from deleting files inside
 +                      parent_dir.  */
 +                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
 +                   NULL, OPEN_EXISTING,
 +                   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
 +                   NULL);
 +  if (hdir == INVALID_HANDLE_VALUE)
 +    return NULL;
 +
 +  if (start_watching (file, hdir, subdirs, flags) == 0)
 +    return hdir;
 +
 +  CloseHandle (hdir);
 +  return NULL;
 +}
 +
 +/* Stop watching a directory specified by its handle HDIR.  */
 +static int
 +remove_watch (HANDLE hdir)
 +{
 +  if (hdir == dirwatch.dir)
 +    {
 +      int i;
 +      BOOL status;
 +      DWORD exit_code, err;
 +
 +      /* Only the thread that issued the outstanding I/O call can call
 +       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))
 +      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;
 +      /* 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))
 +              && exit_code == STILL_ACTIVE))
 +          break;
 +        Sleep (10);
 +      }
 +      if ((status == FALSE && (err = GetLastError ()) == ERROR_INVALID_HANDLE)
 +        || exit_code == STILL_ACTIVE)
 +      {
 +        if (!(status == FALSE && err == ERROR_INVALID_HANDLE))
 +          TerminateThread (dirwatch.thr, 0);
 +      }
 +
 +      /* Clean up.  */
 +      if (dirwatch.thr)
 +      {
 +        CloseHandle (dirwatch.thr);
 +        dirwatch.thr = NULL;
 +      }
 +      return 0;
 +    }
 +  else if (!dirwatch.dir)
 +    {
 +      DebPrint (("Directory handle already closed!\n"));
 +      return 0;
 +    }
 +  else
 +    {
 +      DebPrint (("Unknown directory handle!\n"));
 +      return -1;
 +    }
 +}
 +
 +static DWORD
 +filter_list_to_flags (Lisp_Object filter_list)
 +{
 +  DWORD flags = 0;
 +
 +  if (NILP (filter_list))
 +    return flags;
 +
 +  if (!NILP (Fmember (Qfile_name, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_FILE_NAME;
 +  if (!NILP (Fmember (Qdirectory_name, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_DIR_NAME;
 +  if (!NILP (Fmember (Qattributes, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
 +  if (!NILP (Fmember (Qsize, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_SIZE;
 +  if (!NILP (Fmember (Qlast_write_time, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
 +  if (!NILP (Fmember (Qlast_access_time, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
 +  if (!NILP (Fmember (Qcreation_time, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_CREATION;
 +  if (!NILP (Fmember (Qsecurity_desc, filter_list)))
 +    flags |= FILE_NOTIFY_CHANGE_SECURITY;
 +
 +  return flags;
 +}
 +
 +DEFUN ("w32notify-add-watch", Fw32notify_add_watch,
 +       Sw32notify_add_watch, 3, 3, 0,
 +       doc: /* Add a watch for filesystem events pertaining to FILE.
 +
 +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.
 +
 +FILTER is a list of conditions for reporting an event.  It can include
 +the following symbols:
 +
 +  'file-name'          -- report file creation, deletion, or renaming
 +  'directory-name'     -- report directory creation, deletion, or renaming
 +  'attributes'         -- report changes in attributes
 +  'size'               -- report changes in file-size
 +  'last-write-time'    -- report changes in last-write time
 +  'last-access-time'   -- report changes in last-access time
 +  'creation-time'      -- report changes in creation time
 +  'security-desc'      -- report changes in security descriptor
 +
 +If FILE is a directory, and FILTER includes 'subtree', then all the
 +subdirectories will also be watched and changes in them reported.
 +
 +When any event happens that satisfies the conditions specified by
 +FILTER, Emacs will call the CALLBACK function passing it a single
 +argument EVENT, which is of the form
 +
 +  (DESCRIPTOR ACTION FILE)
 +
 +DESCRIPTOR is the same object as the one returned by this function.
 +ACTION is the description of the event.  It could be any one of the
 +following:
 +
 +  'added'        -- FILE was added
 +  'removed'      -- FILE was deleted
 +  'modified'     -- FILE's contents or its attributes were modified
 +  '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.  */)
 +  (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;
 +  Lisp_Object lisp_errstr;
 +  char *errstr;
 +
 +  CHECK_LIST (filter);
 +
 +  /* The underlying features are available only since XP.  */
 +  if (os_subtype == OS_9X
 +      || (w32_major_version == 5 && w32_major_version < 1))
 +    {
 +      errno = ENOSYS;
 +      report_file_error ("Watching filesystem events is not supported",
 +                       Qnil);
 +    }
 +
 +  if (dirwatch.dir)
 +    error ("File watch already active");
 +
 +  /* We needa 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));
 +  encoded_file = ENCODE_FILE (file);
 +
 +  fn_len = GetFullPathName (SDATA (encoded_file), MAX_PATH, parent_dir,
 +                          &basename);
 +  if (!fn_len)
 +    {
 +      errstr = w32_strerror (0);
 +      errno = EINVAL;
 +      if (!NILP (Vlocale_coding_system))
 +      lisp_errstr
 +        = code_convert_string_norecord (build_unibyte_string (errstr),
 +                                        Vlocale_coding_system, 0);
 +      else
 +      lisp_errstr = build_string (errstr);
 +      report_file_error ("GetFullPathName failed",
 +                       Fcons (lisp_errstr, Fcons (file, Qnil)));
 +    }
 +  /* We need the parent directory without the slash that follows it.
 +     If BASENAME is NULL, the argument was the root directory on its
 +     drive.  */
 +  if (basename)
 +    basename[-1] = '\0';
 +  else
 +    subdirs = TRUE;
 +
 +  if (!NILP (Fmember (Qsubtree, filter)))
 +    subdirs = TRUE;
 +
 +  flags = filter_list_to_flags (filter);
 +
 +  hdir = add_watch (parent_dir, basename, subdirs, flags);
 +  if (!hdir)
 +    {
 +      DWORD err = GetLastError ();
 +
 +      errno = EINVAL;
 +      if (err)
 +      {
 +        errstr = w32_strerror (err);
 +        if (!NILP (Vlocale_coding_system))
 +          lisp_errstr
 +            = code_convert_string_norecord (build_unibyte_string (errstr),
 +                                            Vlocale_coding_system, 0);
 +        else
 +          lisp_errstr = build_string (errstr);
 +        report_file_error ("Cannot watch file",
 +                           Fcons (lisp_errstr, Fcons (file, Qnil)));
 +      }
 +      else
 +      report_file_error ("Cannot watch file", Fcons (file, Qnil));
 +    }
 +  /* Store watch object in watch list. */
 +  watch_descriptor = make_number (hdir);
 +  watch_object = Fcons (watch_descriptor, callback);
 +  watch_list = Fcons (watch_object, watch_list);
 +
 +  return watch_descriptor;
 +}
 +
 +DEFUN ("w32notify-rm-watch", Fw32notify_rm_watch,
 +       Sw32notify_rm_watch, 1, 1, 0,
 +       doc: /* Remove an existing watch specified by its WATCH-DESCRIPTOR.
 +
 +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));
 +
 +  /* Remove watch descriptor from watch list. */
 +  watch_object = Fassoc (watch_descriptor, watch_list);
 +  if (!NILP (watch_object))
 +    watch_list = Fdelete (watch_object, watch_list);
 +
 +  return Qnil;
 +}
 +
 +Lisp_Object
 +get_watch_object (Lisp_Object desc)
 +{
 +  return Fassoc (desc, watch_list);
 +}
 +
 +void
 +syms_of_w32notify (void)
 +{
 +  DEFSYM (Qfile_name, "file-name");
 +  DEFSYM (Qdirectory_name, "directory-name");
 +  DEFSYM (Qattributes, "attributes");
 +  DEFSYM (Qsize, "size");
 +  DEFSYM (Qlast_write_time, "last-write-time");
 +  DEFSYM (Qlast_access_time, "last-access-time");
 +  DEFSYM (Qcreation_time, "creation-time");
 +  DEFSYM (Qsecurity_desc, "security-desc");
 +  DEFSYM (Qsubtree, "subtree");
 +
 +  defsubr (&Sw32notify_add_watch);
 +  defsubr (&Sw32notify_rm_watch);
 +
 +  staticpro (&watch_list);
 +
 +  Fprovide (intern_c_string ("w32notify"), Qnil);
 +}