public inbox for cygwin-apps@cygwin.com
 help / color / mirror / Atom feed
* [PATCH setup 0/2] Add option to choose symlink type
@ 2021-05-23 18:27 Jon Turney
  2021-05-23 18:27 ` [PATCH setup 1/2] Add support for creating WSL symlinks Jon Turney
  2021-05-23 18:27 ` [PATCH setup 2/2] Add a command line option to choose symlink type used Jon Turney
  0 siblings, 2 replies; 3+ messages in thread
From: Jon Turney @ 2021-05-23 18:27 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

This is an initial attempt at adding an option to setup to choose the
symlink type used. The default behaviour is left as creating magic-type
symlinks, as setup currently does.

The idea being that since Cygwin (since 3.1.5) creates WSL symlinks, if
possible, and there's presumably some value over magic symlinks in doing
that, setup should be consistent.

This could be extended to shortcut and native symlinks (which are the kind
which would actually be useful, if they didn't have the all the problems
they do).

This doesn't propagate the chosen setting into post-install scripts using
'CYGWIN=winsymlinks:<something>', because equivalent values of <something>
don't exist (currently), so you still don't get consistent behaviour.

Ideally, we'd have the default option be 'do the same thing as Cygwin does'
(but that's quite complex [as Cygwin embeds some knowledge about what's best
for some filesystem types] and is possibly a moving target), but 'try to
make WSL symlink and fallback to magic if that fails' is probably a better
default choice than insisting on one type.

(Orthogonally, there's still the (long-standing) issue that setup only
considers the mount table, and completely ignores Cygwin symlinks when
creating files (e.g. if you have /usr/share symlinked to
/cygdrive/d/somewhere/, and install a package with some files under
/usr/share, it's just going to ignore that and try to create files using the
Windows path it thinks is right.)

Jon Turney (2):
  Add support for creating WSL symlinks
  Add a command line option to choose symlink type used

 inilintmain.cc |   7 +++
 main.cc        |  29 +++++++++++
 mklink2.cc     | 139 +++++++++++++++++++++++++++++++++++++++++++++++--
 mklink2.h      |  10 ++++
 4 files changed, 182 insertions(+), 3 deletions(-)

-- 
2.31.1


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH setup 1/2] Add support for creating WSL symlinks
  2021-05-23 18:27 [PATCH setup 0/2] Add option to choose symlink type Jon Turney
@ 2021-05-23 18:27 ` Jon Turney
  2021-05-23 18:27 ` [PATCH setup 2/2] Add a command line option to choose symlink type used Jon Turney
  1 sibling, 0 replies; 3+ messages in thread
From: Jon Turney @ 2021-05-23 18:27 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH setup 2/2] Add a command line option to choose symlink type used
  2021-05-23 18:27 [PATCH setup 0/2] Add option to choose symlink type Jon Turney
  2021-05-23 18:27 ` [PATCH setup 1/2] Add support for creating WSL symlinks Jon Turney
@ 2021-05-23 18:27 ` Jon Turney
  1 sibling, 0 replies; 3+ messages in thread
From: Jon Turney @ 2021-05-23 18:27 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

The default is 'magic', the historical behaviour of setup.
---
 main.cc | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/main.cc b/main.cc
index b3854a8..d4ec0d2 100644
--- a/main.cc
+++ b/main.cc
@@ -63,6 +63,7 @@
 #include "getopt++/GetOption.h"
 #include "getopt++/BoolOption.h"
 #include "getopt++/StringOption.h"
+#include "mklink2.h"
 
 #include "Exception.h"
 #include <stdexcept>
@@ -95,6 +96,8 @@ static BoolOption HelpOption (false, 'h', "help", "Print help");
 static BoolOption VersionOption (false, 'V', "version", "Show version");
 static StringOption SetupBaseNameOpt ("setup", 'i', "ini-basename", "Use a different basename, e.g. \"foo\", instead of \"setup\"", false);
 BoolOption UnsupportedOption (false, '\0', "allow-unsupported-windows", "Allow old, unsupported Windows versions");
+static StringOption SymlinkTypeOption ("magic", '\0', "symlink-type", "Symlink type (magic, shortcut, native, wsl)", false);
+
 std::string SetupBaseName;
 
 static void inline
@@ -258,6 +261,32 @@ WinMain (HINSTANCE h,
 	exit (1);
       }
 
+    if (((std::string)SymlinkTypeOption).size())
+      {
+        if (((std::string)SymlinkTypeOption).compare("wsl") == 0)
+          {
+            VersionInfo v = GetVer();
+            if ((v.major() < 10) ||
+                ((v.major() == 10) && (v.buildNumber() < 14393)))
+              {
+                fprintf (stderr, "*** --symlink-type wsl requires Windows 10 1607 or later\n");
+                exit(1);
+              }
+            else
+              symlinkType = SymlinkTypeWsl;
+          }
+        else if (((std::string)SymlinkTypeOption).compare("magic") == 0)
+          symlinkType = SymlinkTypeMagic;
+        else
+          {
+            char buff[80 + ((std::string)SymlinkTypeOption).size()];
+            sprintf (buff, "Invalid choice for --symlink-type option: \"%s\"",
+                     ((std::string)SymlinkTypeOption).c_str());
+            fprintf (stderr, "*** %s\n", buff);
+            exit(1);
+          }
+      }
+
     unattended_mode = PackageManagerOption ? chooseronly
 			: (UnattendedOption ? unattended : attended);
 
-- 
2.31.1


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-05-23 18:28 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-23 18:27 [PATCH setup 0/2] Add option to choose symlink type Jon Turney
2021-05-23 18:27 ` [PATCH setup 1/2] Add support for creating WSL symlinks Jon Turney
2021-05-23 18:27 ` [PATCH setup 2/2] Add a command line option to choose symlink type used Jon Turney

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