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