1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/build/package/mac_osx/pkg-dmg Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1488 @@ 1.4 +#!/usr/bin/perl 1.5 +# This Source Code Form is subject to the terms of the Mozilla Public 1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.8 + 1.9 +use strict; 1.10 +use warnings; 1.11 + 1.12 +=pod 1.13 + 1.14 +=head1 NAME 1.15 + 1.16 +B<pkg-dmg> - Mac OS X disk image (.dmg) packager 1.17 + 1.18 +=head1 SYNOPSIS 1.19 + 1.20 +B<pkg-dmg> 1.21 +B<--source> I<source-folder> 1.22 +B<--target> I<target-image> 1.23 +[B<--format> I<format>] 1.24 +[B<--volname> I<volume-name>] 1.25 +[B<--tempdir> I<temp-dir>] 1.26 +[B<--mkdir> I<directory>] 1.27 +[B<--copy> I<source>[:I<dest>]] 1.28 +[B<--symlink> I<source>[:I<dest>]] 1.29 +[B<--license> I<file>] 1.30 +[B<--resource> I<file>] 1.31 +[B<--icon> I<icns-file>] 1.32 +[B<--attribute> I<a>:I<file>[:I<file>...] 1.33 +[B<--idme>] 1.34 +[B<--sourcefile>] 1.35 +[B<--verbosity> I<level>] 1.36 +[B<--dry-run>] 1.37 + 1.38 +=head1 DESCRIPTION 1.39 + 1.40 +I<pkg-dmg> takes a directory identified by I<source-folder> and transforms 1.41 +it into a disk image stored as I<target-image>. The disk image will 1.42 +occupy the least space possible for its format, or the least space that the 1.43 +authors have been able to figure out how to achieve. 1.44 + 1.45 +=head1 OPTIONS 1.46 + 1.47 +=over 5 1.48 + 1.49 +==item B<--source> I<source-folder> 1.50 + 1.51 +Identifies the directory that will be packaged up. This directory is not 1.52 +touched, a copy will be made in a temporary directory for staging purposes. 1.53 +See B<--tempdir>. 1.54 + 1.55 +==item B<--target> I<target-image> 1.56 + 1.57 +The disk image to create. If it exists and is not in use, it will be 1.58 +overwritten. If I<target-image> already contains a suitable extension, 1.59 +it will be used unmodified. If no extension is present, or the extension 1.60 +is incorrect for the selected format, the proper extension will be added. 1.61 +See B<--format>. 1.62 + 1.63 +==item B<--format> I<format> 1.64 + 1.65 +The format to create the disk image in. Valid values for I<format> are: 1.66 + - UDZO - zlib-compressed, read-only; extension I<.dmg> 1.67 + - UDBZ - bzip2-compressed, read-only; extension I<.dmg>; 1.68 + create and use on 10.4 ("Tiger") and later only 1.69 + - UDRW - read-write; extension I<.dmg> 1.70 + - UDSP - read-write, sparse; extension I<.sparseimage> 1.71 + 1.72 +UDBZ is the default format. 1.73 + 1.74 +See L<hdiutil(1)> for a description of these formats. 1.75 + 1.76 +=item B<--volname> I<volume-name> 1.77 + 1.78 +The name of the volume in the disk image. If not specified, I<volume-name> 1.79 +defaults to the name of the source directory from B<--source>. 1.80 + 1.81 +=item B<--tempdir> I<temp-dir> 1.82 + 1.83 +A temporary directory to stage intermediate files in. I<temp-dir> must 1.84 +have enough space available to accommodate twice the size of the files 1.85 +being packaged. If not specified, defaults to the same directory that 1.86 +the I<target-image> is to be placed in. B<pkg-dmg> will remove any 1.87 +temporary files it places in I<temp-dir>. 1.88 + 1.89 +=item B<--mkdir> I<directory> 1.90 + 1.91 +Specifies a directory that should be created in the disk image. 1.92 +I<directory> and any ancestor directories will be created. This is 1.93 +useful in conjunction with B<--copy>, when copying files to directories 1.94 +that may not exist in I<source-folder>. B<--mkdir> may appear multiple 1.95 +times. 1.96 + 1.97 +=item B<--copy> I<source>[:I<dest>] 1.98 + 1.99 +Additional files to copy into the disk image. If I<dest> is 1.100 +specified, I<source> is copied to the location I<dest> identifies, 1.101 +otherwise, I<source> is copied to the root of the new volume. B<--copy> 1.102 +provides a way to package up a I<source-folder> by adding files to it 1.103 +without modifying the original I<source-folder>. B<--copy> may appear 1.104 +multiple times. 1.105 + 1.106 +This option is useful for adding .DS_Store files and window backgrounds 1.107 +to disk images. 1.108 + 1.109 +=item B<--symlink> I<source>[:I<dest>] 1.110 + 1.111 +Like B<--copy>, but allows symlinks to point out of the volume. Empty symlink 1.112 +destinations are interpreted as "like the source path, but inside the dmg" 1.113 + 1.114 +This option is useful for adding symlinks to external resources, 1.115 +e.g. to /Applications. 1.116 + 1.117 +=item B<--license> I<file> 1.118 + 1.119 +A plain text file containing a license agreement to be displayed before 1.120 +the disk image is mounted. English is the only supported language. To 1.121 +include license agreements in other languages, in multiple languages, 1.122 +or to use formatted text, prepare a resource and use L<--resource>. 1.123 + 1.124 +=item B<--resource> I<file> 1.125 + 1.126 +A resource file to merge into I<target-image>. If I<format> is UDZO or 1.127 +UDBZ, the disk image will be flattened to a single-fork file that contains 1.128 +the resource but may be freely transferred without any special encodings. 1.129 +I<file> must be in a format suitable for L<Rez(1)>. See L<Rez(1)> for a 1.130 +description of the format, and L<hdiutil(1)> for a discussion on flattened 1.131 +disk images. B<--resource> may appear multiple times. 1.132 + 1.133 +This option is useful for adding license agreements and other messages 1.134 +to disk images. 1.135 + 1.136 +=item B<--icon> I<icns-file> 1.137 + 1.138 +Specifies an I<icns> file that will be used as the icon for the root of 1.139 +the volume. This file will be copied to the new volume and the custom 1.140 +icon attribute will be set on the root folder. 1.141 + 1.142 +=item B<--attribute> I<a>:I<file>[:I<file>...] 1.143 + 1.144 +Sets the attributes of I<file> to the attribute list in I<a>. See 1.145 +L<SetFile(1)> 1.146 + 1.147 +=item B<--idme> 1.148 + 1.149 +Enable IDME to make the disk image "Internet-enabled." The first time 1.150 +the image is mounted, if IDME processing is enabled on the system, the 1.151 +contents of the image will be copied out of the image and the image will 1.152 +be placed in the trash with IDME disabled. 1.153 + 1.154 +=item B<--sourcefile> 1.155 + 1.156 +If this option is present, I<source-folder> is treated as a file, and is 1.157 +placed as a file within the volume's root folder. Without this option, 1.158 +I<source-folder> is treated as the volume root itself. 1.159 + 1.160 +=item B<--verbosity> I<level> 1.161 + 1.162 +Adjusts the level of loudness of B<pkg-dmg>. The possible values for 1.163 +I<level> are: 1.164 + 0 - Only error messages are displayed. 1.165 + 1 - Print error messages and command invocations. 1.166 + 2 - Print everything, including command output. 1.167 + 1.168 +The default I<level> is 2. 1.169 + 1.170 +=item B<--dry-run> 1.171 + 1.172 +When specified, the commands that would be executed are printed, without 1.173 +actually executing them. When commands depend on the output of previous 1.174 +commands, dummy values are displayed. 1.175 + 1.176 +=back 1.177 + 1.178 +=head1 NON-OPTIONS 1.179 + 1.180 +=over 5 1.181 + 1.182 +=item 1.183 + 1.184 +Resource forks aren't copied. 1.185 + 1.186 +=item 1.187 + 1.188 +The root folder of the created volume is designated as the folder 1.189 +to open when the volume is mounted. See L<bless(8)>. 1.190 + 1.191 +=item 1.192 + 1.193 +All files in the volume are set to be world-readable, only writable 1.194 +by the owner, and world-executable when appropriate. All other 1.195 +permissions bits are cleared. 1.196 + 1.197 +=item 1.198 + 1.199 +When possible, disk images are created without any partition tables. This 1.200 +is what L<hdiutil(1)> refers to as I<-layout NONE>, and saves a handful of 1.201 +kilobytes. The alternative, I<SPUD>, contains a partition table that 1.202 +is not terribly handy on disk images that are not intended to represent any 1.203 +physical disk. 1.204 + 1.205 +=item 1.206 + 1.207 +Read-write images are created with journaling off. Any read-write image 1.208 +created by this tool is expected to be transient, and the goal of this tool 1.209 +is to create images which consume a minimum of space. 1.210 + 1.211 +=back 1.212 + 1.213 +=head1 EXAMPLE 1.214 + 1.215 +pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg 1.216 + --sourcefile --volname DeerPark --icon ~/DeerPark.icns 1.217 + --mkdir /.background 1.218 + --copy DeerParkBackground.png:/.background/background.png 1.219 + --copy DeerParkDSStore:/.DS_Store 1.220 + --symlink /Applications:"/Drag to here" 1.221 + 1.222 +=head1 REQUIREMENTS 1.223 + 1.224 +I<pkg-dmg> has been tested with Mac OS X releases 10.2 ("Jaguar") 1.225 +through 10.4 ("Tiger"). Certain adjustments to behavior are made 1.226 +depending on the host system's release. Mac OS X 10.3 ("Panther") or 1.227 +later are recommended. 1.228 + 1.229 +=head1 LICENSE 1.230 + 1.231 +MPL 2. 1.232 + 1.233 +=head1 AUTHOR 1.234 + 1.235 +Mark Mentovai 1.236 + 1.237 +=head1 SEE ALSO 1.238 + 1.239 +L<bless(8)>, L<diskutil(8)>, L<hdid(8)>, L<hdiutil(1)>, L<Rez(1)>, 1.240 +L<rsync(1)>, L<SetFile(1)> 1.241 + 1.242 +=cut 1.243 + 1.244 +use Fcntl; 1.245 +use POSIX; 1.246 +use Getopt::Long; 1.247 + 1.248 +sub argumentEscape(@); 1.249 +sub cleanupDie($); 1.250 +sub command(@); 1.251 +sub commandInternal($@); 1.252 +sub commandInternalVerbosity($$@); 1.253 +sub commandOutput(@); 1.254 +sub commandOutputVerbosity($@); 1.255 +sub commandVerbosity($@); 1.256 +sub copyFiles($@); 1.257 +sub diskImageMaker($$$$$$$$); 1.258 +sub giveExtension($$); 1.259 +sub hdidMountImage($@); 1.260 +sub isFormatCompressed($); 1.261 +sub licenseMaker($$); 1.262 +sub pathSplit($); 1.263 +sub setAttributes($@); 1.264 +sub trapSignal($); 1.265 +sub usage(); 1.266 + 1.267 +# Variables used as globals 1.268 +my(@gCleanup, %gConfig, $gDarwinMajor, $gDryRun, $gVerbosity); 1.269 + 1.270 +# Use the commands by name if they're expected to be in the user's 1.271 +# $PATH (/bin:/sbin:/usr/bin:/usr/sbin). Otherwise, go by absolute 1.272 +# path. These may be overridden with --config. 1.273 +%gConfig = ('cmd_bless' => 'bless', 1.274 + 'cmd_chmod' => 'chmod', 1.275 + 'cmd_diskutil' => 'diskutil', 1.276 + 'cmd_du' => 'du', 1.277 + 'cmd_hdid' => 'hdid', 1.278 + 'cmd_hdiutil' => 'hdiutil', 1.279 + 'cmd_mkdir' => 'mkdir', 1.280 + 'cmd_mktemp' => 'mktemp', 1.281 + 'cmd_Rez' => 'Rez', 1.282 + 'cmd_rm' => 'rm', 1.283 + 'cmd_rsync' => 'rsync', 1.284 + 'cmd_SetFile' => 'SetFile', 1.285 + 1.286 + # create_directly indicates whether hdiutil create supports 1.287 + # -srcfolder and -srcdevice. It does on >= 10.3 (Panther). 1.288 + # This is fixed up for earlier systems below. If false, 1.289 + # hdiutil create is used to create empty disk images that 1.290 + # are manually filled. 1.291 + 'create_directly' => 1, 1.292 + 1.293 + # If hdiutil attach -mountpoint exists, use it to avoid 1.294 + # mounting disk images in the default /Volumes. This reduces 1.295 + # the likelihood that someone will notice a mounted image and 1.296 + # interfere with it. Only available on >= 10.3 (Panther), 1.297 + # fixed up for earlier systems below. 1.298 + # 1.299 + # This is presently turned off for all systems, because there 1.300 + # is an infrequent synchronization problem during ejection. 1.301 + # diskutil eject might return before the image is actually 1.302 + # unmounted. If pkg-dmg then attempts to clean up its 1.303 + # temporary directory, it could remove items from a read-write 1.304 + # disk image or attempt to remove items from a read-only disk 1.305 + # image (or a read-only item from a read-write image) and fail, 1.306 + # causing pkg-dmg to abort. This problem is experienced 1.307 + # under Tiger, which appears to eject asynchronously where 1.308 + # previous systems treated it as a synchronous operation. 1.309 + # Using hdiutil attach -mountpoint didn't always keep images 1.310 + # from showing up on the desktop anyway. 1.311 + 'hdiutil_mountpoint' => 0, 1.312 + 1.313 + # hdiutil makehybrid results in optimized disk images that 1.314 + # consume less space and mount more quickly. Use it when 1.315 + # it's available, but that's only on >= 10.3 (Panther). 1.316 + # If false, hdiutil create is used instead. Fixed up for 1.317 + # earlier systems below. 1.318 + 'makehybrid' => 1, 1.319 + 1.320 + # hdiutil create doesn't allow specifying a folder to open 1.321 + # at volume mount time, so those images are mounted and 1.322 + # their root folders made holy with bless -openfolder. But 1.323 + # only on >= 10.3 (Panther). Earlier systems are out of luck. 1.324 + # Even on Panther, bless refuses to run unless root. 1.325 + # Fixed up below. 1.326 + 'openfolder_bless' => 1, 1.327 + 1.328 + # It's possible to save a few more kilobytes by including the 1.329 + # partition only without any partition table in the image. 1.330 + # This is a good idea on any system, so turn this option off. 1.331 + # 1.332 + # Except it's buggy. "-layout NONE" seems to be creating 1.333 + # disk images with more data than just the partition table 1.334 + # stripped out. You might wind up losing the end of the 1.335 + # filesystem - the last file (or several) might be incomplete. 1.336 + 'partition_table' => 1, 1.337 + 1.338 + # To create a partition table-less image from something 1.339 + # created by makehybrid, the hybrid image needs to be 1.340 + # mounted and a new image made from the device associated 1.341 + # with the relevant partition. This requires >= 10.4 1.342 + # (Tiger), presumably because earlier systems have 1.343 + # problems creating images from devices themselves attached 1.344 + # to images. If this is false, makehybrid images will 1.345 + # have partition tables, regardless of the partition_table 1.346 + # setting. Fixed up for earlier systems below. 1.347 + 'recursive_access' => 1); 1.348 + 1.349 +# --verbosity 1.350 +$gVerbosity = 2; 1.351 + 1.352 +# --dry-run 1.353 +$gDryRun = 0; 1.354 + 1.355 +# %gConfig fix-ups based on features and bugs present in certain releases. 1.356 +my($ignore, $uname_r, $uname_s); 1.357 +($uname_s, $ignore, $uname_r, $ignore, $ignore) = POSIX::uname(); 1.358 +if($uname_s eq 'Darwin') { 1.359 + ($gDarwinMajor, $ignore) = split(/\./, $uname_r, 2); 1.360 + 1.361 + # $major is the Darwin major release, which for our purposes, is 4 higher 1.362 + # than the interesting digit in a Mac OS X release. 1.363 + if($gDarwinMajor <= 6) { 1.364 + # <= 10.2 (Jaguar) 1.365 + # hdiutil create does not support -srcfolder or -srcdevice 1.366 + $gConfig{'create_directly'} = 0; 1.367 + # hdiutil attach does not support -mountpoint 1.368 + $gConfig{'hdiutil_mountpoint'} = 0; 1.369 + # hdiutil mkhybrid does not exist 1.370 + $gConfig{'makehybrid'} = 0; 1.371 + } 1.372 + if($gDarwinMajor <= 7) { 1.373 + # <= 10.3 (Panther) 1.374 + # Can't mount a disk image and then make a disk image from the device 1.375 + $gConfig{'recursive_access'} = 0; 1.376 + # bless does not support -openfolder on 10.2 (Jaguar) and must run 1.377 + # as root under 10.3 (Panther) 1.378 + $gConfig{'openfolder_bless'} = 0; 1.379 + } 1.380 +} 1.381 +else { 1.382 + # If it's not Mac OS X, just assume all of those good features are 1.383 + # available. They're not, but things will fail long before they 1.384 + # have a chance to make a difference. 1.385 + # 1.386 + # Now, if someone wanted to document some of these private formats... 1.387 + print STDERR ($0.": warning, not running on Mac OS X, ". 1.388 + "this could be interesting.\n"); 1.389 +} 1.390 + 1.391 +# Non-global variables used in Getopt 1.392 +my(@attributes, @copyFiles, @createSymlinks, $iconFile, $idme, $licenseFile, 1.393 + @makeDirs, $outputFormat, @resourceFiles, $sourceFile, $sourceFolder, 1.394 + $targetImage, $tempDir, $volumeName); 1.395 + 1.396 +# --format 1.397 +$outputFormat = 'UDBZ'; 1.398 + 1.399 +# --idme 1.400 +$idme = 0; 1.401 + 1.402 +# --sourcefile 1.403 +$sourceFile = 0; 1.404 + 1.405 +# Leaving this might screw up the Apple tools. 1.406 +delete $ENV{'NEXT_ROOT'}; 1.407 + 1.408 +# This script can get pretty messy, so trap a few signals. 1.409 +$SIG{'INT'} = \&trapSignal; 1.410 +$SIG{'HUP'} = \&trapSignal; 1.411 +$SIG{'TERM'} = \&trapSignal; 1.412 + 1.413 +Getopt::Long::Configure('pass_through'); 1.414 +GetOptions('source=s' => \$sourceFolder, 1.415 + 'target=s' => \$targetImage, 1.416 + 'volname=s' => \$volumeName, 1.417 + 'format=s' => \$outputFormat, 1.418 + 'tempdir=s' => \$tempDir, 1.419 + 'mkdir=s' => \@makeDirs, 1.420 + 'copy=s' => \@copyFiles, 1.421 + 'symlink=s' => \@createSymlinks, 1.422 + 'license=s' => \$licenseFile, 1.423 + 'resource=s' => \@resourceFiles, 1.424 + 'icon=s' => \$iconFile, 1.425 + 'attribute=s' => \@attributes, 1.426 + 'idme' => \$idme, 1.427 + 'sourcefile' => \$sourceFile, 1.428 + 'verbosity=i' => \$gVerbosity, 1.429 + 'dry-run' => \$gDryRun, 1.430 + 'config=s' => \%gConfig); # "hidden" option not in usage() 1.431 + 1.432 +if(@ARGV) { 1.433 + # All arguments are parsed by Getopt 1.434 + usage(); 1.435 + exit(1); 1.436 +} 1.437 + 1.438 +if($gVerbosity<0 || $gVerbosity>2) { 1.439 + usage(); 1.440 + exit(1); 1.441 +} 1.442 + 1.443 +if(!defined($sourceFolder) || $sourceFolder eq '' || 1.444 + !defined($targetImage) || $targetImage eq '') { 1.445 + # --source and --target are required arguments 1.446 + usage(); 1.447 + exit(1); 1.448 +} 1.449 + 1.450 +# Make sure $sourceFolder doesn't contain trailing slashes. It messes with 1.451 +# rsync. 1.452 +while(substr($sourceFolder, -1) eq '/') { 1.453 + chop($sourceFolder); 1.454 +} 1.455 + 1.456 +if(!defined($volumeName)) { 1.457 + # Default volumeName is the name of the source directory. 1.458 + my(@components); 1.459 + @components = pathSplit($sourceFolder); 1.460 + $volumeName = pop(@components); 1.461 +} 1.462 + 1.463 +my(@tempDirComponents, $targetImageFilename); 1.464 +@tempDirComponents = pathSplit($targetImage); 1.465 +$targetImageFilename = pop(@tempDirComponents); 1.466 + 1.467 +if(defined($tempDir)) { 1.468 + @tempDirComponents = pathSplit($tempDir); 1.469 +} 1.470 +else { 1.471 + # Default tempDir is the same directory as what is specified for 1.472 + # targetImage 1.473 + $tempDir = join('/', @tempDirComponents); 1.474 +} 1.475 + 1.476 +# Ensure that the path of the target image has a suitable extension. If 1.477 +# it didn't, hdiutil would add one, and we wouldn't be able to find the 1.478 +# file. 1.479 +# 1.480 +# Note that $targetImageFilename is not being reset. This is because it's 1.481 +# used to build other names below, and we don't need to be adding all sorts 1.482 +# of extra unnecessary extensions to the name. 1.483 +my($originalTargetImage, $requiredExtension); 1.484 +$originalTargetImage = $targetImage; 1.485 +if($outputFormat eq 'UDSP') { 1.486 + $requiredExtension = '.sparseimage'; 1.487 +} 1.488 +else { 1.489 + $requiredExtension = '.dmg'; 1.490 +} 1.491 +$targetImage = giveExtension($originalTargetImage, $requiredExtension); 1.492 + 1.493 +if($targetImage ne $originalTargetImage) { 1.494 + print STDERR ($0.": warning: target image extension is being added\n"); 1.495 + print STDERR (' The new filename is '. 1.496 + giveExtension($targetImageFilename,$requiredExtension)."\n"); 1.497 +} 1.498 + 1.499 +# Make a temporary directory in $tempDir for our own nefarious purposes. 1.500 +my(@output, $tempSubdir, $tempSubdirTemplate); 1.501 +$tempSubdirTemplate=join('/', @tempDirComponents, 1.502 + 'pkg-dmg.'.$$.'.XXXXXXXX'); 1.503 +if(!(@output = commandOutput($gConfig{'cmd_mktemp'}, '-d', 1.504 + $tempSubdirTemplate)) || $#output != 0) { 1.505 + cleanupDie('mktemp failed'); 1.506 +} 1.507 + 1.508 +if($gDryRun) { 1.509 + (@output)=($tempSubdirTemplate); 1.510 +} 1.511 + 1.512 +($tempSubdir) = @output; 1.513 + 1.514 +push(@gCleanup, 1.515 + sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempSubdir);}); 1.516 + 1.517 +my($tempMount, $tempRoot, @tempsToMake); 1.518 +$tempRoot = $tempSubdir.'/stage'; 1.519 +$tempMount = $tempSubdir.'/mount'; 1.520 +push(@tempsToMake, $tempRoot); 1.521 +if($gConfig{'hdiutil_mountpoint'}) { 1.522 + push(@tempsToMake, $tempMount); 1.523 +} 1.524 + 1.525 +if(command($gConfig{'cmd_mkdir'}, @tempsToMake) != 0) { 1.526 + cleanupDie('mkdir tempRoot/tempMount failed'); 1.527 +} 1.528 + 1.529 +# This cleanup object is not strictly necessary, because $tempRoot is inside 1.530 +# of $tempSubdir, but the rest of the script relies on this object being 1.531 +# on the cleanup stack and expects to remove it. 1.532 +push(@gCleanup, 1.533 + sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempRoot);}); 1.534 + 1.535 +# If $sourceFile is true, it means that $sourceFolder is to be treated as 1.536 +# a file and placed as a file within the volume root, as opposed to being 1.537 +# treated as the volume root itself. rsync will do this by default, if no 1.538 +# trailing '/' is present. With a trailing '/', $sourceFolder becomes 1.539 +# $tempRoot, instead of becoming an entry in $tempRoot. 1.540 +if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', 1.541 + $sourceFolder.($sourceFile?'':'/'),$tempRoot) != 0) { 1.542 + cleanupDie('rsync failed'); 1.543 +} 1.544 + 1.545 +if(@makeDirs) { 1.546 + my($makeDir, @tempDirsToMake); 1.547 + foreach $makeDir (@makeDirs) { 1.548 + if($makeDir =~ /^\//) { 1.549 + push(@tempDirsToMake, $tempRoot.$makeDir); 1.550 + } 1.551 + else { 1.552 + push(@tempDirsToMake, $tempRoot.'/'.$makeDir); 1.553 + } 1.554 + } 1.555 + if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) { 1.556 + cleanupDie('mkdir failed'); 1.557 + } 1.558 +} 1.559 + 1.560 +# copy files and/or create symlinks 1.561 +copyFiles($tempRoot, 'copy', @copyFiles); 1.562 +copyFiles($tempRoot, 'symlink', @createSymlinks); 1.563 + 1.564 +if($gConfig{'create_directly'}) { 1.565 + # If create_directly is false, the contents will be rsynced into a 1.566 + # disk image and they would lose their attributes. 1.567 + setAttributes($tempRoot, @attributes); 1.568 +} 1.569 + 1.570 +if(defined($iconFile)) { 1.571 + if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', $iconFile, 1.572 + $tempRoot.'/.VolumeIcon.icns') != 0) { 1.573 + cleanupDie('rsync failed for volume icon'); 1.574 + } 1.575 + 1.576 + # It's pointless to set the attributes of the root when diskutil create 1.577 + # -srcfolder is being used. In that case, the attributes will be set 1.578 + # later, after the image is already created. 1.579 + if(isFormatCompressed($outputFormat) && 1.580 + (command($gConfig{'cmd_SetFile'}, '-a', 'C', $tempRoot) != 0)) { 1.581 + cleanupDie('SetFile failed'); 1.582 + } 1.583 +} 1.584 + 1.585 +if(command($gConfig{'cmd_chmod'}, '-R', 'a+rX,a-st,u+w,go-w', 1.586 + $tempRoot) != 0) { 1.587 + cleanupDie('chmod failed'); 1.588 +} 1.589 + 1.590 +my($unflattenable); 1.591 +if(isFormatCompressed($outputFormat)) { 1.592 + $unflattenable = 1; 1.593 +} 1.594 +else { 1.595 + $unflattenable = 0; 1.596 +} 1.597 + 1.598 +diskImageMaker($tempRoot, $targetImage, $outputFormat, $volumeName, 1.599 + $tempSubdir, $tempMount, $targetImageFilename, defined($iconFile)); 1.600 + 1.601 +if(defined($licenseFile) && $licenseFile ne '') { 1.602 + my($licenseResource); 1.603 + $licenseResource = $tempSubdir.'/license.r'; 1.604 + if(!licenseMaker($licenseFile, $licenseResource)) { 1.605 + cleanupDie('licenseMaker failed'); 1.606 + } 1.607 + push(@resourceFiles, $licenseResource); 1.608 + # Don't add a cleanup object because licenseResource is in tempSubdir. 1.609 +} 1.610 + 1.611 +if(@resourceFiles) { 1.612 + # Add resources, such as a license agreement. 1.613 + 1.614 + # Only unflatten read-only and compressed images. It's not supported 1.615 + # on other image times. 1.616 + if($unflattenable && 1.617 + (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) { 1.618 + cleanupDie('hdiutil unflatten failed'); 1.619 + } 1.620 + # Don't push flatten onto the cleanup stack. If we fail now, we'll be 1.621 + # removing $targetImage anyway. 1.622 + 1.623 + # Type definitions come from Carbon.r. 1.624 + if(command($gConfig{'cmd_Rez'}, 'Carbon.r', @resourceFiles, '-a', '-o', 1.625 + $targetImage) != 0) { 1.626 + cleanupDie('Rez failed'); 1.627 + } 1.628 + 1.629 + # Flatten. This merges the resource fork into the data fork, so no 1.630 + # special encoding is needed to transfer the file. 1.631 + if($unflattenable && 1.632 + (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) { 1.633 + cleanupDie('hdiutil flatten failed'); 1.634 + } 1.635 +} 1.636 + 1.637 +# $tempSubdir is no longer needed. It's buried on the stack below the 1.638 +# rm of the fresh image file. Splice in this fashion is equivalent to 1.639 +# pop-save, pop, push-save. 1.640 +splice(@gCleanup, -2, 1); 1.641 +# No need to remove licenseResource separately, it's in tempSubdir. 1.642 +if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) { 1.643 + cleanupDie('rm -rf tempSubdir failed'); 1.644 +} 1.645 + 1.646 +if($idme) { 1.647 + if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes', 1.648 + $targetImage) != 0) { 1.649 + cleanupDie('hdiutil internet-enable failed'); 1.650 + } 1.651 +} 1.652 + 1.653 +# Done. 1.654 + 1.655 +exit(0); 1.656 + 1.657 +# argumentEscape(@arguments) 1.658 +# 1.659 +# Takes a list of @arguments and makes them shell-safe. 1.660 +sub argumentEscape(@) { 1.661 + my(@arguments); 1.662 + @arguments = @_; 1.663 + my($argument, @argumentsOut); 1.664 + foreach $argument (@arguments) { 1.665 + $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g; 1.666 + push(@argumentsOut, $argument); 1.667 + } 1.668 + return @argumentsOut; 1.669 +} 1.670 + 1.671 +# cleanupDie($message) 1.672 +# 1.673 +# Displays $message as an error message, and then runs through the 1.674 +# @gCleanup stack, performing any cleanup operations needed before 1.675 +# exiting. Does not return, exits with exit status 1. 1.676 +sub cleanupDie($) { 1.677 + my($message); 1.678 + ($message) = @_; 1.679 + print STDERR ($0.': '.$message.(@gCleanup?' (cleaning up)':'')."\n"); 1.680 + while(@gCleanup) { 1.681 + my($subroutine); 1.682 + $subroutine = pop(@gCleanup); 1.683 + &$subroutine; 1.684 + } 1.685 + exit(1); 1.686 +} 1.687 + 1.688 +# command(@arguments) 1.689 +# 1.690 +# Runs the specified command at the verbosity level defined by $gVerbosity. 1.691 +# Returns nonzero on failure, returning the exit status if appropriate. 1.692 +# Discards command output. 1.693 +sub command(@) { 1.694 + my(@arguments); 1.695 + @arguments = @_; 1.696 + return commandVerbosity($gVerbosity,@arguments); 1.697 +} 1.698 + 1.699 +# commandInternal($command, @arguments) 1.700 +# 1.701 +# Runs the specified internal command at the verbosity level defined by 1.702 +# $gVerbosity. 1.703 +# Returns zero(!) on failure, because commandInternal is supposed to be a 1.704 +# direct replacement for the Perl system call wrappers, which, unlike shell 1.705 +# commands and C equivalent system calls, return true (instead of 0) to 1.706 +# indicate success. 1.707 +sub commandInternal($@) { 1.708 + my(@arguments, $command); 1.709 + ($command, @arguments) = @_; 1.710 + return commandInternalVerbosity($gVerbosity, $command, @arguments); 1.711 +} 1.712 + 1.713 +# commandInternalVerbosity($verbosity, $command, @arguments) 1.714 +# 1.715 +# Run an internal command, printing a bogus command invocation message if 1.716 +# $verbosity is true. 1.717 +# 1.718 +# If $command is unlink: 1.719 +# Removes the files specified by @arguments. Wraps unlink. 1.720 +# 1.721 +# If $command is symlink: 1.722 +# Creates the symlink specified by @arguments. Wraps symlink. 1.723 +sub commandInternalVerbosity($$@) { 1.724 + my(@arguments, $command, $verbosity); 1.725 + ($verbosity, $command, @arguments) = @_; 1.726 + if($command eq 'unlink') { 1.727 + if($verbosity || $gDryRun) { 1.728 + print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n"); 1.729 + } 1.730 + if($gDryRun) { 1.731 + return $#arguments+1; 1.732 + } 1.733 + return unlink(@arguments); 1.734 + } 1.735 + elsif($command eq 'symlink') { 1.736 + if($verbosity || $gDryRun) { 1.737 + print(join(' ', 'ln', '-s', argumentEscape(@arguments))."\n"); 1.738 + } 1.739 + if($gDryRun) { 1.740 + return 1; 1.741 + } 1.742 + my($source, $target); 1.743 + ($source, $target) = @arguments; 1.744 + return symlink($source, $target); 1.745 + } 1.746 +} 1.747 + 1.748 +# commandOutput(@arguments) 1.749 +# 1.750 +# Runs the specified command at the verbosity level defined by $gVerbosity. 1.751 +# Output is returned in an array of lines. undef is returned on failure. 1.752 +# The exit status is available in $?. 1.753 +sub commandOutput(@) { 1.754 + my(@arguments); 1.755 + @arguments = @_; 1.756 + return commandOutputVerbosity($gVerbosity, @arguments); 1.757 +} 1.758 + 1.759 +# commandOutputVerbosity($verbosity, @arguments) 1.760 +# 1.761 +# Runs the specified command at the verbosity level defined by the 1.762 +# $verbosity argument. Output is returned in an array of lines. undef is 1.763 +# returned on failure. The exit status is available in $?. 1.764 +# 1.765 +# If an error occurs in fork or exec, an error message is printed to 1.766 +# stderr and undef is returned. 1.767 +# 1.768 +# If $verbosity is 0, the command invocation is not printed, and its 1.769 +# stdout is not echoed back to stdout. 1.770 +# 1.771 +# If $verbosity is 1, the command invocation is printed. 1.772 +# 1.773 +# If $verbosity is 2, the command invocation is printed and the output 1.774 +# from stdout is echoed back to stdout. 1.775 +# 1.776 +# Regardless of $verbosity, stderr is left connected. 1.777 +sub commandOutputVerbosity($@) { 1.778 + my(@arguments, $verbosity); 1.779 + ($verbosity, @arguments) = @_; 1.780 + my($pid); 1.781 + if($verbosity || $gDryRun) { 1.782 + print(join(' ', argumentEscape(@arguments))."\n"); 1.783 + } 1.784 + if($gDryRun) { 1.785 + return(1); 1.786 + } 1.787 + if (!defined($pid = open(*COMMAND, '-|'))) { 1.788 + printf STDERR ($0.': fork: '.$!."\n"); 1.789 + return undef; 1.790 + } 1.791 + elsif ($pid) { 1.792 + # parent 1.793 + my(@lines); 1.794 + while(!eof(*COMMAND)) { 1.795 + my($line); 1.796 + chop($line = <COMMAND>); 1.797 + if($verbosity > 1) { 1.798 + print($line."\n"); 1.799 + } 1.800 + push(@lines, $line); 1.801 + } 1.802 + close(*COMMAND); 1.803 + if ($? == -1) { 1.804 + printf STDERR ($0.': fork: '.$!."\n"); 1.805 + return undef; 1.806 + } 1.807 + elsif ($? & 127) { 1.808 + printf STDERR ($0.': exited on signal '.($? & 127). 1.809 + ($? & 128 ? ', core dumped' : '')."\n"); 1.810 + return undef; 1.811 + } 1.812 + return @lines; 1.813 + } 1.814 + else { 1.815 + # child; this form of exec is immune to shell games 1.816 + if(!exec {$arguments[0]} (@arguments)) { 1.817 + printf STDERR ($0.': exec: '.$!."\n"); 1.818 + exit(-1); 1.819 + } 1.820 + } 1.821 +} 1.822 + 1.823 +# commandVerbosity($verbosity, @arguments) 1.824 +# 1.825 +# Runs the specified command at the verbosity level defined by the 1.826 +# $verbosity argument. Returns nonzero on failure, returning the exit 1.827 +# status if appropriate. Discards command output. 1.828 +sub commandVerbosity($@) { 1.829 + my(@arguments, $verbosity); 1.830 + ($verbosity, @arguments) = @_; 1.831 + if(!defined(commandOutputVerbosity($verbosity, @arguments))) { 1.832 + return -1; 1.833 + } 1.834 + return $?; 1.835 +} 1.836 + 1.837 +# copyFiles($tempRoot, $method, @arguments) 1.838 +# 1.839 +# Copies files or create symlinks in the disk image. 1.840 +# See --copy and --symlink descriptions for details. 1.841 +# If $method is 'copy', @arguments are interpreted as source:target, if $method 1.842 +# is 'symlink', @arguments are interpreted as symlink:target. 1.843 +sub copyFiles($@) { 1.844 + my(@fileList, $method, $tempRoot); 1.845 + ($tempRoot, $method, @fileList) = @_; 1.846 + my($file, $isSymlink); 1.847 + $isSymlink = ($method eq 'symlink'); 1.848 + foreach $file (@fileList) { 1.849 + my($source, $target); 1.850 + ($source, $target) = split(/:/, $file); 1.851 + if(!defined($target) and $isSymlink) { 1.852 + # empty symlink targets would result in an invalid target and fail, 1.853 + # but they shall be interpreted as "like source path, but inside dmg" 1.854 + $target = $source; 1.855 + } 1.856 + if(!defined($target)) { 1.857 + $target = $tempRoot; 1.858 + } 1.859 + elsif($target =~ /^\//) { 1.860 + $target = $tempRoot.$target; 1.861 + } 1.862 + else { 1.863 + $target = $tempRoot.'/'.$target; 1.864 + } 1.865 + 1.866 + my($success); 1.867 + if($isSymlink) { 1.868 + $success = commandInternal('symlink', $source, $target); 1.869 + } 1.870 + else { 1.871 + $success = !command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', 1.872 + $source, $target); 1.873 + } 1.874 + if(!$success) { 1.875 + cleanupDie('copyFiles failed for method '.$method); 1.876 + } 1.877 + } 1.878 +} 1.879 + 1.880 +# diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount, 1.881 +# $baseName, $setRootIcon) 1.882 +# 1.883 +# Creates a disk image in $destination of format $format corresponding to the 1.884 +# source directory $source. $name is the volume name. $tempDir is a good 1.885 +# place to write temporary files, which should be empty (aside from the other 1.886 +# things that this script might create there, like stage and mount). 1.887 +# $tempMount is a mount point for temporary disk images. $baseName is the 1.888 +# name of the disk image, and is presently unused. $setRootIcon is true if 1.889 +# a volume icon was added to the staged $source and indicates that the 1.890 +# custom volume icon bit on the volume root needs to be set. 1.891 +sub diskImageMaker($$$$$$$$) { 1.892 + my($baseName, $destination, $format, $name, $setRootIcon, $source, 1.893 + $tempDir, $tempMount); 1.894 + ($source, $destination, $format, $name, $tempDir, $tempMount, 1.895 + $baseName, $setRootIcon) = @_; 1.896 + if(isFormatCompressed($format)) { 1.897 + my($uncompressedImage); 1.898 + 1.899 + if($gConfig{'makehybrid'}) { 1.900 + my($hybridImage); 1.901 + $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg'); 1.902 + 1.903 + if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs', 1.904 + '-hfs-volume-name', $name, '-hfs-openfolder', $source, '-ov', 1.905 + $source, '-o', $hybridImage) != 0) { 1.906 + cleanupDie('hdiutil makehybrid failed'); 1.907 + } 1.908 + 1.909 + $uncompressedImage = $hybridImage; 1.910 + 1.911 + # $source is no longer needed and will be removed before anything 1.912 + # else can fail. splice in this form is the same as pop/push. 1.913 + splice(@gCleanup, -1, 1, 1.914 + sub {commandInternalVerbosity(0, 'unlink', $hybridImage);}); 1.915 + 1.916 + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { 1.917 + cleanupDie('rm -rf failed'); 1.918 + } 1.919 + 1.920 + if(!$gConfig{'partition_table'} && $gConfig{'recursive_access'}) { 1.921 + # Even if we do want to create disk images without partition tables, 1.922 + # it's impossible unless recursive_access is set. 1.923 + my($rootDevice, $partitionDevice, $partitionMountPoint); 1.924 + 1.925 + if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = 1.926 + hdidMountImage($tempMount, '-readonly', $hybridImage))) { 1.927 + cleanupDie('hdid mount failed'); 1.928 + } 1.929 + 1.930 + push(@gCleanup, sub {commandVerbosity(0, 1.931 + $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); 1.932 + 1.933 + my($udrwImage); 1.934 + $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); 1.935 + 1.936 + if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', 'UDRW', 1.937 + '-ov', '-srcdevice', $partitionDevice, $udrwImage) != 0) { 1.938 + cleanupDie('hdiutil create failed'); 1.939 + } 1.940 + 1.941 + $uncompressedImage = $udrwImage; 1.942 + 1.943 + # Going to eject before anything else can fail. Get the eject off 1.944 + # the stack. 1.945 + pop(@gCleanup); 1.946 + 1.947 + # $hybridImage will be removed soon, but until then, it needs to 1.948 + # stay on the cleanup stack. It needs to wait until after 1.949 + # ejection. $udrwImage is staying around. Make it appear as 1.950 + # though it's been done before $hybridImage. 1.951 + # 1.952 + # splice in this form is the same as popping one element to 1.953 + # @tempCleanup and pushing the subroutine. 1.954 + my(@tempCleanup); 1.955 + @tempCleanup = splice(@gCleanup, -1, 1, 1.956 + sub {commandInternalVerbosity(0, 'unlink', $udrwImage);}); 1.957 + push(@gCleanup, @tempCleanup); 1.958 + 1.959 + if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { 1.960 + cleanupDie('diskutil eject failed'); 1.961 + } 1.962 + 1.963 + # Pop unlink of $uncompressedImage 1.964 + pop(@gCleanup); 1.965 + 1.966 + if(commandInternal('unlink', $hybridImage) != 1) { 1.967 + cleanupDie('unlink hybridImage failed: '.$!); 1.968 + } 1.969 + } 1.970 + } 1.971 + else { 1.972 + # makehybrid is not available, fall back to making a UDRW and 1.973 + # converting to a compressed image. It ought to be possible to 1.974 + # create a compressed image directly, but those come out far too 1.975 + # large (journaling?) and need to be read-write to fix up the 1.976 + # volume icon anyway. Luckily, we can take advantage of a single 1.977 + # call back into this function. 1.978 + my($udrwImage); 1.979 + $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); 1.980 + 1.981 + diskImageMaker($source, $udrwImage, 'UDRW', $name, $tempDir, 1.982 + $tempMount, $baseName, $setRootIcon); 1.983 + 1.984 + # The call back into diskImageMaker already removed $source. 1.985 + 1.986 + $uncompressedImage = $udrwImage; 1.987 + } 1.988 + 1.989 + # The uncompressed disk image is now in its final form. Compress it. 1.990 + # Jaguar doesn't support hdiutil convert -ov, but it always allows 1.991 + # overwriting. 1.992 + # bzip2-compressed UDBZ images can only be created and mounted on 10.4 1.993 + # and later. The bzip2-level imagekey is only effective when creating 1.994 + # images in 10.5. In 10.4, bzip2-level is harmlessly ignored, and the 1.995 + # default value of 1 is always used. 1.996 + if(command($gConfig{'cmd_hdiutil'}, 'convert', '-format', $format, 1.997 + '-imagekey', ($format eq 'UDBZ' ? 'bzip2-level=9' : 'zlib-level=9'), 1.998 + (defined($gDarwinMajor) && $gDarwinMajor <= 6 ? () : ('-ov')), 1.999 + $uncompressedImage, '-o', $destination) != 0) { 1.1000 + cleanupDie('hdiutil convert failed'); 1.1001 + } 1.1002 + 1.1003 + # $uncompressedImage is going to be unlinked before anything else can 1.1004 + # fail. splice in this form is the same as pop/push. 1.1005 + splice(@gCleanup, -1, 1, 1.1006 + sub {commandInternalVerbosity(0, 'unlink', $destination);}); 1.1007 + 1.1008 + if(commandInternal('unlink', $uncompressedImage) != 1) { 1.1009 + cleanupDie('unlink uncompressedImage failed: '.$!); 1.1010 + } 1.1011 + 1.1012 + # At this point, the only thing that the compressed block has added to 1.1013 + # the cleanup stack is the removal of $destination. $source has already 1.1014 + # been removed, and its cleanup entry has been removed as well. 1.1015 + } 1.1016 + elsif($format eq 'UDRW' || $format eq 'UDSP') { 1.1017 + my(@extraArguments); 1.1018 + if(!$gConfig{'partition_table'}) { 1.1019 + @extraArguments = ('-layout', 'NONE'); 1.1020 + } 1.1021 + 1.1022 + if($gConfig{'create_directly'}) { 1.1023 + # Use -fs HFS+ to suppress the journal. 1.1024 + if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format, 1.1025 + @extraArguments, '-fs', 'HFS+', '-volname', $name, 1.1026 + '-ov', '-srcfolder', $source, $destination) != 0) { 1.1027 + cleanupDie('hdiutil create failed'); 1.1028 + } 1.1029 + 1.1030 + # $source is no longer needed and will be removed before anything 1.1031 + # else can fail. splice in this form is the same as pop/push. 1.1032 + splice(@gCleanup, -1, 1, 1.1033 + sub {commandInternalVerbosity(0, 'unlink', $destination);}); 1.1034 + 1.1035 + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { 1.1036 + cleanupDie('rm -rf failed'); 1.1037 + } 1.1038 + } 1.1039 + else { 1.1040 + # hdiutil create does not support -srcfolder or -srcdevice, it only 1.1041 + # knows how to create blank images. Figure out how large an image 1.1042 + # is needed, create it, and fill it. This is needed for Jaguar. 1.1043 + 1.1044 + # Use native block size for hdiutil create -sectors. 1.1045 + delete $ENV{'BLOCKSIZE'}; 1.1046 + 1.1047 + my(@duOutput, $ignore, $sizeBlocks, $sizeOverhead, $sizeTotal, $type); 1.1048 + if(!(@output = commandOutput($gConfig{'cmd_du'}, '-s', $tempRoot)) || 1.1049 + $? != 0) { 1.1050 + cleanupDie('du failed'); 1.1051 + } 1.1052 + ($sizeBlocks, $ignore) = split(' ', $output[0], 2); 1.1053 + 1.1054 + # The filesystem itself takes up 152 blocks of its own blocks for the 1.1055 + # filesystem up to 8192 blocks, plus 64 blocks for every additional 1.1056 + # 4096 blocks or portion thereof. 1.1057 + $sizeOverhead = 152 + 64 * POSIX::ceil( 1.1058 + (($sizeBlocks - 8192) > 0) ? (($sizeBlocks - 8192) / (4096 - 64)) : 0); 1.1059 + 1.1060 + # The number of blocks must be divisible by 8. 1.1061 + my($mod); 1.1062 + if($mod = ($sizeOverhead % 8)) { 1.1063 + $sizeOverhead += 8 - $mod; 1.1064 + } 1.1065 + 1.1066 + # sectors is taken as the size of a disk, not a filesystem, so the 1.1067 + # partition table eats into it. 1.1068 + if($gConfig{'partition_table'}) { 1.1069 + $sizeOverhead += 80; 1.1070 + } 1.1071 + 1.1072 + # That was hard. Leave some breathing room anyway. Use 1024 sectors 1.1073 + # (512kB). These read-write images wouldn't be useful if they didn't 1.1074 + # have at least a little free space. 1.1075 + $sizeTotal = $sizeBlocks + $sizeOverhead + 1024; 1.1076 + 1.1077 + # Minimum sizes - these numbers are larger on Jaguar than on later 1.1078 + # systems. Just use the Jaguar numbers, since it's unlikely to wind 1.1079 + # up here on any other release. 1.1080 + if($gConfig{'partition_table'} && $sizeTotal < 8272) { 1.1081 + $sizeTotal = 8272; 1.1082 + } 1.1083 + if(!$gConfig{'partition_table'} && $sizeTotal < 8192) { 1.1084 + $sizeTotal = 8192; 1.1085 + } 1.1086 + 1.1087 + # hdiutil create without -srcfolder or -srcdevice will not accept 1.1088 + # -format. It uses -type. Fortunately, the two supported formats 1.1089 + # here map directly to the only two supported types. 1.1090 + if ($format eq 'UDSP') { 1.1091 + $type = 'SPARSE'; 1.1092 + } 1.1093 + else { 1.1094 + $type = 'UDIF'; 1.1095 + } 1.1096 + 1.1097 + if(command($gConfig{'cmd_hdiutil'}, 'create', '-type', $type, 1.1098 + @extraArguments, '-fs', 'HFS+', '-volname', $name, 1.1099 + '-ov', '-sectors', $sizeTotal, $destination) != 0) { 1.1100 + cleanupDie('hdiutil create failed'); 1.1101 + } 1.1102 + 1.1103 + push(@gCleanup, 1.1104 + sub {commandInternalVerbosity(0, 'unlink', $destination);}); 1.1105 + 1.1106 + # The rsync will occur shortly. 1.1107 + } 1.1108 + 1.1109 + my($mounted, $rootDevice, $partitionDevice, $partitionMountPoint); 1.1110 + 1.1111 + $mounted=0; 1.1112 + if(!$gConfig{'create_directly'} || $gConfig{'openfolder_bless'} || 1.1113 + $setRootIcon) { 1.1114 + # The disk image only needs to be mounted if: 1.1115 + # create_directly is false, because the content needs to be copied 1.1116 + # openfolder_bless is true, because bless -openfolder needs to run 1.1117 + # setRootIcon is true, because the root needs its attributes set. 1.1118 + if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = 1.1119 + hdidMountImage($tempMount, $destination))) { 1.1120 + cleanupDie('hdid mount failed'); 1.1121 + } 1.1122 + 1.1123 + $mounted=1; 1.1124 + 1.1125 + push(@gCleanup, sub {commandVerbosity(0, 1.1126 + $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); 1.1127 + } 1.1128 + 1.1129 + if(!$gConfig{'create_directly'}) { 1.1130 + # Couldn't create and copy directly in one fell swoop. Now that 1.1131 + # the volume is mounted, copy the files. --copy-unsafe-links is 1.1132 + # unnecessary since it was used to copy everything to the staging 1.1133 + # area. There can be no more unsafe links. 1.1134 + if(command($gConfig{'cmd_rsync'}, '-a', 1.1135 + $source.'/',$partitionMountPoint) != 0) { 1.1136 + cleanupDie('rsync to new volume failed'); 1.1137 + } 1.1138 + 1.1139 + # We need to get the rm -rf of $source off the stack, because it's 1.1140 + # being cleaned up here. There are two items now on top of it: 1.1141 + # removing the target image and, above that, ejecting it. Splice it 1.1142 + # out. 1.1143 + my(@tempCleanup); 1.1144 + @tempCleanup = splice(@gCleanup, -2); 1.1145 + # The next splice is the same as popping once and pushing @tempCleanup. 1.1146 + splice(@gCleanup, -1, 1, @tempCleanup); 1.1147 + 1.1148 + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { 1.1149 + cleanupDie('rm -rf failed'); 1.1150 + } 1.1151 + } 1.1152 + 1.1153 + if($gConfig{'openfolder_bless'}) { 1.1154 + # On Tiger, the bless docs say to use --openfolder, but only 1.1155 + # --openfolder is accepted on Panther. Tiger takes it with a single 1.1156 + # dash too. Jaguar is out of luck. 1.1157 + if(command($gConfig{'cmd_bless'}, '-openfolder', 1.1158 + $partitionMountPoint) != 0) { 1.1159 + cleanupDie('bless failed'); 1.1160 + } 1.1161 + } 1.1162 + 1.1163 + setAttributes($partitionMountPoint, @attributes); 1.1164 + 1.1165 + if($setRootIcon) { 1.1166 + # When "hdiutil create -srcfolder" is used, the root folder's 1.1167 + # attributes are not copied to the new volume. Fix up. 1.1168 + 1.1169 + if(command($gConfig{'cmd_SetFile'}, '-a', 'C', 1.1170 + $partitionMountPoint) != 0) { 1.1171 + cleanupDie('SetFile failed'); 1.1172 + } 1.1173 + } 1.1174 + 1.1175 + if($mounted) { 1.1176 + # Pop diskutil eject 1.1177 + pop(@gCleanup); 1.1178 + 1.1179 + if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { 1.1180 + cleanupDie('diskutil eject failed'); 1.1181 + } 1.1182 + } 1.1183 + 1.1184 + # End of UDRW/UDSP section. At this point, $source has been removed 1.1185 + # and its cleanup entry has been removed from the stack. 1.1186 + } 1.1187 + else { 1.1188 + cleanupDie('unrecognized format'); 1.1189 + print STDERR ($0.": unrecognized format\n"); 1.1190 + exit(1); 1.1191 + } 1.1192 +} 1.1193 + 1.1194 +# giveExtension($file, $extension) 1.1195 +# 1.1196 +# If $file does not end in $extension, $extension is added. The new 1.1197 +# filename is returned. 1.1198 +sub giveExtension($$) { 1.1199 + my($extension, $file); 1.1200 + ($file, $extension) = @_; 1.1201 + if(substr($file, -length($extension)) ne $extension) { 1.1202 + return $file.$extension; 1.1203 + } 1.1204 + return $file; 1.1205 +} 1.1206 + 1.1207 +# hdidMountImage($mountPoint, @arguments) 1.1208 +# 1.1209 +# Runs the hdid command with arguments specified by @arguments. 1.1210 +# @arguments may be a single-element array containing the name of the 1.1211 +# disk image to mount. Returns a three-element array, with elements 1.1212 +# corresponding to: 1.1213 +# - The root device of the mounted image, suitable for ejection 1.1214 +# - The device corresponding to the mounted partition 1.1215 +# - The mounted partition's mount point 1.1216 +# 1.1217 +# If running on a system that supports easy mounting at points outside 1.1218 +# of the default /Volumes with hdiutil attach, it is used instead of hdid, 1.1219 +# and $mountPoint is used as the mount point. 1.1220 +# 1.1221 +# The root device will differ from the partition device when the disk 1.1222 +# image contains a partition table, otherwise, they will be identical. 1.1223 +# 1.1224 +# If hdid fails, undef is returned. 1.1225 +sub hdidMountImage($@) { 1.1226 + my(@arguments, @command, $mountPoint); 1.1227 + ($mountPoint, @arguments) = @_; 1.1228 + my(@output); 1.1229 + 1.1230 + if($gConfig{'hdiutil_mountpoint'}) { 1.1231 + @command=($gConfig{'cmd_hdiutil'}, 'attach', @arguments, 1.1232 + '-mountpoint', $mountPoint); 1.1233 + } 1.1234 + else { 1.1235 + @command=($gConfig{'cmd_hdid'}, @arguments); 1.1236 + } 1.1237 + 1.1238 + if(!(@output = commandOutput(@command)) || 1.1239 + $? != 0) { 1.1240 + return undef; 1.1241 + } 1.1242 + 1.1243 + if($gDryRun) { 1.1244 + return('/dev/diskX','/dev/diskXsY','/Volumes/'.$volumeName); 1.1245 + } 1.1246 + 1.1247 + my($line, $restOfLine, $rootDevice); 1.1248 + 1.1249 + foreach $line (@output) { 1.1250 + my($device, $mountpoint); 1.1251 + if($line !~ /^\/dev\//) { 1.1252 + # Consider only lines that correspond to /dev entries 1.1253 + next; 1.1254 + } 1.1255 + ($device, $restOfLine) = split(' ', $line, 2); 1.1256 + 1.1257 + if(!defined($rootDevice) || $rootDevice eq '') { 1.1258 + # If this is the first device seen, it's the root device to be 1.1259 + # used for ejection. Keep it. 1.1260 + $rootDevice = $device; 1.1261 + } 1.1262 + 1.1263 + if($restOfLine =~ /(\/.*)/) { 1.1264 + # The first partition with a mount point is the interesting one. It's 1.1265 + # usually Apple_HFS and usually the last one in the list, but beware of 1.1266 + # the possibility of other filesystem types and the Apple_Free partition. 1.1267 + # If the disk image contains no partition table, the partition will not 1.1268 + # have a type, so look for the mount point by looking for a slash. 1.1269 + $mountpoint = $1; 1.1270 + return($rootDevice, $device, $mountpoint); 1.1271 + } 1.1272 + } 1.1273 + 1.1274 + # No mount point? This is bad. If there's a root device, eject it. 1.1275 + if(defined($rootDevice) && $rootDevice ne '') { 1.1276 + # Failing anyway, so don't care about failure 1.1277 + commandVerbosity(0, $gConfig{'cmd_diskutil'}, 'eject', $rootDevice); 1.1278 + } 1.1279 + 1.1280 + return undef; 1.1281 +} 1.1282 + 1.1283 +# isFormatCompressed($format) 1.1284 +# 1.1285 +# Returns true if $format corresponds to a compressed disk image format. 1.1286 +# Returns false otherwise. 1.1287 +sub isFormatCompressed($) { 1.1288 + my($format); 1.1289 + ($format) = @_; 1.1290 + return $format eq 'UDZO' || $format eq 'UDBZ'; 1.1291 +} 1.1292 + 1.1293 +# licenseMaker($text, $resource) 1.1294 +# 1.1295 +# Takes a plain text file at path $text and creates a license agreement 1.1296 +# resource containing the text at path $license. English-only, and 1.1297 +# no special formatting. This is the bare-bones stuff. For more 1.1298 +# intricate license agreements, create your own resource. 1.1299 +# 1.1300 +# ftp://ftp.apple.com/developer/Development_Kits/SLAs_for_UDIFs_1.0.dmg 1.1301 +sub licenseMaker($$) { 1.1302 + my($resource, $text); 1.1303 + ($text, $resource) = @_; 1.1304 + if(!sysopen(*TEXT, $text, O_RDONLY)) { 1.1305 + print STDERR ($0.': licenseMaker: sysopen text: '.$!."\n"); 1.1306 + return 0; 1.1307 + } 1.1308 + if(!sysopen(*RESOURCE, $resource, O_WRONLY|O_CREAT|O_EXCL)) { 1.1309 + print STDERR ($0.': licenseMaker: sysopen resource: '.$!."\n"); 1.1310 + return 0; 1.1311 + } 1.1312 + print RESOURCE << '__EOT__'; 1.1313 +// See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs. 1.1314 +data 'LPic' (5000) { 1.1315 + // Default language ID, 0 = English 1.1316 + $"0000" 1.1317 + // Number of entries in list 1.1318 + $"0001" 1.1319 + 1.1320 + // Entry 1 1.1321 + // Language ID, 0 = English 1.1322 + $"0000" 1.1323 + // Resource ID, 0 = STR#/TEXT/styl 5000 1.1324 + $"0000" 1.1325 + // Multibyte language, 0 = no 1.1326 + $"0000" 1.1327 +}; 1.1328 + 1.1329 +resource 'STR#' (5000, "English") { 1.1330 + { 1.1331 + // Language (unused?) = English 1.1332 + "English", 1.1333 + // Agree 1.1334 + "Agree", 1.1335 + // Disagree 1.1336 + "Disagree", 1.1337 +__EOT__ 1.1338 + # This stuff needs double-quotes for interpolations to work. 1.1339 + print RESOURCE (" // Print, ellipsis is 0xC9\n"); 1.1340 + print RESOURCE (" \"Print\xc9\",\n"); 1.1341 + print RESOURCE (" // Save As, ellipsis is 0xC9\n"); 1.1342 + print RESOURCE (" \"Save As\xc9\",\n"); 1.1343 + print RESOURCE (' // Descriptive text, curly quotes are 0xD2 and 0xD3'. 1.1344 + "\n"); 1.1345 + print RESOURCE (' "If you agree to the terms of this license '. 1.1346 + "agreement, click \xd2Agree\xd3 to access the software. If you ". 1.1347 + "do not agree, press \xd2Disagree.\xd3\"\n"); 1.1348 +print RESOURCE << '__EOT__'; 1.1349 + }; 1.1350 +}; 1.1351 + 1.1352 +// Beware of 1024(?) byte (character?) line length limitation. Split up long 1.1353 +// lines. 1.1354 +// If straight quotes are used ("), remember to escape them (\"). 1.1355 +// Newline is \n, to leave a blank line, use two of them. 1.1356 +// 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly 1.1357 +// single quotes ('), 0xD5 is also the apostrophe. 1.1358 +data 'TEXT' (5000, "English") { 1.1359 +__EOT__ 1.1360 + 1.1361 + while(!eof(*TEXT)) { 1.1362 + my($line); 1.1363 + chop($line = <TEXT>); 1.1364 + 1.1365 + while(defined($line)) { 1.1366 + my($chunk); 1.1367 + 1.1368 + # Rez doesn't care for lines longer than (1024?) characters. Split 1.1369 + # at less than half of that limit, in case everything needs to be 1.1370 + # backwhacked. 1.1371 + if(length($line)>500) { 1.1372 + $chunk = substr($line, 0, 500); 1.1373 + $line = substr($line, 500); 1.1374 + } 1.1375 + else { 1.1376 + $chunk = $line; 1.1377 + $line = undef; 1.1378 + } 1.1379 + 1.1380 + if(length($chunk) > 0) { 1.1381 + # Unsafe characters are the double-quote (") and backslash (\), escape 1.1382 + # them with backslashes. 1.1383 + $chunk =~ s/(["\\])/\\$1/g; 1.1384 + 1.1385 + print RESOURCE ' "'.$chunk.'"'."\n"; 1.1386 + } 1.1387 + } 1.1388 + print RESOURCE ' "\n"'."\n"; 1.1389 + } 1.1390 + close(*TEXT); 1.1391 + 1.1392 + print RESOURCE << '__EOT__'; 1.1393 +}; 1.1394 + 1.1395 +data 'styl' (5000, "English") { 1.1396 + // Number of styles following = 1 1.1397 + $"0001" 1.1398 + 1.1399 + // Style 1. This is used to display the first two lines in bold text. 1.1400 + // Start character = 0 1.1401 + $"0000 0000" 1.1402 + // Height = 16 1.1403 + $"0010" 1.1404 + // Ascent = 12 1.1405 + $"000C" 1.1406 + // Font family = 1024 (Lucida Grande) 1.1407 + $"0400" 1.1408 + // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline 1.1409 + // 0x10=shadow 0x20=condensed 0x40=extended 1.1410 + $"00" 1.1411 + // Style, unused? 1.1412 + $"02" 1.1413 + // Size = 12 point 1.1414 + $"000C" 1.1415 + // Color, RGB 1.1416 + $"0000 0000 0000" 1.1417 +}; 1.1418 +__EOT__ 1.1419 + close(*RESOURCE); 1.1420 + 1.1421 + return 1; 1.1422 +} 1.1423 + 1.1424 +# pathSplit($pathname) 1.1425 +# 1.1426 +# Splits $pathname into an array of path components. 1.1427 +sub pathSplit($) { 1.1428 + my($pathname); 1.1429 + ($pathname) = @_; 1.1430 + return split(/\//, $pathname); 1.1431 +} 1.1432 + 1.1433 +# setAttributes($root, @attributeList) 1.1434 +# 1.1435 +# @attributeList is an array, each element of which must be in the form 1.1436 +# <a>:<file>. <a> is a list of attributes, per SetFile. <file> is a file 1.1437 +# which is taken as relative to $root (even if it appears as an absolute 1.1438 +# path.) SetFile is called to set the attributes on each file in 1.1439 +# @attributeList. 1.1440 +sub setAttributes($@) { 1.1441 + my(@attributes, $root); 1.1442 + ($root, @attributes) = @_; 1.1443 + my($attribute); 1.1444 + foreach $attribute (@attributes) { 1.1445 + my($attrList, $file, @fileList, @fixedFileList); 1.1446 + ($attrList, @fileList) = split(/:/, $attribute); 1.1447 + if(!defined($attrList) || !@fileList) { 1.1448 + cleanupDie('--attribute requires <attributes>:<file>'); 1.1449 + } 1.1450 + @fixedFileList=(); 1.1451 + foreach $file (@fileList) { 1.1452 + if($file =~ /^\//) { 1.1453 + push(@fixedFileList, $root.$file); 1.1454 + } 1.1455 + else { 1.1456 + push(@fixedFileList, $root.'/'.$file); 1.1457 + } 1.1458 + } 1.1459 + if(command($gConfig{'cmd_SetFile'}, '-a', $attrList, @fixedFileList)) { 1.1460 + cleanupDie('SetFile failed to set attributes'); 1.1461 + } 1.1462 + } 1.1463 + return; 1.1464 +} 1.1465 + 1.1466 +sub trapSignal($) { 1.1467 + my($signalName); 1.1468 + ($signalName) = @_; 1.1469 + cleanupDie('exiting on SIG'.$signalName); 1.1470 +} 1.1471 + 1.1472 +sub usage() { 1.1473 + print STDERR ( 1.1474 +"usage: pkg-dmg --source <source-folder>\n". 1.1475 +" --target <target-image>\n". 1.1476 +" [--format <format>] (default: UDZO)\n". 1.1477 +" [--volname <volume-name>] (default: same name as source)\n". 1.1478 +" [--tempdir <temp-dir>] (default: same dir as target)\n". 1.1479 +" [--mkdir <directory>] (make directory in image)\n". 1.1480 +" [--copy <source>[:<dest>]] (extra files to add)\n". 1.1481 +" [--symlink <source>[:<dest>]] (extra symlinks to add)\n". 1.1482 +" [--license <file>] (plain text license agreement)\n". 1.1483 +" [--resource <file>] (flat .r files to merge)\n". 1.1484 +" [--icon <icns-file>] (volume icon)\n". 1.1485 +" [--attribute <a>:<file>] (set file attributes)\n". 1.1486 +" [--idme] (make Internet-enabled image)\n". 1.1487 +" [--sourcefile] (treat --source as a file)\n". 1.1488 +" [--verbosity <level>] (0, 1, 2; default=2)\n". 1.1489 +" [--dry-run] (print what would be done)\n"); 1.1490 + return; 1.1491 +}