191 lines
5.1 KiB
Ruby
Raw Normal View History

require "tempfile"
module UnpackStrategy
class Dmg
include UnpackStrategy
2018-07-24 07:03:24 +02:00
module Bom
DMG_METADATA = 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
private_constant :DMG_METADATA
refine Pathname do
def dmg_metadata?
DMG_METADATA.include?(cleanpath.ascend.to_a.last.to_s)
end
# symlinks to system directories (commonly to /Applications)
def system_dir_symlink?
symlink? && MacOS.system_dir?(readlink)
end
def bom
# rubocop:disable Style/AsciiComments
2018-07-24 07:03:24 +02:00
# We need to use `find` here instead of Ruby in order to properly handle
# file names containing special characters, such as “e” + “´” vs. “é”.
# rubocop:enable Style/AsciiComments
2018-07-24 07:03:24 +02:00
system_command("find", args: [".", "-print0"], chdir: self, print_stderr: false)
.stdout
.split("\0")
.reject { |path| Pathname(path).dmg_metadata? }
.reject { |path| (self/path).system_dir_symlink? }
.join("\n")
end
end
end
private_constant :Bom
using Bom
class Mount
include UnpackStrategy
2018-07-24 18:43:20 +02:00
def eject(verbose: false)
2018-07-24 07:03:24 +02:00
tries ||= 3
return unless path.exist?
if tries > 1
system_command! "diskutil",
args: ["eject", path],
2018-07-24 18:43:20 +02:00
print_stderr: false,
verbose: verbose
2018-07-24 07:03:24 +02:00
else
system_command! "diskutil",
args: ["unmount", "force", path],
2018-07-24 18:43:20 +02:00
print_stderr: false,
verbose: verbose
2018-07-24 07:03:24 +02:00
end
rescue ErrorDuringExecution => e
raise e if (tries -= 1).zero?
sleep 1
retry
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
Tempfile.open(["", ".bom"]) do |bomfile|
bomfile.close
Tempfile.open(["", ".list"]) do |filelist|
filelist.puts(path.bom)
filelist.close
2018-07-24 18:43:20 +02:00
system_command! "mkbom",
args: ["-s", "-i", filelist.path, "--", bomfile.path],
verbose: verbose
2018-07-24 07:03:24 +02:00
end
2018-07-24 18:43:20 +02:00
system_command! "ditto",
args: ["--bom", bomfile.path, "--", path, unpack_dir],
verbose: verbose
FileUtils.chmod "u+w", Pathname.glob(unpack_dir/"**/*", File::FNM_DOTMATCH).reject(&:symlink?)
2018-07-24 07:03:24 +02:00
end
end
end
private_constant :Mount
def self.extensions
[".dmg"]
end
def self.can_extract?(path)
2018-07-24 07:03:24 +02:00
imageinfo = system_command("hdiutil",
args: ["imageinfo", path],
print_stderr: false).stdout
!imageinfo.empty?
end
2018-07-24 07:03:24 +02:00
private
def extract_to_dir(unpack_dir, basename:, verbose:)
mount(verbose: verbose) do |mounts|
2018-07-24 07:03:24 +02:00
raise "No mounts found in '#{path}'; perhaps it is a bad disk image?" if mounts.empty?
mounts.each do |mount|
2018-07-24 18:43:20 +02:00
mount.extract(to: unpack_dir, verbose: verbose)
end
end
end
def mount(verbose: false)
2018-07-24 07:03:24 +02:00
Dir.mktmpdir do |mount_dir|
mount_dir = Pathname(mount_dir)
without_eula = system_command(
"hdiutil",
args: [
"attach", "-plist", "-nobrowse", "-readonly", "-noidme",
"-mountrandom", mount_dir, path
],
input: "qn\n",
print_stderr: false,
verbose: verbose,
)
# If mounting without agreeing to EULA succeeded, there is none.
plist = if without_eula.success?
without_eula.plist
else
2018-07-24 07:03:24 +02:00
cdr_path = mount_dir/path.basename.sub_ext(".cdr")
system_command!(
"hdiutil",
args: [
"convert", "-quiet", "-format", "UDTO", "-o", cdr_path, path
],
verbose: verbose,
)
with_eula = system_command!(
"hdiutil",
args: [
"attach", "-plist", "-nobrowse", "-readonly", "-noidme",
"-mountrandom", mount_dir, cdr_path
],
verbose: verbose,
)
if verbose && !(eula_text = without_eula.stdout).empty?
ohai "Software License Agreement for '#{path}':"
puts eula_text
end
with_eula.plist
end
2018-07-24 07:03:24 +02:00
mounts = if plist.respond_to?(:fetch)
plist.fetch("system-entities", [])
.map { |entity| entity["mount-point"] }
.compact
.map { |path| Mount.new(path) }
else
2018-07-24 07:03:24 +02:00
[]
end
2018-07-24 07:03:24 +02:00
begin
yield mounts
ensure
2018-07-24 18:43:20 +02:00
mounts.each do |mount|
mount.eject(verbose: verbose)
end
end
end
end
end
end