diff --git a/dlls/kernel/tests/change.c b/dlls/kernel/tests/change.c index 3c8e2451c77..dfdaa9e6694 100644 --- a/dlls/kernel/tests/change.c +++ b/dlls/kernel/tests/change.c @@ -560,6 +560,85 @@ static void test_readdirectorychanges_null(void) ok( r == TRUE, "failed to remove directory\n"); } +static void test_readdirectorychanges_filedir(void) +{ + NTSTATUS r; + HANDLE hdir, hfile; + char buffer[0x1000]; + DWORD fflags, filter = 0; + OVERLAPPED ov; + WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH]; + static const WCHAR szBoo[] = { '\\','b','o','o',0 }; + static const WCHAR szHoo[] = { '\\','h','o','o',0 }; + static const WCHAR szFoo[] = { '\\','f','o','o',0 }; + PFILE_NOTIFY_INFORMATION pfni; + + r = GetTempPathW( MAX_PATH, path ); + ok( r != 0, "temp path failed\n"); + if (!r) + return; + + lstrcatW( path, szBoo ); + lstrcpyW( subdir, path ); + lstrcatW( subdir, szHoo ); + + lstrcpyW( file, path ); + lstrcatW( file, szFoo ); + + DeleteFileW( file ); + RemoveDirectoryW( subdir ); + RemoveDirectoryW( path ); + + r = CreateDirectoryW(path, NULL); + ok( r == TRUE, "failed to create directory\n"); + + fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; + hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, fflags, NULL); + ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n"); + + ov.hEvent = CreateEvent( NULL, 0, 0, NULL ); + + filter = FILE_NOTIFY_CHANGE_FILE_NAME; + + r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL); + ok(r==TRUE, "should return true\n"); + + r = WaitForSingleObject( ov.hEvent, 10 ); + ok( r == WAIT_TIMEOUT, "should timeout\n" ); + + r = CreateDirectoryW( subdir, NULL ); + ok( r == TRUE, "failed to create directory\n"); + + hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL ); + ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n"); + ok( CloseHandle(hfile), "failed toc lose file\n"); + + r = WaitForSingleObject( ov.hEvent, INFINITE ); + ok( r == WAIT_OBJECT_0, "event should be ready\n" ); + + ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n"); + ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n"); + + pfni = (PFILE_NOTIFY_INFORMATION) buffer; + ok( pfni->NextEntryOffset == 0, "offset wrong\n" ); + ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" ); + ok( pfni->FileNameLength == 6, "len wrong\n" ); + ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" ); + + r = DeleteFileW( file ); + ok( r == TRUE, "failed to delete file\n"); + + r = RemoveDirectoryW( subdir ); + ok( r == TRUE, "failed to remove directory\n"); + + CloseHandle(hdir); + + r = RemoveDirectoryW( path ); + ok( r == TRUE, "failed to remove directory\n"); +} + START_TEST(change) { HMODULE hkernel32 = GetModuleHandle("kernel32"); @@ -570,4 +649,5 @@ START_TEST(change) test_ffcn(); test_readdirectorychanges(); test_readdirectorychanges_null(); + test_readdirectorychanges_filedir(); } diff --git a/server/change.c b/server/change.c index aa8e0cb13fa..d0de9424f2b 100644 --- a/server/change.c +++ b/server/change.c @@ -485,24 +485,20 @@ static int inotify_get_poll_events( struct fd *fd ) return POLLIN; } -static void inotify_do_change_notify( struct dir *dir, struct inotify_event *ie ) +static void inotify_do_change_notify( struct dir *dir, unsigned int action, + const char *relpath ) { struct change_record *record; if (dir->want_data) { - size_t len = strlen(ie->name); + size_t len = strlen(relpath); record = malloc( offsetof(struct change_record, name[len]) ); 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, len ); + record->action = action; + memcpy( record->name, relpath, len ); record->len = len; list_add_tail( &dir->change_records, &record->entry ); @@ -535,11 +531,39 @@ static unsigned int filter_from_event( struct inotify_event *ie ) return filter; } +static int inode_entry_is_dir( struct inode *inode, const char *name ) +{ + /* distinguish a created file from a directory */ + char *path; + struct list *head; + struct stat st; + int unix_fd, r; + + head = list_head( &inode->dirs ); + if (!head) + return -1; + + path = malloc( strlen(name) + 32 ); + if (!path) + return -1; + + unix_fd = get_unix_fd( LIST_ENTRY( head, struct dir, in_entry )->fd ); + sprintf( path, "/proc/self/fd/%u/%s", unix_fd, name ); + r = stat( path, &st ); + free( path ); + if (-1 == r) + return -1; + + if (S_ISDIR(st.st_mode)) + return 1; + return 0; +} + static void inotify_notify_all( struct inotify_event *ie ) { + unsigned int filter, action; struct inode *inode; struct dir *dir; - unsigned int filter; inode = inode_from_wd( ie->wd ); if (!inode) @@ -549,10 +573,31 @@ static void inotify_notify_all( struct inotify_event *ie ) } filter = filter_from_event( ie ); + + if (ie->mask & IN_CREATE) + { + switch (inode_entry_is_dir( inode, ie->name )) + { + case 1: + filter &= ~FILE_NOTIFY_CHANGE_FILE_NAME; + break; + case 0: + filter &= ~FILE_NOTIFY_CHANGE_DIR_NAME; + break; + default: + break; + /* Maybe the file disappeared before we could check it? */ + } + action = FILE_ACTION_ADDED; + } + else if (ie->mask & IN_DELETE) + action = FILE_ACTION_REMOVED; + else + action = FILE_ACTION_MODIFIED; LIST_FOR_EACH_ENTRY( dir, &inode->dirs, struct dir, in_entry ) if (filter & dir->filter) - inotify_do_change_notify( dir, ie ); + inotify_do_change_notify( dir, action, ie->name ); } static void inotify_poll_event( struct fd *fd, int event )