# HG changeset patch
# User Andrew Janke <andrew@apjanke.net>
# Date 1548119248 18000
#      Mon Jan 21 20:07:28 2019 -0500
# Branch tar-support-BSD-tar-4
# Node ID 40a7241161802834528b1eb09c3b401f971829b4
# Parent  ada14ef3067feca546cf8dc2543e924b58ae6165
tar, unpack: Add support for BSD tar

The current implementation assumes a GNU tar, but some systems have BSD tar as their default, and it behaves differently. This change adds detection of which flavor of tar is present on the system, and adds handling for BSD tar's different output behavior.

Fixes https://savannah.gnu.org/bugs/index.php?53695

diff --git a/scripts/miscellaneous/private/tar_is_bsd.m b/scripts/miscellaneous/private/tar_is_bsd.m
new file mode 100644
--- /dev/null
+++ b/scripts/miscellaneous/private/tar_is_bsd.m
@@ -0,0 +1,42 @@
+## Copyright (C) 2019 Andrew Janke
+##
+## This file is part of Octave.
+##
+## Octave 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 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{out} =} tar_is_bsd ()
+## True if the default tar command is BSD tar.
+##
+## Checks whether the default tar command (the one invoked when an un-prefixed
+## @code{tar} is executed) is BSD tar or another tar. Caches the results for
+## performance.
+##
+## Returns true if the detected tar is BSD tar, and false otherwise. Errors if
+## @code{tar --version} does not succeed.
+## @end deftypefn
+
+function out = tar_is_bsd ()
+  ## BSD tar needs to be handled differently from GNU tar
+  persistent cache
+  if isempty (cache)
+    [status, tar_ver_str] = system ("tar --version");
+    if (status)
+      error ("tar: Failed executing tar --version (status = %d)", status);
+    endif
+    cache = ! isempty (regexp (tar_ver_str, "bsdtar"));
+  endif
+  out = cache;
+endfunction
diff --git a/scripts/miscellaneous/tar.m b/scripts/miscellaneous/tar.m
--- a/scripts/miscellaneous/tar.m
+++ b/scripts/miscellaneous/tar.m
@@ -55,18 +55,24 @@ function filelist = tar (tarfile, files,
 
   tarfile = make_absolute_filename (tarfile);
 
   if (ispc)
     ## Change tarfile into a mingw style acceptable for tar
     tarfile = __w2mpth__ (tarfile);
   endif
 
-  cmd = sprintf ("tar cvf %s -C %s %s",
-                          tarfile, rootdir, sprintf (" %s", files{:}));
+  ## BSD tar emits progress on stderr
+  if (tar_is_bsd ())
+    cmd = sprintf ("tar cvf %s -C %s %s 2>&1",
+                            tarfile, rootdir, sprintf (" '%s'", files{:}));
+  else
+    cmd = sprintf ("tar cvf %s -C %s %s",
+                            tarfile, rootdir, sprintf (" %s", files{:}));
+  end
 
   ## Save and restore the TAR_OPTIONS environment variable used by GNU tar.
   tar_options_env = getenv ("TAR_OPTIONS");
   unwind_protect
     unsetenv ("TAR_OPTIONS");
     [status, output] = system (cmd);
   unwind_protect_cleanup
     if (! isempty (tar_options_env))
@@ -76,16 +82,21 @@ function filelist = tar (tarfile, files,
 
   if (status)
     error ("tar: tar exited with status = %d", status);
   endif
 
   if (nargout > 0)
     filelist = ostrsplit (output, "\r\n", true);
     filelist = filelist';
+
+    ## BSD tar emits file actions in the first 2 columns
+    if (tar_is_bsd ())
+      filelist = cellfun (@(x) x(3:end), filelist, 'UniformOutput', false);
+    endif
   endif
 
 endfunction
 
 
 ## FIXME: This test may fail if the tar command is not installed.  If this
 ##        test fails, it might be better to change it into a testif with a
 ##        runtime condition on the tar program.
diff --git a/scripts/miscellaneous/unpack.m b/scripts/miscellaneous/unpack.m
--- a/scripts/miscellaneous/unpack.m
+++ b/scripts/miscellaneous/unpack.m
@@ -303,17 +303,21 @@ function files = __parse_zip__ (output)
 
   ## Skip first line which is Archive header.
   files = char (output(2:end));
   ## Trim constant width prefix and return cell array.
   files = cellstr (files(:,14:end));
 endfunction
 
 function output = __parse_tar__ (output)
-  ## This is a no-op, but it makes things simpler for other cases.
+  ## BSD tar emits file actions in the first 2 columns
+
+  if (tar_is_bsd ())
+    output = cellfun (@(x) x(3:end), output, 'UniformOutput', false);
+  endif
 endfunction
 
 function files = __parse_gzip__ (output)
   ## Parse the output from gzip and gunzip returning the files
   ## compressed (or decompressed).
 
   files = regexprep (output, '^.+ -- replaced with (.*)$', '$1');
 endfunction
