diff --git a/winsup/cygwin/fhandler_registry.cc b/winsup/cygwin/fhandler_registry.cc index ce4335f..4c95c77 100644 --- a/winsup/cygwin/fhandler_registry.cc +++ b/winsup/cygwin/fhandler_registry.cc @@ -143,6 +143,48 @@ decode_regname (char * dst, const char * src, int len = -1) return 0; } + +/* Hash table to limit calls to key_exists (). + */ +class __DIR_hash +{ +public: + __DIR_hash () + { + memset (table, 0, sizeof(table)); + } + + void set (unsigned h) + { + table [(h >> 3) & (HASH_SIZE - 1)] |= (1 << (h & 0x3)); + } + + bool is_set (unsigned h) const + { + return (table [(h >> 3) & (HASH_SIZE - 1)] & (1 << (h & 0x3))) != 0; + } + +private: + enum { HASH_SIZE = 1024 }; + unsigned char table[HASH_SIZE]; +}; + +#define d_hash(d) ((__DIR_hash *) (d)->__d_internal) + + +/* Return true if subkey NAME exists in key PARENT. + */ +static bool +key_exists (HKEY parent, const char * name, DWORD wow64) +{ + HKEY hKey = (HKEY) INVALID_HANDLE_VALUE; + LONG error = RegOpenKeyEx (parent, name, 0, KEY_READ | wow64, &hKey); + if (error == ERROR_SUCCESS) + RegCloseKey (hKey); + + return (error == ERROR_SUCCESS || error == ERROR_ACCESS_DENIED); +} + /* Returns 0 if path doesn't exist, >0 if path is a directory, * <0 if path is a file. * @@ -381,13 +423,16 @@ fhandler_registry::readdir (DIR *dir, dirent *de) res = 0; goto out; } - if (dir->__handle == INVALID_HANDLE_VALUE && dir->__d_position == 0) + if (dir->__handle == INVALID_HANDLE_VALUE) { + if (dir->__d_position != 0) + goto out; handle = open_key (path + 1, KEY_READ, wow64, false); dir->__handle = handle; + if (dir->__handle == INVALID_HANDLE_VALUE) + goto out; + dir->__d_internal = (unsigned) new __DIR_hash (); } - if (dir->__handle == INVALID_HANDLE_VALUE) - goto out; if (dir->__d_position < SPECIAL_DOT_FILE_COUNT) { strcpy (de->d_name, special_dot_files[dir->__d_position++]); @@ -425,14 +470,37 @@ retry: } /* We get here if `buf' contains valid data. */ + dir->__d_position++; + if (dir->__d_position & REG_ENUM_VALUES_MASK) + dir->__d_position += 0x10000; + if (*buf == 0) strcpy (de->d_name, DEFAULT_VALUE_NAME); - else if (encode_regname (de->d_name, buf)) - goto retry; + else + { + /* Skip value if name is identical to a previous key name. */ + unsigned h = hash_path_name (1, buf); + if (! (dir->__d_position & REG_ENUM_VALUES_MASK)) + d_hash (dir)->set (h); + else if (d_hash (dir)->is_set (h) + && key_exists ((HKEY) dir->__handle, buf, wow64)) + { + buf_size = NAME_MAX + 1; + goto retry; + } + + if (encode_regname (de->d_name, buf)) + { + buf_size = NAME_MAX + 1; + goto retry; + } + } - dir->__d_position++; if (dir->__d_position & REG_ENUM_VALUES_MASK) - dir->__d_position += 0x10000; + de->d_type = DT_REG; + else + de->d_type = DT_DIR; + res = 0; out: syscall_printf ("%d = readdir (%p, %p)", res, dir, de); @@ -473,11 +541,14 @@ int fhandler_registry::closedir (DIR * dir) { int res = 0; - if (dir->__handle != INVALID_HANDLE_VALUE && - RegCloseKey ((HKEY) dir->__handle) != ERROR_SUCCESS) + if (dir->__handle != INVALID_HANDLE_VALUE) { - __seterrno (); - res = -1; + delete d_hash (dir); + if (RegCloseKey ((HKEY) dir->__handle) != ERROR_SUCCESS) + { + __seterrno (); + res = -1; + } } syscall_printf ("%d = closedir (%p)", res, dir); return 0;