From 96c67015dda33dc6e8e7c419c2d7b97c409059f1 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 07/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>
---
 winsup/cygwin/environ.cc |   4 ++
 winsup/cygwin/globals.cc |   5 +-
 winsup/cygwin/path.cc    | 151 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 158 insertions(+), 2 deletions(-)

diff --git a/winsup/cygwin/environ.cc b/winsup/cygwin/environ.cc
index 87650cc..0e60f30 100644
--- a/winsup/cygwin/environ.cc
+++ b/winsup/cygwin/environ.cc
@@ -86,6 +86,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 942bd1c..019af4f 100644
--- a/winsup/cygwin/globals.cc
+++ b/winsup/cygwin/globals.cc
@@ -55,7 +55,8 @@ enum winsym_t
   WSYM_lnk,
   WSYM_native,
   WSYM_nativestrict,
-  WSYM_nfs
+  WSYM_nfs,
+  WSYM_deepcopy
 };
 
 exit_states NO_COPY exit_state;
@@ -69,7 +70,7 @@ bool ignore_case_with_glob;
 bool pipe_byte;
 bool reset_com;
 bool wincmdln;
-winsym_t allow_winsymlinks = WSYM_sysfile;
+winsym_t allow_winsymlinks = WSYM_deepcopy;
 bool disable_pcon;
 
 bool NO_COPY in_forkee;
diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc
index 4db7b24..70c9793 100644
--- a/winsup/cygwin/path.cc
+++ b/winsup/cygwin/path.cc
@@ -1663,6 +1663,86 @@ conv_path_list (const char *src, char *dst, size_t size,
 
 /********************** Symbolic Link Support **************************/
 
+/*
+  Create a deep copy of src as dst, while avoiding descending in origpath.
+*/
+static int
+recursiveCopy (char * src, char * dst, const char * origpath)
+{
+  WIN32_FIND_DATA dHfile;
+  HANDLE dH;
+  BOOL findfiles;
+  int srcpos = strlen (src);
+  int dstpos = strlen (dst);
+  int res = -1;
+
+  debug_printf("recursiveCopy (%s, %s)", src, dst);
+
+  /* Create the destination directory */
+  if (!CreateDirectoryEx (src, dst, NULL))
+    {
+      debug_printf("CreateDirectoryEx(%s, %s, 0) failed", src, dst);
+      __seterrno ();
+      goto done;
+    }
+  /* Descend into the source directory */
+  if (srcpos + 2 >= MAX_PATH || dstpos + 1 >= MAX_PATH)
+    {
+      set_errno (ENAMETOOLONG);
+      goto done;
+    }
+  strcat (src, "\\*");
+  strcat (dst, "\\");
+  dH = FindFirstFile (src, &dHfile);
+  debug_printf("dHfile(1): %s", dHfile.cFileName);
+  findfiles = FindNextFile (dH, &dHfile);
+  debug_printf("dHfile(2): %s", dHfile.cFileName);
+  findfiles = FindNextFile (dH, &dHfile);
+  while (findfiles)
+    {
+      /* Append the directory item filename to both source and destination */
+      int filelen = strlen (dHfile.cFileName);
+      debug_printf("dHfile(3): %s", dHfile.cFileName);
+      if (srcpos + 1 + filelen >= MAX_PATH ||
+          dstpos + 1 + filelen >= MAX_PATH)
+        {
+          set_errno (ENAMETOOLONG);
+          goto done;
+        }
+      strcpy (&src[srcpos+1], dHfile.cFileName);
+      strcpy (&dst[dstpos+1], dHfile.cFileName);
+      debug_printf("%s -> %s", src, dst);
+      if (dHfile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+        {
+          /* Recurse into the child directory */
+          debug_printf("%s <-> %s", src, origpath);
+          if (strcmp (src, origpath)) // avoids endless recursion
+            if (recursiveCopy (src, dst, origpath))
+              goto done;
+        }
+      else
+        {
+          /* Just copy the file */
+          if (!CopyFile (src, dst, FALSE))
+            {
+              __seterrno ();
+              goto done;
+            }
+        }
+      findfiles = FindNextFile (dH, &dHfile);
+    }
+  if (GetLastError() != ERROR_NO_MORE_FILES)
+    {
+      __seterrno ();
+      goto done;
+    }
+  res = 0;
+
+done:
+
+  return res;
+}
+
 /* Create a symlink from FROMPATH to TOPATH. */
 
 extern "C" int
@@ -2195,6 +2275,77 @@ symlink_worker (const char *oldpath, path_conv &win32_newpath, bool isdevice)
 	}
       else
 	{
+          if (wsym_type == WSYM_deepcopy)
+	    {
+	      path_conv src_path;
+	      src_path.check (oldpath, PC_SYM_NOFOLLOW, stat_suffixes);
+	      if (src_path.error)
+		{
+		  set_errno (src_path.error);
+		  __leave;
+		}
+	      if (!src_path.isdevice () && !src_path.is_fs_special ())
+	        {
+		  /* MSYS copy file instead make symlink */
+
+		  char * real_oldpath;
+		  if (isabspath (oldpath))
+		    strcpy (real_oldpath = tp.c_get (), oldpath);
+		  else
+		    /* Find the real source path, relative
+		       to the directory of the destination */
+		    {
+		      /* Determine the character position of the last path component */
+		      const char *newpath = win32_newpath.get_posix();
+		      int pos = strlen (newpath);
+		      while (--pos >= 0)
+			if (isdirsep (newpath[pos]))
+			  break;
+		      /* Append the source path to the directory
+			 component of the destination */
+		      if (pos+1+strlen(oldpath) >= MAX_PATH)
+			{
+			  set_errno(ENAMETOOLONG);
+			  __leave;
+			}
+		      strcpy (real_oldpath = tp.c_get (), newpath);
+		      strcpy (&real_oldpath[pos+1], oldpath);
+		    }
+
+		  /* As a MSYS limitation, the source path must exist. */
+		  path_conv win32_oldpath;
+		  win32_oldpath.check (real_oldpath, PC_SYM_NOFOLLOW, stat_suffixes);
+		  if (!win32_oldpath.exists ())
+		    {
+		      set_errno (ENOENT);
+		      __leave;
+		    }
+
+		  char *w_newpath;
+		  char *w_oldpath;
+		  stpcpy (w_newpath = tp.c_get (), win32_newpath.get_win32());
+		  stpcpy (w_oldpath = tp.c_get (), win32_oldpath.get_win32());
+		  if (win32_oldpath.isdir())
+		    {
+		      char *origpath;
+		      strcpy (origpath = tp.c_get (), w_oldpath);
+		      res = recursiveCopy (w_oldpath, w_newpath, origpath);
+		    }
+		  else
+		    {
+		      if (!CopyFile (w_oldpath, w_newpath, FALSE))
+			{
+			  __seterrno ();
+			}
+		      else
+			{
+			  res = 0;
+			}
+		    }
+		  __leave;
+		}
+	    }
+
 	  /* Default technique creating a symlink. */
 	  buf = tp.t_get ();
 	  cp = stpcpy (buf, SYMLINK_COOKIE);
-- 
2.31.1

