<pre style='margin:0'>
Joshua Root (jmroot) pushed a commit to branch master
in repository macports-base.

</pre>
<p><a href="https://github.com/macports/macports-base/commit/4504942d2283f2950d273b0aee93b9ed867007ea">https://github.com/macports/macports-base/commit/4504942d2283f2950d273b0aee93b9ed867007ea</a></p>
<pre style="white-space: pre; background: #F8F8F8"><span style='display:block; white-space:pre;color:#808000;'>commit 4504942d2283f2950d273b0aee93b9ed867007ea
</span>Author: Joshua Root <jmr@macports.org>
AuthorDate: Mon Jun 24 14:21:10 2024 +1000

<span style='display:block; white-space:pre;color:#404040;'>    Use clonefile for port images, and detect hardlinks
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    New portimage_mode config option controls whether the extracted
</span><span style='display:block; white-space:pre;color:#404040;'>    directory, the archive, or both are kept for port images. Default is
</span><span style='display:block; white-space:pre;color:#404040;'>    directory if the filesystem that the software directory is on supports
</span><span style='display:block; white-space:pre;color:#404040;'>    cloning. Otherwise the default is archive.
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    In directory mode, downloaded archives are extracted only once.
</span><span style='display:block; white-space:pre;color:#404040;'>    Subsequent activations just clone the files. If activation is across
</span><span style='display:block; white-space:pre;color:#404040;'>    devices for some files, fall back to copying for those files.
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    In all cases, hard links are now recreated correctly whenever possible
</span><span style='display:block; white-space:pre;color:#404040;'>    even if the activated location is on a different device than the port
</span><span style='display:block; white-space:pre;color:#404040;'>    image.
</span><span style='display:block; white-space:pre;color:#404040;'>    
</span><span style='display:block; white-space:pre;color:#404040;'>    Closes: https://trac.macports.org/ticket/69555
</span>---
 configure                         |  18 ++++
 configure.ac                      |   7 +-
 doc/macports.conf.in              |  19 +++-
 src/config.h.in                   |   9 ++
 src/images_to_archives.tcl.in     |   3 +-
 src/macports1.0/macports.tcl      |  26 +++++-
 src/pextlib1.0/Pextlib.c          |  41 ++++++++
 src/port1.0/portinstall.tcl       |  43 +++++++--
 src/registry2.0/portimage.tcl     | 191 ++++++++++++++++++++++++++------------
 src/registry2.0/portuninstall.tcl |  26 +++++-
 10 files changed, 306 insertions(+), 77 deletions(-)

<span style='display:block; white-space:pre;color:#808080;'>diff --git a/configure b/configure
</span><span style='display:block; white-space:pre;color:#808080;'>index c20027d7a..f5dbc2f26 100755
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/configure
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/configure
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -7446,12 +7446,24 @@ if test "x$ac_cv_header_spawn_h" = xyes
</span> then :
   printf "%s\n" "#define HAVE_SPAWN_H 1" >>confdefs.h
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+fi
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+ac_fn_c_check_header_compile "$LINENO" "sys/attr.h" "ac_cv_header_sys_attr_h" "$ac_includes_default"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if test "x$ac_cv_header_sys_attr_h" = xyes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+then :
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+  printf "%s\n" "#define HAVE_SYS_ATTR_H 1" >>confdefs.h
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> fi
 ac_fn_c_check_header_compile "$LINENO" "sys/cdefs.h" "ac_cv_header_sys_cdefs_h" "$ac_includes_default"
 if test "x$ac_cv_header_sys_cdefs_h" = xyes
 then :
   printf "%s\n" "#define HAVE_SYS_CDEFS_H 1" >>confdefs.h
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+fi
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+ac_fn_c_check_header_compile "$LINENO" "sys/clonefile.h" "ac_cv_header_sys_clonefile_h" "$ac_includes_default"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if test "x$ac_cv_header_sys_clonefile_h" = xyes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+then :
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+  printf "%s\n" "#define HAVE_SYS_CLONEFILE_H 1" >>confdefs.h
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> fi
 ac_fn_c_check_header_compile "$LINENO" "sys/event.h" "ac_cv_header_sys_event_h" "$ac_includes_default"
 if test "x$ac_cv_header_sys_event_h" = xyes
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -7527,6 +7539,12 @@ if test "x$ac_cv_func_clearenv" = xyes
</span> then :
   printf "%s\n" "#define HAVE_CLEARENV 1" >>confdefs.h
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+fi
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+ac_fn_c_check_func "$LINENO" "clonefile" "ac_cv_func_clonefile"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+if test "x$ac_cv_func_clonefile" = xyes
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+then :
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+  printf "%s\n" "#define HAVE_CLONEFILE 1" >>confdefs.h
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> fi
 ac_fn_c_check_func "$LINENO" "copyfile" "ac_cv_func_copyfile"
 if test "x$ac_cv_func_copyfile" = xyes
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/configure.ac b/configure.ac
</span><span style='display:block; white-space:pre;color:#808080;'>index ab77f3746..8c1883b5a 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/configure.ac
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/configure.ac
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -249,13 +249,14 @@ MP_CHECK_STARTUPITEMS
</span> MP_UNIVERSAL_OPTIONS
 
 # Check for standard header files.
<span style='display:block; white-space:pre;background:#ffe0e0;'>-AC_CHECK_HEADERS([crt_externs.h err.h fcntl.h libkern/OSAtomic.h libproc.h limits.h paths.h pwd.h \
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-   readline/history.h readline/readline.h stdatomic.h spawn.h sys/cdefs.h sys/event.h \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+AC_CHECK_HEADERS([crt_externs.h err.h fcntl.h libkern/OSAtomic.h libproc.h \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    limits.h paths.h pwd.h readline/history.h readline/readline.h \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    stdatomic.h spawn.h sys/attr.h sys/cdefs.h sys/clonefile.h sys/event.h \
</span>   sys/fcntl.h sys/file.h sys/paths.h sys/socket.h sys/sysctl.h utime.h])
 
 # Checks for library functions.
 AC_CHECK_FUNCS([OSAtomicCompareAndSwap32 OSAtomicCompareAndSwap64 \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        OSAtomicCompareAndSwapPtr __getdirentries64 clearenv copyfile \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+   OSAtomicCompareAndSwapPtr __getdirentries64 clearenv clonefile copyfile \
</span>   _dyld_shared_cache_contains_path flock fls kqueue posix_spawn setmode \
        sysctlbyname])
 
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/doc/macports.conf.in b/doc/macports.conf.in
</span><span style='display:block; white-space:pre;color:#808080;'>index 3443c0c59..809826ff7 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/doc/macports.conf.in
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/doc/macports.conf.in
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -46,10 +46,25 @@ variants_conf           @MPCONFIGDIR_EXPANDED@/variants.conf
</span> #   unavailable.
 #buildfromsource       ifneeded
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-# Type of archive to use for port images. Supported types are cpgz,
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-# cpio, tar, tbz, tbz2, tgz, tlz, txz, xar, zip.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Type of archive to use for port images created by local builds.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Supported types are cpgz, cpio, tar, tbz, tbz2, tgz, tlz, txz, xar, zip.
</span> #portarchivetype          tbz2
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+# How to store port images.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# - archive: Image is kept in the archive as downloaded (format
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   determined by the archive site configuration) or built locally
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   (format determined by portarchivetype). Must be extracted to
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   activate the port. This is the default if the filesystem does not
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   support cloning.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# - directory: Image is stored as a directory. Downloaded archives are
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   extracted to create the directory and then deleted. This is the
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   default if the filesystem supports cloning.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# - directory_and_archive: Both archives and extracted directories are
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   kept. This is useful if you want to distribute the ports that you
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   build as archives, and also get the performance benefits of the
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#   directory mode on a filesystem that supports cloning.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#portimage_mode         directory_and_archive
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> # Apply transparent filesystem compression to files on activation.
 # Requires bsdtar with support for --hfsCompression in binpath, which can be
 # provided by installing the libarchive port. This will work with HFS+ or APFS
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/config.h.in b/src/config.h.in
</span><span style='display:block; white-space:pre;color:#808080;'>index dafa3daf4..49b06de0e 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/config.h.in
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/config.h.in
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -7,6 +7,9 @@
</span> /* Define to 1 if you have the 'clearenv' function. */
 #undef HAVE_CLEARENV
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+/* Define to 1 if you have the 'clonefile' function. */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#undef HAVE_CLONEFILE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> /* Define if CommonCrypto is available. */
 #undef HAVE_COMMONCRYPTO_COMMONDIGEST_H
 
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -187,9 +190,15 @@
</span> /* Define to 1 if you have the 'sysctlbyname' function. */
 #undef HAVE_SYSCTLBYNAME
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+/* Define to 1 if you have the <sys/attr.h> header file. */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#undef HAVE_SYS_ATTR_H
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> /* Define to 1 if you have the <sys/cdefs.h> header file. */
 #undef HAVE_SYS_CDEFS_H
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+/* Define to 1 if you have the <sys/clonefile.h> header file. */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#undef HAVE_SYS_CLONEFILE_H
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> /* Define to 1 if you have the <sys/event.h> header file. */
 #undef HAVE_SYS_EVENT_H
 
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/images_to_archives.tcl.in b/src/images_to_archives.tcl.in
</span><span style='display:block; white-space:pre;color:#808080;'>index 69eb980cc..9adfcd1ec 100755
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/images_to_archives.tcl.in
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/images_to_archives.tcl.in
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -32,6 +32,7 @@ puts "This could take a while..."
</span> set archived_list [list]
 set installed_len [llength $ilist]
 set counter 0
<span style='display:block; white-space:pre;background:#e0ffe0;'>+set archive_required [expr {$macports::portimage_mode eq "archive"}]
</span> 
 foreach installed $ilist {
     incr counter
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -50,7 +51,7 @@ foreach installed $ilist {
</span>         set location ""
     }
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    if {$location eq "" || ![file isfile $location]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {$location eq "" || ![file exists $location] || ($archive_required && ![file isfile $location])} {
</span>         # no image archive present, so make one
         set archs [$installed archs]
         if {$archs eq "" || $archs == 0} {
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/macports1.0/macports.tcl b/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 7d378de5a..d7ec17e12 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/macports1.0/macports.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -50,7 +50,7 @@ namespace eval macports {
</span>     variable bootstrap_options [dict create]
     # Config file options with no special handling
     foreach opt [list binpath auto_path extra_env portdbformat \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        portarchivetype hfscompression portautoclean \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        portarchivetype portimage_mode hfscompression portautoclean \
</span>         porttrace portverbose keeplogs destroot_umask rsync_server rsync_options \
         rsync_dir startupitem_autostart startupitem_type startupitem_install \
         place_worksymlink xcodeversion xcodebuildcmd xcodecltversion xcode_license_unaccepted \
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -74,7 +74,8 @@ namespace eval macports {
</span>     variable portinterp_options [list \
         portdbpath porturl portpath portbuildpath auto_path prefix prefix_frozen portsharepath \
         registry.path registry.format user_home user_path user_ssh_auth_sock \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        portarchivetype archivefetch_pubkeys portautoclean porttrace keeplogs portverbose destroot_umask \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        portarchivetype portarchive_hfscompression archivefetch_pubkeys \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        portautoclean portimage_mode porttrace keeplogs portverbose destroot_umask \
</span>         rsync_server rsync_options rsync_dir startupitem_autostart startupitem_type startupitem_install \
         place_worksymlink macportsuser sudo_user \
         configureccache ccache_dir ccache_size configuredistcc configurepipe buildnicevalue buildmakejobs \
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -954,6 +955,7 @@ proc mportinit {{up_ui_options {}} {up_options {}} {up_variations {}}} {
</span>         macports::portarchivetype \
         macports::portautoclean \
         macports::portautoclean_frozen \
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        macports::portimage_mode \
</span>         macports::porttrace \
         macports::porttrace_frozen \
         macports::portverbose \
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -976,6 +978,7 @@ proc mportinit {{up_ui_options {}} {up_options {}} {up_variations {}}} {
</span>         macports::delete_la_files \
         macports::cxx_stdlib \
         macports::hfscompression \
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        macports::portarchive_hfscompression \
</span>         macports::host_cache \
         macports::porturl_prefix_map \
         macports::ui_options \
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -1405,10 +1408,29 @@ Please edit sources.conf and change '$url' to '[string range $url 0 end-6]tarbal
</span>         set portarchivetype [lindex $portarchivetype 0]
     }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    # How to store port images
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {[info exists portimage_mode] &&
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        $portimage_mode ni {archive directory directory_and_archive}} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        ui_warn "Unknown portimage_mode value '$portimage_mode', using default"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        unset portimage_mode
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {![info exists portimage_mode]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # Using an extracted directory is usually only a good idea if
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # the filesystem supports COW clones.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {![catch {fs_clone_capable [file join $portdbpath software]} result] && $result} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set portimage_mode directory
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set portimage_mode archive
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set portimage::keep_imagedir [expr {$portimage_mode ne "archive"}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set portimage::keep_archive [expr {$portimage_mode ne "directory"}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span>     # Enable HFS+ compression by default
     if {![info exists hfscompression]} {
         set hfscompression yes
     }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    set portarchive_hfscompression $hfscompression
</span> 
     # Set rync options
     if {![info exists rsync_server]} {
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/pextlib1.0/Pextlib.c b/src/pextlib1.0/Pextlib.c
</span><span style='display:block; white-space:pre;color:#808080;'>index 97ca985cd..4d0ccadcc 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/pextlib1.0/Pextlib.c
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/pextlib1.0/Pextlib.c
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -70,6 +70,14 @@
</span> #include <unistd.h>
 #include <assert.h>
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+/* For clonefile */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#ifdef HAVE_SYS_ATTR_H
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#include <sys/attr.h>
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#endif
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#ifdef HAVE_SYS_CLONEFILE_H
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#include <sys/clonefile.h>
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#endif
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> #ifdef __MACH__
 #include <mach-o/loader.h>
 #include <mach-o/fat.h>
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -1110,6 +1118,38 @@ int FSCloneCapableCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc
</span>     return TCL_OK;
 }
 
<span style='display:block; white-space:pre;background:#e0ffe0;'>+/**
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+ * Interface to clonefile(2)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+ */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+int ClonefileCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    int ret = -1;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (objc != 3) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_WrongNumArgs(interp, 1, objv, "srcpath dstpath");
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#ifdef HAVE_CLONEFILE
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    char *srcpath = Tcl_GetString(objv[1]);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    char *dstpath = Tcl_GetString(objv[2]);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (!srcpath || !dstpath) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    ret = clonefile(srcpath, dstpath, CLONE_NOFOLLOW);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (ret != 0) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_SetErrno(errno);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_ResetResult(interp);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        Tcl_AppendResult(interp, "clonefile failed: ", (char *)Tcl_PosixError(interp), NULL);
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+#endif /* HAVE_CLONEFILE */
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if (ret == 0) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        return TCL_OK;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    return TCL_ERROR;
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span> int Pextlib_Init(Tcl_Interp *interp)
 {
     if (Tcl_InitStubs(interp, "8.4", 0) == NULL)
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -1172,6 +1212,7 @@ int Pextlib_Init(Tcl_Interp *interp)
</span> 
     Tcl_CreateObjCommand(interp, "fs_case_sensitive", FSCaseSensitiveCmd, NULL, NULL);
     Tcl_CreateObjCommand(interp, "fs_clone_capable", FSCloneCapableCmd, NULL, NULL);
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    Tcl_CreateObjCommand(interp, "clonefile", ClonefileCmd, NULL, NULL);
</span> 
     if (Tcl_PkgProvide(interp, "Pextlib", "1.0") != TCL_OK)
         return TCL_ERROR;
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/port1.0/portinstall.tcl b/src/port1.0/portinstall.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 33ee3509e..18a5a2de7 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/port1.0/portinstall.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/port1.0/portinstall.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -74,7 +74,8 @@ proc portinstall::create_archive {location archive.type} {
</span>     global workpath destpath portpath subport version revision portvariants \
            epoch configure.cxx_stdlib cxx_stdlib PortInfo \
            archive.env archive.cmd archive.pre_args archive.args \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-           archive.post_args archive.dir depends_lib depends_run
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+           archive.post_args archive.dir depends_lib depends_run \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+           portarchive_hfscompression
</span>     set archive.env {}
     set archive.cmd {}
     set archive.pre_args {}
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -119,7 +120,7 @@ proc portinstall::create_archive {location archive.type} {
</span>                 return -code error "No '$pax' was found on this system!"
             }
         }
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        t(ar|bz|lz|xz|gz) {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        t(ar|bz|lz|xz|gz|mptar) {
</span>             set tar "tar"
             if {[catch {set tar [findBinary $tar ${portutil::autoconf::tar_path}]} errmsg] == 0} {
                 ui_debug "Using $tar"
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -159,7 +160,25 @@ proc portinstall::create_archive {location archive.type} {
</span>                         return -code error "No '$gzip' was found on this system!"
                     }
                 } else {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                    set archive.args "[shellescape ${location}] ."
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    if {${archive.type} eq "tmptar"} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        # Pass through tar for hardlink detection and HFS compression,
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        # but extract without saving the tar file.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        if {${portarchive_hfscompression} && [getuid] == 0 &&
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            ![catch {binaryInPath bsdtar}] &&
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            ![catch {exec bsdtar -x --hfsCompression < /dev/null >& /dev/null}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        } then {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            set extract_tar bsdtar
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            set extract_tar_args {-xvp --hfsCompression -f}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            set extract_tar $tar
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                            set extract_tar_args {-xvpf}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        set archive.args {- .}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        set archive.post_args "| $extract_tar -C $location $extract_tar_args -"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        file mkdir $location
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        set archive.args "[shellescape ${location}] ."
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    }
</span>                 }
             } else {
                 ui_debug $errmsg
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -322,7 +341,7 @@ proc portinstall::create_archive {location archive.type} {
</span>     # Now create the archive
     ui_debug "Creating [file tail $location]"
     command_exec archive
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    ui_debug "Archive [file tail $location] packaged"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    ui_debug "Port image [file tail $location] created"
</span> 
     # Cleanup all control files when finished
     set control_files [glob -nocomplain -types f [file join $destpath +*]]
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -339,7 +358,7 @@ proc portinstall::extract_contents {location type} {
</span> proc portinstall::install_main {args} {
     global subport version portpath depends_run revision user_options \
     portvariants requested_variants depends_lib PortInfo epoch \
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    portarchivetype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    portarchivetype portimage_mode
</span>     variable file_is_binary
     variable actual_cxx_stdlib
     variable cxx_stdlib_overridden
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -366,10 +385,18 @@ proc portinstall::install_main {args} {
</span>         set cxxinfo [extract_archive_metadata $location $current_archive_type cxx_info]
         lassign $cxxinfo actual_cxx_stdlib cxx_stdlib_overridden
     } else {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        # throws an error if an unsupported value has been configured
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        archiveTypeIsSupported $portarchivetype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {$portimage_mode eq "directory"} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Special value to avoid writing archive out to disk, since
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # only the extracted dir should be kept.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set archivetype tmptar
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set location [file rootname $location]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # throws an error if an unsupported value has been configured
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            archiveTypeIsSupported $portarchivetype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set archivetype $portarchivetype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span>         # create archive from the destroot
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        create_archive $location $portarchivetype
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        create_archive $location $archivetype
</span>     }
 
     # can't do this inside the write transaction due to deadlock issues with _get_dep_port
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/registry2.0/portimage.tcl b/src/registry2.0/portimage.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index 339f478c1..62ffbca37 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/registry2.0/portimage.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/registry2.0/portimage.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -114,7 +114,7 @@ proc activate {name {version ""} {revision ""} {variants 0} {options ""}} {
</span>             #registry::entry close $requested
             return -code error "Image error: ${name} @${specifier} not installed as an image."
         }
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {![::file isfile $location]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {![::file exists $location]} {
</span>             #registry::entry close $requested
             return -code error "Image error: Can't find image file $location"
         }
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -316,54 +316,124 @@ proc _check_registry {name version revision variants {return_all 0}} {
</span>     throw registry::invalid "Registry error: ${name}${composite_spec} is not installed."
 }
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-## Activates a file from an image into the filesystem. Deals with symlinks
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-## and regular files.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# Mapping of directory paths to device numbers. Used to check if files
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# can be hardlinked or cloned, or must be copied.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+variable dir_devices [dict create]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## Activates a list of files from an image into the filesystem. Deals
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## with symlinks and regular files.
</span> ##
<span style='display:block; white-space:pre;background:#ffe0e0;'>-## @param [in] srcfile path to file in image
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-## @param [in] dstfile path to activate file to
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-## @return 1 if file needs to be explicitly deleted if we have to roll back, 0 otherwise
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-proc _activate_file {srcfile dstfile} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    if {[catch {::file type $srcfile} result]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @param [in] files list of target file paths
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @param [in] imageroot path to root of image directory
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @param [in] imageroot path to root of image directory
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @param [in] rollback_var list name to append activated files to
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @return list of files that need to be explicitly deleted if we have to roll back
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc _activate_files {srcfiles dstfiles imageroot rollback_var} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable progress_step; variable progress_total_steps
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable dir_devices; variable keep_imagedir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    upvar $rollback_var rollback_list
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set use_clone [expr {$keep_imagedir && [fs_clone_capable $imageroot]}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    ::file stat $imageroot statinfo
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set imagedev $statinfo(dev)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set all_attrs [expr {[getuid] == 0}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set hardlinks [dict create]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach srcfile $srcfiles dstfile $dstfiles {
</span>         # this can happen if the archive was built on case-sensitive and we're case-insensitive
         # we know any existing dstfile is ours because we checked for conflicts earlier
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {![catch {file type $dstfile}]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {![catch {::file type $dstfile}]} {
</span>             ui_debug "skipping case-conflicting file: $srcfile"
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            return 0
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        } else {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            error $result
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            continue
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        ui_debug "activating file: $dstfile"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set hardlinked 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        ::file lstat $srcfile statinfo
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {$statinfo(nlink) > 1} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Hard linked file
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {[dict exists $hardlinks $statinfo(ino)]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # Link to the primary link
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if {![catch {::file link -hard $dstfile [dict get $hardlinks $statinfo(ino)]}]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    set hardlinked 1
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # Fall back to normal method if hardlinking failed. The destinations
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # for the links could be on different devices, or a destination
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # filesystem might not even support hard links.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # Set this as the primary link and activate as normal.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                dict set hardlinks $statinfo(ino) $dstfile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span>         }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {!$hardlinked} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {$use_clone && [dict get $dir_devices [::file dirname $dstfile]] == $imagedev} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                clonefile $srcfile $dstfile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # not all permissions are preserved by clonefile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if {$statinfo(type) ne "link"} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    ::file attributes $dstfile -permissions {*}[::file attributes $srcfile -permissions]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } elseif {$keep_imagedir} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ::file copy $srcfile $dstfile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if {$statinfo(type) ne "link"} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    if {$all_attrs} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        ::file attributes $dstfile {*}[::file attributes ${srcfile}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        # not root, so can't set owner/group
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                        ::file attributes $dstfile -permissions {*}[::file attributes ${srcfile} -permissions]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    ::file mtime $dstfile [::file mtime $srcfile]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ::file rename $srcfile $dstfile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        lappend rollback_list $dstfile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        _progress update $progress_step $progress_total_steps
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        incr progress_step
</span>     }
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    ui_debug "activating file: $dstfile"
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    ::file rename $srcfile $dstfile
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    return 1
</span> }
 
 ## Activates a directory from an image into the filesystem.
 ##
<span style='display:block; white-space:pre;background:#ffe0e0;'>-## @param [in] srcdir path to dir in image
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-## @param [in] dstdir path to activate dir to
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-proc _activate_directory {srcdir dstdir} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    # Don't recursively copy directories
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    ui_debug "activating directory: $dstdir"
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    # Don't do anything if the directory already exists.
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    if {![::file isdirectory $dstdir]} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        ::file mkdir $dstdir
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        # fix attributes on the directory.
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        if {[getuid] == 0} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            ::file attributes $dstdir {*}[::file attributes $srcdir]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        } else {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            # not root, so can't set owner/group
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            ::file attributes $dstdir -permissions {*}[::file attributes $srcdir -permissions]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @param [in] dirs list of destination directory paths
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+## @param [in] imageroot path to root of image directory
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc _activate_directories {dirs imageroot} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable progress_step; variable progress_total_steps
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable dir_devices
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set all_attrs [expr {[getuid] == 0}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    foreach dir $dirs {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        ui_debug "activating directory: $dir"
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # Don't do anything if the directory already exists.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {![::file isdirectory $dir]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ::file mkdir $dir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set srcdir ${imageroot}${dir}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # fix attributes on the directory.
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {$all_attrs} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ::file attributes $dir {*}[::file attributes ${srcdir}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                # not root, so can't set owner/group
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                ::file attributes $dir -permissions {*}[::file attributes ${srcdir} -permissions]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # set mtime on installed element
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ::file mtime $dir [::file mtime ${srcdir}]
</span>         }
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        # set mtime on installed element
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        ::file mtime $dstdir [::file mtime $srcdir]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {![dict exists $dir_devices $dir]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ::file stat $dir statinfo
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            dict set dir_devices $dir $statinfo(dev)
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        _progress update $progress_step $progress_total_steps
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        incr progress_step
</span>     }
 }
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-# extract an archive to a temporary location
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+# extract an archive to a directory
</span> # returns: path to the extracted directory
<span style='display:block; white-space:pre;background:#ffe0e0;'>-proc extract_archive_to_tmpdir {location} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    set extractdir [mkdtemp [::file dirname $location]/mpextractXXXXXXXX]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+proc extract_archive_to_imagedir {location} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set extractdir [file rootname $location]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {[file exists $extractdir]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set extractdir [mkdtemp ${extractdir}XXXXXXXX]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        file mkdir $extractdir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span>     set startpwd [pwd]
 
     try {
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -568,6 +638,8 @@ proc _progress {args} {
</span> proc _activate_contents {port {rename_list {}}} {
     variable force
     variable noexec
<span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable keep_archive
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    variable keep_imagedir
</span>     variable progress_step
     variable progress_total_steps
 
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -577,8 +649,24 @@ proc _activate_contents {port {rename_list {}}} {
</span>     set location [$port location]
     set imagefiles [$port imagefiles]
     set num_imagefiles [llength $imagefiles]
<span style='display:block; white-space:pre;background:#ffe0e0;'>-    set progress_total_steps [expr {$num_imagefiles * 3}]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-    set extracted_dir [extract_archive_to_tmpdir $location]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    set progress_step 0
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    if {[::file isfile $location]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set progress_total_steps [expr {$num_imagefiles * 3}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set extracted_dir [extract_archive_to_imagedir $location]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # extract phase complete, assume 1/3 is done
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set progress_step $num_imagefiles
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {!$keep_archive} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            registry::write {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                $port location $extracted_dir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            file delete $location
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set extracted_dir $location
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set progress_total_steps [expr {$num_imagefiles * 2}]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        _progress start
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+    }
</span> 
     set backups [list]
     set seendirs [dict create]
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -593,8 +681,6 @@ proc _activate_contents {port {rename_list {}}} {
</span>     set todeactivate [list]
     try {
         registry::write {
<span style='display:block; white-space:pre;background:#ffe0e0;'>-            # extract phase complete, assume 1/3 is done
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-            set progress_step $num_imagefiles
</span>             foreach file $imagefiles {
                 incr progress_step
                 _progress update $progress_step $progress_total_steps
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -709,27 +795,16 @@ proc _activate_contents {port {rename_list {}}} {
</span> 
             try {
                 $port activate $imagefiles
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                foreach dir $directories {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    _activate_directory ${extracted_dir}${dir} $dir
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    _progress update $progress_step $progress_total_steps
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    incr progress_step
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                }
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                foreach file $files {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    if {[_activate_file ${extracted_dir}${file} $file] == 1} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        lappend rollback_filelist $file
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    }
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    _progress update $progress_step $progress_total_steps
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    incr progress_step
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                _activate_directories $directories $extracted_dir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                _activate_files [lmap f $files {string cat ${extracted_dir}${f}}] \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                $files $extracted_dir rollback_filelist
</span>                 foreach {src dest} $confirmed_rename_list {
                     $port deactivate [list $src]
                     $port activate [list $src] [list $dest]
<span style='display:block; white-space:pre;background:#ffe0e0;'>-                    if {[_activate_file ${extracted_dir}${src} $dest] == 1} {
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                        lappend rollback_filelist $dest
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-                    }
</span>                 }
<span style='display:block; white-space:pre;background:#e0ffe0;'>+                _activate_files [lmap {src _} $confirmed_rename_list {string cat ${extracted_dir}${src}}] \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                [lmap {_ dst} $confirmed_rename_list {set dst}] \
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                                $extracted_dir rollback_filelist
</span> 
                 # Recording that the port has been activated should be done
                 # here so that this information cannot be inconsistent with the
<span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -800,8 +875,10 @@ proc _activate_contents {port {rename_list {}}} {
</span>         #foreach entry $todeactivate {
         #    registry::entry close $entry
         #}
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        # remove temp image dir
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        ::file delete -force $extracted_dir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {!$keep_imagedir} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # remove temp image dir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            ::file delete -force $extracted_dir
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span>     }
 }
 
<span style='display:block; white-space:pre;color:#808080;'>diff --git a/src/registry2.0/portuninstall.tcl b/src/registry2.0/portuninstall.tcl
</span><span style='display:block; white-space:pre;color:#808080;'>index ff70a8a81..0b3f5855d 100644
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>--- a/src/registry2.0/portuninstall.tcl
</span><span style='display:block; white-space:pre;background:#e0e0ff;'>+++ b/src/registry2.0/portuninstall.tcl
</span><span style='display:block; white-space:pre;background:#e0e0e0;'>@@ -267,12 +267,30 @@ proc uninstall {portname {version ""} {revision ""} {variants 0} {options ""}} {
</span>     } else {
         ui_msg "$UI_PREFIX [format [msgcat::mc "Uninstalling %s @%s"] $portname $composite_spec]"
 
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        # Get the full path to the image file
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        set imagefile [$port location]
</span><span style='display:block; white-space:pre;background:#ffe0e0;'>-        file delete $imagefile
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        # Get the full path to the port image
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set imagepath [$port location]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        set imagedir [file dirname $imagepath]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        if {[file isfile $imagepath]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            file delete $imagepath
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Also delete extracted image dir if present
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set extracted_path [file rootname $imagepath]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            if {[file isdirectory $extracted_path]} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                file delete -force $extracted_path
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        } else {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Image is a directory
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            file delete -force $imagepath
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            # Also delete any associated archives
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            set imagename [file tail $imagepath]
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            foreach archive [glob -nocomplain -directory $imagedir ${imagename}.*] {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                if {[file rootname [file tail $archive]] eq $imagename} {
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                    file delete -force $archive
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+                }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+            }
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        }
</span>         # Try to delete the port's image dir; will fail if there are more image
         # files so just ignore the failure
<span style='display:block; white-space:pre;background:#ffe0e0;'>-        catch {file delete [::file dirname $imagefile]}
</span><span style='display:block; white-space:pre;background:#e0ffe0;'>+        catch {file delete $imagedir}
</span> 
         # We want to delete the portfile if not referenced by any other ports
         set portfile [$port portfile]
</pre><pre style='margin:0'>

</pre>