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 02/11] Add support for creating native symlinks
Date: Tue, 10 Aug 2021 18:02:19 +0100	[thread overview]
Message-ID: <20210810170228.1690-3-jon.turney@dronecode.org.uk> (raw)
In-Reply-To: <20210810170228.1690-1-jon.turney@dronecode.org.uk>

---
 filemanip.cc |  27 ++++++++++++
 filemanip.h  |   1 +
 mklink2.cc   | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+)

diff --git a/filemanip.cc b/filemanip.cc
index 48f5117..ca5e4ac 100644
--- a/filemanip.cc
+++ b/filemanip.cc
@@ -247,6 +247,33 @@ mklongpath (wchar_t *tgt, const char *src, size_t len)
   return 0;
 }
 
+int
+mklongrelpath (wchar_t *tgt, const char *src, size_t len)
+{
+  wchar_t *tp;
+  size_t n;
+  mbstate_t mb;
+
+  tp = tgt;
+  memset (&mb, 0, sizeof mb);
+
+  while (len > 0)
+    {
+      n = mbrtowc (tp, src, 6, &mb);
+      if (n == (size_t) -1 || n == (size_t) -2)
+        return -1;
+      if (n == 0)
+        break;
+      src += n;
+      /* Transform char according to Cygwin rules. */
+      if (*tp < 128)
+        *tp = tfx_chars[*tp];
+      ++tp;
+      --len;
+    }
+  return 0;
+}
+
 /* Replacement functions for Win32 API functions.  The joke here is that the
    replacement functions always use the FILE_OPEN_FOR_BACKUP_INTENT flag. */
 
diff --git a/filemanip.h b/filemanip.h
index 451211f..e83b8e9 100644
--- a/filemanip.h
+++ b/filemanip.h
@@ -34,6 +34,7 @@ size_t get_file_size (const std::string& );
 std::string backslash (const std::string& s);
 const char * trail (const char *, const char *);
 int mklongpath (wchar_t *tgt, const char *src, size_t len);
+int mklongrelpath (wchar_t *tgt, const char *src, size_t len);
 FILE *nt_wfopen (const wchar_t *wpath, const char *mode, mode_t perms);
 FILE *nt_fopen (const char *path, const char *mode, mode_t perms = 0644);
 
diff --git a/mklink2.cc b/mklink2.cc
index 3fe5b15..6e7a002 100644
--- a/mklink2.cc
+++ b/mklink2.cc
@@ -196,6 +196,113 @@ mkwslsymlink (const char *from, const char *to)
   return NT_SUCCESS (status) ? 0 : 1;
 }
 
+static int
+mknativesymlink (const char *from, const char *to)
+{
+  /* Construct the absolute Windows path of 'to' ... */
+  std::string absto;
+  if (to[0] == '/')
+    {
+      absto = get_root_dir();
+      absto.append(to);
+    }
+  else
+    {
+      /* 'from' is already absolute */
+      absto.append(from);
+      /* remove the last pathname component */
+      size_t i = absto.rfind('/');
+      if (i != std::string::npos)
+        absto.resize(i);
+      /* ... and add relative path 'to'. */
+      absto.append("/");
+      absto.append(to);
+    }
+
+  /* ... so we can discover if it's a file or directory (if it already exists) */
+  size_t abstlen = strlen (absto.c_str()) + 7;
+  wchar_t wabsto[abstlen];
+  mklongpath (wabsto, absto.c_str(), abstlen);
+  wabsto[1] = '?';
+
+  bool isdir = FALSE;
+  bool isdir_known = FALSE;
+  HANDLE fh;
+  NTSTATUS status;
+  UNICODE_STRING uto;
+  OBJECT_ATTRIBUTES attr;
+  IO_STATUS_BLOCK io;
+  RtlInitUnicodeString (&uto, wabsto);
+  InitializeObjectAttributes (&attr, &uto, OBJ_CASE_INSENSITIVE, NULL, NULL);
+  status = NtOpenFile (&fh, FILE_READ_ATTRIBUTES, &attr, &io, FILE_SHARE_VALID_FLAGS,
+                       FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT);
+  if (NT_SUCCESS (status))
+    {
+      FILE_BASIC_INFORMATION fi;
+      status = NtQueryInformationFile(fh, &io, &fi, sizeof(fi), FileBasicInformation);
+      if (!NT_SUCCESS (status))
+        Log (LOG_BABBLE) << "Querying " << absto << " failed " << std::hex << status << endLog;
+      else
+        {
+          isdir = fi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+          isdir_known = TRUE;
+          Log (LOG_BABBLE) << "Querying " << absto << " isdir is " << isdir << endLog;
+        }
+      NtClose(fh);
+    }
+  else
+    {
+      Log (LOG_BABBLE) << "Opening " << absto << " failed " << std::hex << status << endLog;
+    }
+
+  /*
+    Fail, if we failed to determine if the symlink target is a directory
+    (probably because it doesn't exist (yet))
+
+    (We could guess that it's a file, since that works for Cygwin (and WSL),
+    which don't care if the directory flag in the symlink is wrong (when the
+    target comes into existence), but native tools will fail.
+  */
+
+  if (!isdir_known)
+    return 1;
+
+  /* Try to create the native symlink. */
+  const size_t flen = strlen (from) + 7;
+  WCHAR wfrom[flen];
+  mklongpath (wfrom, from, flen);
+  wfrom[1] = '?';
+
+  size_t tlen = strlen (to) + 7;
+  wchar_t wto[tlen];
+  if (to[0] == '/')
+    {
+      absto = get_root_dir();
+      absto.append(to);
+      mklongpath (wto, to, tlen);
+      wto[1] = '?';
+    }
+  else
+    {
+      mklongrelpath (wto, to, tlen);
+    }
+
+  DWORD flags = isdir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
+  /* Windows 10 1703 and later allow unprivileged symlink creation when
+     'Developer Mode' is on.*/
+  VersionInfo v = GetVer();
+  if ((v.major() > 10) ||
+      ((v.major() == 10) && (v.buildNumber() >= 15063)))
+    flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+
+  status = CreateSymbolicLinkW (wfrom, wto, flags);
+
+  if (!status)
+    Log (LOG_PLAIN) << "Linking " << from << " to " << to << " failed " << std::hex << GetLastError() << endLog;
+
+  return !status;
+}
+
 int
 mkcygsymlink (const char *from, const char *to)
 {
@@ -205,6 +312,12 @@ mkcygsymlink (const char *from, const char *to)
         return 0;
     }
 
+  if (symlinkType == SymlinkTypeNative)
+    {
+      if (!mknativesymlink (from, to))
+        return 0;
+    }
+
   /* fall back to magic symlink, if selected method fails */
   return mkmagiccygsymlink(from, to);
 }
-- 
2.32.0


  parent reply	other threads:[~2021-08-10 17:04 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
2021-08-10 17:02 ` [PATCH setup 01/11] Add support for creating WSL symlinks Jon Turney
2021-08-10 17:02 ` Jon Turney [this message]
2021-08-10 17:02 ` [PATCH setup 03/11] Factor out the iteration over archive files to install Jon Turney
2021-08-10 17:02 ` [PATCH setup 04/11] Add seek() method to archive and compress file classes Jon Turney
2021-08-10 17:02 ` [PATCH setup 05/11] Add separate symlink-creation phase when extracting archive Jon Turney
2021-08-10 17:02 ` [PATCH setup 06/11] Enable SeCreateSymbolicLink privilege Jon Turney
2021-08-11  8:46   ` Corinna Vinschen
2021-09-14 11:53     ` Jon Turney
2021-08-10 17:02 ` [PATCH setup 07/11] Add symlink capabilities to user-agent telemetry Jon Turney
2021-08-10 17:02 ` [PATCH setup 08/11] Factor out StringChoiceOption Jon Turney
2021-08-10 17:02 ` [PATCH setup 09/11] Add a command line option to choose symlink type used Jon Turney
2021-08-10 17:02 ` [PATCH setup 10/11] Propagate --symlink-type setting to post-install scripts Jon Turney
2021-08-10 17:02 ` [PATCH setup 11/11] Default symlink mode from CYGWIN env var Jon Turney
2021-09-14 11:53 ` [PATCH setup 00/11] Add options to choose symlink type (v2) 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=20210810170228.1690-3-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).