File change notification (take 2)

Mike McCormack mike at codeweavers.com
Thu Mar 6 20:10:32 CST 2003


Hi,

This patch implements file change notification. It contains a fix for 
portability and some changes to avoid race conditions with signal handlers.

Mike

ChangeLog:
* implement file change notification using F_NOTIFY
-------------- next part --------------
Index: server/fd.c
===================================================================
RCS file: /cvstrees/crossover/office/wine/server/fd.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 fd.c
--- server/fd.c	21 Feb 2003 01:33:09 -0000	1.1.1.1
+++ server/fd.c	7 Mar 2003 01:41:16 -0000
@@ -238,6 +238,13 @@
     exit(1);
 }
 
+void siginfo_handler( int signum, siginfo_t *si, void *x)
+{
+    /* fprintf(stderr,"signal!\n"); */
+    
+    do_change_notify( si->si_fd );
+}
+
 /* server main poll() loop */
 void main_loop(void)
 {
@@ -252,6 +259,7 @@
     sigaddset( &sigset, SIGINT );
     sigaddset( &sigset, SIGQUIT );
     sigaddset( &sigset, SIGTERM );
+    sigaddset( &sigset, SIGIO );
     sigprocmask( SIG_BLOCK, &sigset, NULL );
 
     /* set the handlers */
@@ -266,6 +274,11 @@
     action.sa_handler = sigterm_handler;
     sigaction( SIGQUIT, &action, NULL );
     sigaction( SIGTERM, &action, NULL );
+
+    action.sa_handler = NULL;
+    action.sa_sigaction = siginfo_handler;
+    action.sa_flags = SA_SIGINFO;
+    sigaction( SIGIO, &action, NULL );
 
     while (active_users)
     {
Index: server/change.c
===================================================================
RCS file: /cvstrees/crossover/office/wine/server/change.c,v
retrieving revision 1.1.1.4
diff -u -r1.1.1.4 change.c
--- server/change.c	21 Feb 2003 01:33:09 -0000	1.1.1.4
+++ server/change.c	7 Mar 2003 01:41:16 -0000
@@ -18,25 +18,40 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include "config.h"
+#include "wine/port.h"
+
 #include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <signal.h>
 
 #include "windef.h"
 
 #include "handle.h"
 #include "thread.h"
 #include "request.h"
+#include "file.h"
 
 struct change
 {
     struct object  obj;      /* object header */
+    int            fd;       /* the unix file descriptor */
     int            subtree;  /* watch all the subtree */
     int            filter;   /* notification filter */
+    char          *name;     /* name of file */
+    int            notified; /* got a SIGIO */
+    int            signaled; /* the file changed */
+    struct change *next;
+    struct change *prev;
 };
 
+static struct change *first_change, *last_change;
+
 static void change_dump( struct object *obj, int verbose );
 static int change_signaled( struct object *obj, struct thread *thread );
+static int change_satisfied( struct object *obj, struct thread *thread );
+static void change_destroy( struct object *obj );
 
 static const struct object_ops change_ops =
 {
@@ -45,20 +60,188 @@
     add_queue,                /* add_queue */
     remove_queue,             /* remove_queue */
     change_signaled,          /* signaled */
-    no_satisfied,             /* satisfied */
+    change_satisfied,         /* satisfied */
     no_get_fd,                /* get_fd */
-    no_destroy                /* destroy */
+    change_destroy            /* destroy */
+};
+
+/* the pipe avoids possible race conditions with signal handlers */
+static int change_pipe_get_poll_events( struct fd *fd );
+static void change_pipe_poll_event( struct fd *fd, int event );
+
+static const struct fd_ops change_pipe_ops =
+{
+    change_pipe_get_poll_events, /* get_poll_events */
+    change_pipe_poll_event,      /* poll_event */
+    no_flush,                    /* flush */
+    no_get_file_info,            /* get_file_info */
+    no_queue_async               /* queue_async */
 };
 
+static int change_pipe[2];
+static struct fd *change_notifier;
+static int pipe_has_data;
+
+static int create_change_notifier( void )
+{
+    int r;
+
+    if( change_notifier )
+        return 0;
+
+    r = pipe( &change_pipe[0] );
+    if( r < 0 )
+        return r;
+    change_notifier = alloc_fd( &change_pipe_ops, change_pipe[0], NULL );
+    if( !change_notifier )
+        return -1;
+
+    set_fd_events( change_notifier, POLLIN );
+
+    return 0;
+}
+
+static void destroy_change_notifier( void )
+{
+    close( change_pipe[1] );
+    release_object( (struct object *) change_notifier );
+    change_notifier = NULL;
+}
+
+static int change_pipe_get_poll_events( struct fd *fd )
+{
+    return POLLIN;
+}
+
+static void change_pipe_poll_event( struct fd *fd, int event )
+{
+    struct change *x;
+    char ch;
+    int unix_fd = get_unix_fd( fd );
+
+    read( unix_fd, &ch, sizeof ch);
+    pipe_has_data = 0;
+    /* fprintf(stderr,"change: read byte\n"); */
+
+    /* search for a matching fd */
+    for( x=first_change; x; x=x->next )
+    {
+        if( x->notified )
+        {
+            /* fprintf(stderr,"waking up %p\n",x); */
+            x->signaled++;
+            x->notified--;
+            wake_up( &x->obj, 0 );
+        }
+    }
+}
+
+static void add_change_notification( struct change *change )
+{
+    change->prev = NULL;
+    change->next = first_change;
+    if ( first_change )
+        first_change->prev = change;
+    else
+        last_change = change;
+    first_change = change;
+}
+
+static void remove_change_notification( struct change *change )
+{
+    if( change->next )
+        change->next->prev = change->prev;
+    else
+        last_change = change->prev;
+    if( change->prev )
+        change->prev->next = change->next;
+    else
+        first_change = change->next;
+    change->prev = change->next = NULL;
+}
+
+struct change *get_change_obj( struct process *process, obj_handle_t handle, unsigned int access )
+{
+    return (struct change *)get_handle_obj( process, handle, access, &change_ops );
+}
+
+static void change_destroy( struct object *obj )
+{
+    struct change *change = (struct change *)obj;
+
+    remove_change_notification( change );
+    free( change->name );
+
+    if( first_change == NULL )
+        destroy_change_notifier();
+}
 
-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;
+    /* fprintf(stderr,"setting F_NOTIFY to %08x for %d (%s)\n",
+               val,fd,change->name); */
+    fcntl( fd, F_NOTIFY, val);
+#endif
+}
+
+static struct change *create_change_notification( 
+        const char *name, int len, int subtree, int filter )
+ {
     struct change *change;
+    char *filename;
+    int fd;
+
+    /* fprintf(stderr, "Creating change notification\n"); */
+    filename = (char *) malloc (len + 1);
+    memcpy(filename, name, len);
+    filename[len]=0;
+
+    if (0>create_change_notifier())
+    {
+        set_error( STATUS_ACCESS_DENIED );
+        return NULL;
+    }
+ 
+    fd = open(filename, O_RDONLY);
+    if( fd < 0 )
+    {
+        free( filename );
+        return NULL;
+    }
+
+    adjust_changes( fd, filter );
+
     if ((change = alloc_object( &change_ops )))
     {
+        change->fd      = fd;
         change->subtree = subtree;
         change->filter  = filter;
+        change->signaled = 0;
+        change->name    = filename;
+
+        add_change_notification( change );
     }
+
+    /* fprintf(stderr,"done\n"); */
+
     return change;
 }
 
@@ -66,27 +249,89 @@
 {
     struct change *change = (struct change *)obj;
     assert( obj->ops == &change_ops );
-    fprintf( stderr, "Change notification sub=%d filter=%08x\n",
+    fprintf( stderr, "Change notification sub=%d filter=%08x file:\n",
              change->subtree, change->filter );
 }
 
 static int change_signaled( struct object *obj, struct thread *thread )
 {
-/*    struct change *change = (struct change *)obj;*/
+    struct change *change = (struct change *)obj;
+    assert( obj->ops == &change_ops );
+    /* fprintf(stderr,"signaled\n"); */
+    return change->signaled;
+}
+
+static int change_satisfied( struct object *obj, struct thread *thread )
+{
     assert( obj->ops == &change_ops );
-    return 0;  /* never signaled for now */
+    /* fprintf(stderr,"satisfied\n"); */
+    return 0; /* not abandoned */
+}
+
+/* FIXME: this is O(n) ... probably can be improved */
+static struct change * change_from_fd ( int fd )
+{
+    struct change *x;
+
+    /* search for a matching fd */
+    for( x=first_change; x; x=x->next )
+        if( x->fd == fd )
+             break;
+
+    return x;
+}
+
+/* enter here from signal handler */
+void do_change_notify( int fd )
+{
+    struct change *change;
+    char ch = 0;   /* value is not important */
+
+    /* fprintf(stderr, "got signal!\n"); */
+
+    change = change_from_fd( fd );
+    if(!change)
+        return;
+
+    /* fprintf(stderr, "found notification %s (%d)\n", change->name, fd ); */
+
+    /* wake up threads waiting on this change notification */
+    change->notified ++;
+
+    if( pipe_has_data )
+        return;
+
+    pipe_has_data = 1;
+    write( change_pipe[1], &ch, sizeof ch );
+    /* fprintf(stderr,"change: wrote byte\n"); */
 }
 
 /* create a change notification */
 DECL_HANDLER(create_change_notification)
 {
     struct change *change;
+    const char *name = get_req_data();
+    int len = get_req_data_size();
 
     reply->handle = 0;
-    if ((change = create_change_notification( req->subtree, req->filter )))
+    if ((change = create_change_notification( name, len, req->subtree, req->filter )))
     {
         reply->handle = alloc_handle( current->process, change,
                                       STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE, 0 );
         release_object( change );
     }
 }
+
+DECL_HANDLER(next_change_notification)
+{
+    struct change *change;
+
+    change = get_change_obj( current->process, req->handle, 0 );
+    if (!change)
+        return;
+
+    if( change->signaled > 0 )
+        change->signaled --;
+    release_object( change );
+}
+
Index: server/protocol.def
===================================================================
RCS file: /cvstrees/crossover/office/wine/server/protocol.def,v
retrieving revision 1.15
diff -u -r1.15 protocol.def
--- server/protocol.def	6 Mar 2003 02:57:11 -0000	1.15
+++ server/protocol.def	7 Mar 2003 01:41:16 -0000
@@ -1061,6 +1061,9 @@
     obj_handle_t handle;        /* handle to the change notification */
 @END
 
+ at REQ(next_change_notification)
+    obj_handle_t handle;        /* handle to the change notification */
+ at END 
 
 /* Create a file mapping */
 @REQ(create_mapping)
Index: server/object.h
===================================================================
RCS file: /cvstrees/crossover/office/wine/server/object.h,v
retrieving revision 1.1.1.8
diff -u -r1.1.1.8 object.h
--- server/object.h	21 Feb 2003 01:33:10 -0000	1.1.1.8
+++ server/object.h	7 Mar 2003 01:41:16 -0000
@@ -119,6 +119,10 @@
 
 extern void abandon_mutexes( struct thread *thread );
 
+/* change functions */
+
+extern void do_change_notify( int fd );
+
 /* serial functions */
 
 int get_serial_async_timeout(struct object *obj, int type, int count);
Index: files/change.c
===================================================================
RCS file: /cvstrees/crossover/office/wine/files/change.c,v
retrieving revision 1.1.1.4
diff -u -r1.1.1.4 change.c
--- files/change.c	29 Aug 2002 20:08:19 -0000	1.1.1.4
+++ files/change.c	7 Mar 2003 01:41:16 -0000
@@ -34,6 +34,9 @@
 #include <time.h>
 #include "winbase.h"
 #include "winerror.h"
+#include "msdos.h"
+#include "file.h"
+#include "heap.h"
 #include "wine/server.h"
 #include "wine/debug.h"
 
@@ -45,17 +48,18 @@
 HANDLE WINAPI FindFirstChangeNotificationA( LPCSTR lpPathName, BOOL bWatchSubtree,
                                             DWORD dwNotifyFilter )
 {
-    HANDLE ret = INVALID_HANDLE_VALUE;
-
-    FIXME("this is not supported yet (non-trivial).\n");
+    HANDLE ret;
+    LPWSTR path;
+    DWORD len;
+
+    TRACE("%s %d %08lx\n", debugstr_a(lpPathName), bWatchSubtree, dwNotifyFilter);
+
+    len = MultiByteToWideChar(CP_ACP, 0, lpPathName, -1, NULL, 0);
+    path = HeapAlloc(GetProcessHeap(), 0, len);
+    MultiByteToWideChar(CP_ACP, 0, lpPathName, -1, path, len);
+    ret = FindFirstChangeNotificationW( path, bWatchSubtree, dwNotifyFilter );
+    HeapFree(GetProcessHeap(), 0, path);
 
-    SERVER_START_REQ( create_change_notification )
-    {
-        req->subtree = bWatchSubtree;
-        req->filter  = dwNotifyFilter;
-        if (!wine_server_call_err( req )) ret = reply->handle;
-    }
-    SERVER_END_REQ;
     return ret;
 }
 
@@ -67,13 +71,23 @@
                                                 DWORD dwNotifyFilter)
 {
     HANDLE ret = INVALID_HANDLE_VALUE;
+    DOS_FULL_NAME full_name;
 
-    FIXME("this is not supported yet (non-trivial).\n");
+    TRACE("%s %d %08lx\n",debugstr_w(lpPathName), bWatchSubtree, dwNotifyFilter);
+
+    if (!DOSFS_GetFullName( lpPathName, TRUE, &full_name ))
+    {
+	WARN("Unable to get full filename from %s (GLE %ld)\n",
+	     debugstr_w(lpPathName), GetLastError());
+        return ret;
+    }
 
     SERVER_START_REQ( create_change_notification )
     {
         req->subtree = bWatchSubtree;
         req->filter  = dwNotifyFilter;
+        wine_server_add_data( req, full_name.long_name, 
+                              strlen(full_name.long_name) );
         if (!wine_server_call_err( req )) ret = reply->handle;
     }
     SERVER_END_REQ;
@@ -85,8 +99,17 @@
  */
 BOOL WINAPI FindNextChangeNotification( HANDLE handle )
 {
-    /* FIXME: do something */
-    return TRUE;
+    BOOL r;
+
+    TRACE("%p\n",handle);
+
+    SERVER_START_REQ( next_change_notification )
+    {
+        req->handle = handle;
+        r = wine_server_call_err( req );
+    }
+    SERVER_END_REQ;
+    return !r;
 }
 
 /****************************************************************************


More information about the wine-patches mailing list