134 lines
3.9 KiB
Ruby
Raw Normal View History

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],
2017-03-08 03:03:49 +01:00
input: "y\n")
2016-09-24 13:52:43 +02:00
.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 ||= 3
if tries > 1
@command.run("/usr/sbin/diskutil",
args: ["eject", mountpath],
print_stderr: false)
else
@command.run("/usr/sbin/diskutil",
args: ["unmount", "force", mountpath],
print_stderr: false)
end
raise CaskError, "Failed to eject #{mountpath}" if mountpath.exist?
2016-09-24 13:52:43 +02:00
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)
2016-10-23 14:44:14 +02:00
Dir.chdir(mount) do
Dir.glob("**/*", File::FNM_DOTMATCH).map do |path|
2016-09-24 13:52:43 +02:00
next if skip_path?(Pathname(path))
2017-05-29 18:24:52 +01:00
(path == ".") ? path : path.prepend("./")
2016-10-23 14:44:14 +02:00
end.compact.join("\n").concat("\n")
end
2016-09-24 13:52:43 +02:00
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
2016-10-14 20:33:16 +02:00
DMG_METADATA_FILES = Set.new %w[
.background
.com.apple.timemachine.donotpresent
.com.apple.timemachine.supported
.DocumentRevisions-V100
.DS_Store
.fseventsd
.MobileBackups
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
].freeze
2016-09-24 13:52:43 +02:00
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)
2016-10-23 14:44:14 +02:00
plist.fetch("system-entities", []).map { |e| e["mount-point"] }.compact
2016-09-24 13:52:43 +02:00
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