Implemented file change notifications, based on a patch by Mike
McCormack.
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",