1 /* Filesystem notifications support for GNU Emacs on the Microsoft Windows API.
3 Copyright (C) 2012-2018 Free Software Foundation, Inc.
5 Author: Eli Zaretskii <eliz@gnu.org>
7 This file is part of GNU Emacs.
9 GNU Emacs is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or (at
12 your option) any later version.
14 GNU Emacs is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
24 For each watch request, we launch a separate worker thread. The
25 worker thread runs the watch_worker function, which issues an
26 asynchronous call to ReadDirectoryChangesW, and then calls
27 WaitForSingleObjectEx to wait that an event be signaled
28 to terminate the thread.
29 Waiting with WaitForSingleObjectEx puts the thread in an
30 "alertable" state, so it wakes up when either (a) the call to
31 ReadDirectoryChangesW completes, or (b) the main thread instructs
32 the worker thread to terminate by signaling an event, see below.
34 When the ReadDirectoryChangesW call completes, its completion
35 routine watch_completion is automatically called. watch_completion
36 stashes the received file events in a linked list used to
37 communicate them to the main thread (using a critical section, so
38 that several threads could alter the same linked list), posts a
39 special message, WM_EMACS_FILENOTIFY, to the Emacs's message queue,
40 and returns. That causes the WaitForSingleObjectEx function call
41 inside watch_worker to return, but the thread won't terminate until
42 the event telling to do so will be signaled. The completion
43 routine issued another call to ReadDirectoryChangesW as quickly as
44 possible. (Except when it does not, see below.)
46 In a GUI session, the WM_EMACS_FILENOTIFY message posted to the
47 message queue gets dispatched to the main Emacs window procedure,
48 which queues it for processing by w32_read_socket. When
49 w32_read_socket sees this message, it accesses the linked list with file
50 notifications (using a critical section), extracts the information,
51 converts it to a series of FILE_NOTIFY_EVENT events, and stuffs
52 them into the input event queue to be processed by keyboard.c input
53 machinery (read_char via a call to kbd_buffer_get_event).
55 In a non-GUI session, we send the WM_EMACS_FILENOTIFY message to
56 the main (a.k.a. "Lisp") thread instead, since there are no window
57 procedures in console programs. That message wakes up
58 MsgWaitForMultipleObjects inside sys_select, which then signals to
59 its caller that some keyboard input is available. This causes
60 w32_console_read_socket to be called, which accesses the linked list
61 with file notifications and stuffs them into the input event queue
62 for keyboard.c to process.
64 When the FILE_NOTIFY_EVENT event is processed by keyboard.c's
65 kbd_buffer_get_event, it is converted to a Lispy event that can be
66 bound to a command. The default binding is file-notify-handle-event,
69 Routines w32_read_socket or w32_console_read_socket process notifications
70 sets as long as some are available.
72 When the watch is removed by a call to w32notify-rm-watch, the main
73 thread requests that the worker thread terminates by signaling the
74 appropriate event and queuing an APC for the worker thread. The
75 APC specifies the watch_end function to be called. watch_end calls
76 CancelIo on the outstanding ReadDirectoryChangesW call. When
77 watch_end returns, the watch_completion function is called one last
78 time with the ERROR_OPERATION_ABORTED status, which causes it to
79 clean up and set a flag telling watch_worker to exit without
80 issuing another ReadDirectoryChangesW call. Since watch_worker is
81 the thread procedure of the worker thread, exiting it causes the
82 thread to exit. The main thread waits for some time for the worker
83 thread to exit, and if it doesn't, terminates it forcibly. */
85 #define DEFER_MS_W32_H
91 /* Include CRT headers *before* ms-w32.h. */
97 #include "w32term.h" /* for enter_crit/leave_crit and WM_EMACS_FILENOTIFY */
98 #include "w32common.h" /* for OS version data */
99 #include "w32.h" /* for w32_strerror */
101 #include "keyboard.h"
102 #include "frame.h" /* needed by termhooks.h */
103 #include "termhooks.h" /* for FILE_NOTIFY_EVENT */
105 #define DIRWATCH_BUFFER_SIZE 16384
106 #define DIRWATCH_SIGNATURE 0x01233210
108 struct notification
{
109 BYTE
*buf
; /* buffer for ReadDirectoryChangesW */
110 OVERLAPPED
*io_info
; /* the OVERLAPPED structure for async I/O */
111 BOOL subtree
; /* whether to watch subdirectories */
112 DWORD filter
; /* bit mask for events to watch */
113 char *watchee
; /* the file we are interested in, UTF-8 encoded */
114 HANDLE dir
; /* handle to the watched directory */
115 HANDLE thr
; /* handle to the thread that watches */
116 HANDLE terminate
; /* event signaling the thread to terminate */
120 /* Used for communicating notifications to the main thread. */
121 struct notifications_set
*notifications_set_head
;
123 static Lisp_Object watch_list
;
125 /* Signal to the main thread that we have file notifications for it to
128 send_notifications (struct notifications_set
*ns
)
130 struct frame
*f
= SELECTED_FRAME ();
132 /* We add the current notification set to the linked list. Use the
133 critical section to make sure only one thread will access the
136 ns
->next
= notifications_set_head
;
137 ns
->prev
= notifications_set_head
->prev
;
139 notifications_set_head
->prev
= ns
;
142 /* If PostMessage fails, the message queue is full. If that
143 happens, the last thing they will worry about is file
144 notifications. So we effectively discard the notification in
146 if (FRAME_TERMCAP_P (f
))
147 /* We send the message to the main (a.k.a. "Lisp") thread, where
148 it will wake up MsgWaitForMultipleObjects inside sys_select,
149 causing it to report that there's some keyboard input
150 available. This will in turn cause w32_console_read_socket to
151 be called, which will pick up the file notifications. */
152 PostThreadMessage (dwMainThreadId
, WM_EMACS_FILENOTIFY
, 0, 0);
153 else if (FRAME_W32_P (f
))
154 PostMessage (FRAME_W32_WINDOW (f
),
155 WM_EMACS_FILENOTIFY
, 0, 0);
156 /* When we are running in batch mode, there's no one to send a
157 message, so we just signal the data is available and hope
158 sys_select will be called soon and will read the data. */
160 else if (FRAME_INITIAL_P (f
) && noninteractive
)
165 /* An APC routine to cancel outstanding directory watch. Invoked by
166 the main thread via QueueUserAPC. This is needed because only the
167 thread that issued the ReadDirectoryChangesW call can call CancelIo
168 to cancel that. (CancelIoEx is only available since Vista, so we
169 cannot use it on XP.) */
170 VOID CALLBACK
watch_end (ULONG_PTR
);
173 watch_end (ULONG_PTR arg
)
175 HANDLE hdir
= (HANDLE
)arg
;
177 if (hdir
&& hdir
!= INVALID_HANDLE_VALUE
)
181 /* A completion routine (a.k.a. "APC function") for handling events
182 read by ReadDirectoryChangesW. Called by the OS when the thread
183 which issued the asynchronous ReadDirectoryChangesW call is in the
184 "alertable state", i.e. waiting inside SleepEx call. */
185 VOID CALLBACK
watch_completion (DWORD
, DWORD
, OVERLAPPED
*);
188 watch_completion (DWORD status
, DWORD bytes_ret
, OVERLAPPED
*io_info
)
190 struct notification
*dirwatch
;
192 struct notifications_set
*ns
= NULL
;
193 BOOL terminate
= FALSE
;
195 /* Who knows what happened? Perhaps the OVERLAPPED structure was
196 freed by someone already? In any case, we cannot do anything
197 with this request, so just punt and skip it. FIXME: should we
198 raise the 'terminate' flag in this case? */
201 DebPrint(("watch_completion: io_info is null.\n"));
205 /* We have a pointer to our dirwatch structure conveniently stashed
206 away in the hEvent member of the OVERLAPPED struct. According to
207 MSDN documentation of ReadDirectoryChangesW: "The hEvent member
208 of the OVERLAPPED structure is not used by the system, so you can
210 dirwatch
= (struct notification
*)io_info
->hEvent
;
212 if (status
== ERROR_OPERATION_ABORTED
)
214 /* We've been called because the main thread told us to issue
215 CancelIo on the directory we watch, and watch_end did so.
216 We must exit, without issuing another call to
217 ReadDirectoryChangesW. */
221 /* We allocate a new set of notifications to be linked to the linked
222 list of notifications set. This will be processed by Emacs event
223 loop in the main thread. We need to duplicate the notifications
224 buffer, but not the dirwatch structure. */
226 /* Implementation note: In general, allocating memory in non-main
227 threads is a no-no in Emacs. We certainly cannot call xmalloc
228 and friends, because it can longjmp when allocation fails, which
229 will crash Emacs because the jmp_buf is set up to a location on
230 the main thread's stack. However, we can call 'malloc' directly,
231 since that is redirected to HeapAlloc that uses our private heap,
232 see w32heap.c, and that is thread-safe. */
233 ns
= malloc (sizeof(struct notifications_set
));
236 memset (ns
, 0, sizeof(struct notifications_set
));
237 ns
->notifications
= malloc (bytes_ret
);
238 if (ns
->notifications
)
240 memcpy (ns
->notifications
, dirwatch
->buf
, bytes_ret
);
241 ns
->size
= bytes_ret
;
251 DebPrint(("Out of memory. Notifications lost."));
253 /* Calling ReadDirectoryChangesW quickly to watch again for new
255 if (!ReadDirectoryChangesW (dirwatch
->dir
, dirwatch
->buf
,
256 DIRWATCH_BUFFER_SIZE
, dirwatch
->subtree
,
257 dirwatch
->filter
, &_bytes
, dirwatch
->io_info
,
260 DebPrint (("ReadDirectoryChangesW error: %lu\n", GetLastError ()));
261 /* If this call fails, it means that the directory is not
262 watchable any more. We need to terminate the worker thread.
263 Still, we will wait until the current notifications have been
264 sent to the main thread. */
269 send_notifications(ns
);
271 /* If we were asked to terminate the thread, then fire the event. */
273 SetEvent(dirwatch
->terminate
);
276 /* Worker routine for the watch thread. */
278 watch_worker (LPVOID arg
)
280 struct notification
*dirwatch
= (struct notification
*)arg
;
287 bErr
= ReadDirectoryChangesW (dirwatch
->dir
, dirwatch
->buf
,
288 DIRWATCH_BUFFER_SIZE
, dirwatch
->subtree
,
289 dirwatch
->filter
, &_bytes
,
290 dirwatch
->io_info
, watch_completion
);
293 DebPrint (("ReadDirectoryChangesW: %lu\n", GetLastError ()));
294 /* We cannot remove the dirwatch object from watch_list,
295 because we are in a separate thread. For the same
296 reason, we also cannot free memory consumed by the
297 buffers allocated for the dirwatch object. So we close
298 the directory handle, but do not free the object itself
299 or its buffers. We also don't touch the signature. This
300 way, remove_watch can still identify the object, remove
301 it, and free its memory. */
302 CloseHandle (dirwatch
->dir
);
303 dirwatch
->dir
= NULL
;
309 status
= WaitForSingleObjectEx(dirwatch
->terminate
, INFINITE
, TRUE
);
310 } while (status
== WAIT_IO_COMPLETION
);
312 /* The thread is about to terminate, so we clean up the dir handle. */
313 CloseHandle (dirwatch
->dir
);
314 dirwatch
->dir
= NULL
;
318 /* Launch a thread to watch changes to FILE in a directory open on
320 static struct notification
*
321 start_watching (const char *file
, HANDLE hdir
, BOOL subdirs
, DWORD flags
)
323 struct notification
*dirwatch
= xzalloc (sizeof (struct notification
));
325 dirwatch
->signature
= DIRWATCH_SIGNATURE
;
326 dirwatch
->buf
= xmalloc (DIRWATCH_BUFFER_SIZE
);
327 dirwatch
->io_info
= xzalloc (sizeof(OVERLAPPED
));
328 /* Stash a pointer to dirwatch structure for use by the completion
329 routine. According to MSDN documentation of ReadDirectoryChangesW:
330 "The hEvent member of the OVERLAPPED structure is not used by the
331 system, so you can use it yourself." */
332 dirwatch
->io_info
->hEvent
= dirwatch
;
333 dirwatch
->subtree
= subdirs
;
334 dirwatch
->filter
= flags
;
335 dirwatch
->watchee
= xstrdup (file
);
337 dirwatch
->terminate
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
339 dirwatch
->dir
= hdir
;
341 /* See w32proc.c where it calls CreateThread for the story behind
342 the 2nd and 5th argument in the call to CreateThread. */
343 dirwatch
->thr
= CreateThread (NULL
, 64 * 1024, watch_worker
, (void *)dirwatch
,
348 CloseHandle(dirwatch
->terminate
);
349 xfree (dirwatch
->buf
);
350 xfree (dirwatch
->io_info
);
351 xfree (dirwatch
->watchee
);
357 /* Called from the main thread to start watching FILE in PARENT_DIR,
358 subject to FLAGS. If SUBDIRS is TRUE, watch the subdirectories of
359 PARENT_DIR as well. Value is a pointer to 'struct notification'
360 used by the thread that watches the changes. */
361 static struct notification
*
362 add_watch (const char *parent_dir
, const char *file
, BOOL subdirs
, DWORD flags
)
365 struct notification
*dirwatch
= NULL
;
370 if (w32_unicode_filenames
)
372 wchar_t dir_w
[MAX_PATH
], file_w
[MAX_PATH
];
374 filename_to_utf16 (parent_dir
, dir_w
);
376 filename_to_utf16 (file
, file_w
);
380 hdir
= CreateFileW (dir_w
,
382 /* FILE_SHARE_DELETE doesn't preclude other
383 processes from deleting files inside
385 FILE_SHARE_READ
|FILE_SHARE_WRITE
|FILE_SHARE_DELETE
,
387 FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED
,
392 char dir_a
[MAX_PATH
], file_a
[MAX_PATH
];
394 filename_to_ansi (parent_dir
, dir_a
);
396 filename_to_ansi (file
, file_a
);
400 hdir
= CreateFileA (dir_a
,
402 FILE_SHARE_READ
|FILE_SHARE_WRITE
|FILE_SHARE_DELETE
,
404 FILE_FLAG_BACKUP_SEMANTICS
| FILE_FLAG_OVERLAPPED
,
407 if (hdir
== INVALID_HANDLE_VALUE
)
410 if ((dirwatch
= start_watching (file
, hdir
, subdirs
, flags
)) == NULL
)
413 dirwatch
->dir
= NULL
;
419 /* Stop watching a directory specified by a pointer to its dirwatch object. */
421 remove_watch (struct notification
*dirwatch
)
423 if (dirwatch
&& dirwatch
->signature
== DIRWATCH_SIGNATURE
)
427 DWORD exit_code
= 0, err
= 0;
429 /* Only the thread that issued the outstanding I/O call can call
430 CancelIo on it. (CancelIoEx is available only since Vista.)
431 So we need to queue an APC for the worker thread telling it
433 if (!QueueUserAPC (watch_end
, dirwatch
->thr
, (ULONG_PTR
)dirwatch
->dir
))
434 DebPrint (("QueueUserAPC failed (%lu)!\n", GetLastError ()));
436 /* We also signal the thread that it can terminate. */
437 SetEvent(dirwatch
->terminate
);
439 /* Wait for the thread to exit. FIXME: is there a better method
440 that is not overly complex? */
441 for (i
= 0; i
< 50; i
++)
443 if (!((status
= GetExitCodeThread (dirwatch
->thr
, &exit_code
))
444 && exit_code
== STILL_ACTIVE
))
449 if ((status
== FALSE
&& (err
= GetLastError ()) == ERROR_INVALID_HANDLE
)
450 || exit_code
== STILL_ACTIVE
)
452 if (!(status
== FALSE
&& err
== ERROR_INVALID_HANDLE
))
454 DebPrint(("Forcing thread termination.\n"));
455 TerminateThread (dirwatch
->thr
, 0);
457 CloseHandle (dirwatch
->dir
);
464 CloseHandle (dirwatch
->thr
);
465 dirwatch
->thr
= NULL
;
467 CloseHandle(dirwatch
->terminate
);
468 xfree (dirwatch
->buf
);
469 xfree (dirwatch
->io_info
);
470 xfree (dirwatch
->watchee
);
476 DebPrint (("Unknown dirwatch object!\n"));
482 filter_list_to_flags (Lisp_Object filter_list
)
486 if (NILP (filter_list
))
489 if (!NILP (Fmember (Qfile_name
, filter_list
)))
490 flags
|= FILE_NOTIFY_CHANGE_FILE_NAME
;
491 if (!NILP (Fmember (Qdirectory_name
, filter_list
)))
492 flags
|= FILE_NOTIFY_CHANGE_DIR_NAME
;
493 if (!NILP (Fmember (Qattributes
, filter_list
)))
494 flags
|= FILE_NOTIFY_CHANGE_ATTRIBUTES
;
495 if (!NILP (Fmember (Qsize
, filter_list
)))
496 flags
|= FILE_NOTIFY_CHANGE_SIZE
;
497 if (!NILP (Fmember (Qlast_write_time
, filter_list
)))
498 flags
|= FILE_NOTIFY_CHANGE_LAST_WRITE
;
499 if (!NILP (Fmember (Qlast_access_time
, filter_list
)))
500 flags
|= FILE_NOTIFY_CHANGE_LAST_ACCESS
;
501 if (!NILP (Fmember (Qcreation_time
, filter_list
)))
502 flags
|= FILE_NOTIFY_CHANGE_CREATION
;
503 if (!NILP (Fmember (Qsecurity_desc
, filter_list
)))
504 flags
|= FILE_NOTIFY_CHANGE_SECURITY
;
509 DEFUN ("w32notify-add-watch", Fw32notify_add_watch
,
510 Sw32notify_add_watch
, 3, 3, 0,
511 doc
: /* Add a watch for filesystem events pertaining to FILE.
513 This arranges for filesystem events pertaining to FILE to be reported
514 to Emacs. Use `w32notify-rm-watch' to cancel the watch.
516 Value is a descriptor for the added watch. If the file cannot be
517 watched for some reason, this function signals a `file-error' error.
519 FILTER is a list of conditions for reporting an event. It can include
520 the following symbols:
522 'file-name' -- report file creation, deletion, or renaming
523 'directory-name' -- report directory creation, deletion, or renaming
524 'attributes' -- report changes in attributes
525 'size' -- report changes in file-size
526 'last-write-time' -- report changes in last-write time
527 'last-access-time' -- report changes in last-access time
528 'creation-time' -- report changes in creation time
529 'security-desc' -- report changes in security descriptor
531 If FILE is a directory, and FILTER includes 'subtree', then all the
532 subdirectories will also be watched and changes in them reported.
534 When any event happens that satisfies the conditions specified by
535 FILTER, Emacs will call the CALLBACK function passing it a single
536 argument EVENT, which is of the form
538 (DESCRIPTOR ACTION FILE)
540 DESCRIPTOR is the same object as the one returned by this function.
541 ACTION is the description of the event. It could be any one of the
544 'added' -- FILE was added
545 'removed' -- FILE was deleted
546 'modified' -- FILE's contents or its attributes were modified
547 'renamed-from' -- a file was renamed whose old name was FILE
548 'renamed-to' -- a file was renamed and its new name is FILE
550 FILE is the name of the file whose event is being reported.
552 Note that some networked filesystems, such as Samba-mounted Unix
553 volumes, might not send notifications about file changes. In these
554 cases, this function will return a valid descriptor, but notifications
555 will never come in. Volumes shared from remote Windows machines do
556 generate notifications correctly, though. */)
557 (Lisp_Object file
, Lisp_Object filter
, Lisp_Object callback
)
559 Lisp_Object dirfn
, basefn
, watch_object
, watch_descriptor
;
561 BOOL subdirs
= FALSE
;
562 struct notification
*dirwatch
= NULL
;
563 Lisp_Object lisp_errstr
;
568 /* The underlying features are available only since XP. */
569 if (os_subtype
== OS_9X
570 || (w32_major_version
== 5 && w32_minor_version
< 1))
573 report_file_notify_error ("Watching filesystem events is not supported",
577 /* filenotify.el always passes us a directory, either the parent
578 directory of a file to be watched, or the directory to be
580 file
= Fdirectory_file_name (Fexpand_file_name (file
, Qnil
));
581 if (NILP (Ffile_directory_p (file
)))
583 /* This should only happen if we are called directly, not via
584 filenotify.el. If BASEFN is empty, the argument was the root
585 directory on its drive. */
586 dirfn
= ENCODE_FILE (Ffile_name_directory (file
));
587 basefn
= ENCODE_FILE (Ffile_name_nondirectory (file
));
588 if (*SDATA (basefn
) == '\0')
593 dirfn
= ENCODE_FILE (file
);
597 if (!NILP (Fmember (Qsubtree
, filter
)))
600 flags
= filter_list_to_flags (filter
);
602 dirwatch
= add_watch (SSDATA (dirfn
), NILP (basefn
) ? "" : SSDATA (basefn
),
606 DWORD err
= GetLastError ();
611 errstr
= w32_strerror (err
);
612 if (!NILP (Vlocale_coding_system
))
614 = code_convert_string_norecord (build_unibyte_string (errstr
),
615 Vlocale_coding_system
, 0);
617 lisp_errstr
= build_string (errstr
);
618 report_file_notify_error ("Cannot watch file",
619 Fcons (lisp_errstr
, Fcons (file
, Qnil
)));
622 report_file_notify_error ("Cannot watch file", Fcons (file
, Qnil
));
624 /* Store watch object in watch list. */
625 watch_descriptor
= make_pointer_integer (dirwatch
);
626 watch_object
= Fcons (watch_descriptor
, callback
);
627 watch_list
= Fcons (watch_object
, watch_list
);
629 return watch_descriptor
;
632 DEFUN ("w32notify-rm-watch", Fw32notify_rm_watch
,
633 Sw32notify_rm_watch
, 1, 1, 0,
634 doc
: /* Remove an existing watch specified by its WATCH-DESCRIPTOR.
636 WATCH-DESCRIPTOR should be an object returned by `w32notify-add-watch'. */)
637 (Lisp_Object watch_descriptor
)
639 Lisp_Object watch_object
;
640 struct notification
*dirwatch
;
643 /* Remove the watch object from watch list. Do this before freeing
644 the object, do that even if we fail to free it, watch_list is
645 kept free of junk. */
646 watch_object
= Fassoc (watch_descriptor
, watch_list
, Qnil
);
647 if (!NILP (watch_object
))
649 watch_list
= Fdelete (watch_object
, watch_list
);
650 dirwatch
= (struct notification
*)XINTPTR (watch_descriptor
);
651 if (w32_valid_pointer_p (dirwatch
, sizeof(struct notification
)))
652 status
= remove_watch (dirwatch
);
656 report_file_notify_error ("Invalid watch descriptor",
657 Fcons (watch_descriptor
, Qnil
));
663 w32_get_watch_object (void *desc
)
665 Lisp_Object descriptor
= make_pointer_integer (desc
);
667 /* This is called from the input queue handling code, inside a
668 critical section, so we cannot possibly quit if watch_list is not
669 in the right condition. */
670 return NILP (watch_list
) ? Qnil
: assoc_no_quit (descriptor
, watch_list
);
673 DEFUN ("w32notify-valid-p", Fw32notify_valid_p
, Sw32notify_valid_p
, 1, 1, 0,
674 doc
: /* Check a watch specified by its WATCH-DESCRIPTOR for validity.
676 WATCH-DESCRIPTOR should be an object returned by `w32notify-add-watch'.
678 A watch can become invalid if the directory it watches is deleted, or if
679 the watcher thread exits abnormally for any other reason. Removing the
680 watch by calling `w32notify-rm-watch' also makes it invalid. */)
681 (Lisp_Object watch_descriptor
)
683 Lisp_Object watch_object
= Fassoc (watch_descriptor
, watch_list
, Qnil
);
685 if (!NILP (watch_object
))
687 struct notification
*dirwatch
=
688 (struct notification
*)XINTPTR (watch_descriptor
);
689 if (w32_valid_pointer_p (dirwatch
, sizeof(struct notification
))
690 && dirwatch
->dir
!= NULL
)
698 globals_of_w32notify (void)
704 syms_of_w32notify (void)
706 DEFSYM (Qfile_name
, "file-name");
707 DEFSYM (Qdirectory_name
, "directory-name");
708 DEFSYM (Qattributes
, "attributes");
709 DEFSYM (Qlast_write_time
, "last-write-time");
710 DEFSYM (Qlast_access_time
, "last-access-time");
711 DEFSYM (Qcreation_time
, "creation-time");
712 DEFSYM (Qsecurity_desc
, "security-desc");
713 DEFSYM (Qsubtree
, "subtree");
715 defsubr (&Sw32notify_add_watch
);
716 defsubr (&Sw32notify_rm_watch
);
717 defsubr (&Sw32notify_valid_p
);
719 staticpro (&watch_list
);
721 Fprovide (intern_c_string ("w32notify"), Qnil
);