OSDN Git Service

* dcrt0.cc (dll_crt0_1): Move internal locale setting prior to potential
[pf3gnuchains/pf3gnuchains3x.git] / winsup / cygwin / fhandler_registry.cc
index 45b8a85..e8f4e68 100644 (file)
@@ -1,6 +1,6 @@
 /* fhandler_registry.cc: fhandler for /proc/registry virtual filesystem
 
-   Copyright 2002 Red Hat, Inc.
+   Copyright 2002, 2003, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Red Hat, Inc.
 
 This file is part of Cygwin.
 
@@ -11,29 +11,24 @@ details. */
 /* FIXME: Access permissions are ignored at the moment.  */
 
 #include "winsup.h"
-#include <errno.h>
-#include <unistd.h>
 #include <stdlib.h>
-#include <sys/cygwin.h>
 #include "cygerrno.h"
 #include "security.h"
-#include "fhandler.h"
 #include "path.h"
+#include "fhandler.h"
 #include "dtable.h"
 #include "cygheap.h"
-#include <assert.h>
 
 #define _COMPILING_NEWLIB
 #include <dirent.h>
 
-static const int registry_len = sizeof ("registry") - 1;
 /* 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 __off32_t REG_ENUM_VALUES_MASK = 0x8000000;
-static const __off32_t REG_POSITION_MASK = 0xffff;
+static const _off_t REG_ENUM_VALUES_MASK = 0x8000000;
+static const _off_t REG_POSITION_MASK = 0xffff;
 
 /* List of root keys in /proc/registry.
  * Possibly we should filter out those not relevant to the flavour of Windows
@@ -48,8 +43,7 @@ static const char *registry_listing[] =
   "HKEY_CURRENT_USER",
   "HKEY_LOCAL_MACHINE",
   "HKEY_USERS",
-  "HKEY_DYN_DATA",             // 95/98/Me
-  "HKEY_PERFOMANCE_DATA",      // NT/2000/XP
+  "HKEY_PERFORMANCE_DATA",     // NT/2000/XP
   NULL
 };
 
@@ -62,7 +56,6 @@ static const HKEY registry_keys[] =
   HKEY_CURRENT_USER,
   HKEY_LOCAL_MACHINE,
   HKEY_USERS,
-  HKEY_DYN_DATA,
   HKEY_PERFORMANCE_DATA
 };
 
@@ -82,10 +75,159 @@ static const char *special_dot_files[] =
 static const int SPECIAL_DOT_FILE_COUNT =
   (sizeof (special_dot_files) / sizeof (const char *)) - 1;
 
-/* Name given to default values */
-static const char *DEFAULT_VALUE_NAME = "@";
+/* 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)
 
-static HKEY open_key (const char *name, REGSAM access, bool isValue);
+
+/* 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);
+}
 
 /* Returns 0 if path doesn't exist, >0 if path is a directory,
  * <0 if path is a file.
@@ -98,85 +240,116 @@ int
 fhandler_registry::exists ()
 {
   int file_type = 0, index = 0, pathlen;
-  DWORD buf_size = MAX_PATH;
+  DWORD buf_size = NAME_MAX + 1;
   LONG error;
-  char buf[buf_size];
+  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 + registry_len + 2;
-  if (*path == 0)
+  path += proc_len + prefix_len + 1;
+  if (*path)
+    path++;
+  else
     {
       file_type = 2;
       goto out;
     }
   pathlen = strlen (path);
   file = path + pathlen - 1;
-  if (SLASH_P (*file) && pathlen > 1)
+  if (isdirsep (*file) && pathlen > 1)
     file--;
-  while (!SLASH_P (*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])))
+       if (path_prefix_p (registry_listing[i], path,
+                          strlen (registry_listing[i]), true))
          {
            file_type = 1;
-           goto out;
+           break;
          }
-      goto out;
     }
-
-  hKey = open_key (path, KEY_READ, false);
-  if (hKey != (HKEY) INVALID_HANDLE_VALUE)
-    file_type = 1;
   else
     {
-      hKey = open_key (path, KEY_READ, true);
-      if (hKey == (HKEY) INVALID_HANDLE_VALUE)
-       return 0;
-
-      while (ERROR_SUCCESS ==
-            (error = RegEnumKeyEx (hKey, index++, buf, &buf_size, NULL, NULL,
-                                   NULL, NULL))
-            || (error == ERROR_MORE_DATA))
+      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 || get_errno () == EACCES)
+       file_type = 1;
+      else
        {
-         if (pathmatch (buf, file))
+         hKey = open_key (path, KEY_READ, wow64, true);
+         if (hKey == (HKEY) INVALID_HANDLE_VALUE)
+           return 0;
+
+         if (hKey == HKEY_PERFORMANCE_DATA)
            {
-             file_type = 1;
+             /* 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 == 0; i++)
+               {
+                 if (strcasematch (perf_data_files[i], file))
+                   file_type = -1;
+               }
+             if (file_type == 0 && !file[strspn (file, " 0123456789")])
+               file_type = -1;
              goto out;
            }
-         buf_size = MAX_PATH;
-       }
-      if (error != ERROR_NO_MORE_ITEMS)
-       {
-         seterrno_from_win_error (__FILE__, __LINE__, error);
-         goto out;
-       }
-      index = 0;
-      buf_size = MAX_PATH;
-      while (ERROR_SUCCESS ==
-            (error = RegEnumValue (hKey, index++, buf, &buf_size, NULL, NULL,
-                                   NULL, NULL))
-            || (error == ERROR_MORE_DATA))
-       {
-         if (pathmatch (buf, file) || (buf[0] == '\0' &&
-                                       pathmatch (file, DEFAULT_VALUE_NAME)))
+
+         if (!val_only && dec_file[0])
            {
-             file_type = -1;
+             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 = 1;
+                     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 = -1;
+                 goto out;
+               }
+             buf_size = NAME_MAX + 1;
+           }
+         if (error != ERROR_NO_MORE_ITEMS)
+           {
+             seterrno_from_win_error (__FILE__, __LINE__, error);
              goto out;
            }
-         buf_size = MAX_PATH;
-       }
-      if (error != ERROR_NO_MORE_ITEMS)
-       {
-         seterrno_from_win_error (__FILE__, __LINE__, error);
-         goto out;
        }
     }
 out:
@@ -185,15 +358,33 @@ out:
   return file_type;
 }
 
+void
+fhandler_registry::set_name (path_conv &in_pc)
+{
+  if (strncasematch (in_pc.normalized_path, "/proc/registry32", 16))
+    {
+      wow64 = KEY_WOW64_32KEY;
+      prefix_len += 2;
+    }
+  else if (strncasematch (in_pc.normalized_path, "/proc/registry64", 16))
+    {
+      wow64 = KEY_WOW64_64KEY;
+      prefix_len += 2;
+    }
+  fhandler_base::set_name (in_pc);
+}
+
 fhandler_registry::fhandler_registry ():
-fhandler_proc (FH_REGISTRY)
+fhandler_proc ()
 {
+  wow64 = 0;
+  prefix_len = sizeof ("registry") - 1;
 }
 
 int
-fhandler_registry::fstat (struct __stat64 *buf, path_conv * pc)
+fhandler_registry::fstat (struct __stat64 *buf)
 {
-  this->fhandler_base::fstat (buf, pc);
+  fhandler_base::fstat (buf);
   buf->st_mode &= ~_IFMT & NO_W;
   int file_type = exists ();
   switch (file_type)
@@ -217,12 +408,16 @@ fhandler_registry::fstat (struct __stat64 *buf, path_conv * pc)
   if (file_type != 0 && file_type != 2)
     {
       HKEY hKey;
-      const char *path = get_name () + proc_len + registry_len + 2;
+      const char *path = get_name () + proc_len + prefix_len + 2;
       hKey =
-       open_key (path, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE,
+       open_key (path, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE, wow64,
                  (file_type < 0) ? true : false);
 
-      if (hKey != (HKEY) INVALID_HANDLE_VALUE)
+      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;
@@ -232,30 +427,46 @@ fhandler_registry::fstat (struct __stat64 *buf, path_conv * pc)
                               &ftLastWriteTime))
            {
              to_timestruc_t (&ftLastWriteTime, &buf->st_mtim);
-             buf->st_ctim = buf->st_mtim;
+             buf->st_ctim = buf->st_birthtim = buf->st_mtim;
              time_as_timestruc_t (&buf->st_atim);
              if (file_type > 0)
-               buf->st_nlink = subkey_count;
+               buf->st_nlink = subkey_count + 2;
              else
                {
                  int pathlen = strlen (path);
                  const char *value_name = path + pathlen - 1;
-                 if (SLASH_P (*value_name) && pathlen > 1)
+                 if (isdirsep (*value_name) && pathlen > 1)
                    value_name--;
-                 while (!SLASH_P (*value_name))
+                 while (!isdirsep (*value_name))
                    value_name--;
                  value_name++;
-                 DWORD dwSize;
-                 if (ERROR_SUCCESS ==
-                     RegQueryValueEx (hKey, value_name, NULL, NULL, NULL,
-                                      &dwSize))
+                 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
+                       buf->st_size = sys_wcstombs (NULL, 0,
+                                                    (wchar_t *) tmpbuf,
+                                                    dwSize / sizeof (wchar_t));
+                     free (tmpbuf);
+                   }
+                 else
                    buf->st_size = dwSize;
                }
              __uid32_t uid;
              __gid32_t gid;
-             if (get_object_attribute
-                 ((HANDLE) hKey, SE_REGISTRY_KEY, &buf->st_mode, &uid,
-                  &gid) == 0)
+             if (get_reg_attribute (hKey, &buf->st_mode, &uid, &gid) == 0)
                {
                  buf->st_uid = uid;
                  buf->st_gid = gid;
@@ -268,144 +479,193 @@ fhandler_registry::fstat (struct __stat64 *buf, path_conv * pc)
            }
          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 = UNKNOWN_UID;
+         buf->st_gid = UNKNOWN_GID;
+         buf->st_mode &= ~0777;
+       }
     }
   return 0;
 }
 
-struct dirent *
-fhandler_registry::readdir (DIR * dir)
+int
+fhandler_registry::readdir (DIR *dir, dirent *de)
 {
-  DWORD buf_size = MAX_PATH;
-  char buf[buf_size];
+  DWORD buf_size = NAME_MAX + 1;
+  wchar_t buf[buf_size];
   HANDLE handle;
-  struct dirent *res = NULL;
-  const char *path = dir->__d_dirname + proc_len + 1 + registry_len;
+  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 (dir->__d_dirent->d_name, registry_listing[dir->__d_position++]);
-      res = dir->__d_dirent;
+      strcpy (de->d_name, registry_listing[dir->__d_position++]);
+      res = 0;
       goto out;
     }
-  if (dir->__d_u.__d_data.__handle == INVALID_HANDLE_VALUE
-      && dir->__d_position == 0)
+  if (dir->__handle == INVALID_HANDLE_VALUE)
     {
-      handle = open_key (path + 1, KEY_READ, false);
-      dir->__d_u.__d_data.__handle = handle;
+      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->__d_u.__d_data.__handle == INVALID_HANDLE_VALUE)
-    goto out;
   if (dir->__d_position < SPECIAL_DOT_FILE_COUNT)
     {
-      strcpy (dir->__d_dirent->d_name,
-             special_dot_files[dir->__d_position++]);
-      res = dir->__d_dirent;
+      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 = RegEnumValue ((HKEY) dir->__d_u.__d_data.__handle,
-                         (dir->__d_position & ~REG_ENUM_VALUES_MASK) >> 16,
-                         buf, &buf_size, NULL, NULL, NULL, NULL);
+    error = RegEnumValueW ((HKEY) dir->__handle,
+                          (dir->__d_position & ~REG_ENUM_VALUES_MASK) >> 16,
+                          buf, &buf_size, NULL, NULL, NULL, NULL);
   else
     error =
-      RegEnumKeyEx ((HKEY) dir->__d_u.__d_data.__handle, dir->__d_position -
-                   SPECIAL_DOT_FILE_COUNT, buf, &buf_size, NULL, NULL, NULL,
-                   NULL);
+      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 = MAX_PATH;
+      buf_size = NAME_MAX + 1;
       goto retry;
     }
   if (error != ERROR_SUCCESS && error != ERROR_MORE_DATA)
     {
-      RegCloseKey ((HKEY) dir->__d_u.__d_data.__handle);
-      dir->__d_u.__d_data.__handle = INVALID_HANDLE_VALUE;
-      seterrno_from_win_error (__FILE__, __LINE__, error);
+      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.  */
-  if (*buf == 0)
-    strcpy (dir->__d_dirent->d_name, DEFAULT_VALUE_NAME);
-  else
-    strcpy (dir->__d_dirent->d_name, buf);
-
   dir->__d_position++;
   if (dir->__d_position & REG_ENUM_VALUES_MASK)
     dir->__d_position += 0x10000;
-  res = dir->__d_dirent;
+
+  {
+    /* 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
+    de->d_type = DT_DIR;
+
+  res = 0;
 out:
-  syscall_printf ("%p = readdir (%p)", &dir->__d_dirent, dir);
+  syscall_printf ("%d = readdir (%p, %p)", res, dir, de);
   return res;
 }
 
-__off64_t
+_off64_t
 fhandler_registry::telldir (DIR * dir)
 {
   return dir->__d_position & REG_POSITION_MASK;
 }
 
 void
-fhandler_registry::seekdir (DIR * dir, __off64_t loc)
+fhandler_registry::seekdir (DIR * dir, _off64_t 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))
+    if (!readdir (dir, dir->__d_dirent))
       break;
 }
 
 void
 fhandler_registry::rewinddir (DIR * dir)
 {
-  if (dir->__d_u.__d_data.__handle != INVALID_HANDLE_VALUE)
+  if (dir->__handle != INVALID_HANDLE_VALUE)
     {
-      (void) RegCloseKey ((HKEY) dir->__d_u.__d_data.__handle);
-      dir->__d_u.__d_data.__handle = INVALID_HANDLE_VALUE;
+      RegCloseKey ((HKEY) dir->__handle);
+      dir->__handle = INVALID_HANDLE_VALUE;
     }
   dir->__d_position = 0;
-  return;
+  dir->__flags = dirent_saw_dot | dirent_saw_dot_dot;
 }
 
 int
 fhandler_registry::closedir (DIR * dir)
 {
   int res = 0;
-  if (dir->__d_u.__d_data.__handle != INVALID_HANDLE_VALUE &&
-      RegCloseKey ((HKEY) dir->__d_u.__d_data.__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;
 }
 
 int
-fhandler_registry::open (path_conv * pc, int flags, mode_t mode)
+fhandler_registry::open (int flags, mode_t mode)
 {
   int pathlen;
   const char *file;
-  HKEY handle;
+  HKEY handle = (HKEY) INVALID_HANDLE_VALUE;
 
-  int res = fhandler_virtual::open (pc, flags, mode);
+  int res = fhandler_virtual::open (flags, mode);
   if (!res)
     goto out;
 
   const char *path;
-  path = get_name () + proc_len + 1 + registry_len;
+  path = get_name () + proc_len + 1 + prefix_len;
   if (!*path)
     {
       if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
@@ -423,23 +683,25 @@ fhandler_registry::open (path_conv * pc, int flags, mode_t mode)
       else
        {
          flags |= O_DIROPEN;
+         /* Marking as nohandle allows to call dup. */
+         nohandle (true);
          goto success;
        }
     }
   path++;
   pathlen = strlen (path);
   file = path + pathlen - 1;
-  if (SLASH_P (*file) && pathlen > 1)
+  if (isdirsep (*file) && pathlen > 1)
     file--;
-  while (!SLASH_P (*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])))
+       if (path_prefix_p (registry_listing[i], path,
+                          strlen (registry_listing[i]), true))
          {
            if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
              {
@@ -455,6 +717,10 @@ fhandler_registry::open (path_conv * pc, int flags, mode_t mode)
              }
            else
              {
+               set_io_handle (registry_keys[i]);
+               /* Marking as nohandle allows to call dup on pseudo registry
+                  handles. */
+               nohandle (true);
                flags |= O_DIROPEN;
                goto success;
              }
@@ -464,48 +730,61 @@ fhandler_registry::open (path_conv * pc, int flags, mode_t mode)
        {
          set_errno (EROFS);
          res = 0;
-         goto out;
        }
       else
        {
          set_errno (ENOENT);
          res = 0;
-         goto out;
        }
+      goto out;
     }
 
   if (flags & O_WRONLY)
     {
       set_errno (EROFS);
       res = 0;
-      goto out;
     }
-
-  handle = open_key (path, KEY_READ, true);
-  if (handle == (HKEY) INVALID_HANDLE_VALUE)
+  else
     {
-      res = 0;
-      goto out;
-    }
+      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;
+       }
 
-  set_io_handle (handle);
+      if (!val_only)
+       handle = open_key (path, KEY_READ, wow64, false);
+      if (handle == (HKEY) INVALID_HANDLE_VALUE)
+       {
+         if (get_errno () != EACCES)
+           handle = open_key (path, KEY_READ, wow64, true);
+         if (handle == (HKEY) INVALID_HANDLE_VALUE)
+           {
+             res = 0;
+             goto out;
+           }
+       }
+      else
+       flags |= O_DIROPEN;
 
-  if (pathmatch (file, DEFAULT_VALUE_NAME))
-    value_name = cstrdup ("");
-  else
-    value_name = cstrdup (file);
+      set_io_handle (handle);
+      value_name = cwcsdup (dec_file);
 
-  if (!fill_filebuf ())
-    {
-      RegCloseKey (handle);
-      res = 0;
-      goto out;
-    }
+      if (!(flags & O_DIROPEN) && !fill_filebuf ())
+       {
+         RegCloseKey (handle);
+         res = 0;
+         goto out;
+       }
 
-  if (flags & O_APPEND)
-    position = filesize;
-  else
-    position = 0;
+      if (flags & O_APPEND)
+       position = filesize;
+      else
+       position = 0;
+  }
 
 success:
   res = 1;
@@ -523,7 +802,7 @@ fhandler_registry::close ()
   if (res != 0)
     return res;
   HKEY handle = (HKEY) get_handle ();
-  if (handle != (HKEY) INVALID_HANDLE_VALUE)
+  if (handle != (HKEY) INVALID_HANDLE_VALUE && handle < HKEY_CLASSES_ROOT)
     {
       if (RegCloseKey (handle) != ERROR_SUCCESS)
        {
@@ -531,9 +810,12 @@ fhandler_registry::close ()
          res = -1;
        }
     }
-  if (value_name)
-    cfree (value_name);
-  return 0;
+  if (!hExeced && value_name)
+    {
+      cfree (value_name);
+      value_name = NULL;
+    }
+  return res;
 }
 
 bool
@@ -542,9 +824,11 @@ fhandler_registry::fill_filebuf ()
   DWORD type, size;
   LONG error;
   HKEY handle = (HKEY) get_handle ();
+  size_t bufalloc;
+
   if (handle != HKEY_PERFORMANCE_DATA)
     {
-      error = RegQueryValueEx (handle, value_name, NULL, &type, NULL, &size);
+      error = RegQueryValueExW (handle, value_name, NULL, &type, NULL, &size);
       if (error != ERROR_SUCCESS)
        {
          if (error != ERROR_FILE_NOT_FOUND)
@@ -554,60 +838,65 @@ fhandler_registry::fill_filebuf ()
            }
          goto value_not_found;
        }
-      bufalloc = size;
-      filebuf = (char *) cmalloc (HEAP_BUF, bufalloc);
+      PBYTE tmpbuf = (PBYTE) cmalloc_abort (HEAP_BUF, size);
       error =
-       RegQueryValueEx (handle, value_name, NULL, NULL, (BYTE *) filebuf,
-                        &size);
+       RegQueryValueExW (handle, value_name, NULL, NULL, tmpbuf, &size);
       if (error != ERROR_SUCCESS)
        {
          seterrno_from_win_error (__FILE__, __LINE__, error);
          return true;
        }
-      filesize = size;
+      if (type == REG_SZ || type == REG_EXPAND_SZ || type == REG_MULTI_SZ
+         || type == REG_LINK)
+       bufalloc = sys_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_MULTI_SZ
+         || type == REG_LINK)
+       sys_wcstombs (filebuf, bufalloc, (wchar_t *) tmpbuf,
+                     size / sizeof (wchar_t));
+      else
+       memcpy (filebuf, tmpbuf, bufalloc);
+      filesize = bufalloc;
     }
   else
     {
       bufalloc = 0;
       do
        {
-         bufalloc += 1000;
-         if (filebuf)
-           {
-             cfree (filebuf);
-             filebuf = (char *) cmalloc (HEAP_BUF, bufalloc);
-           }
-         error =
-           RegQueryValueEx (handle, value_name, NULL, &type,
-                            (BYTE *) filebuf, &size);
+         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)
            {
-             if (error != ERROR_FILE_NOT_FOUND)
-               {
-                 seterrno_from_win_error (__FILE__, __LINE__, error);
-                 return true;
-               }
-             goto value_not_found;
+             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 = MAX_PATH;
-  char buf[buf_size];
+  DWORD buf_size = NAME_MAX + 1;
+  wchar_t buf[buf_size];
   int index = 0;
   while (ERROR_SUCCESS ==
-        (error = RegEnumKeyEx (handle, index++, buf, &buf_size, NULL, NULL,
-                               NULL, NULL)) || (error == ERROR_MORE_DATA))
+        (error = RegEnumKeyExW (handle, index++, buf, &buf_size, NULL, NULL,
+                                NULL, NULL)) || (error == ERROR_MORE_DATA))
     {
-      if (pathmatch (buf, value_name))
+      if (!wcscasecmp (buf, value_name))
        {
          set_errno (EISDIR);
          return false;
        }
-      buf_size = MAX_PATH;
+      buf_size = NAME_MAX + 1;
     }
   if (error != ERROR_NO_MORE_ITEMS)
     {
@@ -620,54 +909,86 @@ value_not_found:
 
 /* Auxillary member function to open registry keys.  */
 static HKEY
-open_key (const char *name, REGSAM access, bool isValue)
+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;
-  char component[MAX_PATH];
+  wchar_t component[NAME_MAX + 1];
 
   while (*name)
     {
       const char *anchor = name;
-      while (*name && !SLASH_P (*name))
+      while (*name && !isdirsep (*name))
        name++;
-      strncpy (component, anchor, name - anchor);
-      component[name - anchor] = '\0';
+      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)
-       goto out;
+       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 =
-           RegOpenKeyEx (hParentKey, component, 0, effective_access, &hKey);
+         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;
            }
-         if (parentOpened)
-           RegCloseKey (hParentKey);
          hParentKey = hKey;
          parentOpened = true;
        }
       else
        {
          for (int i = 0; registry_listing[i]; i++)
-           if (pathmatch (component, registry_listing[i]))
+           if (strncasematch (anchor, registry_listing[i], name - anchor - 1))
              hKey = registry_keys[i];
          if (hKey == (HKEY) INVALID_HANDLE_VALUE)
            return hKey;
          hParentKey = hKey;
        }
     }
-out:
   return hKey;
 }
+
+int
+fhandler_registry::dup (fhandler_base *child)
+{
+  int ret = fhandler_virtual::dup (child);
+  /* 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)
+    child->set_io_handle (get_handle ());
+  return ret;
+}