From 01932119464a2a97421daeee1cdd8200646a67a1 Mon Sep 17 00:00:00 2001 From: Mike McCormack Date: Mon, 6 Feb 2006 11:58:55 +0100 Subject: [PATCH] server: Fill in NtNotifyChangeDirectoryFile's buffer with change data. --- dlls/kernel/change.c | 43 ++++++++++------ dlls/ntdll/directory.c | 93 ++++++++++++++++++++++++++++++++-- include/wine/server_protocol.h | 21 +++++++- server/change.c | 92 +++++++++++++++++++++++++++++++-- server/protocol.def | 11 ++++ server/request.h | 2 + server/trace.c | 20 +++++++- 7 files changed, 257 insertions(+), 25 deletions(-) diff --git a/dlls/kernel/change.c b/dlls/kernel/change.c index 232391716a0..235ac585de9 100644 --- a/dlls/kernel/change.c +++ b/dlls/kernel/change.c @@ -136,33 +136,46 @@ BOOL WINAPI ReadDirectoryChangesW( HANDLE handle, LPVOID buffer, DWORD len, BOOL DWORD filter, LPDWORD returned, LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE completion ) { - IO_STATUS_BLOCK io; + OVERLAPPED ov, *pov; + IO_STATUS_BLOCK *ios; NTSTATUS status; BOOL ret = TRUE; - HANDLE event; TRACE("%p %p %08lx %d %08lx %p %p %p\n", handle, buffer, len, subtree, filter, returned, overlapped, completion ); - if (overlapped) - event = overlapped->hEvent; + if (!overlapped) + { + memset( &ov, 0, sizeof ov ); + ov.hEvent = CreateEventW( NULL, 0, 0, NULL ); + pov = &ov; + } else - event = CreateEventW( NULL, 0, 0, NULL ); + pov = overlapped; - status = NtNotifyChangeDirectoryFile( handle, event, NULL, NULL, - &io, buffer, len, filter, subtree ); - if (status != STATUS_PENDING) + ios = (PIO_STATUS_BLOCK) pov; + ios->Status = STATUS_PENDING; + ios->Information = 0; + + status = NtNotifyChangeDirectoryFile( handle, pov->hEvent, NULL, NULL, + ios, buffer, len, filter, subtree ); + if (status == STATUS_PENDING) + { + if (overlapped) + return TRUE; + + WaitForSingleObjectEx( ov.hEvent, INFINITE, TRUE ); + CloseHandle( ov.hEvent ); + if (returned) + *returned = ios->Information; + status = ios->Status; + } + + if (status != STATUS_SUCCESS) { SetLastError( RtlNtStatusToDosError(status) ); ret = FALSE; } - else if (!overlapped) - WaitForSingleObject( event, INFINITE ); - else - overlapped->Internal = STATUS_PENDING; - - if (!overlapped) - CloseHandle( event ); return ret; } diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c index 6b18d5f52ab..84e01aff88e 100644 --- a/dlls/ntdll/directory.c +++ b/dlls/ntdll/directory.c @@ -1778,6 +1778,75 @@ done: return status; } +struct read_changes_info +{ + HANDLE FileHandle; + HANDLE Event; + PIO_APC_ROUTINE ApcRoutine; + PVOID ApcContext; + PVOID Buffer; + ULONG BufferSize; +}; + +static void WINAPI read_changes_apc( void *user, PIO_STATUS_BLOCK iosb, ULONG status ) +{ + struct read_changes_info *info = user; + char path[PATH_MAX]; + NTSTATUS ret = STATUS_SUCCESS; + int len, action; + + TRACE("%p %p %p %08lx\n", info, info->ApcContext, iosb, status); + + /* + * FIXME: race me! + * + * hEvent/hDir is set before the output buffer and iosb is updated. + * Since the thread that called NtNotifyChangeDirectoryFile is usually + * waiting, we'll be safe since we're called in that thread's context. + * If a different thread is waiting on our hEvent/hDir we're going to be + * in trouble... + */ + SERVER_START_REQ( read_change ) + { + req->handle = info->FileHandle; + wine_server_set_reply( req, path, PATH_MAX ); + ret = wine_server_call( req ); + action = reply->action; + len = wine_server_reply_size( reply ); + } + SERVER_END_REQ; + + if (ret == STATUS_SUCCESS && info->Buffer && + (info->BufferSize > (sizeof (FILE_NOTIFY_INFORMATION) + len*sizeof(WCHAR)))) + { + PFILE_NOTIFY_INFORMATION pfni; + + pfni = (PFILE_NOTIFY_INFORMATION) info->Buffer; + + len = ntdll_umbstowcs( 0, path, len, pfni->FileName, + info->BufferSize - sizeof (*pfni) ); + + pfni->NextEntryOffset = 0; + pfni->Action = action; + pfni->FileNameLength = len * sizeof (WCHAR); + pfni->FileName[len] = 0; + + TRACE("action = %ld name = %s\n", pfni->Action, + debugstr_w(pfni->FileName) ); + len = sizeof (*pfni) - sizeof (DWORD) + pfni->FileNameLength; + } + else + { + ret = STATUS_NOTIFY_ENUM_DIR; + len = 0; + } + + iosb->u.Status = ret; + iosb->Information = len; + + RtlFreeHeap( GetProcessHeap(), 0, info ); +} + #define FILE_NOTIFY_ALL ( \ FILE_NOTIFY_CHANGE_FILE_NAME | \ FILE_NOTIFY_CHANGE_DIR_NAME | \ @@ -1797,6 +1866,7 @@ NtNotifyChangeDirectoryFile( HANDLE FileHandle, HANDLE Event, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG BufferSize, ULONG CompletionFilter, BOOLEAN WatchTree ) { + struct read_changes_info *info; NTSTATUS status; TRACE("%p %p %p %p %p %p %lu %lu %d\n", @@ -1809,18 +1879,35 @@ NtNotifyChangeDirectoryFile( HANDLE FileHandle, HANDLE Event, if (CompletionFilter == 0 || (CompletionFilter & ~FILE_NOTIFY_ALL)) return STATUS_INVALID_PARAMETER; - if (ApcRoutine || ApcContext || Buffer || BufferSize || WatchTree) - FIXME("parameters ignored %p %p %p %lu %d\n", - ApcRoutine, ApcContext, Buffer, BufferSize, WatchTree ); + if (WatchTree || ApcRoutine) + FIXME("parameters ignored %p %p %d\n", + ApcRoutine, ApcContext, WatchTree ); + + info = RtlAllocateHeap( GetProcessHeap(), 0, sizeof *info ); + if (!info) + return STATUS_NO_MEMORY; + + info->FileHandle = FileHandle; + info->Event = Event; + info->Buffer = Buffer; + info->BufferSize = BufferSize; + info->ApcRoutine = ApcRoutine; + info->ApcContext = ApcContext; SERVER_START_REQ( read_directory_changes ) { req->handle = FileHandle; req->event = Event; req->filter = CompletionFilter; + req->io_apc = read_changes_apc; + req->io_sb = IoStatusBlock; + req->io_user = info; status = wine_server_call( req ); } SERVER_END_REQ; + if (status != STATUS_PENDING) + RtlFreeHeap( GetProcessHeap(), 0, info ); + return status; } diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 513d7df9ec9..8a061ed10b6 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -1401,6 +1401,9 @@ struct read_directory_changes_request obj_handle_t handle; obj_handle_t event; unsigned int filter; + void* io_apc; + void* io_sb; + void* io_user; }; struct read_directory_changes_reply { @@ -1408,6 +1411,19 @@ struct read_directory_changes_reply }; +struct read_change_request +{ + struct request_header __header; + obj_handle_t handle; +}; +struct read_change_reply +{ + struct reply_header __header; + int action; + /* VARARG(name,string); */ +}; + + struct create_mapping_request { @@ -3768,6 +3784,7 @@ enum request REQ_move_console_output, REQ_send_console_signal, REQ_read_directory_changes, + REQ_read_change, REQ_create_mapping, REQ_open_mapping, REQ_get_mapping_info, @@ -3986,6 +4003,7 @@ union generic_request struct move_console_output_request move_console_output_request; struct send_console_signal_request send_console_signal_request; struct read_directory_changes_request read_directory_changes_request; + struct read_change_request read_change_request; struct create_mapping_request create_mapping_request; struct open_mapping_request open_mapping_request; struct get_mapping_info_request get_mapping_info_request; @@ -4202,6 +4220,7 @@ union generic_reply struct move_console_output_reply move_console_output_reply; struct send_console_signal_reply send_console_signal_reply; struct read_directory_changes_reply read_directory_changes_reply; + struct read_change_reply read_change_reply; struct create_mapping_reply create_mapping_reply; struct open_mapping_reply open_mapping_reply; struct get_mapping_info_reply get_mapping_info_reply; @@ -4344,6 +4363,6 @@ union generic_reply struct query_symlink_reply query_symlink_reply; }; -#define SERVER_PROTOCOL_VERSION 223 +#define SERVER_PROTOCOL_VERSION 224 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/change.c b/server/change.c index 7ec4c0b44a7..d6213fe3c00 100644 --- a/server/change.c +++ b/server/change.c @@ -126,6 +126,13 @@ static inline int inotify_remove_watch( int fd, int wd ) #endif +struct change_record { + struct list entry; + int action; + int len; + char name[1]; +}; + struct dir { struct object obj; /* object header */ @@ -137,6 +144,8 @@ struct dir long signaled; /* the file changed */ struct fd *inotify_fd; /* inotify file descriptor */ int wd; /* inotify watch descriptor */ + struct list change_q; /* change readers */ + struct list change_records; /* data for the change */ }; static struct fd *dir_get_fd( struct object *obj ); @@ -163,6 +172,7 @@ static const struct object_ops dir_ops = static int dir_get_poll_events( struct fd *fd ); static int dir_get_info( struct fd *fd ); +static void dir_cancel_async( struct fd *fd ); static const struct fd_ops dir_fd_ops = { @@ -171,7 +181,7 @@ static const struct fd_ops dir_fd_ops = no_flush, /* flush */ dir_get_info, /* get_file_info */ default_fd_queue_async, /* queue_async */ - default_fd_cancel_async /* cancel_async */ + dir_cancel_async /* cancel_async */ }; static struct list change_list = LIST_INIT(change_list); @@ -238,6 +248,8 @@ struct object *create_dir_obj( struct fd *fd ) if (!dir) return NULL; + list_init( &dir->change_q ); + list_init( &dir->change_records ); dir->event = NULL; dir->filter = 0; dir->notified = 0; @@ -321,8 +333,17 @@ static unsigned int dir_map_access( struct object *obj, unsigned int access ) return access & ~(GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL); } +static struct change_record *get_first_change_record( struct dir *dir ) +{ + struct list *ptr = list_head( &dir->change_records ); + if (!ptr) return NULL; + list_remove( ptr ); + return LIST_ENTRY( ptr, struct change_record, entry ); +} + static void dir_destroy( struct object *obj ) { + struct change_record *record; struct dir *dir = (struct dir *)obj; assert (obj->ops == &dir_ops); @@ -332,6 +353,9 @@ static void dir_destroy( struct object *obj ) if (dir->inotify_fd) release_object( dir->inotify_fd ); + async_terminate_queue( &dir->change_q, STATUS_CANCELLED ); + while ((record = get_first_change_record( dir ))) free( record ); + if (dir->event) { set_event( dir->event ); @@ -356,6 +380,12 @@ static int dir_get_info( struct fd *fd ) return 0; } +static void dir_cancel_async( struct fd *fd ) +{ + struct dir *dir = (struct dir *) get_fd_user( fd ); + async_terminate_queue( &dir->change_q, STATUS_CANCELLED ); +} + #ifdef USE_INOTIFY @@ -378,10 +408,32 @@ static int inotify_get_poll_events( struct fd *fd ) return POLLIN; } -static void inotify_do_change_notify( struct dir *dir ) +static void inotify_do_change_notify( struct dir *dir, struct inotify_event *ie ) { - dir->signaled++; - dir_signal_changed( dir ); + struct change_record *record; + + record = malloc( sizeof (*record) + ie->len - 1 ) ; + if (!record) + return; + + if( ie->mask & IN_CREATE ) + record->action = FILE_ACTION_ADDED; + else if( ie->mask & IN_DELETE ) + record->action = FILE_ACTION_REMOVED; + else + record->action = FILE_ACTION_MODIFIED; + memcpy( record->name, ie->name, ie->len ); + record->len = strlen( ie->name ); + + list_add_tail( &dir->change_records, &record->entry ); + + if (!list_empty( &dir->change_q )) + async_terminate_head( &dir->change_q, STATUS_ALERTED ); + else + { + dir->signaled++; + dir_signal_changed( dir ); + } } static void inotify_poll_event( struct fd *fd, int event ) @@ -404,7 +456,7 @@ static void inotify_poll_event( struct fd *fd, int event ) ie = (struct inotify_event*) &buffer[ofs]; if (!ie->len) break; - inotify_do_change_notify( dir ); + inotify_do_change_notify( dir, ie ); ofs += (sizeof (*ie) + ie->len - 1); } } @@ -489,6 +541,11 @@ DECL_HANDLER(read_directory_changes) if (dir->event) release_object( dir->event ); dir->event = event; + /* requests don't timeout */ + if ( req->io_apc && !create_async( current, NULL, &dir->change_q, + req->io_apc, req->io_user, req->io_sb )) + return; + /* assign it once */ if (!dir->filter) { @@ -511,3 +568,28 @@ DECL_HANDLER(read_directory_changes) end: release_object( dir ); } + +DECL_HANDLER(read_change) +{ + struct change_record *record; + struct dir *dir; + + dir = get_dir_obj( current->process, req->handle, 0 ); + if (!dir) + return; + + if ((record = get_first_change_record( dir )) != NULL) + { + reply->action = record->action; + set_reply_data( record->name, record->len ); + free( record ); + } + else + set_error( STATUS_NO_DATA_DETECTED ); + + /* now signal it */ + dir->signaled++; + dir_signal_changed( dir ); + + release_object( dir ); +} diff --git a/server/protocol.def b/server/protocol.def index 87fe90da3f9..2b3a5875674 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1045,6 +1045,17 @@ enum char_info_mode obj_handle_t handle; /* handle to the directory */ obj_handle_t event; /* handle to the event */ unsigned int filter; /* notification filter */ + void* io_apc; /* APC routine to queue upon end of async */ + void* io_sb; /* I/O status block (unique across all async on this handle) */ + void* io_user; /* data to pass back to caller */ +@END + + +@REQ(read_change) + obj_handle_t handle; +@REPLY + int action; /* type of change */ + VARARG(name,string); /* name of directory entry that changed */ @END diff --git a/server/request.h b/server/request.h index cad86c865c0..f6df040b4b8 100644 --- a/server/request.h +++ b/server/request.h @@ -181,6 +181,7 @@ DECL_HANDLER(read_console_output); DECL_HANDLER(move_console_output); DECL_HANDLER(send_console_signal); DECL_HANDLER(read_directory_changes); +DECL_HANDLER(read_change); DECL_HANDLER(create_mapping); DECL_HANDLER(open_mapping); DECL_HANDLER(get_mapping_info); @@ -398,6 +399,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_read_directory_changes, + (req_handler)req_read_change, (req_handler)req_create_mapping, (req_handler)req_open_mapping, (req_handler)req_get_mapping_info, diff --git a/server/trace.c b/server/trace.c index 8977813ad95..e1ed8793010 100644 --- a/server/trace.c +++ b/server/trace.c @@ -1448,7 +1448,22 @@ static void dump_read_directory_changes_request( const struct read_directory_cha { fprintf( stderr, " handle=%p,", req->handle ); fprintf( stderr, " event=%p,", req->event ); - fprintf( stderr, " filter=%08x", req->filter ); + fprintf( stderr, " filter=%08x,", req->filter ); + fprintf( stderr, " io_apc=%p,", req->io_apc ); + fprintf( stderr, " io_sb=%p,", req->io_sb ); + fprintf( stderr, " io_user=%p", req->io_user ); +} + +static void dump_read_change_request( const struct read_change_request *req ) +{ + fprintf( stderr, " handle=%p", req->handle ); +} + +static void dump_read_change_reply( const struct read_change_reply *req ) +{ + fprintf( stderr, " action=%d,", req->action ); + fprintf( stderr, " name=" ); + dump_varargs_string( cur_size ); } static void dump_create_mapping_request( const struct create_mapping_request *req ) @@ -3274,6 +3289,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_read_directory_changes_request, + (dump_func)dump_read_change_request, (dump_func)dump_create_mapping_request, (dump_func)dump_open_mapping_request, (dump_func)dump_get_mapping_info_request, @@ -3488,6 +3504,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { (dump_func)0, (dump_func)0, (dump_func)0, + (dump_func)dump_read_change_reply, (dump_func)dump_create_mapping_reply, (dump_func)dump_open_mapping_reply, (dump_func)dump_get_mapping_info_reply, @@ -3702,6 +3719,7 @@ static const char * const req_names[REQ_NB_REQUESTS] = { "move_console_output", "send_console_signal", "read_directory_changes", + "read_change", "create_mapping", "open_mapping", "get_mapping_info",