Implemented file change notifications, based on a patch by Mike
McCormack.

diff --git a/dlls/kernel/Makefile.in b/dlls/kernel/Makefile.in
index b98c018..65970e6 100644
--- a/dlls/kernel/Makefile.in
+++ b/dlls/kernel/Makefile.in
@@ -21,6 +21,7 @@
 
 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 18c619f..f98f884 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 <assert.h>
 #include <stdlib.h>
-#ifdef HAVE_UNISTD_H
-# include <unistd.h>
-#endif
 #include <string.h>
-#include <time.h>
+
 #include "winbase.h"
 #include "winerror.h"
 #include "wine/server.h"
@@ -45,38 +36,49 @@
 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 @@
  */
 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 20883f8..3c937f5 100644
--- a/dlls/ntdll/Makefile.in
+++ b/dlls/ntdll/Makefile.in
@@ -7,7 +7,6 @@
 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 11a59ab..0716131 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -1429,8 +1429,9 @@
 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 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 @@
     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 @@
     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 @@
     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 @@
     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 b1e69e0..f8d2b4c 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 <assert.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <signal.h>
 
 #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 @@
     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 @@
 {
     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 1def8cb..456d94d 100644
--- a/server/file.c
+++ b/server/file.c
@@ -221,7 +221,7 @@
         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 30f7e50..10e61ac 100644
--- a/server/file.h
+++ b/server/file.h
@@ -95,4 +95,9 @@
 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 6af98bc..a3db385 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -1055,13 +1055,19 @@
 
 /* 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 c0983fa..1571f11 100644
--- a/server/request.h
+++ b/server/request.h
@@ -177,6 +177,7 @@
 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 @@
     (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 3388a66..952063f 100644
--- a/server/signal.c
+++ b/server/signal.c
@@ -170,12 +170,6 @@
     exit(1);
 }
 
-/* SIGIO callback */
-static void sigio_callback(void)
-{
-    /* nothing here yet */
-}
-
 /* SIGHUP handler */
 static void do_sighup()
 {
@@ -203,8 +197,8 @@
 /* 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 3dadcc1..2110883 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -1241,8 +1241,9 @@
 
 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 @@
     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 @@
     (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 @@
     (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 @@
     "move_console_output",
     "send_console_signal",
     "create_change_notification",
+    "next_change_notification",
     "create_mapping",
     "open_mapping",
     "get_mapping_info",