build/package/mac_osx/pkg-dmg

changeset 0
6474c204b198
     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 +}

mercurial