[PATCH setup 1/2] Add support for creating WSL symlinks

Jon Turney jon.turney@dronecode.org.uk
Sun May 23 18:27:36 GMT 2021


---
 inilintmain.cc |   7 +++
 mklink2.cc     | 139 +++++++++++++++++++++++++++++++++++++++++++++++--
 mklink2.h      |  10 ++++
 3 files changed, 153 insertions(+), 3 deletions(-)

diff --git a/inilintmain.cc b/inilintmain.cc
index f31e5eb..886c152 100644
--- a/inilintmain.cc
+++ b/inilintmain.cc
@@ -56,3 +56,10 @@ main (int argc, char **argv)
 
   return 0;
 }
+
+const std::string &
+get_root_dir ()
+{
+  static std::string empty;
+  return empty;
+}
diff --git a/mklink2.cc b/mklink2.cc
index 0b73403..3bff4b3 100644
--- a/mklink2.cc
+++ b/mklink2.cc
@@ -5,6 +5,11 @@
 #include "shlobj.h"
 #include "mklink2.h"
 #include "filemanip.h"
+#include "winioctl.h"
+#include "LogSingleton.h"
+#include "mount.h"
+
+SymlinkTypeEnum symlinkType = SymlinkTypeMagic; // default to historical behaviour
 
 /* This part of the code must be in C because the C++ interface to COM
 doesn't work. */
@@ -38,9 +43,8 @@ make_link_2 (char const *exepath, char const *args, char const *icon, char const
 
 #define SYMLINK_COOKIE "!<symlink>"
 
-extern "C"
-int
-mkcygsymlink (const char *from, const char *to)
+static int
+mkmagiccygsymlink (const char *from, const char *to)
 {
   char buf[strlen (SYMLINK_COOKIE) + 4096];
   unsigned long w;
@@ -72,6 +76,135 @@ mkcygsymlink (const char *from, const char *to)
   return 1;
 }
 
+#ifndef IO_REPARSE_TAG_LX_SYMLINK
+#define IO_REPARSE_TAG_LX_SYMLINK (0xa000001d)
+#endif
+
+typedef struct _REPARSE_LX_SYMLINK_BUFFER
+{
+  DWORD ReparseTag;
+  WORD  ReparseDataLength;
+  WORD  Reserved;
+  struct {
+    DWORD FileType;     /* Value is apparently always 2 for symlinks. */
+    char  PathBuffer[1];/* UTF-8 encoded POSIX path
+                           Isn't \0 terminated.
+                           Length is ReparseDataLength - sizeof (FileType).
+                        */
+  } LxSymlinkReparseBuffer;
+} REPARSE_LX_SYMLINK_BUFFER,*PREPARSE_LX_SYMLINK_BUFFER;
+
+static int
+mkwslsymlink (const char *from, const char *to)
+{
+  /* Construct the reparse path */
+  std::string lxsymto;
+  if (to[0] == '/')
+    {
+      /* If 'to' is absolute and starts with '/cygdrive' or /proc/cygdrive',
+         this is a problem because: (i) the cygdrive prefix might be different,
+         and (ii) the target drive might not exist, on the install system.
+
+         Because of these problems, we don't expect any install packages to have
+         links like that (they should instead be created by post-install
+         scripts), but fail if they do.
+      */
+      if ((strncmp(to, "/cygdrive", 9) == 0) ||
+          (strncmp(to, "/proc/cygdrive", 14) == 0))
+        {
+          Log (LOG_PLAIN) << "Refusing to create WSL symlink to" << to << " as it starts with /cygdrive" << endLog;
+          return 1;
+        }
+
+      /* Otherwise, we convert the absolute path 'to' into a form a WSL
+         compatible form, constructed from the '/mnt' prefix and the cygwin root
+         directory e.g. /mnt/c/cygwin64/ */
+      lxsymto = "/mnt/";
+      std::string root = get_root_dir();
+      if (root[1] == ':')
+        {
+          lxsymto.append(1, tolower(root.c_str()[0]));
+          lxsymto.append("/");
+          lxsymto.append(&(root[3]));
+        }
+      else
+        {
+          // root dir is UNC path ???
+          lxsymto.append(root.c_str());
+        }
+      lxsymto.append(to);
+    }
+  else
+    {
+      /* Otherwise 'to' is relative to 'from', so leave it alone */
+      lxsymto = to;
+    }
+
+  /* Create reparse point. */
+  SECURITY_DESCRIPTOR sd;
+  acl_t acl;
+  nt_sec.GetPosixPerms (from, NULL, NULL, 0644, sd, acl);
+
+  const size_t flen = strlen (from) + 7;
+  WCHAR wfrom[flen];
+  mklongpath (wfrom, from, flen);
+  wfrom[1] = '?';
+
+  HANDLE fh;
+  UNICODE_STRING ufrom;
+  IO_STATUS_BLOCK io;
+  OBJECT_ATTRIBUTES attr;
+  RtlInitUnicodeString (&ufrom, wfrom);
+  InitializeObjectAttributes (&attr, &ufrom, OBJ_CASE_INSENSITIVE, NULL, &sd);
+  NTSTATUS status = NtCreateFile (&fh,
+                         DELETE | FILE_GENERIC_WRITE | READ_CONTROL | WRITE_DAC,
+                         &attr,
+                         &io,
+                         NULL,
+                         FILE_ATTRIBUTE_NORMAL,
+                         FILE_SHARE_VALID_FLAGS,
+                         FILE_CREATE,
+                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE
+                         | FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT,
+                         NULL, 0);
+  if (!NT_SUCCESS (status))
+    {
+      Log (LOG_PLAIN) << "NtCreateFile status " << std::hex << status << endLog;
+      return 1;
+    }
+
+  /* Set content of the reparse point */
+  size_t tlen = lxsymto.length();
+  REPARSE_LX_SYMLINK_BUFFER *rpl = (REPARSE_LX_SYMLINK_BUFFER *) new char[sizeof(REPARSE_LX_SYMLINK_BUFFER) + tlen];
+  rpl->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK;
+  rpl->ReparseDataLength = sizeof (DWORD) + tlen;
+  rpl->Reserved = 0;
+  rpl->LxSymlinkReparseBuffer.FileType = 2;
+  memcpy(rpl->LxSymlinkReparseBuffer.PathBuffer, lxsymto.c_str(), tlen);
+
+  status = NtFsControlFile (fh, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT,
+                            (LPVOID) rpl,
+                            REPARSE_DATA_BUFFER_HEADER_SIZE + rpl->ReparseDataLength,
+                            NULL, 0);
+  if (!NT_SUCCESS (status))
+    {
+      Log (LOG_PLAIN) << "FSCTL_SET_REPARSE_POINT status " << std::hex << status << endLog;
+    }
+
+  delete rpl;
+  NtClose(fh);
+  return NT_SUCCESS (status) ? 0 : 1;
+}
+
+int
+mkcygsymlink (const char *from, const char *to)
+{
+  if (symlinkType == SymlinkTypeWsl)
+    return mkwslsymlink (from, to);
+
+  return mkmagiccygsymlink(from, to);
+}
+
 static struct {
   FILE_LINK_INFORMATION fli;
   WCHAR namebuf[32768];
diff --git a/mklink2.h b/mklink2.h
index 0244748..fda17f6 100644
--- a/mklink2.h
+++ b/mklink2.h
@@ -17,4 +17,14 @@ extern "C"
 };
 #endif
 
+typedef enum
+{
+  SymlinkTypeMagic,
+  SymlinkTypeShortcut,
+  SymlinkTypeNative,
+  SymlinkTypeWsl,
+} SymlinkTypeEnum;
+
+extern SymlinkTypeEnum symlinkType;
+
 #endif /* SETUP_MKLINK2_H */
-- 
2.31.1



More information about the Cygwin-apps mailing list