| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  | 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 | 
					
						
							| 
									
										
										
										
											2018-09-02 20:14:54 +01:00
										 |  |  |           # 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. “é”. | 
					
						
							| 
									
										
										
										
											2018-09-02 20:14:54 +01:00
										 |  |  |           # 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", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                           args:         ["eject", path], | 
					
						
							| 
									
										
										
										
											2018-07-24 18:43:20 +02:00
										 |  |  |                           print_stderr: false, | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                           verbose:      verbose | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |         else | 
					
						
							|  |  |  |           system_command! "diskutil", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                           args:         ["unmount", "force", path], | 
					
						
							| 
									
										
										
										
											2018-07-24 18:43:20 +02:00
										 |  |  |                           print_stderr: false, | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                           verbose:      verbose | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       rescue ErrorDuringExecution => e | 
					
						
							|  |  |  |         raise e if (tries -= 1).zero? | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |         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", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                             args:    ["-s", "-i", filelist.path, "--", bomfile.path], | 
					
						
							| 
									
										
										
										
											2018-07-24 18:43:20 +02:00
										 |  |  |                             verbose: verbose | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-24 18:43:20 +02:00
										 |  |  |           system_command! "ditto", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                           args:    ["--bom", bomfile.path, "--", path, unpack_dir], | 
					
						
							| 
									
										
										
										
											2018-07-24 18:43:20 +02:00
										 |  |  |                           verbose: verbose | 
					
						
							| 
									
										
										
										
											2018-07-29 10:04:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-31 13:16:11 +00:00
										 |  |  |           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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 09:49:59 +02:00
										 |  |  |     def self.extensions | 
					
						
							|  |  |  |       [".dmg"] | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-29 10:04:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 09:49:59 +02:00
										 |  |  |     def self.can_extract?(path) | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |       imageinfo = system_command("hdiutil", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |                                  args:         ["imageinfo", path], | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  |                                  print_stderr: false).stdout | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       !imageinfo.empty? | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |     private | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  |     def extract_to_dir(unpack_dir, basename:, verbose:) | 
					
						
							|  |  |  |       mount(verbose: verbose) do |mounts| | 
					
						
							| 
									
										
										
										
											2019-04-08 12:47:15 -04:00
										 |  |  |         raise "No mounts found in '#{path}'; perhaps this is a bad disk image?" if mounts.empty? | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         mounts.each do |mount| | 
					
						
							| 
									
										
										
										
											2018-07-24 18:43:20 +02:00
										 |  |  |           mount.extract(to: unpack_dir, verbose: verbose) | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |         without_eula = system_command( | 
					
						
							|  |  |  |           "hdiutil", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |           args:         [ | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |             "attach", "-plist", "-nobrowse", "-readonly", "-noidme", | 
					
						
							|  |  |  |             "-mountrandom", mount_dir, path | 
					
						
							|  |  |  |           ], | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |           input:        "qn\n", | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |           print_stderr: false, | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |           verbose:      verbose, | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # 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") | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-02 11:44:00 +01:00
										 |  |  |           quiet_flag = "-quiet" unless verbose | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |           system_command!( | 
					
						
							|  |  |  |             "hdiutil", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |             args:    [ | 
					
						
							| 
									
										
										
										
											2018-11-02 11:44:00 +01:00
										 |  |  |               "convert", *quiet_flag, "-format", "UDTO", "-o", cdr_path, path | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |             ], | 
					
						
							|  |  |  |             verbose: verbose, | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           with_eula = system_command!( | 
					
						
							|  |  |  |             "hdiutil", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |             args:    [ | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |               "attach", "-plist", "-nobrowse", "-readonly", "-noidme", | 
					
						
							|  |  |  |               "-mountrandom", mount_dir, cdr_path | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             verbose: verbose, | 
					
						
							|  |  |  |           ) | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           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) } | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  |         else | 
					
						
							| 
									
										
										
										
											2018-07-24 07:03:24 +02:00
										 |  |  |           [] | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +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 | 
					
						
							| 
									
										
										
										
											2018-07-23 23:04:49 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |