[setup - the official Cygwin setup program] branch master, updated. release_2.909-13-g62ab80bf

Jon TURNEY jturney@sourceware.org
Wed Sep 29 14:20:10 GMT 2021




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=62ab80bfc963d9841b30790ba3ad70ff88e8c3bb

commit 62ab80bfc963d9841b30790ba3ad70ff88e8c3bb
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Jul 31 20:37:22 2021 +0100

    Default symlink mode from CYGWIN env var

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=34b58a2ea0a1de61dfeb142e1b346105685e680a

commit 34b58a2ea0a1de61dfeb142e1b346105685e680a
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Jul 31 12:54:00 2021 +0100

    Propagate --symlink-type setting to post-install scripts
    
    Propagate --symlink-type setting to post-install scripts, by setting the
    CYGWIN env var appropriately.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=c17d46d39bbf9a6c7ada12cf41ae450cf4eff7d9

commit c17d46d39bbf9a6c7ada12cf41ae450cf4eff7d9
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed May 19 19:13:57 2021 +0100

    Add a command line option to choose symlink type used
    
    The default is 'sys', the historical behaviour of setup.
    
    v2:
    Adjust to use StringChoiceOption
    Align option names with winsymlink values

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=94c4c95d9c29748e4b98c0d4fa2533814832b7ca

commit 94c4c95d9c29748e4b98c0d4fa2533814832b7ca
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jul 18 15:11:39 2021 +0100

    Factor out StringChoiceOption
    
    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.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=b60d80c64bc08556b4030cc5498493a9a9820145

commit b60d80c64bc08556b4030cc5498493a9a9820145
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Jul 24 16:34:29 2021 +0100

    Add symlink capabilities to user-agent telemetry
    
    Report if (i) we have the symlink creation privilege, and (ii) if
    developer mode is on, so unprivileged symlink creation is allowed.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=449640c40241439272704d91a1854513a72d8ae0

commit 449640c40241439272704d91a1854513a72d8ae0
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Mon Jul 26 22:22:51 2021 +0100

    Add NTSecurity::hasSymlinkCreationRights()
    
    Also report if SeCreateSymbolicLink privilege is available to log.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=dbfd1a64f80a2d09581e1d304862233cdae40246

commit dbfd1a64f80a2d09581e1d304862233cdae40246
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jul 25 15:58:30 2021 +0100

    Add separate symlink-creation phase when extracting archive
    
    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.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=840e95c1cd2caa3ddf78f3c56e11279249e4e778

commit 840e95c1cd2caa3ddf78f3c56e11279249e4e778
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jul 25 15:53:39 2021 +0100

    Add seek() method to archive and compress file classes
    
    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!)

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=be11af20162eec1322302f75a0aa1a158036f091

commit be11af20162eec1322302f75a0aa1a158036f091
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jul 25 15:26:19 2021 +0100

    Factor out the iteration over archive files to install

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=c0aead385029e8a782b92f8c47b11a9ac3a1d645

commit c0aead385029e8a782b92f8c47b11a9ac3a1d645
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Jul 24 16:19:27 2021 +0100

    Add support for creating native symlinks

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/setup.git;h=93b295d68d2903837ae44d57fe6ab40b91ec1b02

commit 93b295d68d2903837ae44d57fe6ab40b91ec1b02
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Tue May 18 23:07:11 2021 +0100

    Add support for creating WSL symlinks


Diff:
---
 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 +-
 libgetopt++/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                                          |  57 +++++
 win32.h                                           |   2 +
 26 files changed, 755 insertions(+), 173 deletions(-)

diff --git a/archive.cc b/archive.cc
index 1ceb3552..448ea0ea 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 d3c795af..adab9f0f 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 c3592386..5b2a7719 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 18773d4d..e4792d64 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 39a0d5ba..a7e865a0 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 55a015e2..e73ccd33 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 50b6e66b..90073e5d 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 a5167d6c..bb645950 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 24cbb099..31d499c3 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 3588dbd6..bb177854 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 be5712ce..3303daa4 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 {
diff --git a/filemanip.cc b/filemanip.cc
index 48f5117f..ca5e4ac0 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 451211f0..e83b8e94 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/inilintmain.cc b/inilintmain.cc
index f31e5eb4..886c152f 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/install.cc b/install.cc
index 51ec4b57..de98b999 100644
--- a/install.cc
+++ b/install.cc
@@ -93,7 +93,14 @@ 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,
+                      bool symlink_phase);
 };
 
 Installer::Installer() : errors(0)
@@ -529,16 +536,54 @@ 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, 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;
+  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 symlink_phase)
+{
   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 ())
     {
+      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
@@ -725,18 +770,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
diff --git a/io_stream_cygfile.cc b/io_stream_cygfile.cc
index a9150e7e..52ef7355 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 34dc6fd9..c20c17db 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 00000000..88007128
--- /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 00000000..185d56af
--- /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;
+}
diff --git a/main.cc b/main.cc
index b3854a84..f4756c73 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
@@ -273,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 ();
@@ -320,6 +332,73 @@ WinMain (HINSTANCE h,
 	Logger ().exit (1, false);
       }
 
+    /* Set default DACL and Group. */
+    nt_sec.setDefaultSecurity ((root_scope == IDC_ROOT_SYSTEM));
+
+    /*
+       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();
+        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];
@@ -337,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))
@@ -354,9 +435,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 ();
diff --git a/mklink2.cc b/mklink2.cc
index 0b734038..6e7a0020 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,252 @@ 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;
+}
+
+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)
+{
+  if (symlinkType == SymlinkTypeWsl)
+    {
+      if (!mkwslsymlink (from, 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);
+}
+
 static struct {
   FILE_LINK_INFORMATION fli;
   WCHAR namebuf[32768];
diff --git a/mklink2.h b/mklink2.h
index 02447481..fda17f65 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 */
diff --git a/nio-ie5.cc b/nio-ie5.cc
index e93d6d4b..8fd7008a 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/script.cc b/script.cc
index 69dc1155..6818f4b2 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);
diff --git a/win32.cc b/win32.cc
index 8ee424f9..b6e7c947 100644
--- a/win32.cc
+++ b/win32.cc
@@ -321,6 +321,12 @@ NTSecurity::setDefaultSecurity (bool isAdmin)
   /* Set backup and restore privileges if available. */
   setBackupPrivileges ();
 
+  /* Log if symlink creation privilege is available. */
+  if (hasSymlinkCreationRights())
+    Log (LOG_TIMESTAMP) << "User has NO symlink creation right" << endLog;
+  else
+    Log (LOG_TIMESTAMP) << "User has symlink creation right" << endLog;
+
   /* If initializing the well-known SIDs didn't work, we're finished here. */
   if (!wellKnownSIDsinitialized ())
     return;
@@ -371,6 +377,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 ()
 {
@@ -434,3 +473,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 daebf2e6..05f931c8 100644
--- a/win32.h
+++ b/win32.h
@@ -132,6 +132,7 @@ public:
   void initialiseWellKnownSIDs ();
   void setDefaultSecurity(bool isAdmin);
   bool isRunAsAdmin ();
+  bool hasSymlinkCreationRights ();
 private:
   void NoteFailedAPI (const std::string &);
   bool wellKnownSIDsinitialized () const { return _wellKnownSIDsinitialized; }
@@ -194,5 +195,6 @@ SetDlgItemRect (HWND h, int item, LPRECT r)
 }
 
 const std::wstring LoadStringW(unsigned int uID);
+bool is_developer_mode(void);
 
 #endif /* SETUP_WIN32_H */



More information about the Cygwin-apps-cvs mailing list