Use XIL/XLI instead of make_number/XINT for converting descriptor to a ptr.
[bpt/emacs.git] / src / w32notify.c
1 /* Filesystem notifications support for GNU Emacs on the Microsoft Windows API.
2 Copyright (C) 2012 Free Software Foundation, Inc.
3
4 This file is part of GNU Emacs.
5
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
18
19 /* Design overview:
20
21 For each watch request, we launch a separate worker thread. The
22 worker thread runs the watch_worker function, which issues an
23 asynchronous call to ReadDirectoryChangesW, and then waits for that
24 call to complete in SleepEx. Waiting in SleepEx puts the thread in
25 an alertable state, so it wakes up when either (a) the call to
26 ReadDirectoryChangesW completes, or (b) the main thread instructs
27 the worker thread to terminate by sending it an APC, see below.
28
29 When the ReadDirectoryChangesW call completes, its completion
30 routine watch_completion is automatically called. watch_completion
31 stashes the received file events in a buffer used to communicate
32 them to the main thread (using a critical section, so that several
33 threads could use the same buffer), posts a special message,
34 WM_EMACS_FILENOTIFY, to the Emacs's message queue, and returns.
35 That causes the SleepEx function call inside watch_worker to
36 return, and watch_worker then issues another call to
37 ReadDirectoryChangesW. (Except when it does not, see below.)
38
39 In a GUI session, The WM_EMACS_FILENOTIFY message, posted to the
40 message queue gets dispatched to the main Emacs window procedure,
41 which queues it for processing by w32_read_socket. When
42 w32_read_socket sees this message, it accesses the buffer with file
43 notifications (using a critical section), extracts the information,
44 converts it to a series of FILE_NOTIFY_EVENT events, and stuffs
45 them into the input event queue to be processed by keyboard.c input
46 machinery (read_char via a call to kbd_buffer_get_event).
47
48 In a non-GUI session, we send the WM_EMACS_FILENOTIFY message to
49 the main (a.k.a. "Lisp") thread instead, since there are no window
50 procedures in console programs. That message wakes up
51 MsgWaitForMultipleObjects inside sys_select, which then signals to
52 its caller that some keyboard input is available. This causes
53 w32_console_read_socket to be called, which accesses the buffer
54 with file notifications and stuffs them into the input event queue
55 for keyboard.c to process.
56
57 When the FILE_NOTIFY_EVENT event is processed by keyboard.c's
58 kbd_buffer_get_event, it is converted to a Lispy event that can be
59 bound to a command. The default binding is w32notify-handle-event,
60 defined on subr.el.
61
62 After w32_read_socket or w32_console_read_socket is done processing
63 the notifications, it resets a flag signaling to all watch worker
64 threads that the notifications buffer is available for more input.
65
66 When the watch is removed by a call to w32notify-rm-watch, the main
67 thread requests that the worker thread terminates by queuing an APC
68 for the worker thread. The APC specifies the watch_end function to
69 be called. watch_end calls CancelIo on the outstanding
70 ReadDirectoryChangesW call and closes the handle on which the
71 watched directory was open. When watch_end returns, the
72 watch_completion function is called one last time with the
73 ERROR_OPERATION_ABORTED status, which causes it to clean up and set
74 a flag telling watch_worker to exit without issuing another
75 ReadDirectoryChangesW call. The main thread waits for some time
76 for the worker thread to exit, and if it doesn't, terminates it
77 forcibly. */
78
79 #include <stddef.h>
80 #include <errno.h>
81
82 /* must include CRT headers *before* config.h */
83 #include <config.h>
84
85 #include <windows.h>
86
87 #include "lisp.h"
88 #include "w32term.h" /* for enter_crit/leave_crit and WM_EMACS_FILENOTIFY */
89 #include "w32common.h" /* for OS version data */
90 #include "w32.h" /* for w32_strerror */
91 #include "coding.h"
92 #include "keyboard.h"
93 #include "frame.h" /* needed by termhooks.h */
94 #include "termhooks.h" /* for FILE_NOTIFY_EVENT */
95
96 #define DIRWATCH_SIGNATURE 0x01233210
97
98 struct notification {
99 BYTE *buf; /* buffer for ReadDirectoryChangesW */
100 OVERLAPPED *io_info; /* the OVERLAPPED structure for async I/O */
101 BOOL subtree; /* whether to watch subdirectories */
102 DWORD filter; /* bit mask for events to watch */
103 char *watchee; /* the file we are interested in */
104 HANDLE dir; /* handle to the watched directory */
105 HANDLE thr; /* handle to the thread that watches */
106 volatile int terminate; /* if non-zero, request for the thread to terminate */
107 unsigned signature;
108 };
109
110 /* Used for communicating notifications to the main thread. */
111 volatile int notification_buffer_in_use;
112 BYTE file_notifications[16384];
113 DWORD notifications_size;
114 void *notifications_desc;
115
116 static Lisp_Object Qfile_name, Qdirectory_name, Qattributes, Qsize;
117 static Lisp_Object Qlast_write_time, Qlast_access_time, Qcreation_time;
118 static Lisp_Object Qsecurity_desc, Qsubtree, watch_list;
119
120 /* Signal to the main thread that we have file notifications for it to
121 process. */
122 static void
123 send_notifications (BYTE *info, DWORD info_size, void *desc,
124 volatile int *terminate)
125 {
126 int done = 0;
127 FRAME_PTR f = SELECTED_FRAME ();
128
129 /* A single buffer is used to communicate all notifications to the
130 main thread. Since both the main thread and several watcher
131 threads could be active at the same time, we use a critical area
132 and an "in-use" flag to synchronize them. A watcher thread can
133 only put its notifications in the buffer if it acquires the
134 critical area and finds the "in-use" flag reset. The main thread
135 resets the flag after it is done processing notifications.
136
137 FIXME: is there a better way of dealing with this? */
138 while (!done && !*terminate)
139 {
140 enter_crit ();
141 if (!notification_buffer_in_use)
142 {
143 if (info_size)
144 memcpy (file_notifications, info, info_size);
145 notifications_size = info_size;
146 notifications_desc = desc;
147 /* If PostMessage fails, the message queue is full. If that
148 happens, the last thing they will worry about is file
149 notifications. So we effectively discard the
150 notification in that case. */
151 if ((FRAME_TERMCAP_P (f)
152 /* We send the message to the main (a.k.a. "Lisp")
153 thread, where it will wake up MsgWaitForMultipleObjects
154 inside sys_select, causing it to report that there's
155 some keyboard input available. This will in turn cause
156 w32_console_read_socket to be called, which will pick
157 up the file notifications. */
158 && PostThreadMessage (dwMainThreadId, WM_EMACS_FILENOTIFY, 0, 0))
159 || (FRAME_W32_P (f)
160 && PostMessage (FRAME_W32_WINDOW (f),
161 WM_EMACS_FILENOTIFY, 0, 0)))
162 notification_buffer_in_use = 1;
163 done = 1;
164 }
165 leave_crit ();
166 if (!done)
167 Sleep (5);
168 }
169 }
170
171 /* An APC routine to cancel outstanding directory watch. Invoked by
172 the main thread via QueueUserAPC. This is needed because only the
173 thread that issued the ReadDirectoryChangesW call can call CancelIo
174 to cancel that. (CancelIoEx is only available since Vista, so we
175 cannot use it on XP.) */
176 VOID CALLBACK
177 watch_end (ULONG_PTR arg)
178 {
179 HANDLE hdir = (HANDLE)arg;
180
181 if (hdir && hdir != INVALID_HANDLE_VALUE)
182 {
183 CancelIo (hdir);
184 CloseHandle (hdir);
185 }
186 }
187
188 /* A completion routine (a.k.a. APC function) for handling events read
189 by ReadDirectoryChangesW. Called by the OS when the thread which
190 issued the asynchronous ReadDirectoryChangesW call is in the
191 "alertable state", i.e. waiting inside SleepEx call. */
192 VOID CALLBACK
193 watch_completion (DWORD status, DWORD bytes_ret, OVERLAPPED *io_info)
194 {
195 struct notification *dirwatch;
196
197 /* Who knows what happened? Perhaps the OVERLAPPED structure was
198 freed by someone already? In any case, we cannot do anything
199 with this request, so just punt and skip it. FIXME: should we
200 raise the 'terminate' flag in this case? */
201 if (!io_info)
202 return;
203
204 /* We have a pointer to our dirwatch structure conveniently stashed
205 away in the hEvent member of the OVERLAPPED struct. According to
206 MSDN documentation of ReadDirectoryChangesW: "The hEvent member
207 of the OVERLAPPED structure is not used by the system, so you can
208 use it yourself." */
209 dirwatch = (struct notification *)io_info->hEvent;
210 if (status == ERROR_OPERATION_ABORTED)
211 {
212 /* We've been called because the main thread told us to issue
213 CancelIo on the directory we watch, and watch_end did so.
214 The directory handle is already closed. We should clean up
215 and exit, signalling to the thread worker routine not to
216 issue another call to ReadDirectoryChangesW. Note that we
217 don't free the dirwatch object itself; this is done by the
218 main thread in remove_watch. */
219 xfree (dirwatch->buf);
220 dirwatch->buf = NULL;
221 xfree (dirwatch->io_info);
222 dirwatch->io_info = NULL;
223 xfree (dirwatch->watchee);
224 dirwatch->watchee = NULL;
225 dirwatch->dir = NULL;
226 dirwatch->terminate = 1;
227 }
228 else
229 {
230 /* Tell the main thread we have notifications for it. */
231 send_notifications (dirwatch->buf, bytes_ret, dirwatch,
232 &dirwatch->terminate);
233 }
234 }
235
236 /* Worker routine for the watch thread. */
237 static DWORD WINAPI
238 watch_worker (LPVOID arg)
239 {
240 struct notification *dirwatch = (struct notification *)arg;
241
242 do {
243 BOOL status;
244 DWORD sleep_result;
245 DWORD bytes_ret = 0;
246
247 if (dirwatch->dir)
248 {
249 status = ReadDirectoryChangesW (dirwatch->dir, dirwatch->buf, 16384,
250 dirwatch->subtree, dirwatch->filter,
251 &bytes_ret,
252 dirwatch->io_info, watch_completion);
253 if (!status)
254 {
255 DebPrint (("watch_worker, abnormal exit: %lu\n", GetLastError ()));
256 /* We cannot remove the dirwatch object from watch_list,
257 because we are in a separate thread. So we free and
258 zero out all the pointers in the object, but do not
259 free the object itself. We also don't touch the
260 signature. This way, remove_watch can still identify
261 the object, remove it, and free its memory. */
262 xfree (dirwatch->buf);
263 dirwatch->buf = NULL;
264 xfree (dirwatch->io_info);
265 dirwatch->io_info = NULL;
266 CloseHandle (dirwatch->dir);
267 dirwatch->dir = NULL;
268 xfree (dirwatch->watchee);
269 dirwatch->watchee = NULL;
270 return 1;
271 }
272 }
273 /* Sleep indefinitely until awoken by the I/O completion, which
274 could be either a change notification or a cancellation of the
275 watch. */
276 sleep_result = SleepEx (INFINITE, TRUE);
277 } while (!dirwatch->terminate);
278
279 return 0;
280 }
281
282 /* Launch a thread to watch changes to FILE in a directory open on
283 handle HDIR. */
284 static struct notification *
285 start_watching (const char *file, HANDLE hdir, BOOL subdirs, DWORD flags)
286 {
287 struct notification *dirwatch = xzalloc (sizeof (struct notification));
288 HANDLE thr;
289
290 dirwatch->signature = DIRWATCH_SIGNATURE;
291 dirwatch->buf = xmalloc (16384);
292 dirwatch->io_info = xzalloc (sizeof(OVERLAPPED));
293 /* Stash a pointer to dirwatch structure for use by the completion
294 routine. According to MSDN documentation of ReadDirectoryChangesW:
295 "The hEvent member of the OVERLAPPED structure is not used by the
296 system, so you can use it yourself." */
297 dirwatch->io_info->hEvent = dirwatch;
298 dirwatch->subtree = subdirs;
299 dirwatch->filter = flags;
300 dirwatch->watchee = xstrdup (file);
301 dirwatch->terminate = 0;
302 dirwatch->dir = hdir;
303
304 /* See w32proc.c where it calls CreateThread for the story behind
305 the 2nd and 5th argument in the call to CreateThread. */
306 dirwatch->thr = CreateThread (NULL, 64 * 1024, watch_worker, (void *)dirwatch,
307 0x00010000, NULL);
308
309 if (!dirwatch->thr)
310 {
311 xfree (dirwatch->buf);
312 xfree (dirwatch->io_info);
313 xfree (dirwatch->watchee);
314 xfree (dirwatch);
315 dirwatch = NULL;
316 }
317 return dirwatch;
318 }
319
320 /* Called from the main thread to start watching FILE in PARENT_DIR,
321 subject to FLAGS. If SUBDIRS is TRUE, watch the subdirectories of
322 PARENT_DIR as well. Value is a pointer to 'struct notification'
323 used by the thread that watches the changes. */
324 static struct notification *
325 add_watch (const char *parent_dir, const char *file, BOOL subdirs, DWORD flags)
326 {
327 HANDLE hdir;
328 struct notification *dirwatch = NULL;
329
330 if (!file || !*file)
331 return NULL;
332
333 hdir = CreateFile (parent_dir,
334 FILE_LIST_DIRECTORY,
335 /* FILE_SHARE_DELETE doesn't preclude other
336 processes from deleting files inside
337 parent_dir. */
338 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
339 NULL, OPEN_EXISTING,
340 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
341 NULL);
342 if (hdir == INVALID_HANDLE_VALUE)
343 return NULL;
344
345 if ((dirwatch = start_watching (file, hdir, subdirs, flags)) == NULL)
346 CloseHandle (hdir);
347
348 return dirwatch;
349 }
350
351 /* Stop watching a directory specified by a pointer to its dirwatch object. */
352 static int
353 remove_watch (struct notification *dirwatch)
354 {
355 if (dirwatch && dirwatch->signature == DIRWATCH_SIGNATURE)
356 {
357 int i;
358 BOOL status;
359 DWORD exit_code, err;
360
361 /* Only the thread that issued the outstanding I/O call can call
362 CancelIo on it. (CancelIoEx is available only since Vista.)
363 So we need to queue an APC for the worker thread telling it
364 to terminate. */
365 if (!QueueUserAPC (watch_end, dirwatch->thr, (ULONG_PTR)dirwatch->dir))
366 DebPrint (("QueueUserAPC failed (%lu)!\n", GetLastError ()));
367 /* We also set the terminate flag, for when the thread is
368 waiting on the critical section that never gets acquired.
369 FIXME: is there a cleaner method? Using SleepEx there is a
370 no-no, as that will lead to recursive APC invocations and
371 stack overflow. */
372 dirwatch->terminate = 1;
373 /* Wait for the thread to exit. FIXME: is there a better method
374 that is not overly complex? */
375 for (i = 0; i < 50; i++)
376 {
377 if (!((status = GetExitCodeThread (dirwatch->thr, &exit_code))
378 && exit_code == STILL_ACTIVE))
379 break;
380 Sleep (10);
381 }
382 if ((status == FALSE && (err = GetLastError ()) == ERROR_INVALID_HANDLE)
383 || exit_code == STILL_ACTIVE)
384 {
385 if (!(status == FALSE && err == ERROR_INVALID_HANDLE))
386 {
387 TerminateThread (dirwatch->thr, 0);
388 if (dirwatch->dir)
389 CloseHandle (dirwatch->dir);
390 }
391 }
392
393 /* Clean up. */
394 if (dirwatch->thr)
395 {
396 CloseHandle (dirwatch->thr);
397 dirwatch->thr = NULL;
398 }
399 xfree (dirwatch->buf);
400 xfree (dirwatch->io_info);
401 xfree (dirwatch->watchee);
402 xfree (dirwatch);
403
404 return 0;
405 }
406 else
407 {
408 DebPrint (("Unknown dirwatch object!\n"));
409 return -1;
410 }
411 }
412
413 static DWORD
414 filter_list_to_flags (Lisp_Object filter_list)
415 {
416 DWORD flags = 0;
417
418 if (NILP (filter_list))
419 return flags;
420
421 if (!NILP (Fmember (Qfile_name, filter_list)))
422 flags |= FILE_NOTIFY_CHANGE_FILE_NAME;
423 if (!NILP (Fmember (Qdirectory_name, filter_list)))
424 flags |= FILE_NOTIFY_CHANGE_DIR_NAME;
425 if (!NILP (Fmember (Qattributes, filter_list)))
426 flags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
427 if (!NILP (Fmember (Qsize, filter_list)))
428 flags |= FILE_NOTIFY_CHANGE_SIZE;
429 if (!NILP (Fmember (Qlast_write_time, filter_list)))
430 flags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
431 if (!NILP (Fmember (Qlast_access_time, filter_list)))
432 flags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
433 if (!NILP (Fmember (Qcreation_time, filter_list)))
434 flags |= FILE_NOTIFY_CHANGE_CREATION;
435 if (!NILP (Fmember (Qsecurity_desc, filter_list)))
436 flags |= FILE_NOTIFY_CHANGE_SECURITY;
437
438 return flags;
439 }
440
441 DEFUN ("w32notify-add-watch", Fw32notify_add_watch,
442 Sw32notify_add_watch, 3, 3, 0,
443 doc: /* Add a watch for filesystem events pertaining to FILE.
444
445 This arranges for filesystem events pertaining to FILE to be reported
446 to Emacs. Use `w32notify-rm-watch' to cancel the watch.
447
448 Value is a descriptor for the added watch, or nil if the file
449 cannot be watched.
450
451 FILTER is a list of conditions for reporting an event. It can include
452 the following symbols:
453
454 'file-name' -- report file creation, deletion, or renaming
455 'directory-name' -- report directory creation, deletion, or renaming
456 'attributes' -- report changes in attributes
457 'size' -- report changes in file-size
458 'last-write-time' -- report changes in last-write time
459 'last-access-time' -- report changes in last-access time
460 'creation-time' -- report changes in creation time
461 'security-desc' -- report changes in security descriptor
462
463 If FILE is a directory, and FILTER includes 'subtree', then all the
464 subdirectories will also be watched and changes in them reported.
465
466 When any event happens that satisfies the conditions specified by
467 FILTER, Emacs will call the CALLBACK function passing it a single
468 argument EVENT, which is of the form
469
470 (DESCRIPTOR ACTION FILE)
471
472 DESCRIPTOR is the same object as the one returned by this function.
473 ACTION is the description of the event. It could be any one of the
474 following:
475
476 'added' -- FILE was added
477 'removed' -- FILE was deleted
478 'modified' -- FILE's contents or its attributes were modified
479 'renamed-from' -- a file was renamed whose old name was FILE
480 'renamed-to' -- a file was renamed and its new name is FILE
481
482 FILE is the name of the file whose event is being reported. */)
483 (Lisp_Object file, Lisp_Object filter, Lisp_Object callback)
484 {
485 Lisp_Object encoded_file, watch_object, watch_descriptor;
486 char parent_dir[MAX_PATH], *basename;
487 size_t fn_len;
488 DWORD flags;
489 BOOL subdirs = FALSE;
490 struct notification *dirwatch = NULL;
491 Lisp_Object lisp_errstr;
492 char *errstr;
493
494 CHECK_LIST (filter);
495
496 /* The underlying features are available only since XP. */
497 if (os_subtype == OS_9X
498 || (w32_major_version == 5 && w32_major_version < 1))
499 {
500 errno = ENOSYS;
501 report_file_error ("Watching filesystem events is not supported",
502 Qnil);
503 }
504
505 /* We need a full absolute file name of FILE, and we need to remove
506 any trailing slashes from it, so that GetFullPathName below gets
507 the basename part correctly. */
508 file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
509 encoded_file = ENCODE_FILE (file);
510
511 fn_len = GetFullPathName (SDATA (encoded_file), MAX_PATH, parent_dir,
512 &basename);
513 if (!fn_len)
514 {
515 errstr = w32_strerror (0);
516 errno = EINVAL;
517 if (!NILP (Vlocale_coding_system))
518 lisp_errstr
519 = code_convert_string_norecord (build_unibyte_string (errstr),
520 Vlocale_coding_system, 0);
521 else
522 lisp_errstr = build_string (errstr);
523 report_file_error ("GetFullPathName failed",
524 Fcons (lisp_errstr, Fcons (file, Qnil)));
525 }
526 /* We need the parent directory without the slash that follows it.
527 If BASENAME is NULL, the argument was the root directory on its
528 drive. */
529 if (basename)
530 basename[-1] = '\0';
531 else
532 subdirs = TRUE;
533
534 if (!NILP (Fmember (Qsubtree, filter)))
535 subdirs = TRUE;
536
537 flags = filter_list_to_flags (filter);
538
539 dirwatch = add_watch (parent_dir, basename, subdirs, flags);
540 if (!dirwatch)
541 {
542 DWORD err = GetLastError ();
543
544 errno = EINVAL;
545 if (err)
546 {
547 errstr = w32_strerror (err);
548 if (!NILP (Vlocale_coding_system))
549 lisp_errstr
550 = code_convert_string_norecord (build_unibyte_string (errstr),
551 Vlocale_coding_system, 0);
552 else
553 lisp_errstr = build_string (errstr);
554 report_file_error ("Cannot watch file",
555 Fcons (lisp_errstr, Fcons (file, Qnil)));
556 }
557 else
558 report_file_error ("Cannot watch file", Fcons (file, Qnil));
559 }
560 /* Store watch object in watch list. */
561 watch_descriptor = XIL ((EMACS_INT)dirwatch);
562 watch_object = Fcons (watch_descriptor, callback);
563 watch_list = Fcons (watch_object, watch_list);
564
565 return watch_descriptor;
566 }
567
568 DEFUN ("w32notify-rm-watch", Fw32notify_rm_watch,
569 Sw32notify_rm_watch, 1, 1, 0,
570 doc: /* Remove an existing watch specified by its WATCH-DESCRIPTOR.
571
572 WATCH-DESCRIPTOR should be an object returned by `w32notify-add-watch'. */)
573 (Lisp_Object watch_descriptor)
574 {
575 Lisp_Object watch_object;
576 struct notification *dirwatch;
577 int status = -1;
578
579 /* Remove the watch object from watch list. Do this before freeing
580 the object, do that even if we fail to free it, watch_list is
581 kept free of junk. */
582 watch_object = Fassoc (watch_descriptor, watch_list);
583 if (!NILP (watch_object))
584 {
585 watch_list = Fdelete (watch_object, watch_list);
586 dirwatch = (struct notification *)XLI (watch_descriptor);
587 if (w32_valid_pointer_p (dirwatch, sizeof(struct notification)))
588 status = remove_watch (dirwatch);
589 }
590
591 if (status == -1)
592 report_file_error ("Invalid watch descriptor", Fcons (watch_descriptor,
593 Qnil));
594
595 return Qnil;
596 }
597
598 Lisp_Object
599 w32_get_watch_object (void *desc)
600 {
601 Lisp_Object descriptor = XIL ((EMACS_INT)desc);
602
603 /* This is called from the input queue handling code, so we cannot
604 possibly QUIT if watch_list is not in the right condition. */
605 return NILP (watch_list) ? Qnil : assoc_no_quit (descriptor, watch_list);
606 }
607
608 void
609 globals_of_w32notify (void)
610 {
611 watch_list = Qnil;
612 }
613
614 void
615 syms_of_w32notify (void)
616 {
617 DEFSYM (Qfile_name, "file-name");
618 DEFSYM (Qdirectory_name, "directory-name");
619 DEFSYM (Qattributes, "attributes");
620 DEFSYM (Qsize, "size");
621 DEFSYM (Qlast_write_time, "last-write-time");
622 DEFSYM (Qlast_access_time, "last-access-time");
623 DEFSYM (Qcreation_time, "creation-time");
624 DEFSYM (Qsecurity_desc, "security-desc");
625 DEFSYM (Qsubtree, "subtree");
626
627 defsubr (&Sw32notify_add_watch);
628 defsubr (&Sw32notify_rm_watch);
629
630 staticpro (&watch_list);
631
632 Fprovide (intern_c_string ("w32notify"), Qnil);
633 }