From 3e588e3a575836c884ed87b4787d81e830c67aa1 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Wed, 26 Mar 2003 23:41:43 +0000 Subject: [PATCH] Implemented file change notifications, based on a patch by Mike McCormack. --- dlls/kernel/Makefile.in | 1 + {files => dlls/kernel}/change.c | 52 ++++++----- dlls/ntdll/Makefile.in | 1 - include/wine/server_protocol.h | 19 +++- server/change.c | 152 +++++++++++++++++++++++++++++--- server/file.c | 2 +- server/file.h | 5 ++ server/protocol.def | 8 +- server/request.h | 2 + server/signal.c | 8 +- server/trace.c | 11 ++- 11 files changed, 215 insertions(+), 46 deletions(-) rename {files => dlls/kernel}/change.c (64%) diff --git a/dlls/kernel/Makefile.in b/dlls/kernel/Makefile.in index b98c01849c5..65970e6b9c9 100644 --- a/dlls/kernel/Makefile.in +++ b/dlls/kernel/Makefile.in @@ -21,6 +21,7 @@ SPEC_SRCS16 = \ C_SRCS = \ $(TOPOBJDIR)/ole/ole2nls.c \ + change.c \ comm.c \ computername.c \ console.c \ diff --git a/files/change.c b/dlls/kernel/change.c similarity index 64% rename from files/change.c rename to dlls/kernel/change.c index 18c619fc603..f98f8848602 100644 --- a/files/change.c +++ b/dlls/kernel/change.c @@ -1,11 +1,6 @@ /* * Win32 file change notification functions * - * FIXME: this is VERY difficult to implement with proper Unix support - * at the wineserver side. - * (Unix doesn't really support this) - * See http://x57.deja.com/getdoc.xp?AN=575483053 for possible solutions. - * * Copyright 1998 Ulrich Weigand * * This library is free software; you can redistribute it and/or @@ -25,13 +20,9 @@ #include "config.h" -#include #include -#ifdef HAVE_UNISTD_H -# include -#endif #include -#include + #include "winbase.h" #include "winerror.h" #include "wine/server.h" @@ -45,38 +36,49 @@ WINE_DEFAULT_DEBUG_CHANNEL(file); HANDLE WINAPI FindFirstChangeNotificationA( LPCSTR lpPathName, BOOL bWatchSubtree, DWORD dwNotifyFilter ) { - HANDLE ret = INVALID_HANDLE_VALUE; + HANDLE file, ret = INVALID_HANDLE_VALUE; - FIXME("this is not supported yet (non-trivial).\n"); + TRACE( "%s %d %lx\n", debugstr_a(lpPathName), bWatchSubtree, dwNotifyFilter ); + + if ((file = CreateFileA( lpPathName, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 )) == INVALID_HANDLE_VALUE) + return INVALID_HANDLE_VALUE; SERVER_START_REQ( create_change_notification ) { + req->handle = file; req->subtree = bWatchSubtree; req->filter = dwNotifyFilter; if (!wine_server_call_err( req )) ret = reply->handle; } SERVER_END_REQ; + CloseHandle( file ); return ret; } /**************************************************************************** * FindFirstChangeNotificationW (KERNEL32.@) */ -HANDLE WINAPI FindFirstChangeNotificationW( LPCWSTR lpPathName, - BOOL bWatchSubtree, - DWORD dwNotifyFilter) +HANDLE WINAPI FindFirstChangeNotificationW( LPCWSTR lpPathName, BOOL bWatchSubtree, + DWORD dwNotifyFilter) { - HANDLE ret = INVALID_HANDLE_VALUE; + HANDLE file, ret = INVALID_HANDLE_VALUE; - FIXME("this is not supported yet (non-trivial).\n"); + TRACE( "%s %d %lx\n", debugstr_w(lpPathName), bWatchSubtree, dwNotifyFilter ); + + if ((file = CreateFileW( lpPathName, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0 )) == INVALID_HANDLE_VALUE) + return INVALID_HANDLE_VALUE; SERVER_START_REQ( create_change_notification ) { + req->handle = file; req->subtree = bWatchSubtree; req->filter = dwNotifyFilter; if (!wine_server_call_err( req )) ret = reply->handle; } SERVER_END_REQ; + CloseHandle( file ); return ret; } @@ -85,15 +87,23 @@ HANDLE WINAPI FindFirstChangeNotificationW( LPCWSTR lpPathName, */ BOOL WINAPI FindNextChangeNotification( HANDLE handle ) { - /* FIXME: do something */ - return TRUE; + BOOL ret; + + TRACE("%p\n",handle); + + SERVER_START_REQ( next_change_notification ) + { + req->handle = handle; + ret = !wine_server_call_err( req ); + } + SERVER_END_REQ; + return ret; } /**************************************************************************** * FindCloseChangeNotification (KERNEL32.@) */ -BOOL WINAPI FindCloseChangeNotification( HANDLE handle) +BOOL WINAPI FindCloseChangeNotification( HANDLE handle ) { return CloseHandle( handle ); } - diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index 20883f8fead..3c937f5daf9 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -7,7 +7,6 @@ MODULE = ntdll.dll EXTRALIBS = $(LIBUNICODE) C_SRCS = \ - $(TOPOBJDIR)/files/change.c \ $(TOPOBJDIR)/files/directory.c \ $(TOPOBJDIR)/files/dos_fs.c \ $(TOPOBJDIR)/files/drive.c \ diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 11a59ab22c0..07161312934 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -1429,8 +1429,9 @@ struct send_console_signal_reply struct create_change_notification_request { struct request_header __header; + obj_handle_t handle; int subtree; - int filter; + unsigned int filter; }; struct create_change_notification_reply { @@ -1440,6 +1441,17 @@ struct create_change_notification_reply +struct next_change_notification_request +{ + struct request_header __header; + obj_handle_t handle; +}; +struct next_change_notification_reply +{ + struct reply_header __header; +}; + + struct create_mapping_request { struct request_header __header; @@ -3095,6 +3107,7 @@ enum request REQ_move_console_output, REQ_send_console_signal, REQ_create_change_notification, + REQ_next_change_notification, REQ_create_mapping, REQ_open_mapping, REQ_get_mapping_info, @@ -3276,6 +3289,7 @@ union generic_request struct move_console_output_request move_console_output_request; struct send_console_signal_request send_console_signal_request; struct create_change_notification_request create_change_notification_request; + struct next_change_notification_request next_change_notification_request; struct create_mapping_request create_mapping_request; struct open_mapping_request open_mapping_request; struct get_mapping_info_request get_mapping_info_request; @@ -3455,6 +3469,7 @@ union generic_reply struct move_console_output_reply move_console_output_reply; struct send_console_signal_reply send_console_signal_reply; struct create_change_notification_reply create_change_notification_reply; + struct next_change_notification_reply next_change_notification_reply; struct create_mapping_reply create_mapping_reply; struct open_mapping_reply open_mapping_reply; struct get_mapping_info_reply get_mapping_info_reply; @@ -3557,6 +3572,6 @@ union generic_reply struct get_next_hook_reply get_next_hook_reply; }; -#define SERVER_PROTOCOL_VERSION 102 +#define SERVER_PROTOCOL_VERSION 103 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/change.c b/server/change.c index b1e69e0dd2e..f8d2b4cd14d 100644 --- a/server/change.c +++ b/server/change.c @@ -18,25 +18,37 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "config.h" +#include "wine/port.h" + #include +#include #include #include +#include #include "windef.h" +#include "file.h" #include "handle.h" #include "thread.h" #include "request.h" +#include "list.h" struct change { struct object obj; /* object header */ + struct fd *fd; /* file descriptor to the directory */ + struct list entry; /* entry in global change notifications list */ int subtree; /* watch all the subtree */ - int filter; /* notification filter */ + unsigned int filter; /* notification filter */ + long notified; /* SIGIO counter */ + long signaled; /* the file changed */ }; static void change_dump( struct object *obj, int verbose ); static int change_signaled( struct object *obj, struct thread *thread ); +static void change_destroy( struct object *obj ); static const struct object_ops change_ops = { @@ -47,17 +59,72 @@ static const struct object_ops change_ops = change_signaled, /* signaled */ no_satisfied, /* satisfied */ no_get_fd, /* get_fd */ - no_destroy /* destroy */ + change_destroy /* destroy */ }; +static struct list change_list = LIST_INIT(change_list); -static struct change *create_change_notification( int subtree, int filter ) +static void adjust_changes( int fd, unsigned int filter ) +{ +#ifdef F_NOTIFY + unsigned int val; + if ( 0 > fcntl( fd, F_SETSIG, SIGIO) ) + return; + + val = DN_MULTISHOT; + if( filter & FILE_NOTIFY_CHANGE_FILE_NAME ) + val |= DN_RENAME | DN_DELETE | DN_CREATE; + if( filter & FILE_NOTIFY_CHANGE_DIR_NAME ) + val |= DN_RENAME | DN_DELETE | DN_CREATE; + if( filter & FILE_NOTIFY_CHANGE_ATTRIBUTES ) + val |= DN_ATTRIB; + if( filter & FILE_NOTIFY_CHANGE_SIZE ) + val |= DN_MODIFY; + if( filter & FILE_NOTIFY_CHANGE_LAST_WRITE ) + val |= DN_MODIFY; + if( filter & FILE_NOTIFY_CHANGE_SECURITY ) + val |= DN_ATTRIB; + fcntl( fd, F_NOTIFY, val ); +#endif +} + +/* insert change in the global list */ +static inline void insert_change( struct change *change ) +{ + sigset_t sigset; + + sigemptyset( &sigset ); + sigaddset( &sigset, SIGIO ); + sigprocmask( SIG_BLOCK, &sigset, NULL ); + list_add_head( &change_list, &change->entry ); + sigprocmask( SIG_UNBLOCK, &sigset, NULL ); +} + +/* remove change from the global list */ +static inline void remove_change( struct change *change ) +{ + sigset_t sigset; + + sigemptyset( &sigset ); + sigaddset( &sigset, SIGIO ); + sigprocmask( SIG_BLOCK, &sigset, NULL ); + list_remove( &change->entry ); + sigprocmask( SIG_UNBLOCK, &sigset, NULL ); +} + +static struct change *create_change_notification( struct fd *fd, int subtree, unsigned int filter ) { struct change *change; + if ((change = alloc_object( &change_ops ))) { - change->subtree = subtree; - change->filter = filter; + change->fd = (struct fd *)grab_object( fd ); + change->subtree = subtree; + change->filter = filter; + change->notified = 0; + change->signaled = 0; + insert_change( change ); + adjust_changes( get_unix_fd(fd), filter ); } return change; } @@ -66,27 +133,88 @@ static void change_dump( struct object *obj, int verbose ) { struct change *change = (struct change *)obj; assert( obj->ops == &change_ops ); - fprintf( stderr, "Change notification sub=%d filter=%08x\n", - change->subtree, change->filter ); + fprintf( stderr, "Change notification fd=%p sub=%d filter=%08x\n", + change->fd, change->subtree, change->filter ); } static int change_signaled( struct object *obj, struct thread *thread ) { -/* struct change *change = (struct change *)obj;*/ - assert( obj->ops == &change_ops ); - return 0; /* never signaled for now */ + struct change *change = (struct change *)obj; + + return change->signaled != 0; +} + +static void change_destroy( struct object *obj ) +{ + struct change *change = (struct change *)obj; + + release_object( change->fd ); + remove_change( change ); +} + +/* enter here directly from SIGIO signal handler */ +void do_change_notify( int unix_fd ) +{ + struct list *ptr; + + /* FIXME: this is O(n) ... probably can be improved */ + LIST_FOR_EACH( ptr, &change_list ) + { + struct change *change = LIST_ENTRY( ptr, struct change, entry ); + if (get_unix_fd( change->fd ) != unix_fd) continue; + interlocked_xchg_add( &change->notified, 1 ); + break; + } +} + +/* SIGIO callback, called synchronously with the poll loop */ +void sigio_callback(void) +{ + struct list *ptr; + + LIST_FOR_EACH( ptr, &change_list ) + { + struct change *change = LIST_ENTRY( ptr, struct change, entry ); + long count = interlocked_xchg( &change->notified, 0 ); + if (count) + { + change->signaled += count; + if (change->signaled == count) /* was it 0? */ + wake_up( &change->obj, 0 ); + } + } } /* create a change notification */ DECL_HANDLER(create_change_notification) { struct change *change; + struct file *file; + struct fd *fd; - reply->handle = 0; - if ((change = create_change_notification( req->subtree, req->filter ))) + if (!(file = get_file_obj( current->process, req->handle, 0 ))) return; + fd = get_obj_fd( (struct object *)file ); + release_object( file ); + if (!fd) return; + + if ((change = create_change_notification( fd, req->subtree, req->filter ))) { reply->handle = alloc_handle( current->process, change, STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE, 0 ); release_object( change ); } + release_object( fd ); +} + +/* move to the next change notification */ +DECL_HANDLER(next_change_notification) +{ + struct change *change; + + if ((change = (struct change *)get_handle_obj( current->process, req->handle, + 0, &change_ops ))) + { + if (change->signaled > 0) change->signaled--; + release_object( change ); + } } diff --git a/server/file.c b/server/file.c index 1def8cb2200..456d94dc4c7 100644 --- a/server/file.c +++ b/server/file.c @@ -221,7 +221,7 @@ static struct file *create_file( const char *nameptr, size_t len, unsigned int a return NULL; } /* refuse to open a directory */ - if (S_ISDIR(mode)) + if (S_ISDIR(mode) && !(file->flags & FILE_FLAG_BACKUP_SEMANTICS)) { set_error( STATUS_ACCESS_DENIED ); release_object( file ); diff --git a/server/file.h b/server/file.h index 30f7e509b42..10e61ac057b 100644 --- a/server/file.h +++ b/server/file.h @@ -95,4 +95,9 @@ extern int grow_file( struct file *file, int size_high, int size_low ); extern struct file *create_temp_file( int access ); extern void file_set_error(void); +/* change notification functions */ + +extern void do_change_notify( int unix_fd ); +extern void sigio_callback(void); + #endif /* __WINE_SERVER_FILE_H */ diff --git a/server/protocol.def b/server/protocol.def index 6af98bc91ad..a3db3858d48 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1055,13 +1055,19 @@ enum char_info_mode /* Create a change notification */ @REQ(create_change_notification) + obj_handle_t handle; /* handle to the directory */ int subtree; /* watch all the subtree */ - int filter; /* notification filter */ + unsigned int filter; /* notification filter */ @REPLY obj_handle_t handle; /* handle to the change notification */ @END +/* Move to the next change notification */ +@REQ(next_change_notification) + obj_handle_t handle; /* handle to the change notification */ +@END + /* Create a file mapping */ @REQ(create_mapping) int size_high; /* mapping size */ diff --git a/server/request.h b/server/request.h index c0983faaaa3..1571f115346 100644 --- a/server/request.h +++ b/server/request.h @@ -177,6 +177,7 @@ DECL_HANDLER(read_console_output); DECL_HANDLER(move_console_output); DECL_HANDLER(send_console_signal); DECL_HANDLER(create_change_notification); +DECL_HANDLER(next_change_notification); DECL_HANDLER(create_mapping); DECL_HANDLER(open_mapping); DECL_HANDLER(get_mapping_info); @@ -357,6 +358,7 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] = (req_handler)req_move_console_output, (req_handler)req_send_console_signal, (req_handler)req_create_change_notification, + (req_handler)req_next_change_notification, (req_handler)req_create_mapping, (req_handler)req_open_mapping, (req_handler)req_get_mapping_info, diff --git a/server/signal.c b/server/signal.c index 3388a664340..952063f0009 100644 --- a/server/signal.c +++ b/server/signal.c @@ -170,12 +170,6 @@ static void sigint_callback(void) exit(1); } -/* SIGIO callback */ -static void sigio_callback(void) -{ - /* nothing here yet */ -} - /* SIGHUP handler */ static void do_sighup() { @@ -203,8 +197,8 @@ static void do_sigchld() /* SIGIO handler */ static void do_sigio( int signum, siginfo_t *si, void *x ) { - /* do_change_notify( si->si_fd ); */ do_signal( handler_sigio ); + do_change_notify( si->si_fd ); } void init_signals(void) diff --git a/server/trace.c b/server/trace.c index 3dadcc1d329..2110883410c 100644 --- a/server/trace.c +++ b/server/trace.c @@ -1241,8 +1241,9 @@ static void dump_send_console_signal_request( const struct send_console_signal_r static void dump_create_change_notification_request( const struct create_change_notification_request *req ) { + fprintf( stderr, " handle=%p,", req->handle ); fprintf( stderr, " subtree=%d,", req->subtree ); - fprintf( stderr, " filter=%d", req->filter ); + fprintf( stderr, " filter=%08x", req->filter ); } static void dump_create_change_notification_reply( const struct create_change_notification_reply *req ) @@ -1250,6 +1251,11 @@ static void dump_create_change_notification_reply( const struct create_change_no fprintf( stderr, " handle=%p", req->handle ); } +static void dump_next_change_notification_request( const struct next_change_notification_request *req ) +{ + fprintf( stderr, " handle=%p", req->handle ); +} + static void dump_create_mapping_request( const struct create_mapping_request *req ) { fprintf( stderr, " size_high=%d,", req->size_high ); @@ -2495,6 +2501,7 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_move_console_output_request, (dump_func)dump_send_console_signal_request, (dump_func)dump_create_change_notification_request, + (dump_func)dump_next_change_notification_request, (dump_func)dump_create_mapping_request, (dump_func)dump_open_mapping_request, (dump_func)dump_get_mapping_info_request, @@ -2672,6 +2679,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { (dump_func)0, (dump_func)0, (dump_func)dump_create_change_notification_reply, + (dump_func)0, (dump_func)dump_create_mapping_reply, (dump_func)dump_open_mapping_reply, (dump_func)dump_get_mapping_info_reply, @@ -2849,6 +2857,7 @@ static const char * const req_names[REQ_NB_REQUESTS] = { "move_console_output", "send_console_signal", "create_change_notification", + "next_change_notification", "create_mapping", "open_mapping", "get_mapping_info",