public inbox for cygwin-apps@cygwin.com
 help / color / mirror / Atom feed
* [PATCH setup 00/11] Add options to choose symlink type (v2)
@ 2021-08-10 17:02 Jon Turney
  2021-08-10 17:02 ` [PATCH setup 01/11] Add support for creating WSL symlinks Jon Turney
                   ` (11 more replies)
  0 siblings, 12 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Revised from [1], this adds an '--symlink-type {sys, native, wsl}' option,
which controls the type of symlinks created by setup and the post-install
scripts it invokes [2].

If creating the symlink using the selected method fails, setup falls back to
using a sys symlink.

'--symlink-type lnk' is not (yet) implemented, because I'm lazy.

If the CYGWIN env var contains a 'winsymlinks' setting, the default is
controlled by that, otherwise, the default behaviour is '--syslink-type
sys', which matches setup's current behaviour (and Cygwin prior to 3.1.5).

[1] https://cygwin.com/pipermail/cygwin-apps/2021-May/041327.html
[2] requires as yet unreleased Cygwin for 'winsymlinks:sys' support for the
'--symlink-type sys' case.

Jon Turney (11):
  Add support for creating WSL symlinks
  Add support for creating native symlinks
  Factor out the iteration over archive files to install
  Add seek() method to archive and compress file classes
  Add separate symlink-creation phase when extracting archive
  Enable SeCreateSymbolicLink privilege
  Add symlink capabilities to user-agent telemetry
  Factor out StringChoiceOption
  Add a command line option to choose symlink type used
  Propagate --symlink-type setting to post-install scripts
  Default symlink mode from CYGWIN env var

 archive.cc                                    |  52 ----
 archive.h                                     |  14 +-
 archive_tar.cc                                |  14 +-
 compress_bz.cc                                |  12 +
 compress_bz.h                                 |   1 +
 compress_gz.cc                                |  65 +++--
 compress_gz.h                                 |   3 +-
 compress_xz.cc                                |  34 ++-
 compress_xz.h                                 |   3 +-
 compress_zstd.cc                              |  19 +-
 compress_zstd.h                               |   3 +-
 filemanip.cc                                  |  27 ++
 filemanip.h                                   |   1 +
 inilintmain.cc                                |   7 +
 install.cc                                    |  66 +++--
 io_stream_cygfile.cc                          |  52 +---
 libgetopt++/Makefile.am                       |   3 +-
 .../include/getopt++/StringChoiceOption.h     |  41 +++
 libgetopt++/src/StringChoiceOption.cc         |  54 ++++
 main.cc                                       |  84 +++++-
 mklink2.cc                                    | 256 +++++++++++++++++-
 mklink2.h                                     |  10 +
 nio-ie5.cc                                    |  12 +-
 script.cc                                     |  36 +++
 win32.cc                                      |  76 ++++++
 win32.h                                       |   3 +
 26 files changed, 775 insertions(+), 173 deletions(-)
 create mode 100644 libgetopt++/include/getopt++/StringChoiceOption.h
 create mode 100644 libgetopt++/src/StringChoiceOption.cc

-- 
2.32.0


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

* [PATCH setup 01/11] Add support for creating WSL symlinks
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 02/11] Add support for creating native symlinks Jon Turney
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 inilintmain.cc |   7 +++
 mklink2.cc     | 143 +++++++++++++++++++++++++++++++++++++++++++++++--
 mklink2.h      |  10 ++++
 3 files changed, 157 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..3fe5b15 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,139 @@ 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)
+    {
+      if (!mkwslsymlink (from, to))
+        return 0;
+    }
+
+  /* fall back to magic symlink, if selected method fails */
+  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.32.0


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

* [PATCH setup 02/11] Add support for creating native symlinks
  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
  2021-08-10 17:02 ` [PATCH setup 03/11] Factor out the iteration over archive files to install Jon Turney
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

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


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

* [PATCH setup 03/11] Factor out the iteration over archive files to install
  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 ` [PATCH setup 02/11] Add support for creating native symlinks Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 04/11] Add seek() method to archive and compress file classes Jon Turney
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 install.cc | 53 +++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 37 insertions(+), 16 deletions(-)

diff --git a/install.cc b/install.cc
index 51ec4b5..cec31a9 100644
--- a/install.cc
+++ b/install.cc
@@ -93,7 +93,13 @@ class Installer
   private:
     bool extract_replace_on_reboot(archive *, const std::string&,
                                    const std::string&, std::string);
-
+    bool _installOne (packagemeta &pkgm,
+                      const std::string& prefixURL,
+                      const std::string& prefixPath,
+                      HWND owner,
+                      io_stream *pkgfile,
+                      archive *tarstream,
+                      io_stream *lst);
 };
 
 Installer::Installer() : errors(0)
@@ -529,13 +535,39 @@ Installer::installOne (packagemeta &pkgm, const packageversion &ver,
         }
     }
 
+  package_bytes = source.size;
+  Log (LOG_PLAIN) << "Extracting from " << source.Cached () << endLog;
+
+  bool error_in_this_package = _installOne(pkgm, prefixURL, prefixPath, owner,
+                                           pkgfile, tarstream, lst);
+
+  if (lst)
+    delete lst;
+  delete tarstream;
+
+  total_bytes_sofar += package_bytes;
+  progress (0);
+
+  int df = diskfull (get_root_dir ().c_str ());
+  Progress.SetBar3 (df);
+
+  if (ver.Type () == package_binary && !error_in_this_package)
+    pkgm.installed = ver;
+}
+
+bool
+Installer::_installOne (packagemeta &pkgm,
+                        const std::string& prefixURL,
+                        const std::string& prefixPath,
+                        HWND owner,
+                        io_stream *pkgfile,
+                        archive *tarstream,
+                        io_stream *lst)
+{
   bool error_in_this_package = false;
   bool ignoreInUseErrors = false;
   bool ignoreExtractErrors = unattended_mode;
 
-  package_bytes = source.size;
-  Log (LOG_PLAIN) << "Extracting from " << source.Cached () << endLog;
-
   std::string fn;
   while ((fn = tarstream->next_file_name ()).size ())
     {
@@ -725,18 +757,7 @@ Installer::installOne (packagemeta &pkgm, const packageversion &ver,
       num_installs++;
     }
 
-  if (lst)
-    delete lst;
-  delete tarstream;
-
-  total_bytes_sofar += package_bytes;
-  progress (0);
-
-  int df = diskfull (get_root_dir ().c_str ());
-  Progress.SetBar3 (df);
-
-  if (ver.Type () == package_binary && !error_in_this_package)
-    pkgm.installed = ver;
+  return error_in_this_package;
 }
 
 static void
-- 
2.32.0


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

* [PATCH setup 04/11] Add seek() method to archive and compress file classes
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (2 preceding siblings ...)
  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 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 05/11] Add separate symlink-creation phase when extracting archive Jon Turney
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Add seek() method to archive and compressed file isostream classes
(which can only rewind to the start).

Also clean up some cruft in archive class.

This still needs testing on a .gz archive (slightly involved as there
aren't any currently!)
---
 archive.cc       | 52 --------------------------------------
 archive.h        | 14 +++--------
 archive_tar.cc   | 14 +++++++----
 compress_bz.cc   | 12 +++++++++
 compress_bz.h    |  1 +
 compress_gz.cc   | 65 +++++++++++++++++++++++++++++-------------------
 compress_gz.h    |  3 ++-
 compress_xz.cc   | 34 ++++++++++++++++++-------
 compress_xz.h    |  3 ++-
 compress_zstd.cc | 19 +++++++++++---
 compress_zstd.h  |  3 ++-
 11 files changed, 111 insertions(+), 109 deletions(-)

diff --git a/archive.cc b/archive.cc
index 1ceb355..448ea0e 100644
--- a/archive.cc
+++ b/archive.cc
@@ -45,7 +45,6 @@
  * offset 257     string  ustar\040\040\0
  */
 
-
 #define longest_magic 265
 
 archive *
@@ -65,15 +64,6 @@ archive::extract (io_stream * original)
 	    return rv;
 	  return NULL;
 	}
-#if 0
-      else if (memcmp (magic, "BZh", 3) == 0)
-	{
-	  archive_bz *rv = new archive_bz (original);
-	  if (!rv->error ())
-	    return rv;
-	  return NULL;
-	}
-#endif
     }
   return NULL;
 }
@@ -194,45 +184,3 @@ out:
 }
 
 archive::~archive () {};
-
-#if 0
-ssize_t archive::read (void *buffer, size_t len)
-{
-  Log (LOG_TIMESTAMP, "archive::read called");
-  return 0;
-}
-
-ssize_t archive::write (void *buffer, size_t len)
-{
-  Log (LOG_TIMESTAMP, "archive::write called");
-  return 0;
-}
-
-ssize_t archive::peek (void *buffer, size_t len)
-{
-  Log (LOG_TIMESTAMP, "archive::peek called");
-  return 0;
-}
-
-long
-archive::tell ()
-{
-  Log (LOG_TIMESTAMP, "bz::tell called");
-  return 0;
-}
-
-int
-archive::error ()
-{
-  Log (LOG_TIMESTAMP, "archive::error called");
-  return 0;
-}
-
-const char *
-archive::next_file_name ()
-{
-  Log (LOG_TIMESTAMP, "archive::next_file_name called");
-  return NULL;
-}
-
-#endif
diff --git a/archive.h b/archive.h
index d3c795a..adab9f0 100644
--- a/archive.h
+++ b/archive.h
@@ -69,14 +69,9 @@ public:
   /* read data - not valid for archives (duh!) 
    * Could be made valid via the read-child-directly model 
    */
-//  virtual ssize_t read(void *buffer, size_t len) {return -1;};
-  /* provide data to (double duh!) */
-//  virtual ssize_t write(void *buffer, size_t len) { return -1;};
-  /* read data without removing it from the class's internal buffer */
-//  virtual ssize_t peek(void *buffer, size_t len);
-//  virtual long tell ();
-  /* try guessing this one */
-//  virtual int error ();
+
+  virtual int seek (long offset, io_stream_seek_t whence) = 0;
+
   /* Find out the next stream name -
    * ie for foo.tar.gz, at offset 0, next_file_name = foo.tar
    * for foobar that is an compress, next_file_name is the next
@@ -88,14 +83,11 @@ public:
   virtual archive_file_t next_file_type () = 0;
   virtual const std::string linktarget () = 0;
   virtual int skip_file () = 0;
-  /* if you are still needing these hints... give up now! */
   virtual ~archive() = 0;
 protected:
   void operator= (const archive &);
   archive () {};
   archive (const archive &);
-private:
-//  archive () {};
 };
 
 #endif /* SETUP_ARCHIVE_H */
diff --git a/archive_tar.cc b/archive_tar.cc
index c359238..5b2a771 100644
--- a/archive_tar.cc
+++ b/archive_tar.cc
@@ -100,11 +100,15 @@ archive_tar::tell ()
 int
 archive_tar::seek (long where, io_stream_seek_t whence)
 {
-  /* seeking in the parent archive doesn't make sense. although we could
-     map to files ? 
-     Also, seeking might make sense for rewing..?? 
-     */
-  return -1; 
+  /* Because the parent stream is compressed, we can only easily support
+     seek()-ing to rewind to the start */
+  if ((whence == IO_SEEK_SET) && (where == 0))
+    {
+      state.header_read = 0;
+      return state.parent->seek(where, whence);
+    }
+
+  return -1;
 }
 
 int
diff --git a/compress_bz.cc b/compress_bz.cc
index 18773d4..e4792d6 100644
--- a/compress_bz.cc
+++ b/compress_bz.cc
@@ -35,7 +35,12 @@ compress_bz::compress_bz (io_stream * parent) : peeklen (0), position (0)
     }
   original = parent;
   owns_original = true;
+  init_state();
+}
 
+void
+compress_bz::init_state(void)
+{
   initialisedOk = 0;
   endReached = 0;
   writing = 0;
@@ -194,6 +199,13 @@ compress_bz::tell ()
 int
 compress_bz::seek (long where, io_stream_seek_t whence)
 {
+  if ((whence == IO_SEEK_SET) && (where == 0))
+    {
+      int result = original->seek(where, whence);
+      init_state();
+      return result;
+    }
+
   throw new std::logic_error ("compress_bz::seek is not implemented");
 }
 
diff --git a/compress_bz.h b/compress_bz.h
index 39a0d5b..a7e865a 100644
--- a/compress_bz.h
+++ b/compress_bz.h
@@ -67,6 +67,7 @@ private:
   char buf[4096];
   int writing;
   size_t position;
+  void init_state(void);
 };
 
 #endif /* SETUP_COMPRESS_BZ_H */
diff --git a/compress_gz.cc b/compress_gz.cc
index 55a015e..e73ccd3 100644
--- a/compress_gz.cc
+++ b/compress_gz.cc
@@ -41,19 +41,23 @@ static int gz_magic[2] = { 0x1f, 0x8b };	/* gzip magic header */
  */
 compress_gz::compress_gz (io_stream * parent)
 {
-  construct (parent, "r");
+  original = parent;
+  owns_original = true;
+  openmode = "r";
+  construct ();
 }
 
-compress_gz::compress_gz (io_stream * parent, const char *openmode)
+compress_gz::compress_gz (io_stream * parent, const char *_openmode)
 {
-  construct (parent, openmode);
+  original = parent;
+  owns_original = true;
+  openmode = _openmode;
+  construct ();
 }
 
 void
-compress_gz::construct (io_stream * parent, const char *openmode)
+compress_gz::construct ()
 {
-  original = parent;
-  owns_original = true;
   peeklen = 0;
   int err;
   int level = Z_DEFAULT_COMPRESSION;	/* compression level */
@@ -76,7 +80,7 @@ compress_gz::construct (io_stream * parent, const char *openmode)
 
   mode = '\0';
 
-  if (!parent)
+  if (!original)
     {
       z_err = Z_STREAM_ERROR;
       return;
@@ -413,6 +417,14 @@ compress_gz::tell ()
 int
 compress_gz::seek (long where, io_stream_seek_t whence)
 {
+  if ((whence == IO_SEEK_SET) && (where == 0))
+    {
+      int result = original->seek(where, whence);
+      destroy();
+      construct();
+      return result;
+    }
+
   throw new std::logic_error("compress_gz::seek is not implemented");
 }
 
@@ -458,7 +470,11 @@ void
 compress_gz::destroy ()
 {
   if (msg)
-    free (msg);
+    {
+      free (msg);
+      msg = NULL;
+    }
+
   if (stream.state != NULL)
     {
       if (mode == 'w')
@@ -472,12 +488,15 @@ compress_gz::destroy ()
     }
 
   if (inbuf)
-
-    free (inbuf);
+    {
+      free (inbuf);
+      inbuf = NULL;
+    }
   if (outbuf)
-    free (outbuf);
-  if (original && owns_original)
-    delete original;
+    {
+      free (outbuf);
+      outbuf = NULL;
+    }
 }
 
 compress_gz::~compress_gz ()
@@ -485,16 +504,15 @@ compress_gz::~compress_gz ()
   if (mode == 'w')
     {
       z_err = do_flush (Z_FINISH);
-      if (z_err != Z_OK)
-	{
-	  destroy ();
-	  return;
-	}
-
-      putLong (crc);
-      putLong (stream.total_in);
+      if (z_err == Z_OK)
+        {
+          putLong (crc);
+          putLong (stream.total_in);
+        }
     }
   destroy ();
+  if (original && owns_original)
+    delete original;
 }
 
 int
@@ -534,11 +552,6 @@ compress_gz::do_flush (int flush)
   return z_err == Z_STREAM_END ? Z_OK : z_err;
 }
 
-
-#if 0
-
-gzclose (lst);
-#endif
 /* ===========================================================================
  *  Read a byte from a gz_stream; update next_in and avail_in. Return EOF
  *  for end of file.
diff --git a/compress_gz.h b/compress_gz.h
index 50b6e66..90073e5 100644
--- a/compress_gz.h
+++ b/compress_gz.h
@@ -60,7 +60,7 @@ private:
   };
   char peekbuf[512];
   size_t peeklen;
-  void construct (io_stream *, const char *);
+  void construct ();
   void check_header ();
   int get_byte ();
   unsigned long getLong ();
@@ -69,6 +69,7 @@ private:
   int do_flush (int);
   io_stream *original;
   bool owns_original;
+  const char *openmode;
   /* from zlib */
   z_stream stream;
   int z_err;			/* error code for last stream operation */
diff --git a/compress_xz.cc b/compress_xz.cc
index a5167d6..bb64595 100644
--- a/compress_xz.cc
+++ b/compress_xz.cc
@@ -51,9 +51,6 @@ compress_xz::compress_xz (io_stream * parent)
   lasterr(0),
   compression_type (COMPRESSION_UNKNOWN)
 {
-  unsigned char * out_block = NULL;
-  unsigned char * in_block = NULL;
-
   /* read only */
   if (!parent || parent->error())
     {
@@ -62,6 +59,16 @@ compress_xz::compress_xz (io_stream * parent)
     }
   original = parent;
 
+  create ();
+  init_decoder ();
+}
+
+void
+compress_xz::create ()
+{
+  unsigned char * out_block = NULL;
+  unsigned char * in_block = NULL;
+
   state = (struct private_data *)calloc(sizeof(*state), 1);
   out_block = (unsigned char *)malloc(out_block_size);
   in_block = (unsigned char *)malloc(in_block_size);
@@ -79,12 +86,10 @@ compress_xz::compress_xz (io_stream * parent)
   state->out_block = out_block;
   state->in_block_size = in_block_size;
   state->in_block = in_block;
-  state->out_p = out_block;
+  state->out_p = state->out_block;
   state->stream.avail_in = 0;
   state->stream.next_out = state->out_block;
   state->stream.avail_out = state->out_block_size;
-
-  init_decoder ();
 }
 
 ssize_t
@@ -267,6 +272,17 @@ compress_xz::tell ()
 int
 compress_xz::seek (long where, io_stream_seek_t whence)
 {
+  if ((whence == IO_SEEK_SET) && (where == 0))
+    {
+      int result = original->seek(where, whence);
+      destroy ();
+      peeklen = 0;
+      lasterr = 0;
+      create ();
+      init_decoder ();
+      return result;
+    }
+
   throw new std::logic_error("compress_xz::seek is not implemented");
 }
 
@@ -334,14 +350,14 @@ compress_xz::destroy ()
 
       compression_type = COMPRESSION_UNKNOWN;
     }
-
-  if (original && owns_original)
-    delete original;
 }
 
 compress_xz::~compress_xz ()
 {
   destroy ();
+
+  if (original && owns_original)
+    delete original;
 }
 
 /* ===========================================================================
diff --git a/compress_xz.h b/compress_xz.h
index 24cbb09..31d499c 100644
--- a/compress_xz.h
+++ b/compress_xz.h
@@ -27,7 +27,7 @@ public:
   virtual ssize_t write (const void *buffer, size_t len); /* not implemented */
   virtual ssize_t peek (void *buffer, size_t len);
   virtual long tell (); /* not implemented */
-  virtual int seek (long where, io_stream_seek_t whence); /* not implemented */
+  virtual int seek (long where, io_stream_seek_t whence);
   virtual int error ();
   virtual const char *next_file_name () { return NULL; };
   virtual int set_mtime (time_t);
@@ -49,6 +49,7 @@ private:
   char peekbuf[512];
   size_t peeklen;
   int lasterr;
+  void create ();
   void destroy ();
 
   struct private_data {
diff --git a/compress_zstd.cc b/compress_zstd.cc
index 3588dbd..bb17785 100644
--- a/compress_zstd.cc
+++ b/compress_zstd.cc
@@ -35,7 +35,12 @@ compress_zstd::compress_zstd (io_stream * parent)
       return;
     }
   original = parent;
+  create();
+}
 
+void
+compress_zstd::create (void)
+{
   state = (struct private_data *)calloc(sizeof(*state), 1);
   if (state == NULL)
     {
@@ -179,6 +184,14 @@ compress_zstd::tell ()
 int
 compress_zstd::seek (long where, io_stream_seek_t whence)
 {
+  if ((whence == IO_SEEK_SET) && (where == 0))
+    {
+      int result = original->seek(where, whence);
+      destroy();
+      create();
+      return result;
+    }
+
   throw new std::logic_error("compress_zstd::seek is not implemented");
 }
 
@@ -240,14 +253,14 @@ compress_zstd::destroy ()
       free(state);
       state = NULL;
     }
-
-  if (original && owns_original)
-    delete original;
 }
 
 compress_zstd::~compress_zstd ()
 {
   destroy ();
+
+  if (original && owns_original)
+    delete original;
 }
 
 bool
diff --git a/compress_zstd.h b/compress_zstd.h
index be5712c..3303daa 100644
--- a/compress_zstd.h
+++ b/compress_zstd.h
@@ -25,7 +25,7 @@ public:
   virtual ssize_t write (const void *buffer, size_t len); /* not implemented */
   virtual ssize_t peek (void *buffer, size_t len);
   virtual long tell (); /* not implemented */
-  virtual int seek (long where, io_stream_seek_t whence); /* not implemented */
+  virtual int seek (long where, io_stream_seek_t whence);
   virtual int error ();
   virtual const char *next_file_name () { return NULL; };
   virtual int set_mtime (time_t);
@@ -42,6 +42,7 @@ private:
   io_stream *original;
   bool owns_original;
   int lasterr;
+  void create ();
   void destroy ();
 
   struct private_data {
-- 
2.32.0


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

* [PATCH setup 05/11] Add separate symlink-creation phase when extracting archive
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (3 preceding siblings ...)
  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 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 06/11] Enable SeCreateSymbolicLink privilege Jon Turney
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Add a separate symlink-creation phase when extracting an archive, by
ignoring symlinks on the first pass, rewinding the archive, and
then extracting only symlinks on the second pass.

This helps a lot with native symlinks (which require the destination to
exist when created, so we can determine if it is a file or directory).

Alternative implementations:

We could collect symlinks, and defer making them until the end of
extracting the archive.  We'd also need to report errors if making those
symlinks failed.

We could close and re-open the archive, rather than rewinding it. Error
handling if the archive isn't accessible the second time could be
complex.
---
 install.cc | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/install.cc b/install.cc
index cec31a9..de98b99 100644
--- a/install.cc
+++ b/install.cc
@@ -99,7 +99,8 @@ class Installer
                       HWND owner,
                       io_stream *pkgfile,
                       archive *tarstream,
-                      io_stream *lst);
+                      io_stream *lst,
+                      bool symlink_phase);
 };
 
 Installer::Installer() : errors(0)
@@ -539,7 +540,12 @@ Installer::installOne (packagemeta &pkgm, const packageversion &ver,
   Log (LOG_PLAIN) << "Extracting from " << source.Cached () << endLog;
 
   bool error_in_this_package = _installOne(pkgm, prefixURL, prefixPath, owner,
-                                           pkgfile, tarstream, lst);
+                                           pkgfile, tarstream, lst, false);
+  if (tarstream->seek(0, IO_SEEK_SET) == -1)
+    Log (LOG_PLAIN) << "Error rewinding to extract symlinks" << source.Cached () << endLog;
+
+  error_in_this_package |= _installOne(pkgm, prefixURL, prefixPath, owner,
+                                       pkgfile, tarstream, lst, true);
 
   if (lst)
     delete lst;
@@ -562,7 +568,8 @@ Installer::_installOne (packagemeta &pkgm,
                         HWND owner,
                         io_stream *pkgfile,
                         archive *tarstream,
-                        io_stream *lst)
+                        io_stream *lst,
+                        bool symlink_phase)
 {
   bool error_in_this_package = false;
   bool ignoreInUseErrors = false;
@@ -571,6 +578,12 @@ Installer::_installOne (packagemeta &pkgm,
   std::string fn;
   while ((fn = tarstream->next_file_name ()).size ())
     {
+      if (symlink_phase != (tarstream->next_file_type () == ARCHIVE_FILE_SYMLINK))
+        {
+          tarstream->skip_file();
+          continue;
+        }
+
       std::string canonicalfn = prefixPath + fn;
 
       // pathnames starting "." (i.e. dotfiles in the root directory) are
-- 
2.32.0


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

* [PATCH setup 06/11] Enable SeCreateSymbolicLink privilege
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (4 preceding siblings ...)
  2021-08-10 17:02 ` [PATCH setup 05/11] Add separate symlink-creation phase when extracting archive Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-08-11  8:46   ` Corinna Vinschen
  2021-08-10 17:02 ` [PATCH setup 07/11] Add symlink capabilities to user-agent telemetry Jon Turney
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

I'm not sure if SeCreateSymbolicLink privilege can get removed by UAC
filtering, but to make sure to enable it, if we can.

Also report if it's available to log.
---
 win32.cc | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 win32.h  |  2 ++
 2 files changed, 60 insertions(+)

diff --git a/win32.cc b/win32.cc
index 8ee424f..6455384 100644
--- a/win32.cc
+++ b/win32.cc
@@ -280,6 +280,28 @@ NTSecurity::setBackupPrivileges ()
     }
 }
 
+void
+NTSecurity::setSymlinkPrivilege ()
+{
+  LUID symlink;
+  if (!LookupPrivilegeValue (NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &symlink))
+    NoteFailedAPI ("LookupPrivilegeValue");
+
+  PTOKEN_PRIVILEGES new_priv;
+  new_priv = (PTOKEN_PRIVILEGES) alloca (sizeof (TOKEN_PRIVILEGES));
+  new_priv->PrivilegeCount = 1;
+  new_priv->Privileges[0].Luid = symlink;
+  new_priv->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+  if (!AdjustTokenPrivileges (token.theHANDLE (), FALSE, new_priv,
+                              0, NULL, NULL))
+    NoteFailedAPI ("AdjustTokenPrivileges");
+  else if (GetLastError () == ERROR_NOT_ALL_ASSIGNED)
+    Log (LOG_TIMESTAMP) << "User has NO symlink creation right" << endLog;
+  else
+    Log (LOG_TIMESTAMP) << "User has symlink creation right" << endLog;
+}
+
 void
 NTSecurity::resetPrimaryGroup ()
 {
@@ -321,6 +343,9 @@ NTSecurity::setDefaultSecurity (bool isAdmin)
   /* Set backup and restore privileges if available. */
   setBackupPrivileges ();
 
+  /* Set symlink creation privilege, if available. */
+  setSymlinkPrivilege ();
+
   /* If initializing the well-known SIDs didn't work, we're finished here. */
   if (!wellKnownSIDsinitialized ())
     return;
@@ -371,6 +396,39 @@ NTSecurity::isRunAsAdmin ()
   return (is_run_as_admin == TRUE);
 }
 
+bool
+NTSecurity::hasSymlinkCreationRights ()
+{
+  LUID symlink;
+  if (!LookupPrivilegeValue (NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &symlink))
+    {
+      NoteFailedAPI ("LookupPrivilegeValue");
+      return FALSE;
+    }
+
+  DWORD size;
+  GetTokenInformation (token.theHANDLE (), TokenPrivileges, NULL, 0, &size);
+  /* Will fail ERROR_INSUFFICIENT_BUFFER, but updates size */
+
+  TOKEN_PRIVILEGES *privileges = (TOKEN_PRIVILEGES *)alloca(size);
+  if (!GetTokenInformation (token.theHANDLE (), TokenPrivileges, privileges,
+                            size, &size))
+    {
+      NoteFailedAPI ("GetTokenInformation(privileges)");
+      return FALSE;
+    }
+
+  unsigned int i;
+  for (i = 0; i < privileges->PrivilegeCount; i++)
+    {
+      if (memcmp(&privileges->Privileges[i].Luid, &symlink, sizeof(LUID)) == 0)
+        {
+          return (privileges->Privileges[i].Attributes & SE_PRIVILEGE_ENABLED);
+        }
+    }
+
+  return FALSE;
+}
 
 VersionInfo::VersionInfo ()
 {
diff --git a/win32.h b/win32.h
index daebf2e..e498077 100644
--- a/win32.h
+++ b/win32.h
@@ -132,12 +132,14 @@ public:
   void initialiseWellKnownSIDs ();
   void setDefaultSecurity(bool isAdmin);
   bool isRunAsAdmin ();
+  bool hasSymlinkCreationRights ();
 private:
   void NoteFailedAPI (const std::string &);
   bool wellKnownSIDsinitialized () const { return _wellKnownSIDsinitialized; }
   void wellKnownSIDsinitialized (bool b) { _wellKnownSIDsinitialized = b; }
   void setDefaultDACL ();
   void setBackupPrivileges ();
+  void setSymlinkPrivilege ();
 
   SIDWrapper nullSID, everyOneSID, administratorsSID, usersSID,
 	     cr_ownerSID, cr_groupSID;
-- 
2.32.0


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

* [PATCH setup 07/11] Add symlink capabilities to user-agent telemetry
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (5 preceding siblings ...)
  2021-08-10 17:02 ` [PATCH setup 06/11] Enable SeCreateSymbolicLink privilege Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 08/11] Factor out StringChoiceOption Jon Turney
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Report if (i) we have the symlink creation privilege, and (ii) if
developer mode is on, so unprivileged symlink creation is allowed.
---
 nio-ie5.cc | 12 +++++++++++-
 win32.cc   | 18 ++++++++++++++++++
 win32.h    |  1 +
 3 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/nio-ie5.cc b/nio-ie5.cc
index e93d6d4..8fd7008 100644
--- a/nio-ie5.cc
+++ b/nio-ie5.cc
@@ -89,7 +89,17 @@ determine_default_useragent(void)
       langid << std::hex << std::setw(4) << std::setfill('0') << l;
     }
 
-  default_useragent = std::string("Cygwin-Setup/") + setup_version + " (" + os.str() + ";" + bitness + ";" + langid.str() + ")";
+  std::string symlinks = "";
+  if (nt_sec.hasSymlinkCreationRights())
+    symlinks.append("SymLinkPriv");
+  if (is_developer_mode())
+    {
+      if (!symlinks.empty())
+        symlinks.append("+");
+      symlinks.append("UnprivilegedSymLink");
+    }
+
+  default_useragent = std::string("Cygwin-Setup/") + setup_version + " (" + os.str() + ";" + bitness + ";" + langid.str() + ";" + symlinks + ")";
   Log (LOG_BABBLE) << "User-Agent: default is \"" << default_useragent << "\"" << endLog;
 
   return default_useragent;
diff --git a/win32.cc b/win32.cc
index 6455384..db79fd7 100644
--- a/win32.cc
+++ b/win32.cc
@@ -492,3 +492,21 @@ LoadStringW(unsigned int uID)
 
   return L"";
 }
+
+bool
+is_developer_mode(void)
+{
+  HKEY hKey;
+  LSTATUS err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, KEY_READ, &hKey);
+  if (err != ERROR_SUCCESS)
+    return false;
+
+  DWORD value;
+  DWORD size = sizeof(DWORD);
+  err = RegQueryValueExW(hKey, L"AllowDevelopmentWithoutDevLicense", NULL, NULL, reinterpret_cast<LPBYTE>(&value), &size);
+  RegCloseKey(hKey);
+  if (err != ERROR_SUCCESS)
+    return false;
+
+  return value != 0;
+}
diff --git a/win32.h b/win32.h
index e498077..5874d8b 100644
--- a/win32.h
+++ b/win32.h
@@ -196,5 +196,6 @@ SetDlgItemRect (HWND h, int item, LPRECT r)
 }
 
 const std::wstring LoadStringW(unsigned int uID);
+bool is_developer_mode(void);
 
 #endif /* SETUP_WIN32_H */
-- 
2.32.0


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

* [PATCH setup 08/11] Factor out StringChoiceOption
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (6 preceding siblings ...)
  2021-08-10 17:02 ` [PATCH setup 07/11] Add symlink capabilities to user-agent telemetry Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 09/11] Add a command line option to choose symlink type used Jon Turney
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Factor out logic for 'a string option which is one of a set of choices'
from CompactOsStringOption as StringChoiceOption.

v2:
Allow different behaviour for option without a choice, and option absent.

Future work: This doesn't say anything other that "Error during option
processing" if you've given an invalid choice string, or left it out
when it's required, because libgetopt++ doesn't output anything, just
returns success or failure after processing the command line arguments.
---
 io_stream_cygfile.cc                          | 52 +++++-------------
 libgetopt++/Makefile.am                       |  3 +-
 .../include/getopt++/StringChoiceOption.h     | 41 ++++++++++++++
 libgetopt++/src/StringChoiceOption.cc         | 54 +++++++++++++++++++
 4 files changed, 109 insertions(+), 41 deletions(-)
 create mode 100644 libgetopt++/include/getopt++/StringChoiceOption.h
 create mode 100644 libgetopt++/src/StringChoiceOption.cc

diff --git a/io_stream_cygfile.cc b/io_stream_cygfile.cc
index a9150e7..52ef735 100644
--- a/io_stream_cygfile.cc
+++ b/io_stream_cygfile.cc
@@ -20,7 +20,7 @@
 #include "mount.h"
 #include "compactos.h"
 
-#include "getopt++/StringOption.h"
+#include "getopt++/StringChoiceOption.h"
 
 #include <stdlib.h>
 #include <errno.h>
@@ -30,45 +30,17 @@
 #include "IOStreamProvider.h"
 #include "LogSingleton.h"
 
-/* Option '--compact-os ALGORITHM' */
-class CompactOsStringOption : public StringOption
-{
-public:
-  CompactOsStringOption ();
-  virtual Result Process (char const *optarg, int prefixIndex) /* override */;
-  operator int () const { return intval; }
-private:
-  int intval;
-};
-
-CompactOsStringOption::CompactOsStringOption ()
-: StringOption ("", '\0', "compact-os",
-    "Compress installed files with Compact OS "
-    "(xpress4k, xpress8k, xpress16k, lzx)", false),
-  intval (-1)
-{
-}
-
-Option::Result CompactOsStringOption::Process (char const *optarg, int prefixIndex)
-{
-  Result res = StringOption::Process (optarg, prefixIndex);
-  if (res != Ok)
-    return res;
-  const std::string& strval = *this;
-  if (strval == "xpress4k")
-    intval = FILE_PROVIDER_COMPRESSION_XPRESS4K;
-  else if (strval == "xpress8k")
-    intval = FILE_PROVIDER_COMPRESSION_XPRESS8K;
-  else if (strval == "xpress16k")
-    intval = FILE_PROVIDER_COMPRESSION_XPRESS16K;
-  else if (strval == "lzx")
-    intval = FILE_PROVIDER_COMPRESSION_LZX;
-  else
-    return Failed;
-  return Ok;
-}
-
-static CompactOsStringOption CompactOsOption;
+static StringChoiceOption::StringChoices algs({
+    {"xpress4k", FILE_PROVIDER_COMPRESSION_XPRESS4K},
+    {"xpress8k", FILE_PROVIDER_COMPRESSION_XPRESS8K},
+    {"xpress16k", FILE_PROVIDER_COMPRESSION_XPRESS16K},
+    {"lzx", FILE_PROVIDER_COMPRESSION_LZX},
+  });
+
+static StringChoiceOption CompactOsOption(algs,
+    '\0', "compact-os",
+    "Compress installed files with Compact OS (xpress4k, xpress8k, xpress16k, lzx)",
+    true, -1, FILE_PROVIDER_COMPRESSION_LZX);
 
 /* completely private iostream registration class */
 class CygFileProvider : public IOStreamProvider
diff --git a/libgetopt++/Makefile.am b/libgetopt++/Makefile.am
index 34dc6fd..c20c17d 100644
--- a/libgetopt++/Makefile.am
+++ b/libgetopt++/Makefile.am
@@ -26,7 +26,7 @@ TESTS = tests/OptionSet tests/optioniterator tests/BoolOptionTest
 
 libgetopt___la_SOURCES = src/GetOption.cc src/Option.cc src/BoolOption.cc \
 	src/OptionSet.cc \
-	src/StringArrayOption.cc src/StringOption.cc
+	src/StringArrayOption.cc src/StringChoiceOption.cc src/StringOption.cc
 
 libgetopt___la_LDFLAGS = -version-info 1:1:0 -no-undefined
 
@@ -36,6 +36,7 @@ getoptinclude_HEADERS = include/getopt++/Option.h \
   include/getopt++/GetOption.h \
   include/getopt++/OptionSet.h \
   include/getopt++/StringArrayOption.h \
+  include/getopt++/StringChoiceOption.h \
   include/getopt++/StringOption.h
 
 tests_testoption_SOURCES = tests/testoption.cc
diff --git a/libgetopt++/include/getopt++/StringChoiceOption.h b/libgetopt++/include/getopt++/StringChoiceOption.h
new file mode 100644
index 0000000..8800712
--- /dev/null
+++ b/libgetopt++/include/getopt++/StringChoiceOption.h
@@ -0,0 +1,41 @@
+/*
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#ifndef _STRINGCHOICEOPTION_H_
+#define _STRINGCHOICEOPTION_H_
+
+#include <vector>
+#include <getopt++/StringOption.h>
+
+class StringChoiceOption : public StringOption
+{
+public:
+  typedef std::vector<std::pair<const char *, int>> StringChoices;
+
+  StringChoiceOption(StringChoices choices,
+                     char shortopt, char const *longopt = 0,
+                     std::string const &shorthelp = std::string(),
+                     bool const optional = true,   // option without choice string is permitted
+                     int const defaultvalue = -1,  // value when option is absent
+                     int const impliedvalue = -1); // value when option is present without choice string
+
+  virtual ~ StringChoiceOption ();
+  virtual Result Process (char const *, int);
+  operator int () const { return intval; }
+
+private:
+  StringChoices choices;
+  int intval;
+  int _impliedvalue;
+};
+
+#endif // _STRINGCHOICEOPTION_H_
diff --git a/libgetopt++/src/StringChoiceOption.cc b/libgetopt++/src/StringChoiceOption.cc
new file mode 100644
index 0000000..185d56a
--- /dev/null
+++ b/libgetopt++/src/StringChoiceOption.cc
@@ -0,0 +1,54 @@
+/*
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     A copy of the GNU General Public License can be found at
+ *     http://www.gnu.org/
+ *
+ */
+
+#include <getopt++/StringChoiceOption.h>
+
+StringChoiceOption::StringChoiceOption (StringChoices _choices,
+                                        char shortopt,
+                                        char const *longopt,
+                                        std::string const &shorthelp,
+                                        bool const optional,
+                                        int const defaultvalue,
+                                        int const impliedvalue) :
+  StringOption ("", shortopt, longopt, shorthelp, optional),
+  choices(_choices), intval (defaultvalue), _impliedvalue(impliedvalue)
+{
+};
+
+StringChoiceOption::~ StringChoiceOption () {};
+
+Option::Result
+StringChoiceOption::Process (char const *optarg, int prefixIndex)
+{
+  Result res = StringOption::Process (optarg, prefixIndex);
+  if (res != Ok)
+    return res;
+
+  const std::string& strval = *this;
+  if (strval.empty())
+    {
+      intval = _impliedvalue;
+      return Ok;
+    }
+
+  for (StringChoiceOption::StringChoices::const_iterator i = choices.begin();
+       i != choices.end();
+       i++)
+    {
+      if (strval == i->first)
+        {
+          intval = i->second;
+          return Ok;
+        }
+    }
+
+  return Failed;
+}
-- 
2.32.0


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

* [PATCH setup 09/11] Add a command line option to choose symlink type used
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (7 preceding siblings ...)
  2021-08-10 17:02 ` [PATCH setup 08/11] Factor out StringChoiceOption Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-08-10 17:02 ` [PATCH setup 10/11] Propagate --symlink-type setting to post-install scripts Jon Turney
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

The default is 'sys', the historical behaviour of setup.

v2:
Adjust to use StringChoiceOption
Align option names with winsymlink values
---
 main.cc | 42 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 39 insertions(+), 3 deletions(-)

diff --git a/main.cc b/main.cc
index b3854a8..023d505 100644
--- a/main.cc
+++ b/main.cc
@@ -63,6 +63,8 @@
 #include "getopt++/GetOption.h"
 #include "getopt++/BoolOption.h"
 #include "getopt++/StringOption.h"
+#include "getopt++/StringChoiceOption.h"
+#include "mklink2.h"
 
 #include "Exception.h"
 #include <stdexcept>
@@ -86,6 +88,13 @@ std::string SetupIniDir;
 
 HINSTANCE hinstance;
 
+static StringChoiceOption::StringChoices symlink_types({
+    {"native", SymlinkTypeNative},
+    {"lnk", SymlinkTypeShortcut},
+    {"sys", SymlinkTypeMagic},
+    {"wsl", SymlinkTypeWsl},
+  });
+
 static StringOption Arch ("", 'a', "arch", "Architecture to install (x86_64 or x86)", false);
 static BoolOption UnattendedOption (false, 'q', "quiet-mode", "Unattended setup mode");
 static BoolOption PackageManagerOption (false, 'M', "package-manager", "Semi-attended chooser-only mode");
@@ -95,6 +104,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 StringChoiceOption SymlinkTypeOption(symlink_types, '\0', "symlink-type", "Symlink type (lnk, native, sys, wsl)", false, SymlinkTypeMagic);
+
 std::string SetupBaseName;
 
 static void inline
@@ -320,6 +331,34 @@ WinMain (HINSTANCE h,
 	Logger ().exit (1, false);
       }
 
+    /* Set default DACL and Group. */
+    nt_sec.setDefaultSecurity ((root_scope == IDC_ROOT_SYSTEM));
+
+    symlinkType = (SymlinkTypeEnum)(int)SymlinkTypeOption;
+    if (symlinkType == SymlinkTypeWsl)
+      {
+        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 if (symlinkType == SymlinkTypeNative)
+      {
+        if (!(elevate || is_developer_mode() || nt_sec.hasSymlinkCreationRights()))
+          {
+            fprintf (stderr, "*** --symlink-type native requires SeCreateSymbolicLink privilege or 'Developer Mode'\n");
+            exit(1);
+          }
+      }
+    else if (symlinkType == SymlinkTypeShortcut)
+      {
+        fprintf (stderr, "*** --symlink-type lnk is not implemented\n");
+        exit(1);
+      }
+
     if (elevate)
       {
 	char exe_path[MAX_PATH];
@@ -354,9 +393,6 @@ WinMain (HINSTANCE h,
       }
     else
       {
-	/* Set default DACL and Group. */
-	nt_sec.setDefaultSecurity ((root_scope == IDC_ROOT_SYSTEM));
-
 	UserSettings Settings;
         UserSettings::instance().load (local_dir);
 	main_display ();
-- 
2.32.0


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

* [PATCH setup 10/11] Propagate --symlink-type setting to post-install scripts
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (8 preceding siblings ...)
  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 ` 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
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

Propagate --symlink-type setting to post-install scripts, by setting the
CYGWIN env var appropriately.
---
 script.cc | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/script.cc b/script.cc
index 69dc115..6818f4b 100644
--- a/script.cc
+++ b/script.cc
@@ -36,6 +36,7 @@
 #define alloca __builtin_alloca
 #endif
 #endif
+#include "mklink2.h"
 
 static std::string sh, dash;
 static const char *cmd;
@@ -71,6 +72,40 @@ sanitize_PATH ()
   SetEnvironmentVariable ("PATH", newpath.c_str());
 }
 
+static void
+modify_CYGWIN ()
+{
+  std::string cygwin;
+  DWORD len = GetEnvironmentVariable ("CYGWIN", &cygwin[0], 0);
+  if (len > 0)
+    {
+      cygwin.resize(len);
+      GetEnvironmentVariable ("CYGWIN", &cygwin[0], len);
+      cygwin.resize(len-1); // trim terminating null
+      cygwin.append(" ");
+    }
+
+  switch (symlinkType)
+    {
+    case SymlinkTypeNative:
+      cygwin.append("winsymlinks:native");
+      break;
+
+    case SymlinkTypeWsl:
+      cygwin.append("winsymlinks:wsl");
+      break;
+
+    case SymlinkTypeMagic:
+      cygwin.append("winsymlinks:sys");
+      break;
+
+    case SymlinkTypeShortcut: /* not yet implemented */
+    default:
+      break;
+    }
+
+  SetEnvironmentVariable ("CYGWIN", cygwin.c_str());
+}
 
 void
 init_run_script ()
@@ -100,6 +135,7 @@ init_run_script ()
       FreeEnvironmentStrings (env);
     }
 
+  modify_CYGWIN();
   SetEnvironmentVariable ("CYGWINROOT", get_root_dir ().c_str());
   SetEnvironmentVariable ("CYGWINFORALL",
                           (root_scope == IDC_ROOT_SYSTEM) ? "-A" : NULL);
-- 
2.32.0


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

* [PATCH setup 11/11] Default symlink mode from CYGWIN env var
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (9 preceding siblings ...)
  2021-08-10 17:02 ` [PATCH setup 10/11] Propagate --symlink-type setting to post-install scripts Jon Turney
@ 2021-08-10 17:02 ` Jon Turney
  2021-09-14 11:53 ` [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-08-10 17:02 UTC (permalink / raw)
  To: cygwin-apps; +Cc: Jon Turney

---
 main.cc | 44 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/main.cc b/main.cc
index 023d505..f4756c7 100644
--- a/main.cc
+++ b/main.cc
@@ -284,6 +284,7 @@ WinMain (HINSTANCE h,
     /* Check if we have to elevate. */
     bool elevate = !output_only && OSMajorVersion () >= 6
 		   && !NoAdminOption && !nt_sec.isRunAsAdmin ();
+    std::string elevate_extra_args;
 
     if (unattended_mode || output_only || !elevate)
       set_cout ();
@@ -334,7 +335,46 @@ WinMain (HINSTANCE h,
     /* Set default DACL and Group. */
     nt_sec.setDefaultSecurity ((root_scope == IDC_ROOT_SYSTEM));
 
-    symlinkType = (SymlinkTypeEnum)(int)SymlinkTypeOption;
+    /*
+       If --symlink-type option isn't given, look for winsymlinks in CYGWIN
+       env var for a default
+
+       Since the current environment doesn't get passed to the process started
+       with with ShellExecuteEx, we need to convert the env var into an option
+       for that elevated instance.
+    */
+    if (!SymlinkTypeOption.isPresent()) {
+      std::string cygwin;
+      DWORD len = GetEnvironmentVariable ("CYGWIN", &cygwin[0], 0);
+      cygwin.resize(len);
+      GetEnvironmentVariable ("CYGWIN", &cygwin[0], len);
+
+      if (cygwin.find("winsymlinks:native") != std::string::npos)
+        {
+          symlinkType = SymlinkTypeNative;
+          elevate_extra_args.append("--symlink-type native");
+        }
+      else if (cygwin.find("winsymlinks:wsl") != std::string::npos)
+        {
+          symlinkType = SymlinkTypeWsl;
+          elevate_extra_args.append("--symlink-type wsl");
+        }
+      else if (cygwin.find("winsymlinks:sys") != std::string::npos)
+        {
+          symlinkType = SymlinkTypeMagic;
+          elevate_extra_args.append("--symlink-type sys");
+        }
+      else if (cygwin.find("winsymlinks:lnk") != std::string::npos)
+        {
+          symlinkType = SymlinkTypeShortcut;
+          elevate_extra_args.append("--symlink-type lnk");
+        }
+      }
+    else
+      {
+        symlinkType = (SymlinkTypeEnum)(int)SymlinkTypeOption;
+      }
+
     if (symlinkType == SymlinkTypeWsl)
       {
         VersionInfo v = GetVer();
@@ -376,6 +416,8 @@ WinMain (HINSTANCE h,
 	std::string command_line_cs (command_line);
 	command_line_cs += " -";
 	command_line_cs += NoAdminOption.shortOption();
+	command_line_cs += " ";
+	command_line_cs += elevate_extra_args;
 	sei.lpParameters = command_line_cs.c_str ();
 
 	if (ShellExecuteEx(&sei))
-- 
2.32.0


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

* Re: [PATCH setup 06/11] Enable SeCreateSymbolicLink privilege
  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
  0 siblings, 1 reply; 15+ messages in thread
From: Corinna Vinschen @ 2021-08-11  8:46 UTC (permalink / raw)
  To: cygwin-apps

On Aug 10 18:02, Jon Turney wrote:
> I'm not sure if SeCreateSymbolicLink privilege can get removed by UAC
> filtering, but to make sure to enable it, if we can.

I'm not sure this is required.  This is one of the privileges which is
enabled automatically on usage if it's present in the token and not
marked as "deny only".  UAC removes the privilege entirely from the
token, so you can't enable it in that case.


Corinna

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

* Re: [PATCH setup 06/11] Enable SeCreateSymbolicLink privilege
  2021-08-11  8:46   ` Corinna Vinschen
@ 2021-09-14 11:53     ` Jon Turney
  0 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-09-14 11:53 UTC (permalink / raw)
  To: cygwin-apps

On 11/08/2021 09:46, Corinna Vinschen via Cygwin-apps wrote:
> On Aug 10 18:02, Jon Turney wrote:
>> I'm not sure if SeCreateSymbolicLink privilege can get removed by UAC
>> filtering, but to make sure to enable it, if we can.
> 
> I'm not sure this is required.  This is one of the privileges which is
> enabled automatically on usage if it's present in the token and not
> marked as "deny only".  UAC removes the privilege entirely from the
> token, so you can't enable it in that case.

Yes, looking into this nonsense a bit more, that's correct.

I'll change this to just log the availability of that privilege.


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

* Re: [PATCH setup 00/11] Add options to choose symlink type (v2)
  2021-08-10 17:02 [PATCH setup 00/11] Add options to choose symlink type (v2) Jon Turney
                   ` (10 preceding siblings ...)
  2021-08-10 17:02 ` [PATCH setup 11/11] Default symlink mode from CYGWIN env var Jon Turney
@ 2021-09-14 11:53 ` Jon Turney
  11 siblings, 0 replies; 15+ messages in thread
From: Jon Turney @ 2021-09-14 11:53 UTC (permalink / raw)
  To: cygwin-apps

On 10/08/2021 18:02, Jon Turney wrote:
> Revised from [1], this adds an '--symlink-type {sys, native, wsl}' option,
> which controls the type of symlinks created by setup and the post-install
> scripts it invokes [2].

In the base install, there are 4 symlinks which can't be created using
the native method (and hence fall back to sys method):

> package          from                                     -> to                         reason
> 
> terminfo        /usr/lib/terminfo                         -> ../share/terminfo          mount table
> terminfo        /usr/share/terminfo/6a/jfbterm            -> ../6b/kon                  target is in terminfo-extra package, unpacked later as unpack order isn't dependency order (only postinstall script run order?)
> file            /usr/share/file/magic                     -> ../magic                   target is also a symlink, later in the same archive
> ca-certificates /usr/libexec/p11-kit/trust-extract-compat -> ../../bin/update-ca-trust  mount table

'mount table' means that in the current setup architecture, 'from' has 
already been translated through the mount table into a native path 
before we can try to locate 'to' relative to it, but it exists relative 
to the untranslated path.

It's probably possible to improve the handling of these cases with more 
effort.

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

end of thread, other threads:[~2021-09-14 11:53 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH setup 02/11] Add support for creating native symlinks Jon Turney
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

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