diff options
Diffstat (limited to 'winsup/cygwin/fhandler/registry.cc')
-rw-r--r-- | winsup/cygwin/fhandler/registry.cc | 1115 |
1 files changed, 1115 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/registry.cc b/winsup/cygwin/fhandler/registry.cc new file mode 100644 index 000000000..2830c708a --- /dev/null +++ b/winsup/cygwin/fhandler/registry.cc @@ -0,0 +1,1115 @@ +/* fhandler_registry.cc: fhandler for /proc/registry virtual filesystem + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +/* FIXME: Access permissions are ignored at the moment. */ + +#include "winsup.h" +#include <stdlib.h> +#include "cygerrno.h" +#include "security.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "child_info.h" + +#define _LIBC +#include <dirent.h> + +/* If this bit is set in __d_position then we are enumerating values, + * else sub-keys. keeping track of where we are is horribly messy + * the bottom 16 bits are the absolute position and the top 15 bits + * make up the value index if we are enuerating values. + */ +static const __int32_t REG_ENUM_VALUES_MASK = 0x8000000; +static const __int32_t REG_POSITION_MASK = 0xffff; + +/* These key paths are used below whenever we return key information. + The problem is UAC virtualization when running an admin account with + restricted rights. In that case the subkey "Classes" in the VirtualStore + points to the HKEY_CLASSES_ROOT key again. If "Classes" is handled as a + normal subdirectory, applications recursing throught the directory + hirarchy will invariably run into an infinite recursion. What we do here + is to handle the "Classes" subkey as a symlink to HKEY_CLASSES_ROOT. This + avoids the infinite recursion, unless the application blindly follows + symlinks pointing to directories, in which case it's their own fault. */ +#define VIRT_CLASSES_KEY_PREFIX "/VirtualStore/MACHINE/SOFTWARE" +#define VIRT_CLASSES_KEY_SUFFIX "Classes" +#define VIRT_CLASSES_KEY VIRT_CLASSES_KEY_PREFIX "/" VIRT_CLASSES_KEY_SUFFIX +#define VIRT_CLASSES_LINKTGT "/proc/registry/HKEY_CLASSES_ROOT" + +/* List of root keys in /proc/registry. + * Possibly we should filter out those not relevant to the flavour of Windows + * Cygwin is running on. + */ +static const char *registry_listing[] = +{ + ".", + "..", + "HKEY_CLASSES_ROOT", + "HKEY_CURRENT_CONFIG", + "HKEY_CURRENT_USER", + "HKEY_LOCAL_MACHINE", + "HKEY_USERS", + "HKEY_PERFORMANCE_DATA", + NULL +}; + +static const HKEY registry_keys[] = +{ + (HKEY) INVALID_HANDLE_VALUE, + (HKEY) INVALID_HANDLE_VALUE, + HKEY_CLASSES_ROOT, + HKEY_CURRENT_CONFIG, + HKEY_CURRENT_USER, + HKEY_LOCAL_MACHINE, + HKEY_USERS, + HKEY_PERFORMANCE_DATA +}; + +static const int ROOT_KEY_COUNT = sizeof (registry_keys) / sizeof (HKEY); + +/* Make sure to access the correct per-user HKCR and HKCU hives, even if + the current user is only impersonated in another user's session. */ +static HKEY +fetch_hkey (int idx) /* idx *must* be valid */ +{ + HKEY key; + + if (registry_keys[idx] == HKEY_CLASSES_ROOT) + { + if (RegOpenUserClassesRoot (cygheap->user.issetuid () + ? cygheap->user.imp_token () : hProcToken, + 0, KEY_READ, &key) == ERROR_SUCCESS) + return key; + } + else if (registry_keys[idx] == HKEY_CURRENT_USER) + { + if (RegOpenCurrentUser (KEY_READ, &key) == ERROR_SUCCESS) + return key; + } + else if (registry_keys[idx] == HKEY_CURRENT_CONFIG) + { + if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, + L"System\\CurrentControlSet\\Hardware Profiles\\Current", + 0, KEY_READ, &key) == ERROR_SUCCESS) + return key; + } + /* Unfortunately there's no way to generate a valid OS registry key for + the other root keys. HKEY_USERS and HKEY_LOCAL_MACHINE are file + handles internally, HKEY_PERFORMANCE_DATA is just a bad hack and + no registry key at all. */ + return registry_keys[idx]; +} + +/* These get added to each subdirectory in /proc/registry. + * If we wanted to implement writing, we could maybe add a '.writable' entry or + * suchlike. + */ +static const char *special_dot_files[] = +{ + ".", + "..", + NULL +}; + +static const int SPECIAL_DOT_FILE_COUNT = + (sizeof (special_dot_files) / sizeof (const char *)) - 1; + +/* Value names for HKEY_PERFORMANCE_DATA. + * + * CAUTION: Never call RegQueryValueEx (HKEY_PERFORMANCE_DATA, "Add", ...). + * It WRITES data and may destroy the perfc009.dat file. Same applies to + * name prefixes "Ad" and "A". + */ +static const char * const perf_data_files[] = +{ + "@", + "Costly", + "Global" +}; + +static const int PERF_DATA_FILE_COUNT = + sizeof (perf_data_files) / sizeof (perf_data_files[0]); + +static HKEY open_key (const char *name, REGSAM access, DWORD wow64, bool isValue); + +/* Return true if char must be encoded. + */ +static inline bool +must_encode (wchar_t c) +{ + return (iswdirsep (c) || c == L':' || c == L'%'); +} + +/* Encode special chars in registry key or value name. + * Returns 0: success, -1: error. + */ +static int +encode_regname (char *dst, const wchar_t *src, bool add_val) +{ + int di = 0; + if (!src[0]) + dst[di++] = '@'; // Default value. + else + for (int si = 0; src[si]; si++) + { + wchar_t c = src[si]; + if (must_encode (c) || + (si == 0 && ((c == L'.' + && (!src[1] || (src[1] == L'.' && !src[2]))) + || (c == L'@' && !src[1])))) + { + if (di + 3 >= NAME_MAX + 1) + return -1; + __small_sprintf (dst + di, "%%%02x", c); + di += 3; + } + else + di += sys_wcstombs (dst + di, NAME_MAX + 1 - di, &c, 1); + } + + if (add_val) + { + if (di + 4 >= NAME_MAX + 1) + return -1; + memcpy (dst + di, "%val", 4); + di += 4; + } + + dst[di] = 0; + return 0; +} + +/* Decode special chars in registry key or value name. + * Returns 0: success, 1: "%val" detected, -1: error. + */ +static int +decode_regname (wchar_t *wdst, const char *src, int len = -1) +{ + if (len < 0) + len = strlen (src); + char dst[len + 1]; + int res = 0; + + if (len > 4 && !memcmp (src + len - 4, "%val", 4)) + { + len -= 4; + res = 1; + } + + int di = 0; + if (len == 1 && src[0] == '@') + ; // Default value. + else + for (int si = 0; si < len; si++) + { + char c = src[si]; + if (c == '%') + { + if (si + 2 >= len) + return -1; + char s[] = {src[si+1], src[si+2], '\0'}; + char *p; + c = strtoul (s, &p, 16); + if (!(must_encode ((wchar_t) c) || + (si == 0 && ((c == '.' && (len == 3 || (src[3] == '.' && len == 4))) || + (c == '@' && len == 3))))) + return -1; + dst[di++] = c; + si += 2; + } + else + dst[di++] = c; + } + + dst[di] = 0; + sys_mbstowcs (wdst, NAME_MAX + 1, dst); + return res; +} + + +/* 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 wchar_t *name, DWORD wow64) +{ + HKEY hKey = (HKEY) INVALID_HANDLE_VALUE; + LONG error = RegOpenKeyExW (parent, name, 0, KEY_READ | wow64, &hKey); + if (error == ERROR_SUCCESS) + RegCloseKey (hKey); + + return (error == ERROR_SUCCESS || error == ERROR_ACCESS_DENIED); +} + +static size_t +multi_wcstombs (char *dst, size_t len, const wchar_t *src, size_t nwc) +{ + size_t siz, sum = 0; + const wchar_t *nsrc; + + while (nwc) + { + siz = sys_wcstombs (dst, len, src, nwc) + 1; + sum += siz; + if (dst) + { + dst += siz; + len -= siz; + } + nsrc = wcschr (src, L'\0') + 1; + if ((size_t) (nsrc - src) >= nwc) + break; + nwc -= nsrc - src; + src = nsrc; + if (*src == L'\0') + { + if (dst) + *dst++ = '\0'; + ++sum; + break; + } + } + return sum; +} + +virtual_ftype_t +fhandler_registry::exists () +{ + virtual_ftype_t file_type = virt_none; + int index = 0, pathlen; + DWORD buf_size = NAME_MAX + 1; + LONG error; + wchar_t buf[buf_size]; + const char *file; + HKEY hKey = (HKEY) INVALID_HANDLE_VALUE; + + const char *path = get_name (); + debug_printf ("exists (%s)", path); + path += proc_len + prefix_len + 1; + if (*path) + path++; + else + { + file_type = virt_rootdir; + goto out; + } + pathlen = strlen (path); + file = path + pathlen - 1; + if (isdirsep (*file) && pathlen > 1) + file--; + while (!isdirsep (*file)) + file--; + file++; + + if (file == path) + { + for (int i = 0; registry_listing[i]; i++) + if (path_prefix_p (registry_listing[i], path, + strlen (registry_listing[i]), true)) + { + file_type = virt_directory; + break; + } + } + else + { + wchar_t dec_file[NAME_MAX + 1]; + + int val_only = decode_regname (dec_file, file); + if (val_only < 0) + goto out; + + if (!val_only) + hKey = open_key (path, KEY_READ, wow64, false); + if (hKey != (HKEY) INVALID_HANDLE_VALUE) + { + if (!strcasecmp (path + strlen (path) + - sizeof (VIRT_CLASSES_KEY) + 1, + VIRT_CLASSES_KEY)) + file_type = virt_symlink; + else + file_type = virt_directory; + } + else + { + /* Key does not exist or open failed with EACCES, + enumerate subkey and value names of parent key. */ + hKey = open_key (path, KEY_READ, wow64, true); + if (hKey == (HKEY) INVALID_HANDLE_VALUE) + return virt_none; + + if (hKey == HKEY_PERFORMANCE_DATA) + { + /* RegEnumValue () returns garbage for this key. + RegQueryValueEx () returns a PERF_DATA_BLOCK even + if a value does not contain any counter objects. + So allow access to the generic names and to + (blank separated) lists of counter numbers. + Never allow access to "Add", see above comment. */ + for (int i = 0; i < PERF_DATA_FILE_COUNT + && file_type == virt_none; i++) + { + if (strcasematch (perf_data_files[i], file)) + file_type = virt_file; + } + if (file_type == virt_none && !file[strspn (file, " 0123456789")]) + file_type = virt_file; + goto out; + } + + if (!val_only && dec_file[0]) + { + while (ERROR_SUCCESS == + (error = RegEnumKeyExW (hKey, index++, buf, &buf_size, + NULL, NULL, NULL, NULL)) + || (error == ERROR_MORE_DATA)) + { + if (!wcscasecmp (buf, dec_file)) + { + file_type = virt_directory; + goto out; + } + buf_size = NAME_MAX + 1; + } + if (error != ERROR_NO_MORE_ITEMS) + { + seterrno_from_win_error (__FILE__, __LINE__, error); + goto out; + } + index = 0; + buf_size = NAME_MAX + 1; + } + + while (ERROR_SUCCESS == + (error = RegEnumValueW (hKey, index++, buf, &buf_size, + NULL, NULL, NULL, NULL)) + || (error == ERROR_MORE_DATA)) + { + if (!wcscasecmp (buf, dec_file)) + { + file_type = virt_file; + goto out; + } + buf_size = NAME_MAX + 1; + } + if (error != ERROR_NO_MORE_ITEMS) + { + seterrno_from_win_error (__FILE__, __LINE__, error); + goto out; + } + } + } +out: + if (hKey != (HKEY) INVALID_HANDLE_VALUE) + RegCloseKey (hKey); + return file_type; +} + +void +fhandler_registry::set_name (path_conv &in_pc) +{ + if (strncasematch (in_pc.get_posix (), "/proc/registry32", 16)) + { + wow64 = KEY_WOW64_32KEY; + prefix_len += 2; + } + else if (strncasematch (in_pc.get_posix (), "/proc/registry64", 16)) + prefix_len += 2; + fhandler_base::set_name (in_pc); +} + +fhandler_registry::fhandler_registry (): +fhandler_proc () +{ + wow64 = 0; + prefix_len = sizeof ("registry") - 1; +} + +int +fhandler_registry::fstat (struct stat *buf) +{ + fhandler_base::fstat (buf); + buf->st_mode &= ~_IFMT & NO_W; + virtual_ftype_t file_type = exists (); + switch (file_type) + { + case virt_none: + set_errno (ENOENT); + return -1; + case virt_symlink: + buf->st_mode |= S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; + break; + case virt_directory: + buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + break; + case virt_rootdir: + buf->st_mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + buf->st_nlink = ROOT_KEY_COUNT; + break; + default: + case virt_file: + buf->st_mode |= S_IFREG; + buf->st_mode &= NO_X; + break; + } + if (file_type != virt_none && file_type != virt_rootdir) + { + HKEY hKey; + const char *path = get_name () + proc_len + prefix_len + 2; + hKey = + open_key (path, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE, wow64, + virt_ftype_isfile (file_type) ? true : false); + + if (hKey == HKEY_PERFORMANCE_DATA) + /* RegQueryInfoKey () always returns write time 0, + RegQueryValueEx () does not return required buffer size. */ + ; + else if (hKey != (HKEY) INVALID_HANDLE_VALUE) + { + FILETIME ftLastWriteTime; + DWORD subkey_count; + if (ERROR_SUCCESS == + RegQueryInfoKeyW (hKey, NULL, NULL, NULL, &subkey_count, NULL, + NULL, NULL, NULL, NULL, NULL, &ftLastWriteTime)) + { + to_timestruc_t ((PLARGE_INTEGER) &ftLastWriteTime, &buf->st_mtim); + buf->st_ctim = buf->st_birthtim = buf->st_mtim; + time_as_timestruc_t (&buf->st_atim); + if (virt_ftype_isdir (file_type)) + buf->st_nlink = subkey_count + 2; + else + { + int pathlen = strlen (path); + const char *value_name = path + pathlen - 1; + if (isdirsep (*value_name) && pathlen > 1) + value_name--; + while (!isdirsep (*value_name)) + value_name--; + value_name++; + wchar_t dec_value_name[NAME_MAX + 1]; + DWORD dwSize = 0; + DWORD type; + if (decode_regname (dec_value_name, value_name) >= 0 + && RegQueryValueExW (hKey, dec_value_name, NULL, &type, + NULL, &dwSize) == ERROR_SUCCESS + && (type == REG_SZ || type == REG_EXPAND_SZ + || type == REG_MULTI_SZ || type == REG_LINK)) + { + PBYTE tmpbuf = (PBYTE) malloc (dwSize); + if (!tmpbuf + || RegQueryValueExW (hKey, dec_value_name, + NULL, NULL, tmpbuf, &dwSize) + != ERROR_SUCCESS) + buf->st_size = dwSize / sizeof (wchar_t); + else if (type == REG_MULTI_SZ) + buf->st_size = multi_wcstombs (NULL, 0, + (wchar_t *) tmpbuf, + dwSize / sizeof (wchar_t)); + else + buf->st_size = sys_wcstombs (NULL, 0, + (wchar_t *) tmpbuf, + dwSize / sizeof (wchar_t)) + + 1; + if (tmpbuf) + free (tmpbuf); + } + else + buf->st_size = dwSize; + } + uid_t uid; + gid_t gid; + if (get_reg_attribute (hKey, &buf->st_mode, &uid, &gid) == 0) + { + buf->st_uid = uid; + buf->st_gid = gid; + buf->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + if (virt_ftype_isdir (file_type)) + buf->st_mode |= S_IFDIR; + else + buf->st_mode &= NO_X; + } + } + RegCloseKey (hKey); + } + else + { + /* Here's the problem: If we can't open the key, we don't know + nothing at all about the key/value. It's only clear that + the current user has no read access. At this point it's + rather unlikely that the user has write or execute access + and it's also rather unlikely that the user is the owner. + Therefore it's probably most safe to assume unknown ownership + and no permissions for nobody. */ + buf->st_uid = ILLEGAL_UID; + buf->st_gid = ILLEGAL_GID; + buf->st_mode &= ~0777; + } + } + return 0; +} + +DIR * +fhandler_registry::opendir (int fd) +{ + /* Skip fhandler_proc::opendir, which allocates dir->_d_handle for its + own devilish purposes... */ + return fhandler_virtual::opendir (fd); +} + +int +fhandler_registry::readdir (DIR *dir, dirent *de) +{ + DWORD buf_size = NAME_MAX + 1; + wchar_t buf[buf_size]; + const char *path = dir->__d_dirname + proc_len + 1 + prefix_len; + LONG error; + int res = ENMFILE; + + dir->__flags |= dirent_saw_dot | dirent_saw_dot_dot; + if (*path == 0) + { + if (dir->__d_position >= ROOT_KEY_COUNT) + goto out; + strcpy (de->d_name, registry_listing[dir->__d_position++]); + res = 0; + goto out; + } + if (dir->__handle == INVALID_HANDLE_VALUE) + { + if (dir->__d_position != 0) + goto out; + dir->__handle = open_key (path + 1, KEY_READ, wow64, false); + if (dir->__handle == INVALID_HANDLE_VALUE) + goto out; + dir->__d_internal = (uintptr_t) new __DIR_hash (); + } + if (dir->__d_position < SPECIAL_DOT_FILE_COUNT) + { + strcpy (de->d_name, special_dot_files[dir->__d_position++]); + res = 0; + goto out; + } + if ((HKEY) dir->__handle == HKEY_PERFORMANCE_DATA) + { + /* RegEnumValue () returns garbage for this key, + simulate only a minimal listing of the generic names. */ + if (dir->__d_position >= SPECIAL_DOT_FILE_COUNT + PERF_DATA_FILE_COUNT) + goto out; + strcpy (de->d_name, perf_data_files[dir->__d_position - SPECIAL_DOT_FILE_COUNT]); + dir->__d_position++; + res = 0; + goto out; + } + +retry: + if (dir->__d_position & REG_ENUM_VALUES_MASK) + /* For the moment, the type of key is ignored here. when write access is added, + * maybe add an extension for the type of each value? + */ + error = RegEnumValueW ((HKEY) dir->__handle, + (dir->__d_position & ~REG_ENUM_VALUES_MASK) >> 16, + buf, &buf_size, NULL, NULL, NULL, NULL); + else + error = + RegEnumKeyExW ((HKEY) dir->__handle, dir->__d_position - + SPECIAL_DOT_FILE_COUNT, buf, &buf_size, + NULL, NULL, NULL, NULL); + if (error == ERROR_NO_MORE_ITEMS + && (dir->__d_position & REG_ENUM_VALUES_MASK) == 0) + { + /* If we're finished with sub-keys, start on values under this key. */ + dir->__d_position |= REG_ENUM_VALUES_MASK; + buf_size = NAME_MAX + 1; + goto retry; + } + if (error != ERROR_SUCCESS && error != ERROR_MORE_DATA) + { + delete d_hash (dir); + RegCloseKey ((HKEY) dir->__handle); + dir->__handle = INVALID_HANDLE_VALUE; + if (error != ERROR_NO_MORE_ITEMS) + seterrno_from_win_error (__FILE__, __LINE__, error); + goto out; + } + + /* We get here if `buf' contains valid data. */ + dir->__d_position++; + if (dir->__d_position & REG_ENUM_VALUES_MASK) + dir->__d_position += 0x10000; + + { + /* Append "%val" if value name is identical to a previous key name. */ + unsigned h = hash_path_name (1, buf); + bool add_val = false; + 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)) + add_val = true; + + if (encode_regname (de->d_name, buf, add_val)) + { + buf_size = NAME_MAX + 1; + goto retry; + } + } + + if (dir->__d_position & REG_ENUM_VALUES_MASK) + de->d_type = DT_REG; + else if (!strcasecmp (de->d_name, "Classes") + && !strcasecmp (path + strlen (path) + - sizeof (VIRT_CLASSES_KEY_PREFIX) + 1, + VIRT_CLASSES_KEY_PREFIX)) + de->d_type = DT_LNK; + else + de->d_type = DT_DIR; + + res = 0; +out: + syscall_printf ("%d = readdir(%p, %p)", res, dir, de); + return res; +} + +long +fhandler_registry::telldir (DIR * dir) +{ + return dir->__d_position & REG_POSITION_MASK; +} + +void +fhandler_registry::seekdir (DIR * dir, long loc) +{ + /* Unfortunately cannot simply set __d_position due to transition from sub-keys to + * values. + */ + rewinddir (dir); + while (loc > (dir->__d_position & REG_POSITION_MASK)) + if (readdir (dir, dir->__d_dirent)) + break; +} + +void +fhandler_registry::rewinddir (DIR * dir) +{ + if (dir->__handle != INVALID_HANDLE_VALUE) + { + delete d_hash (dir); + RegCloseKey ((HKEY) dir->__handle); + dir->__handle = INVALID_HANDLE_VALUE; + } + dir->__d_position = 0; + dir->__flags = dirent_saw_dot | dirent_saw_dot_dot; +} + +int +fhandler_registry::closedir (DIR * dir) +{ + int res = 0; + if (dir->__handle != INVALID_HANDLE_VALUE) + { + delete d_hash (dir); + if (RegCloseKey ((HKEY) dir->__handle) != ERROR_SUCCESS) + { + __seterrno (); + res = -1; + } + } + syscall_printf ("%d = closedir(%p)", res, dir); + return 0; +} + +int +fhandler_registry::open (int flags, mode_t mode) +{ + int pathlen; + const char *file; + HKEY handle = (HKEY) INVALID_HANDLE_VALUE; + + int res = fhandler_virtual::open (flags, mode); + if (!res) + goto out; + + const char *path; + path = get_name () + proc_len + 1 + prefix_len; + if (!*path) + { + if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + { + set_errno (EEXIST); + res = 0; + goto out; + } + else if (flags & O_WRONLY) + { + set_errno (EISDIR); + res = 0; + goto out; + } + else + { + diropen = true; + /* Marking as nohandle allows to call dup. */ + nohandle (true); + goto success; + } + } + path++; + pathlen = strlen (path); + file = path + pathlen - 1; + if (isdirsep (*file) && pathlen > 1) + file--; + while (!isdirsep (*file)) + file--; + file++; + + if (file == path) + { + for (int i = 0; registry_listing[i]; i++) + if (path_prefix_p (registry_listing[i], path, + strlen (registry_listing[i]), true)) + { + if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + { + set_errno (EEXIST); + res = 0; + goto out; + } + else if (flags & O_WRONLY) + { + set_errno (EISDIR); + res = 0; + goto out; + } + else + { + set_handle (fetch_hkey (i)); + /* Marking as nohandle allows to call dup on pseudo registry + handles. */ + if (get_handle () >= HKEY_CLASSES_ROOT) + nohandle (true); + diropen = true; + goto success; + } + } + + if (flags & O_CREAT) + { + set_errno (EROFS); + res = 0; + } + else + { + set_errno (ENOENT); + res = 0; + } + goto out; + } + + if (flags & O_WRONLY) + { + set_errno (EROFS); + res = 0; + goto out; + } + else + { + wchar_t dec_file[NAME_MAX + 1]; + int val_only = decode_regname (dec_file, file); + if (val_only < 0) + { + set_errno (EINVAL); + res = 0; + goto out; + } + + if (!val_only) + handle = open_key (path, KEY_READ, wow64, false); + if (handle == (HKEY) INVALID_HANDLE_VALUE) + { + if (val_only || get_errno () != EACCES) + handle = open_key (path, KEY_READ, wow64, true); + if (handle == (HKEY) INVALID_HANDLE_VALUE) + { + res = 0; + goto out; + } + } + else + diropen = true; + + set_handle (handle); + set_close_on_exec (!!(flags & O_CLOEXEC)); + value_name = cwcsdup (dec_file); + + if (!diropen && !fill_filebuf ()) + { + RegCloseKey (handle); + res = 0; + goto out; + } + + if (flags & O_APPEND) + position = filesize; + else + position = 0; + } + +success: + res = 1; + set_flags ((flags & ~O_TEXT) | O_BINARY); + set_open_status (); +out: + syscall_printf ("%d = fhandler_registry::open(%p, 0%o)", res, flags, mode); + return res; +} + +int +fhandler_registry::close () +{ + int res = fhandler_virtual::close (); + if (res != 0) + return res; + HKEY handle = (HKEY) get_handle (); + if (handle != (HKEY) INVALID_HANDLE_VALUE && handle < HKEY_CLASSES_ROOT) + { + if (RegCloseKey (handle) != ERROR_SUCCESS) + { + __seterrno (); + res = -1; + } + } + if (!have_execed && value_name) + { + cfree (value_name); + value_name = NULL; + } + return res; +} + +bool +fhandler_registry::fill_filebuf () +{ + DWORD type, size; + LONG error; + HKEY handle = (HKEY) get_handle (); + size_t bufalloc; + + if (handle != HKEY_PERFORMANCE_DATA) + { + error = RegQueryValueExW (handle, value_name, NULL, &type, NULL, &size); + if (error != ERROR_SUCCESS) + { + if (error == ERROR_INVALID_HANDLE + && !strcasecmp (get_name () + strlen (get_name ()) + - sizeof (VIRT_CLASSES_KEY) + 1, + VIRT_CLASSES_KEY)) + { + filesize = sizeof (VIRT_CLASSES_LINKTGT); + filebuf = (char *) cmalloc_abort (HEAP_BUF, filesize); + strcpy (filebuf, VIRT_CLASSES_LINKTGT); + return true; + } + if (error != ERROR_FILE_NOT_FOUND) + { + seterrno_from_win_error (__FILE__, __LINE__, error); + return false; + } + goto value_not_found; + } + PBYTE tmpbuf = (PBYTE) cmalloc_abort (HEAP_BUF, size); + error = + RegQueryValueExW (handle, value_name, NULL, NULL, tmpbuf, &size); + if (error != ERROR_SUCCESS) + { + seterrno_from_win_error (__FILE__, __LINE__, error); + return true; + } + if (type == REG_SZ || type == REG_EXPAND_SZ || type == REG_LINK) + bufalloc = sys_wcstombs (NULL, 0, (wchar_t *) tmpbuf, + size / sizeof (wchar_t)) + 1; + else if (type == REG_MULTI_SZ) + bufalloc = multi_wcstombs (NULL, 0, (wchar_t *) tmpbuf, + size / sizeof (wchar_t)); + else + bufalloc = size; + filebuf = (char *) cmalloc_abort (HEAP_BUF, bufalloc); + if (type == REG_SZ || type == REG_EXPAND_SZ || type == REG_LINK) + sys_wcstombs (filebuf, bufalloc, (wchar_t *) tmpbuf, + size / sizeof (wchar_t)); + else if (type == REG_MULTI_SZ) + multi_wcstombs (filebuf, bufalloc, (wchar_t *) tmpbuf, + size / sizeof (wchar_t)); + else + memcpy (filebuf, tmpbuf, bufalloc); + filesize = bufalloc; + } + else + { + bufalloc = 0; + do + { + bufalloc += 16 * 1024; + filebuf = (char *) crealloc_abort (filebuf, bufalloc); + size = bufalloc; + error = RegQueryValueExW (handle, value_name, NULL, &type, + (PBYTE) filebuf, &size); + if (error != ERROR_SUCCESS && error != ERROR_MORE_DATA) + { + seterrno_from_win_error (__FILE__, __LINE__, error); + return false; + } + } + while (error == ERROR_MORE_DATA); + filesize = size; + /* RegQueryValueEx () opens HKEY_PERFORMANCE_DATA. */ + RegCloseKey (handle); + } + return true; +value_not_found: + DWORD buf_size = NAME_MAX + 1; + wchar_t buf[buf_size]; + int index = 0; + while (ERROR_SUCCESS == + (error = RegEnumKeyExW (handle, index++, buf, &buf_size, NULL, NULL, + NULL, NULL)) || (error == ERROR_MORE_DATA)) + { + if (!wcscasecmp (buf, value_name)) + { + set_errno (EISDIR); + return false; + } + buf_size = NAME_MAX + 1; + } + if (error != ERROR_NO_MORE_ITEMS) + { + seterrno_from_win_error (__FILE__, __LINE__, error); + return false; + } + set_errno (ENOENT); + return false; +} + +/* Auxillary member function to open registry keys. */ +static HKEY +open_key (const char *name, REGSAM access, DWORD wow64, bool isValue) +{ + HKEY hKey = (HKEY) INVALID_HANDLE_VALUE; + HKEY hParentKey = (HKEY) INVALID_HANDLE_VALUE; + bool parentOpened = false; + wchar_t component[NAME_MAX + 1]; + + while (*name) + { + const char *anchor = name; + while (*name && !isdirsep (*name)) + name++; + int val_only = decode_regname (component, anchor, name - anchor); + if (val_only < 0) + { + set_errno (EINVAL); + if (parentOpened) + RegCloseKey (hParentKey); + hKey = (HKEY) INVALID_HANDLE_VALUE; + break; + } + if (*name) + name++; + if (*name == 0 && isValue == true) + break; + + if (val_only || !component[0] || hKey == HKEY_PERFORMANCE_DATA) + { + set_errno (ENOENT); + if (parentOpened) + RegCloseKey (hParentKey); + hKey = (HKEY) INVALID_HANDLE_VALUE; + break; + } + + if (hParentKey != (HKEY) INVALID_HANDLE_VALUE) + { + REGSAM effective_access = KEY_READ; + if ((strchr (name, '/') == NULL && isValue == true) || *name == 0) + effective_access = access; + LONG error = RegOpenKeyExW (hParentKey, component, 0, + effective_access | wow64, &hKey); + if (error == ERROR_ACCESS_DENIED) /* Try opening with backup intent */ + error = RegCreateKeyExW (hParentKey, component, 0, NULL, + REG_OPTION_BACKUP_RESTORE, + effective_access | wow64, NULL, + &hKey, NULL); + if (parentOpened) + RegCloseKey (hParentKey); + if (error != ERROR_SUCCESS) + { + hKey = (HKEY) INVALID_HANDLE_VALUE; + seterrno_from_win_error (__FILE__, __LINE__, error); + return hKey; + } + hParentKey = hKey; + parentOpened = true; + } + else + { + for (int i = 0; registry_listing[i]; i++) + if (strncasematch (anchor, registry_listing[i], name - anchor - 1)) + hKey = fetch_hkey (i); + if (hKey == (HKEY) INVALID_HANDLE_VALUE) + return hKey; + hParentKey = hKey; + } + } + return hKey; +} + +int +fhandler_registry::dup (fhandler_base *child, int flags) +{ + debug_printf ("here"); + fhandler_registry *fhs = (fhandler_registry *) child; + + int ret = fhandler_virtual::dup (fhs, flags); + /* Pseudo registry handles can't be duplicated using DuplicateHandle. + Therefore those fhandlers are marked with the nohandle flag. This + allows fhandler_base::dup to succeed as usual for nohandle fhandlers. + Here we just have to fix up by copying the pseudo handle value. */ + if ((HKEY) get_handle () >= HKEY_CLASSES_ROOT) + fhs->set_handle (get_handle ()); + if (value_name) + fhs->value_name = cwcsdup (value_name); + return ret; +} |