public inbox for cygwin-apps@cygwin.com
 help / color / mirror / Atom feed
From: Jon Turney <jon.turney@dronecode.org.uk>
To: cygwin-apps@cygwin.com
Cc: Jon Turney <jon.turney@dronecode.org.uk>
Subject: [PATCH setup 1/2] Add support for creating WSL symlinks
Date: Sun, 23 May 2021 19:27:36 +0100	[thread overview]
Message-ID: <20210523182738.14292-2-jon.turney@dronecode.org.uk> (raw)
In-Reply-To: <20210523182738.14292-1-jon.turney@dronecode.org.uk>

---
 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


  reply	other threads:[~2021-05-23 18:28 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-05-23 18:27 [PATCH setup 0/2] Add option to choose symlink type Jon Turney
2021-05-23 18:27 ` Jon Turney [this message]
2021-05-23 18:27 ` [PATCH setup 2/2] Add a command line option to choose symlink type used Jon Turney

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210523182738.14292-2-jon.turney@dronecode.org.uk \
    --to=jon.turney@dronecode.org.uk \
    --cc=cygwin-apps@cygwin.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).