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

Reply via email to