server: implement move/rename change notifications and support for multiple change notification in one reply
Michael Pujos
pujos.michael at laposte.net
Tue Dec 16 18:04:33 CST 2008
---
dlls/kernel32/tests/change.c | 128 +++++++++++++++++++++++++++++++++-
dlls/ntdll/directory.c | 90 ++++++++++++++++++------
include/wine/server_protocol.h | 2 +-
server/change.c | 150 +++++++++++++++++++++++++++++++--------
server/trace.c | 2 +-
5 files changed, 317 insertions(+), 55 deletions(-)
diff --git a/dlls/kernel32/tests/change.c b/dlls/kernel32/tests/change.c
index cb500ef..ba230df 100644
--- a/dlls/kernel32/tests/change.c
+++ b/dlls/kernel32/tests/change.c
@@ -379,9 +379,10 @@ static void test_readdirectorychanges(void)
char buffer[0x1000];
DWORD fflags, filter = 0, r, dwCount;
OVERLAPPED ov;
- WCHAR path[MAX_PATH], subdir[MAX_PATH], subsubdir[MAX_PATH];
+ WCHAR path[MAX_PATH], subdir[MAX_PATH], subdir2[MAX_PATH], subsubdir[MAX_PATH], movedsubdir[MAX_PATH];
static const WCHAR szBoo[] = { '\\','b','o','o',0 };
static const WCHAR szHoo[] = { '\\','h','o','o',0 };
+ static const WCHAR szHoo2[] = { '\\','h','o','o', '2', 0 };
static const WCHAR szGa[] = { '\\','h','o','o','\\','g','a',0 };
PFILE_NOTIFY_INFORMATION pfni;
@@ -402,15 +403,24 @@ static void test_readdirectorychanges(void)
if (!r)
return;
+ movedsubdir[0] = 0;
+ lstrcpyW(movedsubdir, path);
+ lstrcatW(movedsubdir, szHoo );
+
lstrcatW( path, szBoo );
lstrcpyW( subdir, path );
lstrcatW( subdir, szHoo );
+ lstrcpyW( subdir2, path );
+ lstrcatW( subdir2, szHoo2 );
+
lstrcpyW( subsubdir, path );
lstrcatW( subsubdir, szGa );
RemoveDirectoryW( subsubdir );
RemoveDirectoryW( subdir );
+ RemoveDirectoryW( subdir2 );
+ RemoveDirectoryW( movedsubdir );
RemoveDirectoryW( path );
r = CreateDirectoryW(path, NULL);
@@ -594,6 +604,122 @@ static void test_readdirectorychanges(void)
dwCount = (char *)&pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] - buffer;
ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong %lu/%u\n",ov.InternalHigh, dwCount );
+
+ /* MOVE AND RENAME DIRECTORY TESTS */
+
+ /* Rename directory inside watched dir */
+
+ filter = 0x1f;
+ r = CreateDirectoryW( subdir, NULL );
+ ok( r == TRUE, "failed to create directory\n");
+
+ r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
+ ok( r == TRUE , "should return true\n");
+ r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
+ ok( r == TRUE, "getoverlappedresult failed\n");
+
+ ov.Internal = 1;
+ ov.InternalHigh = 1;
+ ResetEvent(ov.hEvent);
+ memset( buffer, 0, sizeof buffer );
+ r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
+ ok(r==TRUE, "should return true\n");
+
+ r = MoveFileW( subdir, subdir2);
+ ok( r == TRUE, "failed to rename directory (%u)\n", GetLastError());
+
+ r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
+ ok( r == TRUE, "getoverlappedresult failed\n");
+ /* next record is aligned on DWORD that'w why first record is not 18 bytes */
+ ok( dwCount == 20 + 20, "dwCount wrong (%d)\n", dwCount);
+
+ pfni = (PFILE_NOTIFY_INFORMATION) buffer;
+ ok( pfni->NextEntryOffset == 20, "offset wrong %u\n", pfni->NextEntryOffset );
+ ok( pfni->Action == FILE_ACTION_RENAMED_OLD_NAME, "action wrong %u\n", pfni->Action );
+ ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
+ ok( !memcmp(pfni->FileName,&szHoo[1],3*sizeof(WCHAR)), "name wrong\n" );
+
+
+ pfni = (PFILE_NOTIFY_INFORMATION)&buffer[pfni->NextEntryOffset];
+ ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
+ ok( pfni->Action == FILE_ACTION_RENAMED_NEW_NAME, "action wrong %u\n", pfni->Action );
+ ok( pfni->FileNameLength == 4*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
+ ok( !memcmp(pfni->FileName,&szHoo2[1],4*sizeof(WCHAR)), "name wrong\n" );
+
+
+ /* Same as above but with buffer too small that can only hold the first record */
+
+ ov.Internal = 1;
+ ov.InternalHigh = 1;
+ ResetEvent(ov.hEvent);
+ filter = 0x1f;
+ memset( buffer, 0, sizeof buffer );
+ r = pReadDirectoryChangesW(hdir,buffer, 28, FALSE,filter,NULL,&ov,NULL);
+ ok(r==TRUE, "should return true\n");
+
+ r = MoveFileW( subdir2, subdir);
+ ok( r == TRUE, "failed to rename directory (%u)\n", GetLastError());
+
+ r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
+ ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
+ ok( dwCount == 0, "dwCount wrong\n");
+
+ /* Move directory out of watched tree : should receive FILE_ACTION_REMOVED */
+
+ ov.Internal = 1;
+ ov.InternalHigh = 1;
+ ResetEvent(ov.hEvent);
+ filter = 0x1f;
+ memset( buffer, 0, sizeof buffer );
+ r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
+ ok(r==TRUE, "should return true\n");
+
+ r = MoveFileW( subdir, movedsubdir );
+ ok( r == TRUE, "failed to move directory (%u)\n", GetLastError());
+
+ r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
+ ok( r == TRUE, "getoverlappedresult failed\n");
+ ok( dwCount == 0x12, "count wrong (%d)\n", dwCount);
+ ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
+ ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong\n");
+
+ pfni = (PFILE_NOTIFY_INFORMATION) buffer;
+ ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
+ ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong %u\n", pfni->Action );
+ ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
+ ok( !memcmp(pfni->FileName,&szHoo[1],3*sizeof(WCHAR)), "name wrong\n" );
+
+
+ /* Move external directory inside watched tree : should receive FILE_ACTION_ADDED */
+
+
+ ov.Internal = 1;
+ ov.InternalHigh = 1;
+ ResetEvent(ov.hEvent);
+ filter = 0x1f;
+ memset( buffer, 0, sizeof buffer );
+ r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
+ ok(r==TRUE, "should return true\n");
+
+ r = MoveFileW( movedsubdir, subdir );
+ ok( r == TRUE, "failed to move directory (%u)\n", GetLastError());
+
+ r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
+ ok( r == TRUE, "getoverlappedresult failed\n");
+ ok( dwCount == 0x12, "count wrong (%d)\n", dwCount);
+ ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
+ ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong\n");
+
+ pfni = (PFILE_NOTIFY_INFORMATION) buffer;
+ ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
+ ok( pfni->Action == FILE_ACTION_ADDED, "action wrong %u\n", pfni->Action );
+ ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
+ ok( !memcmp(pfni->FileName,&szHoo[1],3*sizeof(WCHAR)), "name wrong\n" );
+
+ r = RemoveDirectoryW( subdir );
+ ok( r == TRUE, "failed to remove directory\n");
+
+
CloseHandle(hdir);
r = RemoveDirectoryW( path );
diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index baff45a..66d8512 100644
--- a/dlls/ntdll/directory.c
+++ b/dlls/ntdll/directory.c
@@ -2306,48 +2306,96 @@ static NTSTATUS read_changes_apc( void *user, PIO_STATUS_BLOCK iosb, NTSTATUS st
{
struct read_changes_info *info = user;
char path[PATH_MAX];
+ WCHAR wpath[PATH_MAX];
+ char *reply_data;
+ const int reply_data_max_size = 16384;
NTSTATUS ret = STATUS_SUCCESS;
- int len, action, i;
+ int len, pfni_len, action_count, i, j;
+ struct change_record {
+ int action;
+ int len;
+ char name[1];
+ } *cur_record;
+
+ reply_data = malloc(reply_data_max_size);
+ *total = 0;
SERVER_START_REQ( read_change )
{
- req->handle = wine_server_obj_handle( info->FileHandle );
- wine_server_set_reply( req, path, PATH_MAX );
+ req->handle = info->FileHandle;
+ wine_server_set_reply( req, reply_data, reply_data_max_size);
ret = wine_server_call( req );
- action = reply->action;
len = wine_server_reply_size( reply );
+ action_count = reply->action_count;
}
SERVER_END_REQ;
- if (ret == STATUS_SUCCESS && info->Buffer &&
- (info->BufferSize > (sizeof (FILE_NOTIFY_INFORMATION) + len*sizeof(WCHAR))))
+ /* action_count == -1 => reply_data buffer was too small */
+ if (ret == STATUS_SUCCESS && info->Buffer && action_count != -1)
{
- PFILE_NOTIFY_INFORMATION pfni;
+ PFILE_NOTIFY_INFORMATION pfni = (PFILE_NOTIFY_INFORMATION) info->Buffer;
+ cur_record = (struct change_record *) reply_data;
+
+ for(i = 0 ; i < action_count ; i++)
+ {
+
+ memcpy(path, cur_record->name, cur_record->len);
+
+ /* convert to an NT style path */
+ for (j = 0; j < cur_record->len ; j++)
+ {
+ if (path[j] == '/')
+ path[j] = '\\';
+ }
+
+ len = ntdll_umbstowcs( 0, path, cur_record->len, wpath, PATH_MAX * sizeof(WCHAR) );
+ pfni_len = sizeof (FILE_NOTIFY_INFORMATION) - sizeof (DWORD) + len * sizeof(WCHAR);
- pfni = (PFILE_NOTIFY_INFORMATION) info->Buffer;
+ if((char *)pfni + pfni_len <= (char *)info->Buffer + info->BufferSize)
+ {
- /* convert to an NT style path */
- for (i=0; i<len; i++)
- if (path[i] == '/')
- path[i] = '\\';
+ if(i == action_count - 1) {
+ pfni->NextEntryOffset = 0;
+ *total += pfni_len;
+ } else {
+ /* next record must be aligned on DWORD */
+ if(pfni_len % sizeof(DWORD) != 0)
+ pfni->NextEntryOffset = pfni_len - (pfni_len % sizeof(DWORD)) + sizeof(DWORD);
+ else
+ pfni->NextEntryOffset = pfni_len;
- len = ntdll_umbstowcs( 0, path, len, pfni->FileName,
- info->BufferSize - sizeof (*pfni) );
+ *total += pfni->NextEntryOffset;
+ }
+
+ pfni->Action = cur_record->action;
+ pfni->FileNameLength = len * sizeof (WCHAR);
+ memcpy(pfni->FileName, wpath, pfni->FileNameLength);
+ pfni->FileName[len] = 0;
+
+ pfni = (PFILE_NOTIFY_INFORMATION) ((char *)pfni + pfni->NextEntryOffset);
+ cur_record = (struct change_record *) ((char *)cur_record + offsetof(struct change_record, name[cur_record->len]));
+ }
+ else
+ {
+ /* no room in buffer: discard all data */
+ free(reply_data);
+
+ iosb->u.Status = STATUS_NOTIFY_ENUM_DIR;
+ iosb->Information = *total = 0;
+ return STATUS_NOTIFY_ENUM_DIR;
+ }
+ }
- pfni->NextEntryOffset = 0;
- pfni->Action = action;
- pfni->FileNameLength = len * sizeof (WCHAR);
- pfni->FileName[len] = 0;
- len = sizeof (*pfni) - sizeof (DWORD) + pfni->FileNameLength;
}
else
{
ret = STATUS_NOTIFY_ENUM_DIR;
- len = 0;
}
+ free(reply_data);
+
iosb->u.Status = ret;
- iosb->Information = *total = len;
+ iosb->Information = *total;
return ret;
}
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h
index 129ca7a..f7f3e80 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -1661,7 +1661,7 @@ struct read_change_request
struct read_change_reply
{
struct reply_header __header;
- int action;
+ int action_count;
/* VARARG(name,string); */
};
diff --git a/server/change.c b/server/change.c
index aded849..ac278da 100644
--- a/server/change.c
+++ b/server/change.c
@@ -24,6 +24,7 @@
#include <assert.h>
#include <fcntl.h>
+#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
@@ -157,6 +158,7 @@ struct dir
int subtree; /* do we want to watch subdirectories? */
struct list change_records; /* data for the change */
struct list in_entry; /* entry in the inode dirs list */
+ struct list changed_dir_entry;
struct inode *inode; /* inode of the associated directory */
};
@@ -627,8 +629,8 @@ static int inotify_get_poll_events( struct fd *fd )
return POLLIN;
}
-static void inotify_do_change_notify( struct dir *dir, unsigned int action,
- const char *relpath )
+static void inotify_add_change_record( struct dir *dir, unsigned int action,
+ const char *relpath )
{
struct change_record *record;
@@ -647,8 +649,6 @@ static void inotify_do_change_notify( struct dir *dir, unsigned int action,
list_add_tail( &dir->change_records, &record->entry );
}
-
- fd_async_wake_up( dir->fd, ASYNC_TYPE_WAIT, STATUS_ALERTED );
}
static unsigned int filter_from_event( struct inotify_event *ie )
@@ -792,12 +792,14 @@ static int prepend( char **path, const char *segment )
return 1;
}
-static void inotify_notify_all( struct inotify_event *ie )
+
+static void inotify_notify_all( struct inotify_event *ie, struct inotify_event *ie_prev, struct inotify_event *ie_next, struct list *changed_dirs )
{
unsigned int filter, action;
struct inode *inode, *i;
char *path = NULL;
- struct dir *dir;
+ struct dir *dir, *changed_dir;
+ int found;
inode = inode_from_wd( ie->wd );
if (!inode)
@@ -808,8 +810,8 @@ static void inotify_notify_all( struct inotify_event *ie )
filter = filter_from_event( ie );
- if (ie->mask & IN_CREATE)
- {
+ if (ie->mask & IN_CREATE ||
+ ((ie->mask & IN_MOVED_TO) && (!ie_prev || !(ie_prev->mask & IN_MOVED_FROM)))) {
switch (inode_check_dir( inode, ie->name ))
{
case 1:
@@ -823,11 +825,16 @@ static void inotify_notify_all( struct inotify_event *ie )
/* Maybe the file disappeared before we could check it? */
}
action = FILE_ACTION_ADDED;
- }
- else if (ie->mask & IN_DELETE)
+ } else if (ie->mask & IN_DELETE ||
+ ((ie->mask & IN_MOVED_FROM) && (!ie_next || !(ie_next->mask & IN_MOVED_TO)))) {
action = FILE_ACTION_REMOVED;
- else
+ } else if (ie->mask & IN_MOVED_FROM) {
+ action = FILE_ACTION_RENAMED_OLD_NAME;
+ } else if (ie->mask & IN_MOVED_TO) {
+ action = FILE_ACTION_RENAMED_NEW_NAME;
+ } else {
action = FILE_ACTION_MODIFIED;
+ }
/*
* Work our way up the inode hierarchy
@@ -839,9 +846,26 @@ static void inotify_notify_all( struct inotify_event *ie )
for (i = inode; i; i = i->parent)
{
- LIST_FOR_EACH_ENTRY( dir, &i->dirs, struct dir, in_entry )
- if ((filter & dir->filter) && (i==inode || dir->subtree))
- inotify_do_change_notify( dir, action, path );
+ LIST_FOR_EACH_ENTRY( dir, &i->dirs, struct dir, in_entry ) {
+ if ((filter & dir->filter) && (i==inode || dir->subtree)) {
+ inotify_add_change_record( dir, action, path);
+
+ /* add dir to list of chaged dirs if not already in list */
+ found = FALSE;
+ LIST_FOR_EACH_ENTRY( changed_dir, changed_dirs, struct dir, changed_dir_entry )
+ {
+ if(changed_dir == dir)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if(!found) {
+ list_add_tail(changed_dirs, &dir->changed_dir_entry);
+ }
+ }
+ }
if (!i->name || !prepend( &path, i->name ))
break;
@@ -849,7 +873,7 @@ static void inotify_notify_all( struct inotify_event *ie )
free( path );
- if (ie->mask & IN_DELETE)
+ if(action == FILE_ACTION_REMOVED)
{
i = inode_from_name( inode, ie->name );
if (i)
@@ -859,27 +883,60 @@ static void inotify_notify_all( struct inotify_event *ie )
static void inotify_poll_event( struct fd *fd, int event )
{
- int r, ofs, unix_fd;
+ int count, total_count, ofs, unix_fd, to_read;
char buffer[0x1000];
- struct inotify_event *ie;
+ struct inotify_event *ie, *ie_prev, *ie_next;
+ struct list changed_dirs;
+ struct dir *changed_dir;
unix_fd = get_unix_fd( fd );
- r = read( unix_fd, buffer, sizeof buffer );
- if (r < 0)
+
+ for( total_count = 0 ; ; total_count += count )
{
- fprintf(stderr,"inotify_poll_event(): inotify read failed!\n");
- return;
+ if(ioctl(unix_fd, FIONREAD, &to_read) == -1) {
+ fprintf(stderr,"inotify_poll_event(): ioctl failed (%s)\n", strerror(errno));
+ break;
+ }
+
+ if(to_read <= 0 || (total_count + to_read) > sizeof(buffer))
+ break;
+
+ count = read( unix_fd, &buffer[total_count], to_read);
+ if (count < 0)
+ {
+ break; /* we may have read some bytes previously, process them */
+ }
+ usleep(10000);
}
- for( ofs = 0; ofs < r - offsetof(struct inotify_event, name); )
+ list_init( &changed_dirs );
+
+ for( ofs = 0, ie_prev = NULL; ofs < total_count - offsetof(struct inotify_event, name); ie_prev = ie)
{
- ie = (struct inotify_event*) &buffer[ofs];
+ ie = (struct inotify_event*) &buffer[ofs];
+
if (!ie->len)
break;
ofs += offsetof( struct inotify_event, name[ie->len] );
- if (ofs > r) break;
- inotify_notify_all( ie );
+ if (ofs > total_count) break;
+
+ ie_next = (struct inotify_event*) &buffer[ofs];
+ if(ofs >= total_count - offsetof(struct inotify_event, name) ||
+ !ie_next->len ||
+ ofs + offsetof( struct inotify_event, name[ie_next->len] ) > total_count)
+ {
+ ie_next = NULL;
+ }
+
+ inotify_notify_all( ie, ie_prev, ie_next, &changed_dirs );
}
+
+ /* notify all changed dirs */
+ LIST_FOR_EACH_ENTRY( changed_dir, &changed_dirs, struct dir, changed_dir_entry )
+ {
+ fd_async_wake_up( changed_dir->fd, ASYNC_TYPE_WAIT, STATUS_ALERTED );
+ }
+
}
static inline struct fd *create_inotify_fd( void )
@@ -1176,18 +1233,49 @@ DECL_HANDLER(read_change)
struct change_record *record;
struct dir *dir;
+ int reply_max_size = get_reply_max_size();
+ char *reply_data, *reply_data_cur;
+
+ int len;
+
dir = get_dir_obj( current->process, req->handle, 0 );
- if (!dir)
+ if (!dir) {
+ fprintf(stderr, "read_change: can't get dir\n");
return;
+ }
- if ((record = get_first_change_record( dir )) != NULL)
+ reply_data_cur = reply_data = malloc(reply_max_size);
+ reply->action_count = 0;
+
+ while((record = get_first_change_record( dir )) != NULL)
{
- reply->action = record->action;
- set_reply_data( record->name, record->len );
- free( record );
+ if(reply->action_count != -1) {
+
+ len = 2*sizeof(int) + record->len;
+
+ if(reply_data_cur + len <= reply_data + reply_max_size)
+ {
+ memcpy(reply_data_cur, &record->action, len);
+ reply_data_cur += len;
+ reply->action_count++;
+ }
+ else
+ {
+ /* not enough room in buffer : discard all remaining records */
+ reply->action_count = -1;
+ reply_data_cur = reply_data;
+ }
+ }
+ free( record );
+ }
+
+ len = reply_data_cur - reply_data;
+ if(len > 0) {
+ set_reply_data( reply_data, len);
}
else
- set_error( STATUS_NO_DATA_DETECTED );
+ set_error( STATUS_NO_DATA_DETECTED );
+ free(reply_data);
release_object( dir );
}
diff --git a/server/trace.c b/server/trace.c
index 89dbb8c..b463ada 100644
--- a/server/trace.c
+++ b/server/trace.c
@@ -1730,7 +1730,7 @@ static void dump_read_change_request( const struct read_change_request *req )
static void dump_read_change_reply( const struct read_change_reply *req )
{
- fprintf( stderr, " action=%d,", req->action );
+ fprintf( stderr, " action count=%d,", req->action_count );
fprintf( stderr, " name=" );
dump_varargs_string( cur_size );
}
--
1.4.4.2
--------------060802020303050006050105--
More information about the wine-patches
mailing list