2016-08-18 22:11:42 +03:00
|
|
|
require "set"
|
|
|
|
require "tempfile"
|
|
|
|
|
|
|
|
require "hbc/container/base"
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
module Hbc
|
|
|
|
class Container
|
|
|
|
class Dmg < Base
|
|
|
|
def self.me?(criteria)
|
|
|
|
!criteria.command.run("/usr/bin/hdiutil",
|
|
|
|
# realpath is a failsafe against unusual filenames
|
|
|
|
args: ["imageinfo", Pathname.new(criteria.path).realpath],
|
|
|
|
print_stderr: false).stdout.empty?
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
attr_reader :mounts
|
|
|
|
def initialize(*args)
|
|
|
|
super(*args)
|
|
|
|
@mounts = []
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def extract
|
|
|
|
mount!
|
|
|
|
assert_mounts_found
|
|
|
|
extract_mounts
|
|
|
|
ensure
|
|
|
|
eject!
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def mount!
|
|
|
|
plist = @command.run!("/usr/bin/hdiutil",
|
|
|
|
# realpath is a failsafe against unusual filenames
|
|
|
|
args: %w[mount -plist -nobrowse -readonly -noidme -mountrandom /tmp] + [Pathname.new(@path).realpath],
|
|
|
|
input: %w[y])
|
|
|
|
.plist
|
|
|
|
@mounts = mounts_from_plist(plist)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def eject!
|
|
|
|
@mounts.each do |mount|
|
|
|
|
# realpath is a failsafe against unusual filenames
|
|
|
|
mountpath = Pathname.new(mount).realpath
|
|
|
|
next unless mountpath.exist?
|
|
|
|
|
|
|
|
begin
|
|
|
|
tries ||= 2
|
|
|
|
@command.run("/usr/sbin/diskutil",
|
|
|
|
args: ["eject", mountpath],
|
|
|
|
print_stderr: false)
|
|
|
|
|
|
|
|
raise CaskError, "Failed to eject #{mountpath}" if mountpath.exist?
|
|
|
|
rescue CaskError => e
|
|
|
|
raise e if (tries -= 1).zero?
|
|
|
|
sleep 1
|
|
|
|
retry
|
|
|
|
end
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
private
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def extract_mounts
|
|
|
|
@mounts.each(&method(:extract_mount))
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def extract_mount(mount)
|
|
|
|
Tempfile.open(["", ".bom"]) do |bomfile|
|
|
|
|
bomfile.close
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
Tempfile.open(["", ".list"]) do |filelist|
|
|
|
|
filelist.write(bom_filelist_from_path(mount))
|
|
|
|
filelist.close
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
@command.run!("/usr/bin/mkbom", args: ["-s", "-i", filelist.path, "--", bomfile.path])
|
|
|
|
@command.run!("/usr/bin/ditto", args: ["--bom", bomfile.path, "--", mount, @cask.staged_path])
|
|
|
|
end
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def bom_filelist_from_path(mount)
|
|
|
|
Dir.chdir(mount) {
|
|
|
|
Dir.glob("**/*", File::FNM_DOTMATCH).map { |path|
|
|
|
|
next if skip_path?(Pathname(path))
|
|
|
|
path == "." ? path : path.prepend("./")
|
|
|
|
}.compact.join("\n").concat("\n")
|
|
|
|
}
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def skip_path?(path)
|
|
|
|
dmg_metadata?(path) || system_dir_symlink?(path)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
# unnecessary DMG metadata
|
|
|
|
DMG_METADATA_FILES = %w[
|
|
|
|
.background
|
|
|
|
.com.apple.timemachine.donotpresent
|
2016-10-01 04:35:04 +02:00
|
|
|
.com.apple.timemachine.supported
|
2016-09-24 13:52:43 +02:00
|
|
|
.DocumentRevisions-V100
|
|
|
|
.DS_Store
|
|
|
|
.fseventsd
|
|
|
|
.MobileBackups
|
|
|
|
.Spotlight-V100
|
|
|
|
.TemporaryItems
|
|
|
|
.Trashes
|
|
|
|
.VolumeIcon.icns
|
|
|
|
].to_set.freeze
|
|
|
|
|
|
|
|
def dmg_metadata?(path)
|
|
|
|
relative_root = path.sub(%r{/.*}, "")
|
|
|
|
DMG_METADATA_FILES.include?(relative_root.basename.to_s)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def system_dir_symlink?(path)
|
|
|
|
# symlinks to system directories (commonly to /Applications)
|
|
|
|
path.symlink? && MacOS.system_dir?(path.readlink)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def mounts_from_plist(plist)
|
|
|
|
return [] unless plist.respond_to?(:fetch)
|
|
|
|
plist.fetch("system-entities", []).map { |entity|
|
|
|
|
entity["mount-point"]
|
|
|
|
}.compact
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def assert_mounts_found
|
|
|
|
raise CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if @mounts.empty?
|
|
|
|
end
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
end
|