OSDN Git Service

* fhandler_clipboard.cc: new file
[pf3gnuchains/pf3gnuchains4x.git] / winsup / cygwin / path.cc
index 6400699..63a8efd 100644 (file)
@@ -1,6 +1,6 @@
 /* path.cc: path support.
 
-   Copyright 1996, 1997, 1998, 1999, 2000 Cygnus Solutions.
+   Copyright 1996, 1997, 1998, 1999, 2000 Red Hat, Inc.
 
 This file is part of Cygwin.
 
@@ -15,46 +15,22 @@ details. */
 
    Pathnames are handled as follows:
 
-   - / is equivalent to \
+   - A \ or : in a path denotes a pure windows spec.
    - Paths beginning with // (or \\) are not translated (i.e. looked
      up in the mount table) and are assumed to be UNC path names.
-   - Paths containing a : are not translated (paths like
-   /foo/bar/baz:qux: don't make much sense but having the rule written
-   this way allows one to use strchr).
 
    The goal in the above set of rules is to allow both POSIX and Win32
    flavors of pathnames without either interfering.  The rules are
    intended to be as close to a superset of both as possible.
 
-   A possible future enhancement would be to allow people to
-   disable/enable the mount table handling to support pure Win32
-   pathnames.  Hopefully this won't be needed.  The suggested way to
-   do this would be an environment variable because
-   a) we need something that is inherited from parent to child,
-   b) environment variables can be passed from the DOS shell to a
-   cygwin app,
-   c) it allows disabling the feature on an app by app basis within
-   the same session (whereas playing about with the registry wouldn't
-   -- without getting too complicated).  Example:
-   CYGWIN=pathrules[=@]{win32,posix}.  If CYGWIN=pathrules=win32,
-   mount table handling is disabled.  [The intent is to have CYGWIN be
-   a catchall for tweaking various cygwin.dll features].
-
    Note that you can have more than one path to a file.  The mount
    table is always prefered when translating Win32 paths to POSIX
    paths.  Win32 paths in mount table entries may be UNC paths or
    standard Win32 paths starting with <drive-letter>:
 
    Text vs Binary issues are not considered here in path style
-   decisions.
-
-   / and \ are treated as equivalent.  One or the other is prefered in
-   certain situations (e.g. / is preferred in result of getcwd, \ is
-   preferred in arguments to Win32 api calls), but this code will
-   translate as necessary.
-
-   Apps wishing to translate to/from pure Win32 and POSIX-like
-   pathnames can use cygwin_foo.
+   decisions, although the appropriate flags are retrieved and
+   stored in various structures.
 
    Removing mounted filesystem support would simplify things greatly,
    but having it gives us a mechanism of treating disk that lives on a
@@ -68,7 +44,7 @@ details. */
 
    Each DOS drive is defined to have a current directory.  Supporting
    this would complicate things so for now things are defined so that
-   c: means c:\.
+   c: means c:\.  FIXME: Is this still true?
 */
 
 #include "winsup.h"
@@ -81,23 +57,24 @@ details. */
 #include <errno.h>
 #include <ctype.h>
 #include <winioctl.h>
+#include <sys/cygwin.h>
+#include <cygwin/version.h>
 #include "cygerrno.h"
 #include "fhandler.h"
 #include "path.h"
-#include "thread.h"
 #include "sync.h"
 #include "sigproc.h"
 #include "pinfo.h"
 #include "cygheap.h"
+#include "shared_info.h"
+#include "registry.h"
+#include "security.h"
+#include <assert.h>
 
-static int normalize_win32_path (const char *cwd, const char *src, char *dst);
-static char *getcwd_inner (char *buf, size_t ulen, int posix_p, int with_chroot);
+static int normalize_win32_path (const char *src, char *dst);
 static void slashify (const char *src, char *dst, int trailing_slash_p);
 static void backslashify (const char *src, char *dst, int trailing_slash_p);
 static int path_prefix_p_ (const char *path1, const char *path2, int len1);
-static int get_cwd_win32 ();
-
-static NO_COPY const char escape_char = '^';
 
 struct symlink_info
 {
@@ -113,7 +90,7 @@ struct symlink_info
   int check (const char *path, const suffix_info *suffixes);
 };
 
-/********************** Path Helper Functions *************************/
+cwdstuff cygcwd;       /* The current working directory. */
 
 #define path_prefix_p(p1, p2, l1) \
        ((tolower(*(p1))==tolower(*(p2))) && \
@@ -123,20 +100,6 @@ struct symlink_info
   (((x) & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY)) == \
    FILE_ATTRIBUTE_SYSTEM)
 
-/* Return non-zero if PATH1 is a prefix of PATH2.
-   Both are assumed to be of the same path style and / vs \ usage.
-   Neither may be "".
-   LEN1 = strlen (PATH1).  It's passed because often it's already known.
-
-   Examples:
-   /foo/ is a prefix of /foo  <-- may seem odd, but desired
-   /foo is a prefix of /foo/
-   / is a prefix of /foo/bar
-   / is not a prefix of foo/bar
-   foo/ is a prefix foo/bar
-   /foo is not a prefix of /foobar
-*/
-
 /* Determine if path prefix matches current cygdrive */
 #define iscygdrive(path) \
   (path_prefix_p (cygwin_shared->mount.cygdrive, (path), cygwin_shared->mount.cygdrive_len))
@@ -146,82 +109,25 @@ struct symlink_info
    (isdirsep(path[cygwin_shared->mount.cygdrive_len + 1]) || \
     !path[cygwin_shared->mount.cygdrive_len + 1]))
 
-/******************** Directory-related Support **************************/
-
-/* Cache getcwd value.  FIXME: We need a lock for these in order to
-   support multiple threads.  */
-
-#define TMPCWD ((char *) alloca (MAX_PATH + 1))
-
-struct cwdstuff
-{
-  char *posix;
-  char *win32;
-  DWORD hash;
-  muto *lock;
-};
-
-cwdstuff cwd;
-
-char * __stdcall
-cwd_win32 (char *buf)
-{
-  char *ret;
-  cwd.lock->acquire ();
-  if (cwd.win32 == NULL)
-    ret = NULL;
-  else if (buf == NULL)
-    ret = cwd.win32;
-  else
-    ret = strcpy (buf, cwd.win32);
-  cwd.lock->release ();
-  return ret;
-}
-
-char * __stdcall
-cwd_posix (char *buf)
-{
-  char *ret;
-  cwd.lock->acquire ();
-  if (cwd.posix == NULL)
-    ret = NULL;
-  else if (buf == NULL)
-    ret = cwd.posix;
-  else
-    ret = strcpy (buf, cwd.posix);
-  cwd.lock->release ();
-  return ret;
-}
-
-DWORD __stdcall
-cwd_hash ()
-{
-  DWORD hashnow;
-  cwd.lock->acquire ();
-  hashnow = cwd.hash;
-  cwd.lock->release ();
-  return hashnow;
-}
-
-void __stdcall
-cwd_init ()
-{
-  cwd.lock = new_muto (FALSE, "cwd");
-}
-
-void __stdcall
-cwd_fixup_after_exec (char *win32, char *posix, DWORD hash)
-{
-  cwd.win32 = win32;
-  cwd.posix = posix;
-  cwd.hash = hash;
-}
-
 #define ischrootpath(path) \
        (myself->rootlen && \
         strncasematch (myself->root, path, myself->rootlen) && \
         (path[myself->rootlen] == '/' || path[myself->rootlen] == '\0'))
 
+/* Return non-zero if PATH1 is a prefix of PATH2.
+   Both are assumed to be of the same path style and / vs \ usage.
+   Neither may be "".
+   LEN1 = strlen (PATH1).  It's passed because often it's already known.
+
+   Examples:
+   /foo/ is a prefix of /foo  <-- may seem odd, but desired
+   /foo is a prefix of /foo/
+   / is a prefix of /foo/bar
+   / is not a prefix of foo/bar
+   foo/ is a prefix foo/bar
+   /foo is not a prefix of /foobar
+*/
+
 static int
 path_prefix_p_ (const char *path1, const char *path2, int len1)
 {
@@ -262,6 +168,7 @@ path_conv::check (const char *src, unsigned opt,
   char path_copy[MAX_PATH];
   char tmp_buf[MAX_PATH];
   symlink_info sym;
+  bool need_directory = 0;
 
   char *rel_path, *full_path;
 
@@ -283,6 +190,16 @@ path_conv::check (const char *src, unsigned opt,
   for (;;)
     {
       MALLOC_CHECK;
+      assert (src);
+      char *p = strrchr (src, '/');
+      if (p)
+       {
+         if (p[1] == '\0' || strcmp (p, "/.") == 0)
+           need_directory = 1;
+       }
+      else if ((p = strrchr (src, '\\')) &&
+              (p[1] == '\0' || strcmp (p, "\\.") == 0))
+       need_directory = 1;
       /* Must look up path in mount table, etc.  */
       error = cygwin_shared->mount.conv_to_win32_path (src, rel_path,
                                                       full_path,
@@ -369,7 +286,7 @@ path_conv::check (const char *src, unsigned opt,
             these operations again on the newly derived path. */
          else if (len > 0)
            {
-             if (component == 0 && !(opt & PC_SYM_FOLLOW))
+             if (component == 0 && !need_directory && !(opt & PC_SYM_FOLLOW))
                {
                  set_symlink (); // last component of path is a symlink.
                  fileattr = sym.fileattr;
@@ -411,7 +328,6 @@ path_conv::check (const char *src, unsigned opt,
          }
 
       /* Copy tail of full_path to discovered symlink. */
-      char *p;
       for (p = sym.contents + buflen; *tail; tail++)
        *p++ = *tail == '\\' ? '/' : *tail;
       *p = '\0';
@@ -448,6 +364,18 @@ fillin:
     }
 
 out:
+  /* Deal with Windows stupidity which considers filename\. to be valid
+     even when "filename" is not a directory. */
+  if (!need_directory || error)
+    /* nothing to do */;
+  else if (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+    path_flags &= ~PATH_SYMLINK;
+  else
+    {
+      debug_printf ("%s is a non-directory", path);
+      error = ENOTDIR;
+      return;
+    }
   DWORD serial, volflags;
 
   strcpy (tmp_buf, full_path);
@@ -502,6 +430,8 @@ const char *windows_device_names[] =
   "nul",
   "\\dev\\zero",
   "\\dev\\%srandom",
+  "\\dev\\mem",
+  "\\dev\\clipboard",
 };
 
 static int
@@ -571,6 +501,18 @@ get_device_number (const char *name, int &unit, BOOL from_conv)
          devn = FH_RANDOM;
          unit = 8 + (deveqn ("u", 1) ? 1 : 0); /* Keep unit Linux conformant */
        }
+      else if (deveq ("mem"))
+        {
+          devn = FH_MEM;
+          unit = 1;
+        }
+      else if (deveq ("clipboard"))
+        devn = FH_CLIPBOARD;
+      else if (deveq ("port"))
+        {
+          devn = FH_MEM;
+          unit = 4;
+        }
       else if (deveqn ("com", 3) && (unit = digits (name + 3)) >= 0)
        devn = FH_SERIAL;
       else if (deveq ("pipe") || deveq ("piper") || deveq ("pipew"))
@@ -621,12 +563,12 @@ win32_device_name (const char *src_path, char *win32_path,
 #define isslash(c) ((c) == '/')
 
 static int
-normalize_posix_path (const char *cwd, const char *src, char *dst)
+normalize_posix_path (const char *src, char *dst)
 {
   const char *src_start = src;
   char *dst_start = dst;
 
-  syscall_printf ("cwd %s, src %s", cwd, src);
+  syscall_printf ("src %s", src);
   if (isdrive (src) || strpbrk (src, "\\:"))
     {
       cygwin_conv_to_full_posix_path (src, dst);
@@ -634,19 +576,16 @@ normalize_posix_path (const char *cwd, const char *src, char *dst)
     }
   if (!isslash (src[0]))
     {
-      if (strlen (cwd) + 1 + strlen (src) >= MAX_PATH)
+      if (!cygcwd.get (dst))
+       return get_errno ();
+      if (strlen (dst) + 1 + strlen (src) >= MAX_PATH)
        {
          debug_printf ("ENAMETOOLONG = normalize_posix_path (%s)", src);
          return ENAMETOOLONG;
        }
-      strcpy (dst, cwd);
       dst = strchr (dst, '\0');
       if (*src == '.')
-       {
-         if (dst == dst_start + 1)
-           dst--;
-         goto sawdot;
-       }
+       goto sawdot;
       if (dst > dst_start && !isslash (dst[-1]))
        *dst++ = '/';
     }
@@ -684,8 +623,8 @@ normalize_posix_path (const char *cwd, const char *src, char *dst)
        {
          while (*++src)
            {
-             while (isslash (*src))
-               src++;
+             if (isslash (*src))
+               continue;
 
              if (*src != '.')
                break;
@@ -693,13 +632,19 @@ normalize_posix_path (const char *cwd, const char *src, char *dst)
            sawdot:
              if (src[1] != '.')
                {
-                 if (!src[1] || !isslash (src[1]))
+                 if (!src[1])
+                   {
+                     if (dst == dst_start)
+                       *dst++ = '/';
+                     goto done;
+                   }
+                 if (!isslash (src[1]))
                    break;
                }
+             else if (src[2] && !isslash (src[2]))
+               break;
              else
                {
-                 if (src[2] && !isslash (src[2]))
-                   break;
                  if (!ischrootpath (dst_start) ||
                      dst - dst_start != (int) myself->rootlen)
                    while (dst > dst_start && !isslash (*--dst))
@@ -712,6 +657,7 @@ normalize_posix_path (const char *cwd, const char *src, char *dst)
        }
     }
 
+done:
   *dst = '\0';
   if (--dst > dst_start && isslash (*dst))
     *dst = '\0';
@@ -727,7 +673,7 @@ normalize_posix_path (const char *cwd, const char *src, char *dst)
    The result is 0 for success, or an errno error value.
    FIXME: A lot of this should be mergeable with the POSIX critter.  */
 static int
-normalize_win32_path (const char *cwd, const char *src, char *dst)
+normalize_win32_path (const char *src, char *dst)
 {
   const char *src_start = src;
   char *dst_start = dst;
@@ -735,14 +681,15 @@ normalize_win32_path (const char *cwd, const char *src, char *dst)
 
   if (!SLASH_P (src[0]) && strchr (src, ':') == NULL)
     {
-      if (strlen (cwd) + 1 + strlen (src) >= MAX_PATH)
+      if (!cygcwd.get (dst, 0))
+       return get_errno ();
+      if (strlen (dst) + 1 + strlen (src) >= MAX_PATH)
        {
          debug_printf ("ENAMETOOLONG = normalize_win32_path (%s)", src);
          return ENAMETOOLONG;
        }
-      strcpy (dst, cwd);
       dst += strlen (dst);
-      if (!*cwd || !SLASH_P (dst[-1]))
+      if (!SLASH_P (dst[-1]))
        *dst++ = '\\';
     }
   /* Two leading \'s?  If so, preserve them.  */
@@ -1005,9 +952,6 @@ mount_info::conv_to_win32_path (const char *src_path, char *win32_path,
   mount_item *mi = NULL;       /* initialized to avoid compiler warning */
   char pathbuf[MAX_PATH];
 
-  char cwd[MAX_PATH];
-  getcwd_inner (cwd, MAX_PATH, TRUE, 0); /* FIXME: check rc */
-
   /* Determine where the destination should be placed. */
   if (full_win32_path != NULL)
     dst = full_win32_path;
@@ -1022,7 +966,7 @@ mount_info::conv_to_win32_path (const char *src_path, char *win32_path,
   if (strpbrk (src_path, ":\\") != NULL)
     {
       debug_printf ("%s already win32", src_path);
-      rc = normalize_win32_path (cwd_win32 (TMPCWD), src_path, dst);
+      rc = normalize_win32_path (src_path, dst);
       if (rc)
        {
          debug_printf ("normalize_win32_path failed, rc %d", rc);
@@ -1065,10 +1009,7 @@ mount_info::conv_to_win32_path (const char *src_path, char *win32_path,
      converting it to a DOS-style path, looking up the appropriate drive
      in the mount table.  */
 
-  /* No need to fetch cwd if path is absolute.  */
-  isrelpath = !isslash (*src_path);
-
-  rc = normalize_posix_path (cwd, src_path, pathbuf);
+  rc = normalize_posix_path (src_path, pathbuf);
 
   if (rc)
     {
@@ -1077,6 +1018,8 @@ mount_info::conv_to_win32_path (const char *src_path, char *win32_path,
       return rc;
     }
 
+  isrelpath = !isslash (*src_path);
+
   /* See if this is a cygwin "device" */
   if (win32_device_name (pathbuf, dst, devn, unit))
     {
@@ -1137,31 +1080,35 @@ mount_info::conv_to_win32_path (const char *src_path, char *win32_path,
 
 fillin:
   /* Compute relative path if asked to and able to.  */
-  unsigned cwdlen;
-  cwdlen = 0;  /* avoid a (hopefully) bogus compiler warning */
-  char *cwd_win32_now;
-  cwd_win32_now = cwd_win32 (TMPCWD);
   if (win32_path == NULL)
     /* nothing to do */;
-  else if (isrelpath &&
-          path_prefix_p (cwd_win32_now, dst, cwdlen = strlen (cwd_win32_now)))
+  else if (isrelpath)
     {
-      size_t n = strlen (dst);
-      if (n < cwdlen)
+      char cwd_win32[MAX_PATH];
+      if (!cygcwd.get (cwd_win32, 0))
+       return get_errno ();
+      unsigned cwdlen = strlen (cwd_win32);
+      if (!path_prefix_p (cwd_win32, dst, cwdlen))
        strcpy (win32_path, dst);
       else
        {
-         if (n == cwdlen)
-           dst += cwdlen;
+         size_t n = strlen (dst);
+         if (n < cwdlen)
+           strcpy (win32_path, dst);
          else
-           dst += isdirsep (cwd_win32_now[cwdlen - 1]) ? cwdlen : cwdlen + 1;
-
-         memmove (win32_path, dst, strlen (dst) + 1);
-         if (!*win32_path)
            {
-             strcpy (win32_path, ".");
-             if (trailing_slash_p)
-               strcat (win32_path, "\\");
+             if (n == cwdlen)
+               dst += cwdlen;
+             else
+               dst += isdirsep (cwd_win32[cwdlen - 1]) ? cwdlen : cwdlen + 1;
+
+             memmove (win32_path, dst, strlen (dst) + 1);
+             if (!*win32_path)
+               {
+                 strcpy (win32_path, ".");
+                 if (trailing_slash_p)
+                   strcat (win32_path, "\\");
+               }
            }
        }
     }
@@ -1209,8 +1156,13 @@ mount_info::cygdrive_posix_path (const char *src, char *dst, int trailing_slash_
     dst[len++] = '\000';
   else
     {
+      int n;
       dst[len++] = '/';
-      strcpy (dst + len, src + 3);
+      if (SLASH_P (src[2]))
+       n = 3;
+      else
+       n = 2;
+      strcpy (dst + len, src + n);
     }
   slashify (dst, dst, trailing_slash_p);
 }
@@ -1273,15 +1225,7 @@ mount_info::conv_to_posix_path (const char *src_path, char *posix_path,
     }
 
   char pathbuf[MAX_PATH];
-  char cwd[MAX_PATH];
-
-  /* No need to fetch cwd if path is absolute. */
-  if (relative_path_p)
-    getcwd_inner (cwd, MAX_PATH, 0, 0); /* FIXME: check rc */
-  else
-    strcpy (cwd, "/"); /* some innocuous value */
-
-  int rc = normalize_win32_path (cwd, src_path, pathbuf);
+  int rc = normalize_win32_path (src_path, pathbuf);
   if (rc != 0)
     {
       debug_printf ("%d = conv_to_posix_path (%s)", rc, src_path);
@@ -1362,13 +1306,13 @@ mount_info::read_mounts (reg_key& r)
   char posix_path[MAX_PATH];
   HKEY key = r.get_key ();
   DWORD i, posix_path_size;
-  int found_cygdrive = FALSE;
+  int res;
 
   /* Loop through subkeys */
   /* FIXME: we would like to not check MAX_MOUNTS but the heap in the
      shared area is currently statically allocated so we can't have an
      arbitrarily large number of mounts. */
-  for (DWORD i = 0; ; i++)
+  for (i = 0; ; i++)
     {
       char native_path[MAX_PATH];
       int mount_flags;
@@ -1377,23 +1321,17 @@ mount_info::read_mounts (reg_key& r)
       /* FIXME: if maximum posix_path_size is 256, we're going to
         run into problems if we ever try to store a mount point that's
         over 256 but is under MAX_PATH! */
-      LONG err = RegEnumKeyEx (key, i, posix_path, &posix_path_size, NULL,
+      res = RegEnumKeyEx (key, i, posix_path, &posix_path_size, NULL,
                          NULL, NULL, NULL);
 
-      if (err == ERROR_NO_MORE_ITEMS)
+      if (res == ERROR_NO_MORE_ITEMS)
        break;
-      else if (err != ERROR_SUCCESS)
+      else if (res != ERROR_SUCCESS)
        {
-         debug_printf ("RegEnumKeyEx failed, error %d!\n", err);
+         debug_printf ("RegEnumKeyEx failed, error %d!\n", res);
          break;
        }
 
-      if (iscygdrive (posix_path))
-       {
-         found_cygdrive = TRUE;
-         continue;
-       }
-
       /* Get a reg_key based on i. */
       reg_key subkey = reg_key (key, KEY_READ, posix_path, NULL);
 
@@ -1402,31 +1340,10 @@ mount_info::read_mounts (reg_key& r)
       mount_flags = subkey.get_int ("flags", 0);
 
       /* Add mount_item corresponding to registry mount point. */
-      int res = cygwin_shared->mount.add_item (native_path, posix_path, mount_flags, FALSE);
+      res = cygwin_shared->mount.add_item (native_path, posix_path, mount_flags, FALSE);
       if (res && get_errno () == EMFILE)
        break; /* The number of entries exceeds MAX_MOUNTS */
     }
-
-  if (!found_cygdrive)
-    return;
-
-loop:
-  for (i = 0; ;i++)
-    {
-      posix_path_size = MAX_PATH;
-      LONG err = RegEnumKeyEx (key, i, posix_path, &posix_path_size, NULL,
-                         NULL, NULL, NULL);
-
-      if (err != ERROR_SUCCESS)
-       break;
-
-      if (iscygdrive (posix_path))
-       {
-         /* This shouldn't be in the mount table. */
-         (void) r.kill (posix_path);
-         goto loop;
-       }
-    }
 }
 
 /* from_registry: Build the entire mount table from the registry.  Also,
@@ -1469,6 +1386,8 @@ mount_info::from_registry ()
 int
 mount_info::add_reg_mount (const char * native_path, const char * posix_path, unsigned mountflags)
 {
+  int res = 0;
+
   /* Add the mount to the right registry location, depending on
      whether MOUNT_SYSTEM is set in the mount flags. */
   if (!(mountflags & MOUNT_SYSTEM)) /* current_user mount */
@@ -1477,14 +1396,18 @@ mount_info::add_reg_mount (const char * native_path, const char * posix_path, un
       reg_key reg_user;
 
       /* Start by deleting existing mount if one exists. */
-      reg_user.kill (posix_path);
+      res = reg_user.kill (posix_path);
+      if (res != ERROR_SUCCESS && res != ERROR_FILE_NOT_FOUND)
+       goto err;
 
       /* Create the new mount. */
       reg_key subkey = reg_key (reg_user.get_key (),
                                KEY_ALL_ACCESS,
                                posix_path, NULL);
-      subkey.set_string ("native", native_path);
-      subkey.set_int ("flags", mountflags);
+      res = subkey.set_string ("native", native_path);
+      if (res != ERROR_SUCCESS)
+       goto err;
+      res = subkey.set_int ("flags", mountflags);
     }
   else /* local_machine mount */
     {
@@ -1495,24 +1418,25 @@ mount_info::add_reg_mount (const char * native_path, const char * posix_path, un
                       CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME,
                       NULL);
 
-      if (reg_sys.get_key () == INVALID_HANDLE_VALUE)
-       {
-         set_errno (EACCES);
-         return -1;
-       }
-
       /* Start by deleting existing mount if one exists. */
-      reg_sys.kill (posix_path);
+      res = reg_sys.kill (posix_path);
+      if (res != ERROR_SUCCESS && res != ERROR_FILE_NOT_FOUND)
+       goto err;
 
       /* Create the new mount. */
       reg_key subkey = reg_key (reg_sys.get_key (),
                                KEY_ALL_ACCESS,
                                posix_path, NULL);
-      subkey.set_string ("native", native_path);
-      subkey.set_int ("flags", mountflags);
+      res = subkey.set_string ("native", native_path);
+      if (res != ERROR_SUCCESS)
+       goto err;
+      res = subkey.set_int ("flags", mountflags);
     }
 
   return 0; /* Success! */
+ err:
+  __seterrno_from_win_error (res);
+  return -1;
 }
 
 /* del_reg_mount: delete mount item from registry indicated in flags.
@@ -1522,13 +1446,13 @@ mount_info::add_reg_mount (const char * native_path, const char * posix_path, un
 int
 mount_info::del_reg_mount (const char * posix_path, unsigned flags)
 {
-  int killres;
+  int res;
 
   if ((flags & MOUNT_SYSTEM) == 0)     /* Delete from user registry */
     {
       reg_key reg_user (KEY_ALL_ACCESS,
                        CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, NULL);
-      killres = reg_user.kill (posix_path);
+      res = reg_user.kill (posix_path);
     }
   else                                 /* Delete from system registry */
     {
@@ -1537,19 +1461,12 @@ mount_info::del_reg_mount (const char * posix_path, unsigned flags)
                       CYGWIN_INFO_CYGWIN_REGISTRY_NAME,
                       CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME,
                       NULL);
-
-      if (reg_sys.get_key () == INVALID_HANDLE_VALUE)
-       {
-         set_errno (EACCES);
-         return -1;
-       }
-
-      killres = reg_sys.kill (posix_path);
+      res = reg_sys.kill (posix_path);
     }
 
-  if (killres != ERROR_SUCCESS)
+  if (res != ERROR_SUCCESS)
     {
-      __seterrno_from_win_error (killres);
+      __seterrno_from_win_error (res);
       return -1;
     }
 
@@ -1634,7 +1551,13 @@ mount_info::write_cygdrive_info_to_registry (const char *cygdrive_prefix, unsign
   /* Ensure that there is never a final slash */
   nofinalslash (cygdrive_prefix, hold_cygdrive_prefix);
 
-  r.set_string ("cygdrive prefix", hold_cygdrive_prefix);
+  int res;
+  res = r.set_string ("cygdrive prefix", hold_cygdrive_prefix);
+  if (res != ERROR_SUCCESS)
+    {
+      __seterrno_from_win_error (res);
+      return -1;
+    }
   r.set_int ("cygdrive flags", flags);
 
   /* This also needs to go in the in-memory copy of "cygdrive", but only if
@@ -1804,7 +1727,8 @@ mount_info::add_item (const char *native, const char *posix, unsigned mountflags
 
   if ((native == NULL) || (*native == 0) ||
       (posix == NULL) || (*posix == 0) ||
-      (!slash_unc_prefix_p (native) && !isabspath (native)))
+      !isabspath (native) || !isabspath (posix) ||
+      slash_unc_prefix_p (posix) || isdrive (posix))
     {
       set_errno (EINVAL);
       return -1;
@@ -1846,20 +1770,17 @@ mount_info::add_item (const char *native, const char *posix, unsigned mountflags
        break;
     }
 
-  if (i == nmounts)
+  if (i == nmounts && nmounts == MAX_MOUNTS)
     {
-      if (nmounts < MAX_MOUNTS)
-       i = nmounts++;
-      else
-       {
-         set_errno (EMFILE);
-         return -1;
-       }
+      set_errno (EMFILE);
+      return -1;
     }
 
   if (reg_p && add_reg_mount (nativetmp, posixtmp, mountflags))
     return -1;
 
+  if (i == nmounts)
+    nmounts++;
   mount[i].init (nativetmp, posixtmp, mountflags);
   sort ();
 
@@ -2094,15 +2015,7 @@ mount (const char *win32_path, const char *posix_path, unsigned flags)
       win32_path = NULL;
     }
   else
-    {
-      if (iscygdrive (posix_path))
-       {
-         set_errno (EINVAL);
-         return res;   /* Don't try to add cygdrive prefix. */
-       }
-
-      res = cygwin_shared->mount.add_item (win32_path, posix_path, flags, TRUE);
-    }
+    res = cygwin_shared->mount.add_item (win32_path, posix_path, flags, TRUE);
 
   syscall_printf ("%d = mount (%s, %s, %p)", res, win32_path, posix_path, flags);
   return res;
@@ -2212,7 +2125,7 @@ symlink (const char *topath, const char *frompath)
       goto done;
     }
 
-  h = CreateFileA(win32_path.get_win32 (), GENERIC_WRITE, 0, &sec_none_nih,
+  h = CreateFileA(win32_path, GENERIC_WRITE, 0, &sec_none_nih,
                  CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
   if (h == INVALID_HANDLE_VALUE)
       __seterrno ();
@@ -2250,6 +2163,7 @@ done:
 static __inline char *
 has_suffix (const char *path, const suffix_info *suffixes)
 {
+  assert (path);
   char *ext = strrchr (path, '.');
   if (ext)
     for (const suffix_info *ex = suffixes; ex->name != NULL; ex++)
@@ -2333,21 +2247,6 @@ symlink_info::check (const char *in_path, const suffix_info *suffixes)
          continue;
        }
 
-      /* Windows allows path\. even when `path' isn't a directory.
-        Detect this scenario and disallow it, since it is non-UNIX like.
-        FIXME: This code actually checks for things like foo/ and foo/..
-        even though those usages have already been (erroneously?) eaten
-        by cygwin_shared->mount.conv_to_win32_path in path_conv::check. */
-
-      char *p = strrchr (path, '\\');
-      if (p && !(fileattr & FILE_ATTRIBUTE_DIRECTORY) &&
-         (*++p == '\0' || (*p == '.' && (*++p == '\0' || (*p == '.' && p[1] == '\0')))))
-       {
-         debug_printf ("%s is a non-directory", path);
-         error = ENOTDIR;
-         goto file_not_symlink;
-       }
-
       /* A symlink will have the `system' file attribute. */
       /* Only files can be symlinks (which can be symlinks to directories). */
       if (!(pflags & PATH_SYMLINK) && !SYMLINKATTR (fileattr))
@@ -2401,9 +2300,10 @@ symlink_info::check (const char *in_path, const suffix_info *suffixes)
          else
            {
              /* Not a symlink, see if executable.  */
-             if (!(pflags & (PATH_EXEC | PATH_CYGWIN_EXEC)) && got >= 2 &&
+             if (!(pflags & PATH_ALL_EXEC) && got >= 2 &&
                  ((cookie_buf[0] == '#' && cookie_buf[1] == '!') ||
-                  (cookie_buf[0] == ':' && cookie_buf[1] == '\n')))
+                  (cookie_buf[0] == ':' && cookie_buf[1] == '\n') ||
+                  (cookie_buf[0] == 'M' && cookie_buf[1] == 'Z')))
                pflags |= PATH_EXEC;
            close_and_return:
              CloseHandle (h);
@@ -2436,6 +2336,13 @@ int
 readlink (const char *path, char *buf, int buflen)
 {
   extern suffix_info stat_suffixes[];
+
+  if (buflen < 0)
+    {
+      set_errno (ENAMETOOLONG);
+      return -1;
+    }
+
   path_conv pathbuf (path, PC_SYM_CONTENTS, stat_suffixes);
 
   if (pathbuf.error)
@@ -2452,14 +2359,8 @@ readlink (const char *path, char *buf, int buflen)
       return -1;
     }
 
-  int len = strlen (pathbuf.get_win32 ());
-  if (len > (buflen - 1))
-    {
-      set_errno (ENAMETOOLONG);
-      return -1;
-    }
+  int len = min (buflen, (int) strlen (pathbuf.get_win32 ()));
   memcpy (buf, pathbuf.get_win32 (), len);
-  buf[len] = '\0';
 
   /* errno set by symlink.check if error */
   return len;
@@ -2493,8 +2394,8 @@ hash_path_name (unsigned long hash, const char *name)
        {
          char *nn, *newname = (char *) alloca (strlen (name) + 2);
          nn = strncpy (newname, name, 2);
-         if (islower (*nn))
-           *newname = toupper (*nn);
+         if (isupper (*nn))
+           *newname = tolower (*nn);
          *(nn += 2) = '\0';
          name += 2;
          if (*name != '\\')
@@ -2512,12 +2413,12 @@ hash_path_name (unsigned long hash, const char *name)
         Otherwise the inodes same will differ depending on whether a file is
         referenced with an absolute value or relatively. */
 
-      if (*name != '\\' && (cwd_win32 (TMPCWD) == NULL || get_cwd_win32 ()))
+      if (!hash && !isabspath (name))
        {
-         hash = cwd_hash ();
+         hash = cygcwd.get_hash ();
          if (name[0] == '.' && name[1] == '\0')
            return hash;
-         hash = hash_path_name (hash, "\\");
+         hash += hash_path_name (hash, "\\");
        }
     }
 
@@ -2526,7 +2427,8 @@ hashit:
      \a\b\.  but allow a single \ if that's all there is. */
   do
     {
-      hash += *name + (*name << 17);
+      int ch = tolower(*name);
+      hash += ch + (ch << 17);
       hash ^= hash >> 2;
     }
   while (*++name != '\0' &&
@@ -2534,135 +2436,10 @@ hashit:
   return hash;
 }
 
-static int
-get_cwd_win32 ()
-{
-  DWORD dlen, len;
-
-  cwd.lock->acquire ();
-  for (dlen = 256; ; dlen *= 2)
-    {
-      cwd.win32 = (char *) crealloc (cwd.win32, dlen + 2);
-      if ((len = GetCurrentDirectoryA (dlen, cwd.win32)) < dlen)
-       break;
-    }
-
-  if (len == 0)
-    __seterrno ();
-  else
-    cwd.hash = hash_path_name (0, cwd.win32);
-
-  cwd.lock->release ();
-  return len;
-}
-
-/* getcwd */
-
-char *
-getcwd_inner (char *buf, size_t ulen, int posix_p, int with_chroot)
-{
-  char *resbuf = NULL;
-  size_t len = ulen;
-
-  if (cwd_win32 (TMPCWD) == NULL && !get_cwd_win32 ())
-    return NULL;
-
-  char *cwd_win32_now = cwd_win32 (TMPCWD);
-  char *cwd_posix_now = cwd_posix (TMPCWD);
-  if (!posix_p)
-    {
-      if (strlen (cwd_win32_now) >= len)
-       set_errno (ERANGE);
-      else
-       {
-         strcpy (buf, cwd_win32_now);
-         resbuf = buf;
-       }
-
-      syscall_printf ("%p (%s) = getcwd_inner (%p, %d, win32) (cached)",
-                     resbuf, resbuf ? resbuf : "", buf, len);
-      return resbuf;
-    }
-  else if (cwd_posix_now != NULL)
-    {
-      debug_printf("myself->root: %s, cwd_posix: %s", myself->root, cwd_posix_now);
-      if (strlen (cwd_posix_now) >= len)
-       set_errno (ERANGE);
-      else if (with_chroot && ischrootpath(cwd_posix_now))
-       {
-         strcpy (buf, cwd_posix_now + myself->rootlen);
-         if (!buf[0])
-           strcpy (buf, "/");
-         resbuf = buf;
-       }
-      else
-       {
-         strcpy (buf, cwd_posix_now);
-         resbuf = buf;
-       }
-
-      syscall_printf ("%p (%s) = getcwd_inner (%p, %d, posix) (cached)",
-                     resbuf, resbuf ? resbuf : "", buf, len);
-      return resbuf;
-    }
-
-  /* posix_p required and cwd_posix == NULL */
-
-  char temp[MAX_PATH];
-
-  /* Turn from Win32 style to our style.  */
-  cygwin_shared->mount.conv_to_posix_path (cwd_win32_now, temp, 0);
-
-  size_t tlen = strlen (temp);
-
-  if (with_chroot && ischrootpath (temp))
-    tlen -= myself->rootlen;
-
-  cwd.lock->acquire ();
-  cwd.posix = (char *) crealloc (cwd.posix, tlen + 1);
-  if (cwd.posix != NULL)
-    if (with_chroot && ischrootpath (temp))
-      {
-       strcpy (cwd.posix, temp + myself->rootlen);
-       if (!buf[0])
-         strcpy (buf, "/");
-      }
-    else
-      strcpy (cwd.posix, temp);
-
-  cwd.lock->release ();
-
-  if (tlen >= ulen)
-    set_errno (ERANGE);        /* len was too small */
-  else
-    {
-      strcpy (buf, temp);
-      resbuf = buf;
-    }
-
-  syscall_printf ("%p (%s) = getcwd_inner (%p, %d, %s)",
-                 resbuf, resbuf ? resbuf : "",
-                 buf, len, posix_p ? "posix" : "win32");
-  return resbuf;
-}
-
 char *
 getcwd (char *buf, size_t ulen)
 {
-  char *res;
-
-  if (buf == NULL || ulen == 0)
-    {
-      buf = (char *) alloca (MAX_PATH);
-      res = getcwd_inner (buf, MAX_PATH, 1, 1);
-      res = strdup (buf);
-    }
-  else
-    {
-      res = getcwd_inner (buf, ulen, 1, 1);
-    }
-
-  return res;
+  return cygcwd.get (buf, 1, 1, ulen);
 }
 
 /* getwd: standards? */
@@ -2678,8 +2455,9 @@ extern "C"
 int
 chdir (const char *dir)
 {
+  MALLOC_CHECK;
   syscall_printf ("dir %s", dir);
-  path_conv path (dir);
+  path_conv path (dir, PC_FULL | PC_SYM_FOLLOW);
 
   if (path.error)
     {
@@ -2688,6 +2466,23 @@ chdir (const char *dir)
       return -1;
     }
 
+  /* Look for trailing path component consisting entirely of dots.  This
+     is needed only in case of chdir since Windows simply ignores count
+     of dots > 2 here instead of returning an error code.  Counts of dots
+     <= 2 are already eliminated by normalize_posix_path. */
+  const char *p = strrchr (dir, '/');
+  if (!p)
+    p = dir;
+  else
+    p++;
+
+  size_t len = strlen (p);
+  if (len > 2 && strspn (p, ".") == len)
+    {
+      set_errno (ENOENT);
+      return -1;
+    }
+
   char *native_dir = path.get_win32 ();
 
   /* Check to see if path translates to something like C:.
@@ -2703,27 +2498,13 @@ chdir (const char *dir)
   if (res == -1)
     __seterrno ();
   else
-    {
-      cwd.lock->acquire ();
-      /* Store new cache information */
-      cfree (cwd.win32);
-      cwd.win32 = cstrdup (path);
-
-      char pathbuf[MAX_PATH];
-      (void) normalize_posix_path (cwd.posix, dir, pathbuf);
-      /* Look for trailing path component consisting entirely of dots.  This
-        is needed only in case of chdir since Windows simply ignores count
-        of dots > 2 here instead of returning an error code.  Counts of dots
-        <= 2 are already eliminated by normalize_posix_path. */
-      char *last_slash = strrchr (pathbuf, '/');
-      if (last_slash > pathbuf && strspn (last_slash + 1, ".") == strlen (last_slash + 1))
-       *last_slash = '\0';
-      cfree (cwd.posix);
-      cwd.posix = cstrdup (pathbuf);
-      cwd.lock->release ();
-    }
-
-  syscall_printf ("%d = chdir() cwd.posix '%s' native '%s'", res, cwd.posix, native_dir);
+    cygcwd.set (path, dir);
+
+  /* Note that we're accessing cwd.posix without a lock here.  I didn't think
+     it was worth locking just for strace. */
+  syscall_printf ("%d = chdir() cygcwd.posix '%s' native '%s'", res,
+                 cygcwd.posix, native_dir);
+  MALLOC_CHECK;
   return res;
 }
 
@@ -2992,7 +2773,7 @@ cygwin_split_path (const char *path, char *dir, char *file)
     })
 
 /* Return TRUE if two strings match up to length n */
-int __stdcall
+extern "C" int __stdcall
 strncasematch (const char *s1, const char *s2, size_t n)
 {
   if (s1 == s2)
@@ -3009,7 +2790,7 @@ strncasematch (const char *s1, const char *s2, size_t n)
 }
 
 /* Return TRUE if two strings match */
-int __stdcall
+extern "C" int __stdcall
 strcasematch (const char *s1, const char *s2)
 {
   if (s1 == s2)
@@ -3024,7 +2805,7 @@ strcasematch (const char *s1, const char *s2)
   return *s2 == '\0';
 }
 
-char * __stdcall
+extern "C" char * __stdcall
 strcasestr (const char *searchee, const char *lookfor)
 {
   if (*searchee == 0)
@@ -3064,3 +2845,152 @@ check_null_empty_path (const char *name)
 
   return 0;
 }
+
+/* Return the hash value for the current win32 value.
+   This is used when constructing inodes. */
+DWORD
+cwdstuff::get_hash ()
+{
+  DWORD hashnow;
+  lock->acquire ();
+  hashnow = hash;
+  lock->release ();
+  return hashnow;
+}
+
+/* Initialize cygcwd 'muto' for serializing access to cwd info. */
+void
+cwdstuff::init ()
+{
+  lock = new_muto (FALSE, "cwd");
+}
+
+/* Called to fill in cwd values after an exec. */
+void
+cwdstuff::fixup_after_exec (char *win32_cwd, char *posix_cwd, DWORD hash_cwd)
+{
+  win32 = win32_cwd;
+  posix = posix_cwd;
+  hash = hash_cwd;
+}
+
+/* Get initial cwd.  Should only be called once in a
+   process tree. */
+bool
+cwdstuff::get_initial ()
+{
+  lock->acquire ();
+
+  if (win32)
+    return 1;
+
+  int i;
+  DWORD len, dlen;
+  for (i = 0, dlen = MAX_PATH, len = 0; i < 3; dlen *= 2, i++)
+    {
+      win32 = (char *) crealloc (win32, dlen + 2);
+      if ((len = GetCurrentDirectoryA (dlen, win32)) < dlen)
+       break;
+    }
+
+  if (len == 0)
+    {
+      __seterrno ();
+      lock->release ();
+      debug_printf ("get_initial_cwd failed, %E");
+      lock->release ();
+      return 0;
+    }
+  set (NULL);
+  return 1;    /* Leaves cwd lock unreleased */
+}
+
+/* Fill out the elements of a cwdstuff struct.
+   It is assumed that the lock for the cwd is acquired if
+   win32_cwd == NULL. */
+void
+cwdstuff::set (const char *win32_cwd, const char *posix_cwd)
+{
+  char pathbuf[MAX_PATH];
+
+  if (win32_cwd)
+    {
+      lock->acquire ();
+      win32 = (char *) crealloc (win32, strlen (win32_cwd) + 1);
+      strcpy (win32, win32_cwd);
+    }
+
+  if (!posix_cwd)
+    cygwin_shared->mount.conv_to_posix_path (win32, pathbuf, 0);
+  else
+    (void) normalize_posix_path (posix_cwd, pathbuf);
+
+  posix = (char *) crealloc (posix, strlen (pathbuf) + 1);
+  strcpy (posix, pathbuf);
+
+  hash = hash_path_name (0, win32);
+
+  if (win32_cwd)
+    lock->release ();
+
+  return;
+}
+
+/* Copy the value for either the posix or the win32 cwd into a buffer. */
+char *
+cwdstuff::get (char *buf, int need_posix, int with_chroot, unsigned ulen)
+{
+  MALLOC_CHECK;
+
+  if (ulen == 0)
+    {
+      set_errno (EINVAL);
+      goto out;
+    }
+
+  if (!get_initial ()) /* Get initial cwd and set cwd lock */
+    return NULL;
+
+  char *tocopy;
+  if (!need_posix)
+    tocopy = win32;
+  else
+    tocopy = with_chroot && ischrootpath(posix) ?
+            posix + myself->rootlen : posix;
+
+  debug_printf("myself->root: %s, posix: %s", myself->root, posix);
+  if (strlen (tocopy) >= ulen)
+    {
+      set_errno (ERANGE);
+      buf = NULL;
+    }
+  else
+    {
+      if (!buf)
+       buf = (char *) malloc (strlen (tocopy) + 1);
+      strcpy (buf, tocopy);
+      if (!buf[0])     /* Should only happen when chroot */
+       strcpy (buf, "/");
+    }
+
+  lock->release ();
+
+out:
+  syscall_printf ("(%s) = cwdstuff::get (%p, %d, %d, %d), errno %d",
+                 buf, buf, ulen, need_posix, with_chroot, errno);
+  MALLOC_CHECK;
+  return buf;
+}
+
+/* Get copies of all cwdstuff elements.  Used by spawn_guts. */
+void
+cwdstuff::copy (char * &posix_cwd, char * &win32_cwd, DWORD hash_cwd)
+{
+  lock->acquire ();
+  get_initial (); /* FIXME: Check return someday */
+  posix_cwd = cstrdup (posix);
+  win32_cwd = cstrdup (win32);
+  hash_cwd = hash;
+  MALLOC_CHECK;
+  lock->release ();
+}