| 
									
										
										
										
											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
										 |  |  |       def extract | 
					
						
							| 
									
										
										
										
											2017-06-11 03:55:20 +02:00
										 |  |  |         mount do |mounts| | 
					
						
							|  |  |  |           begin | 
					
						
							|  |  |  |             raise CaskError, "No mounts found in '#{@path}'; perhaps it is a bad DMG?" if mounts.empty? | 
					
						
							|  |  |  |             mounts.each(&method(:extract_mount)) | 
					
						
							|  |  |  |           ensure | 
					
						
							|  |  |  |             mounts.each(&method(:eject)) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-11 03:55:20 +02:00
										 |  |  |       def mount | 
					
						
							|  |  |  |         # realpath is a failsafe against unusual filenames | 
					
						
							|  |  |  |         path = Pathname.new(@path).realpath | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-11 03:55:20 +02:00
										 |  |  |         Dir.mktmpdir do |unpack_dir| | 
					
						
							|  |  |  |           cdr_path = Pathname.new(unpack_dir).join("#{path.basename(".dmg")}.cdr") | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-11 04:41:43 +02:00
										 |  |  |           without_eula = @command.run("/usr/bin/hdiutil", | 
					
						
							|  |  |  |                                  args:  ["attach", "-plist", "-nobrowse", "-readonly", "-noidme", "-mountrandom", unpack_dir, path], | 
					
						
							|  |  |  |                                  input: "qn\n", | 
					
						
							|  |  |  |                                  print_stderr: false) | 
					
						
							| 
									
										
										
										
											2017-06-11 03:55:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-06-11 04:41:43 +02:00
										 |  |  |           # If mounting without agreeing to EULA succeeded, there is none. | 
					
						
							|  |  |  |           plist = if without_eula.success? | 
					
						
							|  |  |  |             without_eula.plist | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             @command.run!("/usr/bin/hdiutil", args: ["convert", "-quiet", "-format", "UDTO", "-o", cdr_path, path]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             with_eula = @command.run!("/usr/bin/hdiutil", | 
					
						
							|  |  |  |                           args: ["attach", "-plist", "-nobrowse", "-readonly", "-noidme", "-mountrandom", unpack_dir, cdr_path]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if verbose? && !(eula_text = without_eula.stdout).empty? | 
					
						
							|  |  |  |               ohai "Software License Agreement for '#{path}':" | 
					
						
							|  |  |  |               puts eula_text | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             with_eula.plist | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2017-06-11 03:55:20 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |           yield mounts_from_plist(plist) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       def eject(mount) | 
					
						
							|  |  |  |         # realpath is a failsafe against unusual filenames | 
					
						
							|  |  |  |         mountpath = Pathname.new(mount).realpath | 
					
						
							|  |  |  |         return 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) | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2017-06-11 03:55:20 +02:00
										 |  |  |           raise CaskError, "Failed to eject #{mountpath}" if mountpath.exist? | 
					
						
							|  |  |  |         rescue CaskError => e | 
					
						
							|  |  |  |           raise e if (tries -= 1).zero? | 
					
						
							|  |  |  |           sleep 1
 | 
					
						
							|  |  |  |           retry | 
					
						
							| 
									
										
										
										
											2016-09-24 13:52:43 +02:00
										 |  |  |         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_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 | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |   end | 
					
						
							|  |  |  | end |