From aaf1b29d8f4ca24846e3a69c0d6b3c96766baaeb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B8=CC=86=20=D0=9F?=
 =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=BE=D0=B2?= <alexey.pawlow@gmail.com>
Date: Sun, 14 Apr 2019 21:47:21 +0300
Subject: [PATCH 06/N] Instead of creating Cygwin symlinks, use deep copy by
 default

The new `winsymlinks` mode `deepcopy` (which is made the default) lets
calls to `symlink()` create (deep) copies of the source file/directory.

This is necessary because unlike Cygwin, MSYS2 does not try to be its
own little ecosystem that lives its life separate from regular Win32
programs: the latter have _no idea_ about Cygwin-emulated symbolic links
(i.e. system files whose contents start with `!<symlink>\xff\xfe` and
the remainder consists of the NUL-terminated, UTF-16LE-encoded symlink
target).

To support Cygwin-style symlinks, the new mode `sysfile` is introduced.

Co-authored-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Co-authored-by: Jeremy Drake <github@jdrake.com>
---
 winsup/cygwin/environ.cc |   4 +
 winsup/cygwin/globals.cc |   3 +-
 winsup/cygwin/path.cc    | 252 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 258 insertions(+), 1 deletion(-)

diff --git a/winsup/cygwin/environ.cc b/winsup/cygwin/environ.cc
index b9f7e05..5fb3f53 100644
--- a/winsup/cygwin/environ.cc
+++ b/winsup/cygwin/environ.cc
@@ -88,6 +88,10 @@ set_winsymlinks (const char *buf)
   else if (ascii_strncasematch (buf, "native", 6))
     allow_winsymlinks = ascii_strcasematch (buf + 6, "strict")
 			? WSYM_nativestrict : WSYM_native;
+  else if (ascii_strncasematch (buf, "deepcopy", 8))
+    allow_winsymlinks = WSYM_deepcopy;
+  else
+    allow_winsymlinks = WSYM_sysfile;
 }
 
 /* The structure below is used to set up an array which is used to
diff --git a/winsup/cygwin/globals.cc b/winsup/cygwin/globals.cc
index d8e058f..b7e0e21 100644
--- a/winsup/cygwin/globals.cc
+++ b/winsup/cygwin/globals.cc
@@ -57,6 +57,7 @@ enum winsym_t
   WSYM_nativestrict,
   WSYM_nfs,
   WSYM_sysfile,
+  WSYM_deepcopy
 };
 
 exit_states NO_COPY exit_state;
@@ -70,7 +71,7 @@ bool ignore_case_with_glob;
 bool pipe_byte = true; /* Default to byte mode so that C# programs work. */
 bool reset_com;
 bool wincmdln;
-winsym_t allow_winsymlinks = WSYM_default;
+winsym_t allow_winsymlinks = WSYM_deepcopy;
 bool disable_pcon;
 bool winjitdebug = false;
 
diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc
index b758bc5..f6bfe08 100644
--- a/winsup/cygwin/path.cc
+++ b/winsup/cygwin/path.cc
@@ -1722,6 +1722,173 @@ conv_path_list (const char *src, char *dst, size_t size,
 
 /********************** Symbolic Link Support **************************/
 
+static int
+recursiveCopyCheckSymlink(PUNICODE_STRING src, bool& isdirlink)
+{
+  path_conv pc (src, PC_SYM_NOFOLLOW|PC_SYM_NOFOLLOW_REP);
+  if (pc.error)
+    {
+      set_errno (pc.error);
+      return -1;
+    }
+  isdirlink = pc.issymlink ();
+  return 0;
+}
+
+/*
+  Create a deep copy of src as dst, while avoiding descending in origpath.
+*/
+static int
+recursiveCopy (PUNICODE_STRING src, PUNICODE_STRING dst, USHORT origsrclen,
+	       USHORT origdstlen, PWIN32_FIND_DATAW dHfile = NULL)
+{
+  HANDLE dH = INVALID_HANDLE_VALUE;
+  NTSTATUS status;
+  int srcpos = src->Length;
+  int dstpos = dst->Length;
+  int res = -1;
+  bool freedHfile = false;
+
+  if (!dHfile)
+    {
+      dHfile = (PWIN32_FIND_DATAW) cmalloc_abort (HEAP_STR, sizeof (*dHfile));
+      freedHfile = true;
+    }
+
+  debug_printf ("recursiveCopy (%S, %S)", src, dst);
+
+  /* Create the destination directory */
+  if (!CreateDirectoryExW (src->Buffer, dst->Buffer, NULL))
+    {
+      debug_printf ("CreateDirectoryExW(%S, %S, 0) failed", src, dst);
+      __seterrno ();
+      goto done;
+    }
+  /* Descend into the source directory */
+  if (src->Buffer[(src->Length - 1) / sizeof (WCHAR)] != L'\\')
+    {
+      status = RtlAppendUnicodeToString (src, L"\\*");
+    }
+  else
+    {
+      status = RtlAppendUnicodeToString (src, L"*");
+      srcpos -= sizeof (WCHAR);
+    }
+  if (!NT_SUCCESS (status))
+    {
+      __seterrno_from_nt_status (status);
+      goto done;
+    }
+  if (dst->Buffer[(dst->Length - 1) / sizeof (WCHAR)] != L'\\')
+      status = RtlAppendUnicodeToString (dst, L"\\");
+  else
+      dstpos -= sizeof (WCHAR);
+  if (!NT_SUCCESS (status))
+    {
+      __seterrno_from_nt_status (status);
+      goto done;
+    }
+
+  dH = FindFirstFileExW (src->Buffer, FindExInfoBasic, dHfile,
+			 FindExSearchNameMatch, NULL,
+			 FIND_FIRST_EX_LARGE_FETCH);
+  if (dH == INVALID_HANDLE_VALUE)
+    {
+      __seterrno ();
+      goto done;
+    }
+
+  do
+    {
+      bool isdirlink = false;
+      debug_printf ("dHfile: %W", dHfile->cFileName);
+      if (dHfile->cFileName[0] == L'.' &&
+	  (!dHfile->cFileName[1] ||
+	   (dHfile->cFileName[1] == L'.' && !dHfile->cFileName[2])))
+	continue;
+      /* Append the directory item filename to both source and destination */
+      src->Length = srcpos + sizeof (WCHAR);
+      dst->Length = dstpos + sizeof (WCHAR);
+      status = RtlAppendUnicodeToString (src, dHfile->cFileName);
+      if (!NT_SUCCESS (status))
+	{
+	  __seterrno_from_nt_status (status);
+	  goto done;
+	}
+      status = RtlAppendUnicodeToString (dst, dHfile->cFileName);
+      if (!NT_SUCCESS (status))
+	{
+	  __seterrno_from_nt_status (status);
+	  goto done;
+	}
+      debug_printf ("%S -> %S", src, dst);
+      if ((dHfile->dwFileAttributes &
+	    (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT)) ==
+	  (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT))
+	{
+	  /* I was really hoping to avoid using path_conv in the recursion,
+	     but maybe putting it in its own function will prevent it from
+	     taking up space in the stack frame */
+	  if (recursiveCopyCheckSymlink (src, isdirlink))
+	    goto done;
+	}
+      if (isdirlink)
+        {
+	  /* CreateDirectoryEx seems to "copy" directory reparse points, which
+	     CopyFileEx can only do with a flag introduced in 19041. */
+	  if (!CreateDirectoryExW (src->Buffer, dst->Buffer, NULL))
+	    {
+	      debug_printf ("CreateDirectoryExW(%S, %S, 0) failed", src, dst);
+	      __seterrno ();
+	      goto done;
+	    }
+	}
+      else if (dHfile->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+	{
+	  /* Recurse into the child directory */
+	  /* avoids endless recursion */
+	  if (src->Length <= origsrclen ||
+	      (!wcsncmp (src->Buffer, dst->Buffer, origdstlen / sizeof (WCHAR)) &&
+	       (!src->Buffer[origdstlen / sizeof (WCHAR)] ||
+		iswdirsep(src->Buffer[origdstlen / sizeof (WCHAR)]))))
+	    {
+	      set_errno (ELOOP);
+	      goto done;
+	    }
+	  if (recursiveCopy (src, dst, origsrclen, origdstlen, dHfile))
+	    goto done;
+	}
+      else
+	{
+	  /* Just copy the file */
+	  if (!CopyFileExW (src->Buffer, dst->Buffer, NULL, NULL, NULL,
+			    COPY_FILE_COPY_SYMLINK))
+	    {
+	      __seterrno ();
+	      goto done;
+	    }
+	}
+    }
+  while (FindNextFileW (dH, dHfile));
+
+  if (GetLastError() != ERROR_NO_MORE_FILES)
+    {
+      __seterrno ();
+      goto done;
+    }
+  res = 0;
+
+done:
+
+  if (dH != INVALID_HANDLE_VALUE)
+    FindClose (dH);
+
+  if (freedHfile)
+    cfree (dHfile);
+
+  return res;
+}
+
 /* Create a symlink from FROMPATH to TOPATH. */
 
 extern "C" int
@@ -2048,6 +2215,84 @@ symlink_wsl (const char *oldpath, path_conv &win32_newpath)
   return 0;
 }
 
+int
+symlink_deepcopy (const char *oldpath, path_conv &win32_newpath)
+{
+  tmp_pathbuf tp;
+  path_conv win32_oldpath;
+
+  resolve_symlink_target (oldpath, win32_newpath, win32_oldpath);
+  if (win32_oldpath.error)
+    {
+      set_errno (win32_oldpath.error);
+      return -1;
+    }
+  if (win32_oldpath.isspecial ())
+    return -2;
+
+  /* MSYS copy file instead make symlink */
+  /* As a MSYS limitation, the source path must exist. */
+  if (!win32_oldpath.exists ())
+    {
+      set_errno (ENOENT);
+      return -1;
+    }
+
+  PUNICODE_STRING w_oldpath = win32_oldpath.get_nt_native_path ();
+  PUNICODE_STRING w_newpath = win32_newpath.get_nt_native_path ();
+  if (w_oldpath->Buffer[1] == L'?')
+    w_oldpath->Buffer[1] = L'\\';
+  if (w_newpath->Buffer[1] == L'?')
+    w_newpath->Buffer[1] = L'\\';
+  if (win32_oldpath.isdir ())
+    {
+      /* we need a larger UNICODE_STRING MaximumLength than
+	 get_nt_native_path allocates for the recursive copy */
+      UNICODE_STRING u_oldpath, u_newpath;
+      RtlCopyUnicodeString (tp.u_get (&u_oldpath), w_oldpath);
+      RtlCopyUnicodeString (tp.u_get (&u_newpath), w_newpath);
+      return recursiveCopy (&u_oldpath, &u_newpath,
+			    u_oldpath.Length, u_newpath.Length);
+    }
+  else
+    {
+      bool isdirlink = false;
+      if (win32_oldpath.issymlink () &&
+	  win32_oldpath.is_known_reparse_point ())
+	{
+	  /* Is there a better way to know this? */
+	  DWORD attr = getfileattr (win32_oldpath.get_win32 (),
+				    !!win32_oldpath.objcaseinsensitive ());
+	  if (attr == INVALID_FILE_ATTRIBUTES)
+	    {
+	      __seterrno ();
+	      return -1;
+	    }
+	  isdirlink = attr & FILE_ATTRIBUTE_DIRECTORY;
+	}
+      if (isdirlink)
+        {
+	  /* CreateDirectoryEx seems to "copy" directory reparse points, which
+	     CopyFileEx can only do with a flag introduced in 19041. */
+	  if (!CreateDirectoryExW (w_oldpath->Buffer, w_newpath->Buffer, NULL))
+	    {
+	      debug_printf ("CreateDirectoryExW(%S, %S, 0) failed", w_oldpath,
+			    w_newpath);
+	      __seterrno ();
+	      return -1;
+	    }
+	}
+      else if (!CopyFileExW (w_oldpath->Buffer, w_newpath->Buffer, NULL, NULL,
+			NULL, COPY_FILE_COPY_SYMLINK))
+	{
+	  __seterrno ();
+	  return -1;
+	}
+    }
+
+  return 0;
+}
+
 int
 symlink_worker (const char *oldpath, path_conv &win32_newpath, bool isdevice)
 {
@@ -2115,6 +2360,13 @@ symlink_worker (const char *oldpath, path_conv &win32_newpath, bool isdevice)
 	case WSYM_nfs:
 	  res = symlink_nfs (oldpath, win32_newpath);
 	  __leave;
+	case WSYM_deepcopy:
+	  res = symlink_deepcopy (oldpath, win32_newpath);
+	  if (!res || res == -1)
+	    __leave;
+	  /* fall back to sysfile symlink type */
+	  wsym_type = WSYM_sysfile;
+	  break;
 	case WSYM_native:
 	case WSYM_nativestrict:
 	  res = symlink_native (oldpath, win32_newpath);
