server: Distinguish between a directory and a file changing in ReadDirectoryChangesW. Add a test for it.
diff --git a/server/change.c b/server/change.c index aa8e0cb..d0de942 100644 --- a/server/change.c +++ b/server/change.c
@@ -485,24 +485,20 @@ 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 @@ 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 @@ } 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 )