X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/dd8c2f5adeba029790a007ec829e18442a4ade36..287d70764c83f6054eb2a0ede942eda01c20fcdf:/src/w32notify.c diff --git a/src/w32notify.c b/src/w32notify.c index bdfdf3472a..da7c5513dc 100644 --- a/src/w32notify.c +++ b/src/w32notify.c @@ -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 . */ -/* Design overview: +/* Written by Eli Zaretskii . + + 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 . */ 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 . */ 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 . */ 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 #include @@ -93,26 +98,25 @@ along with GNU Emacs. If not, see . */ #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 */ BOOL subtree; /* whether to watch subdirectories */ DWORD filter; /* bit mask for events to watch */ - char *watchee; /* the file we are interested in */ + char *watchee; /* the file we are interested in, UTF-8 encoded */ 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 @@ -159,7 +163,12 @@ send_notifications (BYTE *info, DWORD info_size, HANDLE hdir, int *terminate) && PostThreadMessage (dwMainThreadId, WM_EMACS_FILENOTIFY, 0, 0)) || (FRAME_W32_P (f) && PostMessage (FRAME_W32_WINDOW (f), - WM_EMACS_FILENOTIFY, 0, 0))) + WM_EMACS_FILENOTIFY, 0, 0)) + /* When we are running in batch mode, there's no one to + send a message, so we just signal the data is + available and hope sys_select will be called soon and + will read the data. */ + || (FRAME_INITIAL_P (f) && noninteractive)) notification_buffer_in_use = 1; done = 1; } @@ -186,9 +195,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 +222,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 +259,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,78 +283,106 @@ 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) + if (!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 (w32_unicode_filenames) + { + wchar_t dir_w[MAX_PATH], file_w[MAX_PATH]; + + filename_to_utf16 (parent_dir, dir_w); + if (*file) + filename_to_utf16 (file, file_w); + else + file_w[0] = 0; + + hdir = CreateFileW (dir_w, + 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); + } + else + { + char dir_a[MAX_PATH], file_a[MAX_PATH]; + + filename_to_ansi (parent_dir, dir_a); + if (*file) + filename_to_ansi (file, file_a); + else + file_a[0] = '\0'; + + hdir = CreateFileA (dir_a, + FILE_LIST_DIRECTORY, + 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; + 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 +392,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 +413,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 +475,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 +509,19 @@ 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; + Lisp_Object dirfn, basefn, watch_object, watch_descriptor; DWORD flags; BOOL subdirs = FALSE; + struct notification *dirwatch = NULL; Lisp_Object lisp_errstr; char *errstr; @@ -491,45 +536,34 @@ 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 - any trailing slashes from it, so that GetFullPathName below gets - the basename part correctly. */ + /* filenotify.el always passes us a directory, either the parent + directory of a file to be watched, or the directory to be + watched. */ 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) + if (NILP (Ffile_directory_p (file))) { - 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))); + /* This should only happen if we are called directly, not via + filenotify.el. If BASEFN is empty, the argument was the root + directory on its drive. */ + dirfn = ENCODE_FILE (Ffile_name_directory (file)); + basefn = ENCODE_FILE (Ffile_name_nondirectory (file)); + if (*SDATA (basefn) == '\0') + subdirs = TRUE; } - /* 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; + { + dirfn = ENCODE_FILE (file); + basefn = Qnil; + } if (!NILP (Fmember (Qsubtree, filter))) subdirs = TRUE; flags = filter_list_to_flags (filter); - hdir = add_watch (parent_dir, basename, subdirs, flags); - if (!hdir) + dirwatch = add_watch (SSDATA (dirfn), NILP (basefn) ? "" : SSDATA (basefn), + subdirs, flags); + if (!dirwatch) { DWORD err = GetLastError (); @@ -550,7 +584,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 +599,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