diff --git a/.travis.yml b/.travis.yml index 4f36209..1111476 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,15 @@ sudo: false language: ruby cache: bundler +addons: + apt: + packages: + - libarchive-dev + matrix: include: - - rvm: 2.3.6 - - rvm: 2.4.3 - - rvm: 2.5.0 + - rvm: 2.4.4 + - rvm: 2.5.1 - rvm: ruby-head allow_failures: - rvm: ruby-head diff --git a/Gemfile b/Gemfile index 1d3facf..c54a684 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,4 @@ source "https://rubygems.org" # Specify your gem's dependencies in mixlib-archive.gemspec gemspec -group(:changelog) do - gem "github_changelog_generator", git: "https://github.com/chef/github-changelog-generator" -end +gem "ffi-libarchive" 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 diff --git a/lib/mixlib/archive/tar.rb b/lib/mixlib/archive/tar.rb index ea6933c..626b033 100644 --- a/lib/mixlib/archive/tar.rb +++ b/lib/mixlib/archive/tar.rb @@ -119,11 +119,11 @@ def reader(&block) ensure if file file.close unless file.closed? - file = nil + file = nil # rubocop:disable Lint/UselessAssignment end if raw raw.close unless raw.closed? - raw = nil + raw = nil # rubocop:disable Lint/UselessAssignment end end diff --git a/spec/mixlib/archive_spec.rb b/spec/mixlib/archive_spec.rb index 48dd331..f6f95c7 100644 --- a/spec/mixlib/archive_spec.rb +++ b/spec/mixlib/archive_spec.rb @@ -2,15 +2,17 @@ describe Mixlib::Archive do let(:target) { "../fixtures/foo.tar" } - let (:destination) { Dir.mktmpdir } - let (:files) { %w{ file dir/file new_file.rb } } + let(:destination) { ::Dir.mktmpdir } + let(:files) { %w{ file dir/file new_file.rb } } - let (:archive) { described_class.new(target) } - let (:archiver) { double(Mixlib::Archive::Tar, extract: true) } + let(:archive) { described_class.new(target) } + let(:tar_archiver) { double(Mixlib::Archive::Tar, extract: true) } + let(:archiver) { double(Mixlib::Archive::LibArchive, extract: true) } before do allow(File).to receive(:expand_path).and_call_original allow(Mixlib::Archive::Tar).to receive(:new).with(any_args).and_return(archiver) + allow(Mixlib::Archive::LibArchive).to receive(:new).with(any_args).and_return(archiver) end after do @@ -31,11 +33,12 @@ before do end - it "with the correct archiver" do + it "with the default archiver" do expect(File).to receive(:expand_path).with(target).and_return(expanded_target) - expect(Mixlib::Archive::Tar).to receive(:new).with(expanded_target) + expect(Mixlib::Archive::LibArchive).to receive(:new).with(expanded_target) described_class.new(target) end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e98e7c8..331ffa0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,5 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) +require "tmpdir" require "mixlib/archive" +require "mixlib/archive/tar" +require "mixlib/archive/lib_archive"