diff --git a/Gemfile b/Gemfile index 1d3facf..0d0a00c 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ source "https://rubygems.org" # Specify your gem's dependencies in mixlib-archive.gemspec gemspec +gem "ffi-libarchive" + group(:changelog) do gem "github_changelog_generator", git: "https://github.com/chef/github-changelog-generator" end diff --git a/lib/mixlib/archive.rb b/lib/mixlib/archive.rb index ae31cd9..6a259fa 100644 --- a/lib/mixlib/archive.rb +++ b/lib/mixlib/archive.rb @@ -11,8 +11,14 @@ def initialize(archive, empty: false) @empty = empty archive = File.expand_path(archive) - # for now we only support Tar format archives. - @archiver = Mixlib::Archive::Tar.new(archive) + begin + # we prefer to use libarchive, which supports a great big pile o' stuff + require "mixlib/archive/lib_archive" + @archiver = Mixlib::Archive::LibArchive.new(archive) + rescue LoadError + # but if we can't use that, we'll fall back to ruby's native tar implementation + @archiver = Mixlib::Archive::Tar.new(archive) + end end class Log diff --git a/lib/mixlib/archive/lib_archive.rb b/lib/mixlib/archive/lib_archive.rb new file mode 100644 index 0000000..29aa1ac --- /dev/null +++ b/lib/mixlib/archive/lib_archive.rb @@ -0,0 +1,63 @@ +require "ffi-libarchive" + +module Mixlib + class Archive + class LibArchive + attr_reader :options + attr_reader :archive + + def initialize(archive, options = {}) + @archive = archive + @options = options + end + + # Extracts the archive to the given +destination+ + # + # === Parameters + # perms:: should the extracter use permissions from the archive. + # ignore[Array]:: an array of matches of file paths to ignore + def extract(destination, perms: true, ignore: []) + ignore_re = Regexp.union(ignore) + flags = perms ? ::Archive::EXTRACT_PERM : nil + Dir.chdir(destination) do + reader = ::Archive::Reader.open_filename(@archive) + + reader.each_entry do |entry| + if entry.pathname =~ ignore_re + Mixlib::Archive::Log.warn "ignoring entry #{entry.pathname}" + next + end + + reader.extract(entry, flags.to_i) + end + reader.close + end + end + + # Creates an archive with the given set of +files+ + # + # === Parameters + # gzip:: should the archive be gzipped? + def create(files, gzip: false) + compression = gzip ? ::Archive::COMPRESSION_GZIP : ::Archive::COMPRESSION_NONE + # "PAX restricted" will use PAX extensions when it has to, but will otherwise + # use ustar for maximum compatibility + format = ::Archive::FORMAT_TAR_PAX_RESTRICTED + + ::Archive.write_open_filename(archive, compression, format) do |tar| + files.each do |fn| + tar.new_entry do |entry| + entry.pathname = fn + entry.copy_stat(fn) + tar.write_header(entry) + if File.file?(fn) + content = File.read(fn) + tar.write_data(content) + end + end + end + end + end + end + end +end