commit 07c343ebd82178b716266a529a12cfc7fd0c1a1c Author: Matteo Bruni Date: Mon Jul 20 15:38:26 2015 +0100 ntdll: Fix read_directory_getdirentries(). (v2) --- v2: This still abuses the fd position but arguably in a significantly less nasty way. I fixed the other points reported by Ken too. diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c index f9562d2..2c5d940 100644 --- a/dlls/ntdll/directory.c +++ b/dlls/ntdll/directory.c @@ -1820,133 +1820,118 @@ static int read_directory_getdirentries( int fd, IO_STATUS_BLOCK *io, void *buff BOOLEAN restart_scan, FILE_INFORMATION_CLASS class ) { long restart_pos; - ULONG_PTR restart_info_pos = 0; - size_t size, initial_size = length; - int res, fake_dot_dot = 1; - char *data, local_buffer[8192]; + int bytes_read, bytes_left, prev_bytes_left; + off_t i; + char data[8192]; struct dirent *de; - union file_directory_info *info, *last_info = NULL, *restart_last_info = NULL; + union file_directory_info *info, *last_info = NULL; - size = initial_size; - data = local_buffer; - if (size > sizeof(local_buffer) && !(data = RtlAllocateHeap( GetProcessHeap(), 0, size ))) + io->u.Status = STATUS_SUCCESS; + + if (restart_scan) { - io->u.Status = STATUS_NO_MEMORY; - return io->u.Status; + lseek( fd, 0, SEEK_SET ); + i = 0; + } + else + { + i = lseek( fd, 0, SEEK_CUR ); + if (i == OFF_MAX - 2 || i == OFF_MAX - 1) + lseek( fd, 0, SEEK_SET ); } - if (restart_scan) lseek( fd, 0, SEEK_SET ); - - io->u.Status = STATUS_SUCCESS; - - /* FIXME: should make sure size is larger than filesystem block size */ - res = wine_getdirentries( fd, data, size, &restart_pos ); - if (res == -1) + bytes_read = bytes_left = wine_getdirentries( fd, data, sizeof(data), &restart_pos ); + if (bytes_read == -1) { io->u.Status = FILE_GetNtStatus(); - res = 0; goto done; } de = (struct dirent *)data; - if (restart_scan) + for (;;) { - /* check if we got . and .. from getdirentries */ - if (res > 0) + if (i == 0) { - if (!strcmp( de->d_name, "." ) && res > dir_reclen(de)) - { - struct dirent *next_de = (struct dirent *)(data + dir_reclen(de)); - if (!strcmp( next_de->d_name, ".." )) fake_dot_dot = 0; - } + info = append_entry( buffer, io, length, ".", NULL, mask, class ); } - /* make sure we have enough room for both entries */ - if (fake_dot_dot) + else if (i == OFF_MAX - 2) { - const ULONG min_info_size = dir_info_size( class, 1 ) + dir_info_size( class, 2 ); - if (length < min_info_size || single_entry) - { - FIXME( "not enough room %u/%u for fake . and .. entries\n", length, single_entry ); - fake_dot_dot = 0; - } + info = append_entry( buffer, io, length, "..", NULL, mask, class ); } - - if (fake_dot_dot) + else if (bytes_left > 0) { - if ((info = append_entry( buffer, io, length, ".", NULL, mask, class ))) - last_info = info; - if ((info = append_entry( buffer, io, length, "..", NULL, mask, class ))) - last_info = info; + prev_bytes_left = bytes_left; + bytes_left -= dir_reclen(de); + if (de->d_fileno && strcmp( de->d_name, "." ) && strcmp( de->d_name, ".." )) + info = append_entry( buffer, io, length, de->d_name, NULL, mask, class ); + else + info = NULL; + de = (struct dirent *)((char *)de + dir_reclen(de)); + } + else + { + info = NULL; + } - restart_last_info = last_info; - restart_info_pos = io->Information; + /* We count like 0, OFF_MAX - 2, OFF_MAX - 1, 1, 2, ... */ + if (i == 0) + i = OFF_MAX - 2; + else + i++; + if (i == OFF_MAX) + i = 1; + + if (info) + { + last_info = info; + if (single_entry) + break; + } + else if (io->u.Status == STATUS_BUFFER_OVERFLOW) + { + i--; + if (i == 0) + i = OFF_MAX - 1; + else if (i == OFF_MAX - 3) + i = 0; + bytes_left = prev_bytes_left; + if (last_info) + io->u.Status = STATUS_SUCCESS; + break; } - } - while (res > 0) - { - res -= dir_reclen(de); - if (de->d_fileno && - !(fake_dot_dot && (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." )))) + if (!bytes_left) { - if ((info = append_entry( buffer, io, length, de->d_name, NULL, mask, class ))) + if (bytes_read >= sizeof(struct dirent)) { - last_info = info; - if (!has_wildcard( mask )) break; - /* if we have to return but the buffer contains more data, restart with a smaller size */ - if (res > 0 && (single_entry || io->Information + max_dir_info_size(class) > length)) - { - lseek( fd, (unsigned long)restart_pos, SEEK_SET ); - size = (char *)de + dir_reclen(de) - data; - io->Information = restart_info_pos; - last_info = restart_last_info; - goto restart; - } + bytes_read = bytes_left = wine_getdirentries( fd, data, sizeof(data), &restart_pos ); + de = (struct dirent *)data; } else - { - if (io->u.Status == STATUS_BUFFER_OVERFLOW) - { - lseek( fd, (unsigned long)restart_pos, SEEK_SET ); - if (restart_info_pos) /* if we have a complete read already, return it */ - { - io->u.Status = STATUS_SUCCESS; - io->Information = restart_info_pos; - last_info = restart_last_info; - break; - } - /* otherwise restart from the start with a smaller size */ - size = (char *)de - data; - if (!size) break; - io->Information = 0; - last_info = NULL; - goto restart; - } - } - } - /* move on to the next entry */ - if (res > 0) - { - de = (struct dirent *)((char *)de + dir_reclen(de)); - continue; + break; } - if (size < initial_size) break; /* already restarted once, give up now */ - restart_last_info = last_info; - restart_info_pos = io->Information; - restart: - res = wine_getdirentries( fd, data, size, &restart_pos ); - de = (struct dirent *)data; + } + + /* Store the updated position in the fd. */ + if (i == OFF_MAX - 2 || i == OFF_MAX - 1) + { + i = lseek( fd, i, SEEK_SET ); + } + else + { + lseek( fd, restart_pos, SEEK_SET ); + if (bytes_left != bytes_read) + wine_getdirentries( fd, data, bytes_read - bytes_left, &restart_pos ); } if (last_info) last_info->next = 0; else if (io->u.Status != STATUS_BUFFER_OVERFLOW) io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES; - res = 0; + done: - if (data != local_buffer) RtlFreeHeap( GetProcessHeap(), 0, data ); - return res; + return 0; } #ifdef _DARWIN_FEATURE_64_BIT_INODE