build/package/mac_osx/pkg-dmg

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 #!/usr/bin/perl
michael@0 2 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 5
michael@0 6 use strict;
michael@0 7 use warnings;
michael@0 8
michael@0 9 =pod
michael@0 10
michael@0 11 =head1 NAME
michael@0 12
michael@0 13 B<pkg-dmg> - Mac OS X disk image (.dmg) packager
michael@0 14
michael@0 15 =head1 SYNOPSIS
michael@0 16
michael@0 17 B<pkg-dmg>
michael@0 18 B<--source> I<source-folder>
michael@0 19 B<--target> I<target-image>
michael@0 20 [B<--format> I<format>]
michael@0 21 [B<--volname> I<volume-name>]
michael@0 22 [B<--tempdir> I<temp-dir>]
michael@0 23 [B<--mkdir> I<directory>]
michael@0 24 [B<--copy> I<source>[:I<dest>]]
michael@0 25 [B<--symlink> I<source>[:I<dest>]]
michael@0 26 [B<--license> I<file>]
michael@0 27 [B<--resource> I<file>]
michael@0 28 [B<--icon> I<icns-file>]
michael@0 29 [B<--attribute> I<a>:I<file>[:I<file>...]
michael@0 30 [B<--idme>]
michael@0 31 [B<--sourcefile>]
michael@0 32 [B<--verbosity> I<level>]
michael@0 33 [B<--dry-run>]
michael@0 34
michael@0 35 =head1 DESCRIPTION
michael@0 36
michael@0 37 I<pkg-dmg> takes a directory identified by I<source-folder> and transforms
michael@0 38 it into a disk image stored as I<target-image>. The disk image will
michael@0 39 occupy the least space possible for its format, or the least space that the
michael@0 40 authors have been able to figure out how to achieve.
michael@0 41
michael@0 42 =head1 OPTIONS
michael@0 43
michael@0 44 =over 5
michael@0 45
michael@0 46 ==item B<--source> I<source-folder>
michael@0 47
michael@0 48 Identifies the directory that will be packaged up. This directory is not
michael@0 49 touched, a copy will be made in a temporary directory for staging purposes.
michael@0 50 See B<--tempdir>.
michael@0 51
michael@0 52 ==item B<--target> I<target-image>
michael@0 53
michael@0 54 The disk image to create. If it exists and is not in use, it will be
michael@0 55 overwritten. If I<target-image> already contains a suitable extension,
michael@0 56 it will be used unmodified. If no extension is present, or the extension
michael@0 57 is incorrect for the selected format, the proper extension will be added.
michael@0 58 See B<--format>.
michael@0 59
michael@0 60 ==item B<--format> I<format>
michael@0 61
michael@0 62 The format to create the disk image in. Valid values for I<format> are:
michael@0 63 - UDZO - zlib-compressed, read-only; extension I<.dmg>
michael@0 64 - UDBZ - bzip2-compressed, read-only; extension I<.dmg>;
michael@0 65 create and use on 10.4 ("Tiger") and later only
michael@0 66 - UDRW - read-write; extension I<.dmg>
michael@0 67 - UDSP - read-write, sparse; extension I<.sparseimage>
michael@0 68
michael@0 69 UDBZ is the default format.
michael@0 70
michael@0 71 See L<hdiutil(1)> for a description of these formats.
michael@0 72
michael@0 73 =item B<--volname> I<volume-name>
michael@0 74
michael@0 75 The name of the volume in the disk image. If not specified, I<volume-name>
michael@0 76 defaults to the name of the source directory from B<--source>.
michael@0 77
michael@0 78 =item B<--tempdir> I<temp-dir>
michael@0 79
michael@0 80 A temporary directory to stage intermediate files in. I<temp-dir> must
michael@0 81 have enough space available to accommodate twice the size of the files
michael@0 82 being packaged. If not specified, defaults to the same directory that
michael@0 83 the I<target-image> is to be placed in. B<pkg-dmg> will remove any
michael@0 84 temporary files it places in I<temp-dir>.
michael@0 85
michael@0 86 =item B<--mkdir> I<directory>
michael@0 87
michael@0 88 Specifies a directory that should be created in the disk image.
michael@0 89 I<directory> and any ancestor directories will be created. This is
michael@0 90 useful in conjunction with B<--copy>, when copying files to directories
michael@0 91 that may not exist in I<source-folder>. B<--mkdir> may appear multiple
michael@0 92 times.
michael@0 93
michael@0 94 =item B<--copy> I<source>[:I<dest>]
michael@0 95
michael@0 96 Additional files to copy into the disk image. If I<dest> is
michael@0 97 specified, I<source> is copied to the location I<dest> identifies,
michael@0 98 otherwise, I<source> is copied to the root of the new volume. B<--copy>
michael@0 99 provides a way to package up a I<source-folder> by adding files to it
michael@0 100 without modifying the original I<source-folder>. B<--copy> may appear
michael@0 101 multiple times.
michael@0 102
michael@0 103 This option is useful for adding .DS_Store files and window backgrounds
michael@0 104 to disk images.
michael@0 105
michael@0 106 =item B<--symlink> I<source>[:I<dest>]
michael@0 107
michael@0 108 Like B<--copy>, but allows symlinks to point out of the volume. Empty symlink
michael@0 109 destinations are interpreted as "like the source path, but inside the dmg"
michael@0 110
michael@0 111 This option is useful for adding symlinks to external resources,
michael@0 112 e.g. to /Applications.
michael@0 113
michael@0 114 =item B<--license> I<file>
michael@0 115
michael@0 116 A plain text file containing a license agreement to be displayed before
michael@0 117 the disk image is mounted. English is the only supported language. To
michael@0 118 include license agreements in other languages, in multiple languages,
michael@0 119 or to use formatted text, prepare a resource and use L<--resource>.
michael@0 120
michael@0 121 =item B<--resource> I<file>
michael@0 122
michael@0 123 A resource file to merge into I<target-image>. If I<format> is UDZO or
michael@0 124 UDBZ, the disk image will be flattened to a single-fork file that contains
michael@0 125 the resource but may be freely transferred without any special encodings.
michael@0 126 I<file> must be in a format suitable for L<Rez(1)>. See L<Rez(1)> for a
michael@0 127 description of the format, and L<hdiutil(1)> for a discussion on flattened
michael@0 128 disk images. B<--resource> may appear multiple times.
michael@0 129
michael@0 130 This option is useful for adding license agreements and other messages
michael@0 131 to disk images.
michael@0 132
michael@0 133 =item B<--icon> I<icns-file>
michael@0 134
michael@0 135 Specifies an I<icns> file that will be used as the icon for the root of
michael@0 136 the volume. This file will be copied to the new volume and the custom
michael@0 137 icon attribute will be set on the root folder.
michael@0 138
michael@0 139 =item B<--attribute> I<a>:I<file>[:I<file>...]
michael@0 140
michael@0 141 Sets the attributes of I<file> to the attribute list in I<a>. See
michael@0 142 L<SetFile(1)>
michael@0 143
michael@0 144 =item B<--idme>
michael@0 145
michael@0 146 Enable IDME to make the disk image "Internet-enabled." The first time
michael@0 147 the image is mounted, if IDME processing is enabled on the system, the
michael@0 148 contents of the image will be copied out of the image and the image will
michael@0 149 be placed in the trash with IDME disabled.
michael@0 150
michael@0 151 =item B<--sourcefile>
michael@0 152
michael@0 153 If this option is present, I<source-folder> is treated as a file, and is
michael@0 154 placed as a file within the volume's root folder. Without this option,
michael@0 155 I<source-folder> is treated as the volume root itself.
michael@0 156
michael@0 157 =item B<--verbosity> I<level>
michael@0 158
michael@0 159 Adjusts the level of loudness of B<pkg-dmg>. The possible values for
michael@0 160 I<level> are:
michael@0 161 0 - Only error messages are displayed.
michael@0 162 1 - Print error messages and command invocations.
michael@0 163 2 - Print everything, including command output.
michael@0 164
michael@0 165 The default I<level> is 2.
michael@0 166
michael@0 167 =item B<--dry-run>
michael@0 168
michael@0 169 When specified, the commands that would be executed are printed, without
michael@0 170 actually executing them. When commands depend on the output of previous
michael@0 171 commands, dummy values are displayed.
michael@0 172
michael@0 173 =back
michael@0 174
michael@0 175 =head1 NON-OPTIONS
michael@0 176
michael@0 177 =over 5
michael@0 178
michael@0 179 =item
michael@0 180
michael@0 181 Resource forks aren't copied.
michael@0 182
michael@0 183 =item
michael@0 184
michael@0 185 The root folder of the created volume is designated as the folder
michael@0 186 to open when the volume is mounted. See L<bless(8)>.
michael@0 187
michael@0 188 =item
michael@0 189
michael@0 190 All files in the volume are set to be world-readable, only writable
michael@0 191 by the owner, and world-executable when appropriate. All other
michael@0 192 permissions bits are cleared.
michael@0 193
michael@0 194 =item
michael@0 195
michael@0 196 When possible, disk images are created without any partition tables. This
michael@0 197 is what L<hdiutil(1)> refers to as I<-layout NONE>, and saves a handful of
michael@0 198 kilobytes. The alternative, I<SPUD>, contains a partition table that
michael@0 199 is not terribly handy on disk images that are not intended to represent any
michael@0 200 physical disk.
michael@0 201
michael@0 202 =item
michael@0 203
michael@0 204 Read-write images are created with journaling off. Any read-write image
michael@0 205 created by this tool is expected to be transient, and the goal of this tool
michael@0 206 is to create images which consume a minimum of space.
michael@0 207
michael@0 208 =back
michael@0 209
michael@0 210 =head1 EXAMPLE
michael@0 211
michael@0 212 pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg
michael@0 213 --sourcefile --volname DeerPark --icon ~/DeerPark.icns
michael@0 214 --mkdir /.background
michael@0 215 --copy DeerParkBackground.png:/.background/background.png
michael@0 216 --copy DeerParkDSStore:/.DS_Store
michael@0 217 --symlink /Applications:"/Drag to here"
michael@0 218
michael@0 219 =head1 REQUIREMENTS
michael@0 220
michael@0 221 I<pkg-dmg> has been tested with Mac OS X releases 10.2 ("Jaguar")
michael@0 222 through 10.4 ("Tiger"). Certain adjustments to behavior are made
michael@0 223 depending on the host system's release. Mac OS X 10.3 ("Panther") or
michael@0 224 later are recommended.
michael@0 225
michael@0 226 =head1 LICENSE
michael@0 227
michael@0 228 MPL 2.
michael@0 229
michael@0 230 =head1 AUTHOR
michael@0 231
michael@0 232 Mark Mentovai
michael@0 233
michael@0 234 =head1 SEE ALSO
michael@0 235
michael@0 236 L<bless(8)>, L<diskutil(8)>, L<hdid(8)>, L<hdiutil(1)>, L<Rez(1)>,
michael@0 237 L<rsync(1)>, L<SetFile(1)>
michael@0 238
michael@0 239 =cut
michael@0 240
michael@0 241 use Fcntl;
michael@0 242 use POSIX;
michael@0 243 use Getopt::Long;
michael@0 244
michael@0 245 sub argumentEscape(@);
michael@0 246 sub cleanupDie($);
michael@0 247 sub command(@);
michael@0 248 sub commandInternal($@);
michael@0 249 sub commandInternalVerbosity($$@);
michael@0 250 sub commandOutput(@);
michael@0 251 sub commandOutputVerbosity($@);
michael@0 252 sub commandVerbosity($@);
michael@0 253 sub copyFiles($@);
michael@0 254 sub diskImageMaker($$$$$$$$);
michael@0 255 sub giveExtension($$);
michael@0 256 sub hdidMountImage($@);
michael@0 257 sub isFormatCompressed($);
michael@0 258 sub licenseMaker($$);
michael@0 259 sub pathSplit($);
michael@0 260 sub setAttributes($@);
michael@0 261 sub trapSignal($);
michael@0 262 sub usage();
michael@0 263
michael@0 264 # Variables used as globals
michael@0 265 my(@gCleanup, %gConfig, $gDarwinMajor, $gDryRun, $gVerbosity);
michael@0 266
michael@0 267 # Use the commands by name if they're expected to be in the user's
michael@0 268 # $PATH (/bin:/sbin:/usr/bin:/usr/sbin). Otherwise, go by absolute
michael@0 269 # path. These may be overridden with --config.
michael@0 270 %gConfig = ('cmd_bless' => 'bless',
michael@0 271 'cmd_chmod' => 'chmod',
michael@0 272 'cmd_diskutil' => 'diskutil',
michael@0 273 'cmd_du' => 'du',
michael@0 274 'cmd_hdid' => 'hdid',
michael@0 275 'cmd_hdiutil' => 'hdiutil',
michael@0 276 'cmd_mkdir' => 'mkdir',
michael@0 277 'cmd_mktemp' => 'mktemp',
michael@0 278 'cmd_Rez' => 'Rez',
michael@0 279 'cmd_rm' => 'rm',
michael@0 280 'cmd_rsync' => 'rsync',
michael@0 281 'cmd_SetFile' => 'SetFile',
michael@0 282
michael@0 283 # create_directly indicates whether hdiutil create supports
michael@0 284 # -srcfolder and -srcdevice. It does on >= 10.3 (Panther).
michael@0 285 # This is fixed up for earlier systems below. If false,
michael@0 286 # hdiutil create is used to create empty disk images that
michael@0 287 # are manually filled.
michael@0 288 'create_directly' => 1,
michael@0 289
michael@0 290 # If hdiutil attach -mountpoint exists, use it to avoid
michael@0 291 # mounting disk images in the default /Volumes. This reduces
michael@0 292 # the likelihood that someone will notice a mounted image and
michael@0 293 # interfere with it. Only available on >= 10.3 (Panther),
michael@0 294 # fixed up for earlier systems below.
michael@0 295 #
michael@0 296 # This is presently turned off for all systems, because there
michael@0 297 # is an infrequent synchronization problem during ejection.
michael@0 298 # diskutil eject might return before the image is actually
michael@0 299 # unmounted. If pkg-dmg then attempts to clean up its
michael@0 300 # temporary directory, it could remove items from a read-write
michael@0 301 # disk image or attempt to remove items from a read-only disk
michael@0 302 # image (or a read-only item from a read-write image) and fail,
michael@0 303 # causing pkg-dmg to abort. This problem is experienced
michael@0 304 # under Tiger, which appears to eject asynchronously where
michael@0 305 # previous systems treated it as a synchronous operation.
michael@0 306 # Using hdiutil attach -mountpoint didn't always keep images
michael@0 307 # from showing up on the desktop anyway.
michael@0 308 'hdiutil_mountpoint' => 0,
michael@0 309
michael@0 310 # hdiutil makehybrid results in optimized disk images that
michael@0 311 # consume less space and mount more quickly. Use it when
michael@0 312 # it's available, but that's only on >= 10.3 (Panther).
michael@0 313 # If false, hdiutil create is used instead. Fixed up for
michael@0 314 # earlier systems below.
michael@0 315 'makehybrid' => 1,
michael@0 316
michael@0 317 # hdiutil create doesn't allow specifying a folder to open
michael@0 318 # at volume mount time, so those images are mounted and
michael@0 319 # their root folders made holy with bless -openfolder. But
michael@0 320 # only on >= 10.3 (Panther). Earlier systems are out of luck.
michael@0 321 # Even on Panther, bless refuses to run unless root.
michael@0 322 # Fixed up below.
michael@0 323 'openfolder_bless' => 1,
michael@0 324
michael@0 325 # It's possible to save a few more kilobytes by including the
michael@0 326 # partition only without any partition table in the image.
michael@0 327 # This is a good idea on any system, so turn this option off.
michael@0 328 #
michael@0 329 # Except it's buggy. "-layout NONE" seems to be creating
michael@0 330 # disk images with more data than just the partition table
michael@0 331 # stripped out. You might wind up losing the end of the
michael@0 332 # filesystem - the last file (or several) might be incomplete.
michael@0 333 'partition_table' => 1,
michael@0 334
michael@0 335 # To create a partition table-less image from something
michael@0 336 # created by makehybrid, the hybrid image needs to be
michael@0 337 # mounted and a new image made from the device associated
michael@0 338 # with the relevant partition. This requires >= 10.4
michael@0 339 # (Tiger), presumably because earlier systems have
michael@0 340 # problems creating images from devices themselves attached
michael@0 341 # to images. If this is false, makehybrid images will
michael@0 342 # have partition tables, regardless of the partition_table
michael@0 343 # setting. Fixed up for earlier systems below.
michael@0 344 'recursive_access' => 1);
michael@0 345
michael@0 346 # --verbosity
michael@0 347 $gVerbosity = 2;
michael@0 348
michael@0 349 # --dry-run
michael@0 350 $gDryRun = 0;
michael@0 351
michael@0 352 # %gConfig fix-ups based on features and bugs present in certain releases.
michael@0 353 my($ignore, $uname_r, $uname_s);
michael@0 354 ($uname_s, $ignore, $uname_r, $ignore, $ignore) = POSIX::uname();
michael@0 355 if($uname_s eq 'Darwin') {
michael@0 356 ($gDarwinMajor, $ignore) = split(/\./, $uname_r, 2);
michael@0 357
michael@0 358 # $major is the Darwin major release, which for our purposes, is 4 higher
michael@0 359 # than the interesting digit in a Mac OS X release.
michael@0 360 if($gDarwinMajor <= 6) {
michael@0 361 # <= 10.2 (Jaguar)
michael@0 362 # hdiutil create does not support -srcfolder or -srcdevice
michael@0 363 $gConfig{'create_directly'} = 0;
michael@0 364 # hdiutil attach does not support -mountpoint
michael@0 365 $gConfig{'hdiutil_mountpoint'} = 0;
michael@0 366 # hdiutil mkhybrid does not exist
michael@0 367 $gConfig{'makehybrid'} = 0;
michael@0 368 }
michael@0 369 if($gDarwinMajor <= 7) {
michael@0 370 # <= 10.3 (Panther)
michael@0 371 # Can't mount a disk image and then make a disk image from the device
michael@0 372 $gConfig{'recursive_access'} = 0;
michael@0 373 # bless does not support -openfolder on 10.2 (Jaguar) and must run
michael@0 374 # as root under 10.3 (Panther)
michael@0 375 $gConfig{'openfolder_bless'} = 0;
michael@0 376 }
michael@0 377 }
michael@0 378 else {
michael@0 379 # If it's not Mac OS X, just assume all of those good features are
michael@0 380 # available. They're not, but things will fail long before they
michael@0 381 # have a chance to make a difference.
michael@0 382 #
michael@0 383 # Now, if someone wanted to document some of these private formats...
michael@0 384 print STDERR ($0.": warning, not running on Mac OS X, ".
michael@0 385 "this could be interesting.\n");
michael@0 386 }
michael@0 387
michael@0 388 # Non-global variables used in Getopt
michael@0 389 my(@attributes, @copyFiles, @createSymlinks, $iconFile, $idme, $licenseFile,
michael@0 390 @makeDirs, $outputFormat, @resourceFiles, $sourceFile, $sourceFolder,
michael@0 391 $targetImage, $tempDir, $volumeName);
michael@0 392
michael@0 393 # --format
michael@0 394 $outputFormat = 'UDBZ';
michael@0 395
michael@0 396 # --idme
michael@0 397 $idme = 0;
michael@0 398
michael@0 399 # --sourcefile
michael@0 400 $sourceFile = 0;
michael@0 401
michael@0 402 # Leaving this might screw up the Apple tools.
michael@0 403 delete $ENV{'NEXT_ROOT'};
michael@0 404
michael@0 405 # This script can get pretty messy, so trap a few signals.
michael@0 406 $SIG{'INT'} = \&trapSignal;
michael@0 407 $SIG{'HUP'} = \&trapSignal;
michael@0 408 $SIG{'TERM'} = \&trapSignal;
michael@0 409
michael@0 410 Getopt::Long::Configure('pass_through');
michael@0 411 GetOptions('source=s' => \$sourceFolder,
michael@0 412 'target=s' => \$targetImage,
michael@0 413 'volname=s' => \$volumeName,
michael@0 414 'format=s' => \$outputFormat,
michael@0 415 'tempdir=s' => \$tempDir,
michael@0 416 'mkdir=s' => \@makeDirs,
michael@0 417 'copy=s' => \@copyFiles,
michael@0 418 'symlink=s' => \@createSymlinks,
michael@0 419 'license=s' => \$licenseFile,
michael@0 420 'resource=s' => \@resourceFiles,
michael@0 421 'icon=s' => \$iconFile,
michael@0 422 'attribute=s' => \@attributes,
michael@0 423 'idme' => \$idme,
michael@0 424 'sourcefile' => \$sourceFile,
michael@0 425 'verbosity=i' => \$gVerbosity,
michael@0 426 'dry-run' => \$gDryRun,
michael@0 427 'config=s' => \%gConfig); # "hidden" option not in usage()
michael@0 428
michael@0 429 if(@ARGV) {
michael@0 430 # All arguments are parsed by Getopt
michael@0 431 usage();
michael@0 432 exit(1);
michael@0 433 }
michael@0 434
michael@0 435 if($gVerbosity<0 || $gVerbosity>2) {
michael@0 436 usage();
michael@0 437 exit(1);
michael@0 438 }
michael@0 439
michael@0 440 if(!defined($sourceFolder) || $sourceFolder eq '' ||
michael@0 441 !defined($targetImage) || $targetImage eq '') {
michael@0 442 # --source and --target are required arguments
michael@0 443 usage();
michael@0 444 exit(1);
michael@0 445 }
michael@0 446
michael@0 447 # Make sure $sourceFolder doesn't contain trailing slashes. It messes with
michael@0 448 # rsync.
michael@0 449 while(substr($sourceFolder, -1) eq '/') {
michael@0 450 chop($sourceFolder);
michael@0 451 }
michael@0 452
michael@0 453 if(!defined($volumeName)) {
michael@0 454 # Default volumeName is the name of the source directory.
michael@0 455 my(@components);
michael@0 456 @components = pathSplit($sourceFolder);
michael@0 457 $volumeName = pop(@components);
michael@0 458 }
michael@0 459
michael@0 460 my(@tempDirComponents, $targetImageFilename);
michael@0 461 @tempDirComponents = pathSplit($targetImage);
michael@0 462 $targetImageFilename = pop(@tempDirComponents);
michael@0 463
michael@0 464 if(defined($tempDir)) {
michael@0 465 @tempDirComponents = pathSplit($tempDir);
michael@0 466 }
michael@0 467 else {
michael@0 468 # Default tempDir is the same directory as what is specified for
michael@0 469 # targetImage
michael@0 470 $tempDir = join('/', @tempDirComponents);
michael@0 471 }
michael@0 472
michael@0 473 # Ensure that the path of the target image has a suitable extension. If
michael@0 474 # it didn't, hdiutil would add one, and we wouldn't be able to find the
michael@0 475 # file.
michael@0 476 #
michael@0 477 # Note that $targetImageFilename is not being reset. This is because it's
michael@0 478 # used to build other names below, and we don't need to be adding all sorts
michael@0 479 # of extra unnecessary extensions to the name.
michael@0 480 my($originalTargetImage, $requiredExtension);
michael@0 481 $originalTargetImage = $targetImage;
michael@0 482 if($outputFormat eq 'UDSP') {
michael@0 483 $requiredExtension = '.sparseimage';
michael@0 484 }
michael@0 485 else {
michael@0 486 $requiredExtension = '.dmg';
michael@0 487 }
michael@0 488 $targetImage = giveExtension($originalTargetImage, $requiredExtension);
michael@0 489
michael@0 490 if($targetImage ne $originalTargetImage) {
michael@0 491 print STDERR ($0.": warning: target image extension is being added\n");
michael@0 492 print STDERR (' The new filename is '.
michael@0 493 giveExtension($targetImageFilename,$requiredExtension)."\n");
michael@0 494 }
michael@0 495
michael@0 496 # Make a temporary directory in $tempDir for our own nefarious purposes.
michael@0 497 my(@output, $tempSubdir, $tempSubdirTemplate);
michael@0 498 $tempSubdirTemplate=join('/', @tempDirComponents,
michael@0 499 'pkg-dmg.'.$$.'.XXXXXXXX');
michael@0 500 if(!(@output = commandOutput($gConfig{'cmd_mktemp'}, '-d',
michael@0 501 $tempSubdirTemplate)) || $#output != 0) {
michael@0 502 cleanupDie('mktemp failed');
michael@0 503 }
michael@0 504
michael@0 505 if($gDryRun) {
michael@0 506 (@output)=($tempSubdirTemplate);
michael@0 507 }
michael@0 508
michael@0 509 ($tempSubdir) = @output;
michael@0 510
michael@0 511 push(@gCleanup,
michael@0 512 sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempSubdir);});
michael@0 513
michael@0 514 my($tempMount, $tempRoot, @tempsToMake);
michael@0 515 $tempRoot = $tempSubdir.'/stage';
michael@0 516 $tempMount = $tempSubdir.'/mount';
michael@0 517 push(@tempsToMake, $tempRoot);
michael@0 518 if($gConfig{'hdiutil_mountpoint'}) {
michael@0 519 push(@tempsToMake, $tempMount);
michael@0 520 }
michael@0 521
michael@0 522 if(command($gConfig{'cmd_mkdir'}, @tempsToMake) != 0) {
michael@0 523 cleanupDie('mkdir tempRoot/tempMount failed');
michael@0 524 }
michael@0 525
michael@0 526 # This cleanup object is not strictly necessary, because $tempRoot is inside
michael@0 527 # of $tempSubdir, but the rest of the script relies on this object being
michael@0 528 # on the cleanup stack and expects to remove it.
michael@0 529 push(@gCleanup,
michael@0 530 sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempRoot);});
michael@0 531
michael@0 532 # If $sourceFile is true, it means that $sourceFolder is to be treated as
michael@0 533 # a file and placed as a file within the volume root, as opposed to being
michael@0 534 # treated as the volume root itself. rsync will do this by default, if no
michael@0 535 # trailing '/' is present. With a trailing '/', $sourceFolder becomes
michael@0 536 # $tempRoot, instead of becoming an entry in $tempRoot.
michael@0 537 if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links',
michael@0 538 $sourceFolder.($sourceFile?'':'/'),$tempRoot) != 0) {
michael@0 539 cleanupDie('rsync failed');
michael@0 540 }
michael@0 541
michael@0 542 if(@makeDirs) {
michael@0 543 my($makeDir, @tempDirsToMake);
michael@0 544 foreach $makeDir (@makeDirs) {
michael@0 545 if($makeDir =~ /^\//) {
michael@0 546 push(@tempDirsToMake, $tempRoot.$makeDir);
michael@0 547 }
michael@0 548 else {
michael@0 549 push(@tempDirsToMake, $tempRoot.'/'.$makeDir);
michael@0 550 }
michael@0 551 }
michael@0 552 if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) {
michael@0 553 cleanupDie('mkdir failed');
michael@0 554 }
michael@0 555 }
michael@0 556
michael@0 557 # copy files and/or create symlinks
michael@0 558 copyFiles($tempRoot, 'copy', @copyFiles);
michael@0 559 copyFiles($tempRoot, 'symlink', @createSymlinks);
michael@0 560
michael@0 561 if($gConfig{'create_directly'}) {
michael@0 562 # If create_directly is false, the contents will be rsynced into a
michael@0 563 # disk image and they would lose their attributes.
michael@0 564 setAttributes($tempRoot, @attributes);
michael@0 565 }
michael@0 566
michael@0 567 if(defined($iconFile)) {
michael@0 568 if(command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links', $iconFile,
michael@0 569 $tempRoot.'/.VolumeIcon.icns') != 0) {
michael@0 570 cleanupDie('rsync failed for volume icon');
michael@0 571 }
michael@0 572
michael@0 573 # It's pointless to set the attributes of the root when diskutil create
michael@0 574 # -srcfolder is being used. In that case, the attributes will be set
michael@0 575 # later, after the image is already created.
michael@0 576 if(isFormatCompressed($outputFormat) &&
michael@0 577 (command($gConfig{'cmd_SetFile'}, '-a', 'C', $tempRoot) != 0)) {
michael@0 578 cleanupDie('SetFile failed');
michael@0 579 }
michael@0 580 }
michael@0 581
michael@0 582 if(command($gConfig{'cmd_chmod'}, '-R', 'a+rX,a-st,u+w,go-w',
michael@0 583 $tempRoot) != 0) {
michael@0 584 cleanupDie('chmod failed');
michael@0 585 }
michael@0 586
michael@0 587 my($unflattenable);
michael@0 588 if(isFormatCompressed($outputFormat)) {
michael@0 589 $unflattenable = 1;
michael@0 590 }
michael@0 591 else {
michael@0 592 $unflattenable = 0;
michael@0 593 }
michael@0 594
michael@0 595 diskImageMaker($tempRoot, $targetImage, $outputFormat, $volumeName,
michael@0 596 $tempSubdir, $tempMount, $targetImageFilename, defined($iconFile));
michael@0 597
michael@0 598 if(defined($licenseFile) && $licenseFile ne '') {
michael@0 599 my($licenseResource);
michael@0 600 $licenseResource = $tempSubdir.'/license.r';
michael@0 601 if(!licenseMaker($licenseFile, $licenseResource)) {
michael@0 602 cleanupDie('licenseMaker failed');
michael@0 603 }
michael@0 604 push(@resourceFiles, $licenseResource);
michael@0 605 # Don't add a cleanup object because licenseResource is in tempSubdir.
michael@0 606 }
michael@0 607
michael@0 608 if(@resourceFiles) {
michael@0 609 # Add resources, such as a license agreement.
michael@0 610
michael@0 611 # Only unflatten read-only and compressed images. It's not supported
michael@0 612 # on other image times.
michael@0 613 if($unflattenable &&
michael@0 614 (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) {
michael@0 615 cleanupDie('hdiutil unflatten failed');
michael@0 616 }
michael@0 617 # Don't push flatten onto the cleanup stack. If we fail now, we'll be
michael@0 618 # removing $targetImage anyway.
michael@0 619
michael@0 620 # Type definitions come from Carbon.r.
michael@0 621 if(command($gConfig{'cmd_Rez'}, 'Carbon.r', @resourceFiles, '-a', '-o',
michael@0 622 $targetImage) != 0) {
michael@0 623 cleanupDie('Rez failed');
michael@0 624 }
michael@0 625
michael@0 626 # Flatten. This merges the resource fork into the data fork, so no
michael@0 627 # special encoding is needed to transfer the file.
michael@0 628 if($unflattenable &&
michael@0 629 (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) {
michael@0 630 cleanupDie('hdiutil flatten failed');
michael@0 631 }
michael@0 632 }
michael@0 633
michael@0 634 # $tempSubdir is no longer needed. It's buried on the stack below the
michael@0 635 # rm of the fresh image file. Splice in this fashion is equivalent to
michael@0 636 # pop-save, pop, push-save.
michael@0 637 splice(@gCleanup, -2, 1);
michael@0 638 # No need to remove licenseResource separately, it's in tempSubdir.
michael@0 639 if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) {
michael@0 640 cleanupDie('rm -rf tempSubdir failed');
michael@0 641 }
michael@0 642
michael@0 643 if($idme) {
michael@0 644 if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes',
michael@0 645 $targetImage) != 0) {
michael@0 646 cleanupDie('hdiutil internet-enable failed');
michael@0 647 }
michael@0 648 }
michael@0 649
michael@0 650 # Done.
michael@0 651
michael@0 652 exit(0);
michael@0 653
michael@0 654 # argumentEscape(@arguments)
michael@0 655 #
michael@0 656 # Takes a list of @arguments and makes them shell-safe.
michael@0 657 sub argumentEscape(@) {
michael@0 658 my(@arguments);
michael@0 659 @arguments = @_;
michael@0 660 my($argument, @argumentsOut);
michael@0 661 foreach $argument (@arguments) {
michael@0 662 $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g;
michael@0 663 push(@argumentsOut, $argument);
michael@0 664 }
michael@0 665 return @argumentsOut;
michael@0 666 }
michael@0 667
michael@0 668 # cleanupDie($message)
michael@0 669 #
michael@0 670 # Displays $message as an error message, and then runs through the
michael@0 671 # @gCleanup stack, performing any cleanup operations needed before
michael@0 672 # exiting. Does not return, exits with exit status 1.
michael@0 673 sub cleanupDie($) {
michael@0 674 my($message);
michael@0 675 ($message) = @_;
michael@0 676 print STDERR ($0.': '.$message.(@gCleanup?' (cleaning up)':'')."\n");
michael@0 677 while(@gCleanup) {
michael@0 678 my($subroutine);
michael@0 679 $subroutine = pop(@gCleanup);
michael@0 680 &$subroutine;
michael@0 681 }
michael@0 682 exit(1);
michael@0 683 }
michael@0 684
michael@0 685 # command(@arguments)
michael@0 686 #
michael@0 687 # Runs the specified command at the verbosity level defined by $gVerbosity.
michael@0 688 # Returns nonzero on failure, returning the exit status if appropriate.
michael@0 689 # Discards command output.
michael@0 690 sub command(@) {
michael@0 691 my(@arguments);
michael@0 692 @arguments = @_;
michael@0 693 return commandVerbosity($gVerbosity,@arguments);
michael@0 694 }
michael@0 695
michael@0 696 # commandInternal($command, @arguments)
michael@0 697 #
michael@0 698 # Runs the specified internal command at the verbosity level defined by
michael@0 699 # $gVerbosity.
michael@0 700 # Returns zero(!) on failure, because commandInternal is supposed to be a
michael@0 701 # direct replacement for the Perl system call wrappers, which, unlike shell
michael@0 702 # commands and C equivalent system calls, return true (instead of 0) to
michael@0 703 # indicate success.
michael@0 704 sub commandInternal($@) {
michael@0 705 my(@arguments, $command);
michael@0 706 ($command, @arguments) = @_;
michael@0 707 return commandInternalVerbosity($gVerbosity, $command, @arguments);
michael@0 708 }
michael@0 709
michael@0 710 # commandInternalVerbosity($verbosity, $command, @arguments)
michael@0 711 #
michael@0 712 # Run an internal command, printing a bogus command invocation message if
michael@0 713 # $verbosity is true.
michael@0 714 #
michael@0 715 # If $command is unlink:
michael@0 716 # Removes the files specified by @arguments. Wraps unlink.
michael@0 717 #
michael@0 718 # If $command is symlink:
michael@0 719 # Creates the symlink specified by @arguments. Wraps symlink.
michael@0 720 sub commandInternalVerbosity($$@) {
michael@0 721 my(@arguments, $command, $verbosity);
michael@0 722 ($verbosity, $command, @arguments) = @_;
michael@0 723 if($command eq 'unlink') {
michael@0 724 if($verbosity || $gDryRun) {
michael@0 725 print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n");
michael@0 726 }
michael@0 727 if($gDryRun) {
michael@0 728 return $#arguments+1;
michael@0 729 }
michael@0 730 return unlink(@arguments);
michael@0 731 }
michael@0 732 elsif($command eq 'symlink') {
michael@0 733 if($verbosity || $gDryRun) {
michael@0 734 print(join(' ', 'ln', '-s', argumentEscape(@arguments))."\n");
michael@0 735 }
michael@0 736 if($gDryRun) {
michael@0 737 return 1;
michael@0 738 }
michael@0 739 my($source, $target);
michael@0 740 ($source, $target) = @arguments;
michael@0 741 return symlink($source, $target);
michael@0 742 }
michael@0 743 }
michael@0 744
michael@0 745 # commandOutput(@arguments)
michael@0 746 #
michael@0 747 # Runs the specified command at the verbosity level defined by $gVerbosity.
michael@0 748 # Output is returned in an array of lines. undef is returned on failure.
michael@0 749 # The exit status is available in $?.
michael@0 750 sub commandOutput(@) {
michael@0 751 my(@arguments);
michael@0 752 @arguments = @_;
michael@0 753 return commandOutputVerbosity($gVerbosity, @arguments);
michael@0 754 }
michael@0 755
michael@0 756 # commandOutputVerbosity($verbosity, @arguments)
michael@0 757 #
michael@0 758 # Runs the specified command at the verbosity level defined by the
michael@0 759 # $verbosity argument. Output is returned in an array of lines. undef is
michael@0 760 # returned on failure. The exit status is available in $?.
michael@0 761 #
michael@0 762 # If an error occurs in fork or exec, an error message is printed to
michael@0 763 # stderr and undef is returned.
michael@0 764 #
michael@0 765 # If $verbosity is 0, the command invocation is not printed, and its
michael@0 766 # stdout is not echoed back to stdout.
michael@0 767 #
michael@0 768 # If $verbosity is 1, the command invocation is printed.
michael@0 769 #
michael@0 770 # If $verbosity is 2, the command invocation is printed and the output
michael@0 771 # from stdout is echoed back to stdout.
michael@0 772 #
michael@0 773 # Regardless of $verbosity, stderr is left connected.
michael@0 774 sub commandOutputVerbosity($@) {
michael@0 775 my(@arguments, $verbosity);
michael@0 776 ($verbosity, @arguments) = @_;
michael@0 777 my($pid);
michael@0 778 if($verbosity || $gDryRun) {
michael@0 779 print(join(' ', argumentEscape(@arguments))."\n");
michael@0 780 }
michael@0 781 if($gDryRun) {
michael@0 782 return(1);
michael@0 783 }
michael@0 784 if (!defined($pid = open(*COMMAND, '-|'))) {
michael@0 785 printf STDERR ($0.': fork: '.$!."\n");
michael@0 786 return undef;
michael@0 787 }
michael@0 788 elsif ($pid) {
michael@0 789 # parent
michael@0 790 my(@lines);
michael@0 791 while(!eof(*COMMAND)) {
michael@0 792 my($line);
michael@0 793 chop($line = <COMMAND>);
michael@0 794 if($verbosity > 1) {
michael@0 795 print($line."\n");
michael@0 796 }
michael@0 797 push(@lines, $line);
michael@0 798 }
michael@0 799 close(*COMMAND);
michael@0 800 if ($? == -1) {
michael@0 801 printf STDERR ($0.': fork: '.$!."\n");
michael@0 802 return undef;
michael@0 803 }
michael@0 804 elsif ($? & 127) {
michael@0 805 printf STDERR ($0.': exited on signal '.($? & 127).
michael@0 806 ($? & 128 ? ', core dumped' : '')."\n");
michael@0 807 return undef;
michael@0 808 }
michael@0 809 return @lines;
michael@0 810 }
michael@0 811 else {
michael@0 812 # child; this form of exec is immune to shell games
michael@0 813 if(!exec {$arguments[0]} (@arguments)) {
michael@0 814 printf STDERR ($0.': exec: '.$!."\n");
michael@0 815 exit(-1);
michael@0 816 }
michael@0 817 }
michael@0 818 }
michael@0 819
michael@0 820 # commandVerbosity($verbosity, @arguments)
michael@0 821 #
michael@0 822 # Runs the specified command at the verbosity level defined by the
michael@0 823 # $verbosity argument. Returns nonzero on failure, returning the exit
michael@0 824 # status if appropriate. Discards command output.
michael@0 825 sub commandVerbosity($@) {
michael@0 826 my(@arguments, $verbosity);
michael@0 827 ($verbosity, @arguments) = @_;
michael@0 828 if(!defined(commandOutputVerbosity($verbosity, @arguments))) {
michael@0 829 return -1;
michael@0 830 }
michael@0 831 return $?;
michael@0 832 }
michael@0 833
michael@0 834 # copyFiles($tempRoot, $method, @arguments)
michael@0 835 #
michael@0 836 # Copies files or create symlinks in the disk image.
michael@0 837 # See --copy and --symlink descriptions for details.
michael@0 838 # If $method is 'copy', @arguments are interpreted as source:target, if $method
michael@0 839 # is 'symlink', @arguments are interpreted as symlink:target.
michael@0 840 sub copyFiles($@) {
michael@0 841 my(@fileList, $method, $tempRoot);
michael@0 842 ($tempRoot, $method, @fileList) = @_;
michael@0 843 my($file, $isSymlink);
michael@0 844 $isSymlink = ($method eq 'symlink');
michael@0 845 foreach $file (@fileList) {
michael@0 846 my($source, $target);
michael@0 847 ($source, $target) = split(/:/, $file);
michael@0 848 if(!defined($target) and $isSymlink) {
michael@0 849 # empty symlink targets would result in an invalid target and fail,
michael@0 850 # but they shall be interpreted as "like source path, but inside dmg"
michael@0 851 $target = $source;
michael@0 852 }
michael@0 853 if(!defined($target)) {
michael@0 854 $target = $tempRoot;
michael@0 855 }
michael@0 856 elsif($target =~ /^\//) {
michael@0 857 $target = $tempRoot.$target;
michael@0 858 }
michael@0 859 else {
michael@0 860 $target = $tempRoot.'/'.$target;
michael@0 861 }
michael@0 862
michael@0 863 my($success);
michael@0 864 if($isSymlink) {
michael@0 865 $success = commandInternal('symlink', $source, $target);
michael@0 866 }
michael@0 867 else {
michael@0 868 $success = !command($gConfig{'cmd_rsync'}, '-a', '--copy-unsafe-links',
michael@0 869 $source, $target);
michael@0 870 }
michael@0 871 if(!$success) {
michael@0 872 cleanupDie('copyFiles failed for method '.$method);
michael@0 873 }
michael@0 874 }
michael@0 875 }
michael@0 876
michael@0 877 # diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount,
michael@0 878 # $baseName, $setRootIcon)
michael@0 879 #
michael@0 880 # Creates a disk image in $destination of format $format corresponding to the
michael@0 881 # source directory $source. $name is the volume name. $tempDir is a good
michael@0 882 # place to write temporary files, which should be empty (aside from the other
michael@0 883 # things that this script might create there, like stage and mount).
michael@0 884 # $tempMount is a mount point for temporary disk images. $baseName is the
michael@0 885 # name of the disk image, and is presently unused. $setRootIcon is true if
michael@0 886 # a volume icon was added to the staged $source and indicates that the
michael@0 887 # custom volume icon bit on the volume root needs to be set.
michael@0 888 sub diskImageMaker($$$$$$$$) {
michael@0 889 my($baseName, $destination, $format, $name, $setRootIcon, $source,
michael@0 890 $tempDir, $tempMount);
michael@0 891 ($source, $destination, $format, $name, $tempDir, $tempMount,
michael@0 892 $baseName, $setRootIcon) = @_;
michael@0 893 if(isFormatCompressed($format)) {
michael@0 894 my($uncompressedImage);
michael@0 895
michael@0 896 if($gConfig{'makehybrid'}) {
michael@0 897 my($hybridImage);
michael@0 898 $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg');
michael@0 899
michael@0 900 if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs',
michael@0 901 '-hfs-volume-name', $name, '-hfs-openfolder', $source, '-ov',
michael@0 902 $source, '-o', $hybridImage) != 0) {
michael@0 903 cleanupDie('hdiutil makehybrid failed');
michael@0 904 }
michael@0 905
michael@0 906 $uncompressedImage = $hybridImage;
michael@0 907
michael@0 908 # $source is no longer needed and will be removed before anything
michael@0 909 # else can fail. splice in this form is the same as pop/push.
michael@0 910 splice(@gCleanup, -1, 1,
michael@0 911 sub {commandInternalVerbosity(0, 'unlink', $hybridImage);});
michael@0 912
michael@0 913 if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) {
michael@0 914 cleanupDie('rm -rf failed');
michael@0 915 }
michael@0 916
michael@0 917 if(!$gConfig{'partition_table'} && $gConfig{'recursive_access'}) {
michael@0 918 # Even if we do want to create disk images without partition tables,
michael@0 919 # it's impossible unless recursive_access is set.
michael@0 920 my($rootDevice, $partitionDevice, $partitionMountPoint);
michael@0 921
michael@0 922 if(!(($rootDevice, $partitionDevice, $partitionMountPoint) =
michael@0 923 hdidMountImage($tempMount, '-readonly', $hybridImage))) {
michael@0 924 cleanupDie('hdid mount failed');
michael@0 925 }
michael@0 926
michael@0 927 push(@gCleanup, sub {commandVerbosity(0,
michael@0 928 $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);});
michael@0 929
michael@0 930 my($udrwImage);
michael@0 931 $udrwImage = giveExtension($tempDir.'/udrw', '.dmg');
michael@0 932
michael@0 933 if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', 'UDRW',
michael@0 934 '-ov', '-srcdevice', $partitionDevice, $udrwImage) != 0) {
michael@0 935 cleanupDie('hdiutil create failed');
michael@0 936 }
michael@0 937
michael@0 938 $uncompressedImage = $udrwImage;
michael@0 939
michael@0 940 # Going to eject before anything else can fail. Get the eject off
michael@0 941 # the stack.
michael@0 942 pop(@gCleanup);
michael@0 943
michael@0 944 # $hybridImage will be removed soon, but until then, it needs to
michael@0 945 # stay on the cleanup stack. It needs to wait until after
michael@0 946 # ejection. $udrwImage is staying around. Make it appear as
michael@0 947 # though it's been done before $hybridImage.
michael@0 948 #
michael@0 949 # splice in this form is the same as popping one element to
michael@0 950 # @tempCleanup and pushing the subroutine.
michael@0 951 my(@tempCleanup);
michael@0 952 @tempCleanup = splice(@gCleanup, -1, 1,
michael@0 953 sub {commandInternalVerbosity(0, 'unlink', $udrwImage);});
michael@0 954 push(@gCleanup, @tempCleanup);
michael@0 955
michael@0 956 if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) {
michael@0 957 cleanupDie('diskutil eject failed');
michael@0 958 }
michael@0 959
michael@0 960 # Pop unlink of $uncompressedImage
michael@0 961 pop(@gCleanup);
michael@0 962
michael@0 963 if(commandInternal('unlink', $hybridImage) != 1) {
michael@0 964 cleanupDie('unlink hybridImage failed: '.$!);
michael@0 965 }
michael@0 966 }
michael@0 967 }
michael@0 968 else {
michael@0 969 # makehybrid is not available, fall back to making a UDRW and
michael@0 970 # converting to a compressed image. It ought to be possible to
michael@0 971 # create a compressed image directly, but those come out far too
michael@0 972 # large (journaling?) and need to be read-write to fix up the
michael@0 973 # volume icon anyway. Luckily, we can take advantage of a single
michael@0 974 # call back into this function.
michael@0 975 my($udrwImage);
michael@0 976 $udrwImage = giveExtension($tempDir.'/udrw', '.dmg');
michael@0 977
michael@0 978 diskImageMaker($source, $udrwImage, 'UDRW', $name, $tempDir,
michael@0 979 $tempMount, $baseName, $setRootIcon);
michael@0 980
michael@0 981 # The call back into diskImageMaker already removed $source.
michael@0 982
michael@0 983 $uncompressedImage = $udrwImage;
michael@0 984 }
michael@0 985
michael@0 986 # The uncompressed disk image is now in its final form. Compress it.
michael@0 987 # Jaguar doesn't support hdiutil convert -ov, but it always allows
michael@0 988 # overwriting.
michael@0 989 # bzip2-compressed UDBZ images can only be created and mounted on 10.4
michael@0 990 # and later. The bzip2-level imagekey is only effective when creating
michael@0 991 # images in 10.5. In 10.4, bzip2-level is harmlessly ignored, and the
michael@0 992 # default value of 1 is always used.
michael@0 993 if(command($gConfig{'cmd_hdiutil'}, 'convert', '-format', $format,
michael@0 994 '-imagekey', ($format eq 'UDBZ' ? 'bzip2-level=9' : 'zlib-level=9'),
michael@0 995 (defined($gDarwinMajor) && $gDarwinMajor <= 6 ? () : ('-ov')),
michael@0 996 $uncompressedImage, '-o', $destination) != 0) {
michael@0 997 cleanupDie('hdiutil convert failed');
michael@0 998 }
michael@0 999
michael@0 1000 # $uncompressedImage is going to be unlinked before anything else can
michael@0 1001 # fail. splice in this form is the same as pop/push.
michael@0 1002 splice(@gCleanup, -1, 1,
michael@0 1003 sub {commandInternalVerbosity(0, 'unlink', $destination);});
michael@0 1004
michael@0 1005 if(commandInternal('unlink', $uncompressedImage) != 1) {
michael@0 1006 cleanupDie('unlink uncompressedImage failed: '.$!);
michael@0 1007 }
michael@0 1008
michael@0 1009 # At this point, the only thing that the compressed block has added to
michael@0 1010 # the cleanup stack is the removal of $destination. $source has already
michael@0 1011 # been removed, and its cleanup entry has been removed as well.
michael@0 1012 }
michael@0 1013 elsif($format eq 'UDRW' || $format eq 'UDSP') {
michael@0 1014 my(@extraArguments);
michael@0 1015 if(!$gConfig{'partition_table'}) {
michael@0 1016 @extraArguments = ('-layout', 'NONE');
michael@0 1017 }
michael@0 1018
michael@0 1019 if($gConfig{'create_directly'}) {
michael@0 1020 # Use -fs HFS+ to suppress the journal.
michael@0 1021 if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format,
michael@0 1022 @extraArguments, '-fs', 'HFS+', '-volname', $name,
michael@0 1023 '-ov', '-srcfolder', $source, $destination) != 0) {
michael@0 1024 cleanupDie('hdiutil create failed');
michael@0 1025 }
michael@0 1026
michael@0 1027 # $source is no longer needed and will be removed before anything
michael@0 1028 # else can fail. splice in this form is the same as pop/push.
michael@0 1029 splice(@gCleanup, -1, 1,
michael@0 1030 sub {commandInternalVerbosity(0, 'unlink', $destination);});
michael@0 1031
michael@0 1032 if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) {
michael@0 1033 cleanupDie('rm -rf failed');
michael@0 1034 }
michael@0 1035 }
michael@0 1036 else {
michael@0 1037 # hdiutil create does not support -srcfolder or -srcdevice, it only
michael@0 1038 # knows how to create blank images. Figure out how large an image
michael@0 1039 # is needed, create it, and fill it. This is needed for Jaguar.
michael@0 1040
michael@0 1041 # Use native block size for hdiutil create -sectors.
michael@0 1042 delete $ENV{'BLOCKSIZE'};
michael@0 1043
michael@0 1044 my(@duOutput, $ignore, $sizeBlocks, $sizeOverhead, $sizeTotal, $type);
michael@0 1045 if(!(@output = commandOutput($gConfig{'cmd_du'}, '-s', $tempRoot)) ||
michael@0 1046 $? != 0) {
michael@0 1047 cleanupDie('du failed');
michael@0 1048 }
michael@0 1049 ($sizeBlocks, $ignore) = split(' ', $output[0], 2);
michael@0 1050
michael@0 1051 # The filesystem itself takes up 152 blocks of its own blocks for the
michael@0 1052 # filesystem up to 8192 blocks, plus 64 blocks for every additional
michael@0 1053 # 4096 blocks or portion thereof.
michael@0 1054 $sizeOverhead = 152 + 64 * POSIX::ceil(
michael@0 1055 (($sizeBlocks - 8192) > 0) ? (($sizeBlocks - 8192) / (4096 - 64)) : 0);
michael@0 1056
michael@0 1057 # The number of blocks must be divisible by 8.
michael@0 1058 my($mod);
michael@0 1059 if($mod = ($sizeOverhead % 8)) {
michael@0 1060 $sizeOverhead += 8 - $mod;
michael@0 1061 }
michael@0 1062
michael@0 1063 # sectors is taken as the size of a disk, not a filesystem, so the
michael@0 1064 # partition table eats into it.
michael@0 1065 if($gConfig{'partition_table'}) {
michael@0 1066 $sizeOverhead += 80;
michael@0 1067 }
michael@0 1068
michael@0 1069 # That was hard. Leave some breathing room anyway. Use 1024 sectors
michael@0 1070 # (512kB). These read-write images wouldn't be useful if they didn't
michael@0 1071 # have at least a little free space.
michael@0 1072 $sizeTotal = $sizeBlocks + $sizeOverhead + 1024;
michael@0 1073
michael@0 1074 # Minimum sizes - these numbers are larger on Jaguar than on later
michael@0 1075 # systems. Just use the Jaguar numbers, since it's unlikely to wind
michael@0 1076 # up here on any other release.
michael@0 1077 if($gConfig{'partition_table'} && $sizeTotal < 8272) {
michael@0 1078 $sizeTotal = 8272;
michael@0 1079 }
michael@0 1080 if(!$gConfig{'partition_table'} && $sizeTotal < 8192) {
michael@0 1081 $sizeTotal = 8192;
michael@0 1082 }
michael@0 1083
michael@0 1084 # hdiutil create without -srcfolder or -srcdevice will not accept
michael@0 1085 # -format. It uses -type. Fortunately, the two supported formats
michael@0 1086 # here map directly to the only two supported types.
michael@0 1087 if ($format eq 'UDSP') {
michael@0 1088 $type = 'SPARSE';
michael@0 1089 }
michael@0 1090 else {
michael@0 1091 $type = 'UDIF';
michael@0 1092 }
michael@0 1093
michael@0 1094 if(command($gConfig{'cmd_hdiutil'}, 'create', '-type', $type,
michael@0 1095 @extraArguments, '-fs', 'HFS+', '-volname', $name,
michael@0 1096 '-ov', '-sectors', $sizeTotal, $destination) != 0) {
michael@0 1097 cleanupDie('hdiutil create failed');
michael@0 1098 }
michael@0 1099
michael@0 1100 push(@gCleanup,
michael@0 1101 sub {commandInternalVerbosity(0, 'unlink', $destination);});
michael@0 1102
michael@0 1103 # The rsync will occur shortly.
michael@0 1104 }
michael@0 1105
michael@0 1106 my($mounted, $rootDevice, $partitionDevice, $partitionMountPoint);
michael@0 1107
michael@0 1108 $mounted=0;
michael@0 1109 if(!$gConfig{'create_directly'} || $gConfig{'openfolder_bless'} ||
michael@0 1110 $setRootIcon) {
michael@0 1111 # The disk image only needs to be mounted if:
michael@0 1112 # create_directly is false, because the content needs to be copied
michael@0 1113 # openfolder_bless is true, because bless -openfolder needs to run
michael@0 1114 # setRootIcon is true, because the root needs its attributes set.
michael@0 1115 if(!(($rootDevice, $partitionDevice, $partitionMountPoint) =
michael@0 1116 hdidMountImage($tempMount, $destination))) {
michael@0 1117 cleanupDie('hdid mount failed');
michael@0 1118 }
michael@0 1119
michael@0 1120 $mounted=1;
michael@0 1121
michael@0 1122 push(@gCleanup, sub {commandVerbosity(0,
michael@0 1123 $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);});
michael@0 1124 }
michael@0 1125
michael@0 1126 if(!$gConfig{'create_directly'}) {
michael@0 1127 # Couldn't create and copy directly in one fell swoop. Now that
michael@0 1128 # the volume is mounted, copy the files. --copy-unsafe-links is
michael@0 1129 # unnecessary since it was used to copy everything to the staging
michael@0 1130 # area. There can be no more unsafe links.
michael@0 1131 if(command($gConfig{'cmd_rsync'}, '-a',
michael@0 1132 $source.'/',$partitionMountPoint) != 0) {
michael@0 1133 cleanupDie('rsync to new volume failed');
michael@0 1134 }
michael@0 1135
michael@0 1136 # We need to get the rm -rf of $source off the stack, because it's
michael@0 1137 # being cleaned up here. There are two items now on top of it:
michael@0 1138 # removing the target image and, above that, ejecting it. Splice it
michael@0 1139 # out.
michael@0 1140 my(@tempCleanup);
michael@0 1141 @tempCleanup = splice(@gCleanup, -2);
michael@0 1142 # The next splice is the same as popping once and pushing @tempCleanup.
michael@0 1143 splice(@gCleanup, -1, 1, @tempCleanup);
michael@0 1144
michael@0 1145 if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) {
michael@0 1146 cleanupDie('rm -rf failed');
michael@0 1147 }
michael@0 1148 }
michael@0 1149
michael@0 1150 if($gConfig{'openfolder_bless'}) {
michael@0 1151 # On Tiger, the bless docs say to use --openfolder, but only
michael@0 1152 # --openfolder is accepted on Panther. Tiger takes it with a single
michael@0 1153 # dash too. Jaguar is out of luck.
michael@0 1154 if(command($gConfig{'cmd_bless'}, '-openfolder',
michael@0 1155 $partitionMountPoint) != 0) {
michael@0 1156 cleanupDie('bless failed');
michael@0 1157 }
michael@0 1158 }
michael@0 1159
michael@0 1160 setAttributes($partitionMountPoint, @attributes);
michael@0 1161
michael@0 1162 if($setRootIcon) {
michael@0 1163 # When "hdiutil create -srcfolder" is used, the root folder's
michael@0 1164 # attributes are not copied to the new volume. Fix up.
michael@0 1165
michael@0 1166 if(command($gConfig{'cmd_SetFile'}, '-a', 'C',
michael@0 1167 $partitionMountPoint) != 0) {
michael@0 1168 cleanupDie('SetFile failed');
michael@0 1169 }
michael@0 1170 }
michael@0 1171
michael@0 1172 if($mounted) {
michael@0 1173 # Pop diskutil eject
michael@0 1174 pop(@gCleanup);
michael@0 1175
michael@0 1176 if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) {
michael@0 1177 cleanupDie('diskutil eject failed');
michael@0 1178 }
michael@0 1179 }
michael@0 1180
michael@0 1181 # End of UDRW/UDSP section. At this point, $source has been removed
michael@0 1182 # and its cleanup entry has been removed from the stack.
michael@0 1183 }
michael@0 1184 else {
michael@0 1185 cleanupDie('unrecognized format');
michael@0 1186 print STDERR ($0.": unrecognized format\n");
michael@0 1187 exit(1);
michael@0 1188 }
michael@0 1189 }
michael@0 1190
michael@0 1191 # giveExtension($file, $extension)
michael@0 1192 #
michael@0 1193 # If $file does not end in $extension, $extension is added. The new
michael@0 1194 # filename is returned.
michael@0 1195 sub giveExtension($$) {
michael@0 1196 my($extension, $file);
michael@0 1197 ($file, $extension) = @_;
michael@0 1198 if(substr($file, -length($extension)) ne $extension) {
michael@0 1199 return $file.$extension;
michael@0 1200 }
michael@0 1201 return $file;
michael@0 1202 }
michael@0 1203
michael@0 1204 # hdidMountImage($mountPoint, @arguments)
michael@0 1205 #
michael@0 1206 # Runs the hdid command with arguments specified by @arguments.
michael@0 1207 # @arguments may be a single-element array containing the name of the
michael@0 1208 # disk image to mount. Returns a three-element array, with elements
michael@0 1209 # corresponding to:
michael@0 1210 # - The root device of the mounted image, suitable for ejection
michael@0 1211 # - The device corresponding to the mounted partition
michael@0 1212 # - The mounted partition's mount point
michael@0 1213 #
michael@0 1214 # If running on a system that supports easy mounting at points outside
michael@0 1215 # of the default /Volumes with hdiutil attach, it is used instead of hdid,
michael@0 1216 # and $mountPoint is used as the mount point.
michael@0 1217 #
michael@0 1218 # The root device will differ from the partition device when the disk
michael@0 1219 # image contains a partition table, otherwise, they will be identical.
michael@0 1220 #
michael@0 1221 # If hdid fails, undef is returned.
michael@0 1222 sub hdidMountImage($@) {
michael@0 1223 my(@arguments, @command, $mountPoint);
michael@0 1224 ($mountPoint, @arguments) = @_;
michael@0 1225 my(@output);
michael@0 1226
michael@0 1227 if($gConfig{'hdiutil_mountpoint'}) {
michael@0 1228 @command=($gConfig{'cmd_hdiutil'}, 'attach', @arguments,
michael@0 1229 '-mountpoint', $mountPoint);
michael@0 1230 }
michael@0 1231 else {
michael@0 1232 @command=($gConfig{'cmd_hdid'}, @arguments);
michael@0 1233 }
michael@0 1234
michael@0 1235 if(!(@output = commandOutput(@command)) ||
michael@0 1236 $? != 0) {
michael@0 1237 return undef;
michael@0 1238 }
michael@0 1239
michael@0 1240 if($gDryRun) {
michael@0 1241 return('/dev/diskX','/dev/diskXsY','/Volumes/'.$volumeName);
michael@0 1242 }
michael@0 1243
michael@0 1244 my($line, $restOfLine, $rootDevice);
michael@0 1245
michael@0 1246 foreach $line (@output) {
michael@0 1247 my($device, $mountpoint);
michael@0 1248 if($line !~ /^\/dev\//) {
michael@0 1249 # Consider only lines that correspond to /dev entries
michael@0 1250 next;
michael@0 1251 }
michael@0 1252 ($device, $restOfLine) = split(' ', $line, 2);
michael@0 1253
michael@0 1254 if(!defined($rootDevice) || $rootDevice eq '') {
michael@0 1255 # If this is the first device seen, it's the root device to be
michael@0 1256 # used for ejection. Keep it.
michael@0 1257 $rootDevice = $device;
michael@0 1258 }
michael@0 1259
michael@0 1260 if($restOfLine =~ /(\/.*)/) {
michael@0 1261 # The first partition with a mount point is the interesting one. It's
michael@0 1262 # usually Apple_HFS and usually the last one in the list, but beware of
michael@0 1263 # the possibility of other filesystem types and the Apple_Free partition.
michael@0 1264 # If the disk image contains no partition table, the partition will not
michael@0 1265 # have a type, so look for the mount point by looking for a slash.
michael@0 1266 $mountpoint = $1;
michael@0 1267 return($rootDevice, $device, $mountpoint);
michael@0 1268 }
michael@0 1269 }
michael@0 1270
michael@0 1271 # No mount point? This is bad. If there's a root device, eject it.
michael@0 1272 if(defined($rootDevice) && $rootDevice ne '') {
michael@0 1273 # Failing anyway, so don't care about failure
michael@0 1274 commandVerbosity(0, $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);
michael@0 1275 }
michael@0 1276
michael@0 1277 return undef;
michael@0 1278 }
michael@0 1279
michael@0 1280 # isFormatCompressed($format)
michael@0 1281 #
michael@0 1282 # Returns true if $format corresponds to a compressed disk image format.
michael@0 1283 # Returns false otherwise.
michael@0 1284 sub isFormatCompressed($) {
michael@0 1285 my($format);
michael@0 1286 ($format) = @_;
michael@0 1287 return $format eq 'UDZO' || $format eq 'UDBZ';
michael@0 1288 }
michael@0 1289
michael@0 1290 # licenseMaker($text, $resource)
michael@0 1291 #
michael@0 1292 # Takes a plain text file at path $text and creates a license agreement
michael@0 1293 # resource containing the text at path $license. English-only, and
michael@0 1294 # no special formatting. This is the bare-bones stuff. For more
michael@0 1295 # intricate license agreements, create your own resource.
michael@0 1296 #
michael@0 1297 # ftp://ftp.apple.com/developer/Development_Kits/SLAs_for_UDIFs_1.0.dmg
michael@0 1298 sub licenseMaker($$) {
michael@0 1299 my($resource, $text);
michael@0 1300 ($text, $resource) = @_;
michael@0 1301 if(!sysopen(*TEXT, $text, O_RDONLY)) {
michael@0 1302 print STDERR ($0.': licenseMaker: sysopen text: '.$!."\n");
michael@0 1303 return 0;
michael@0 1304 }
michael@0 1305 if(!sysopen(*RESOURCE, $resource, O_WRONLY|O_CREAT|O_EXCL)) {
michael@0 1306 print STDERR ($0.': licenseMaker: sysopen resource: '.$!."\n");
michael@0 1307 return 0;
michael@0 1308 }
michael@0 1309 print RESOURCE << '__EOT__';
michael@0 1310 // See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs.
michael@0 1311 data 'LPic' (5000) {
michael@0 1312 // Default language ID, 0 = English
michael@0 1313 $"0000"
michael@0 1314 // Number of entries in list
michael@0 1315 $"0001"
michael@0 1316
michael@0 1317 // Entry 1
michael@0 1318 // Language ID, 0 = English
michael@0 1319 $"0000"
michael@0 1320 // Resource ID, 0 = STR#/TEXT/styl 5000
michael@0 1321 $"0000"
michael@0 1322 // Multibyte language, 0 = no
michael@0 1323 $"0000"
michael@0 1324 };
michael@0 1325
michael@0 1326 resource 'STR#' (5000, "English") {
michael@0 1327 {
michael@0 1328 // Language (unused?) = English
michael@0 1329 "English",
michael@0 1330 // Agree
michael@0 1331 "Agree",
michael@0 1332 // Disagree
michael@0 1333 "Disagree",
michael@0 1334 __EOT__
michael@0 1335 # This stuff needs double-quotes for interpolations to work.
michael@0 1336 print RESOURCE (" // Print, ellipsis is 0xC9\n");
michael@0 1337 print RESOURCE (" \"Print\xc9\",\n");
michael@0 1338 print RESOURCE (" // Save As, ellipsis is 0xC9\n");
michael@0 1339 print RESOURCE (" \"Save As\xc9\",\n");
michael@0 1340 print RESOURCE (' // Descriptive text, curly quotes are 0xD2 and 0xD3'.
michael@0 1341 "\n");
michael@0 1342 print RESOURCE (' "If you agree to the terms of this license '.
michael@0 1343 "agreement, click \xd2Agree\xd3 to access the software. If you ".
michael@0 1344 "do not agree, press \xd2Disagree.\xd3\"\n");
michael@0 1345 print RESOURCE << '__EOT__';
michael@0 1346 };
michael@0 1347 };
michael@0 1348
michael@0 1349 // Beware of 1024(?) byte (character?) line length limitation. Split up long
michael@0 1350 // lines.
michael@0 1351 // If straight quotes are used ("), remember to escape them (\").
michael@0 1352 // Newline is \n, to leave a blank line, use two of them.
michael@0 1353 // 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly
michael@0 1354 // single quotes ('), 0xD5 is also the apostrophe.
michael@0 1355 data 'TEXT' (5000, "English") {
michael@0 1356 __EOT__
michael@0 1357
michael@0 1358 while(!eof(*TEXT)) {
michael@0 1359 my($line);
michael@0 1360 chop($line = <TEXT>);
michael@0 1361
michael@0 1362 while(defined($line)) {
michael@0 1363 my($chunk);
michael@0 1364
michael@0 1365 # Rez doesn't care for lines longer than (1024?) characters. Split
michael@0 1366 # at less than half of that limit, in case everything needs to be
michael@0 1367 # backwhacked.
michael@0 1368 if(length($line)>500) {
michael@0 1369 $chunk = substr($line, 0, 500);
michael@0 1370 $line = substr($line, 500);
michael@0 1371 }
michael@0 1372 else {
michael@0 1373 $chunk = $line;
michael@0 1374 $line = undef;
michael@0 1375 }
michael@0 1376
michael@0 1377 if(length($chunk) > 0) {
michael@0 1378 # Unsafe characters are the double-quote (") and backslash (\), escape
michael@0 1379 # them with backslashes.
michael@0 1380 $chunk =~ s/(["\\])/\\$1/g;
michael@0 1381
michael@0 1382 print RESOURCE ' "'.$chunk.'"'."\n";
michael@0 1383 }
michael@0 1384 }
michael@0 1385 print RESOURCE ' "\n"'."\n";
michael@0 1386 }
michael@0 1387 close(*TEXT);
michael@0 1388
michael@0 1389 print RESOURCE << '__EOT__';
michael@0 1390 };
michael@0 1391
michael@0 1392 data 'styl' (5000, "English") {
michael@0 1393 // Number of styles following = 1
michael@0 1394 $"0001"
michael@0 1395
michael@0 1396 // Style 1. This is used to display the first two lines in bold text.
michael@0 1397 // Start character = 0
michael@0 1398 $"0000 0000"
michael@0 1399 // Height = 16
michael@0 1400 $"0010"
michael@0 1401 // Ascent = 12
michael@0 1402 $"000C"
michael@0 1403 // Font family = 1024 (Lucida Grande)
michael@0 1404 $"0400"
michael@0 1405 // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline
michael@0 1406 // 0x10=shadow 0x20=condensed 0x40=extended
michael@0 1407 $"00"
michael@0 1408 // Style, unused?
michael@0 1409 $"02"
michael@0 1410 // Size = 12 point
michael@0 1411 $"000C"
michael@0 1412 // Color, RGB
michael@0 1413 $"0000 0000 0000"
michael@0 1414 };
michael@0 1415 __EOT__
michael@0 1416 close(*RESOURCE);
michael@0 1417
michael@0 1418 return 1;
michael@0 1419 }
michael@0 1420
michael@0 1421 # pathSplit($pathname)
michael@0 1422 #
michael@0 1423 # Splits $pathname into an array of path components.
michael@0 1424 sub pathSplit($) {
michael@0 1425 my($pathname);
michael@0 1426 ($pathname) = @_;
michael@0 1427 return split(/\//, $pathname);
michael@0 1428 }
michael@0 1429
michael@0 1430 # setAttributes($root, @attributeList)
michael@0 1431 #
michael@0 1432 # @attributeList is an array, each element of which must be in the form
michael@0 1433 # <a>:<file>. <a> is a list of attributes, per SetFile. <file> is a file
michael@0 1434 # which is taken as relative to $root (even if it appears as an absolute
michael@0 1435 # path.) SetFile is called to set the attributes on each file in
michael@0 1436 # @attributeList.
michael@0 1437 sub setAttributes($@) {
michael@0 1438 my(@attributes, $root);
michael@0 1439 ($root, @attributes) = @_;
michael@0 1440 my($attribute);
michael@0 1441 foreach $attribute (@attributes) {
michael@0 1442 my($attrList, $file, @fileList, @fixedFileList);
michael@0 1443 ($attrList, @fileList) = split(/:/, $attribute);
michael@0 1444 if(!defined($attrList) || !@fileList) {
michael@0 1445 cleanupDie('--attribute requires <attributes>:<file>');
michael@0 1446 }
michael@0 1447 @fixedFileList=();
michael@0 1448 foreach $file (@fileList) {
michael@0 1449 if($file =~ /^\//) {
michael@0 1450 push(@fixedFileList, $root.$file);
michael@0 1451 }
michael@0 1452 else {
michael@0 1453 push(@fixedFileList, $root.'/'.$file);
michael@0 1454 }
michael@0 1455 }
michael@0 1456 if(command($gConfig{'cmd_SetFile'}, '-a', $attrList, @fixedFileList)) {
michael@0 1457 cleanupDie('SetFile failed to set attributes');
michael@0 1458 }
michael@0 1459 }
michael@0 1460 return;
michael@0 1461 }
michael@0 1462
michael@0 1463 sub trapSignal($) {
michael@0 1464 my($signalName);
michael@0 1465 ($signalName) = @_;
michael@0 1466 cleanupDie('exiting on SIG'.$signalName);
michael@0 1467 }
michael@0 1468
michael@0 1469 sub usage() {
michael@0 1470 print STDERR (
michael@0 1471 "usage: pkg-dmg --source <source-folder>\n".
michael@0 1472 " --target <target-image>\n".
michael@0 1473 " [--format <format>] (default: UDZO)\n".
michael@0 1474 " [--volname <volume-name>] (default: same name as source)\n".
michael@0 1475 " [--tempdir <temp-dir>] (default: same dir as target)\n".
michael@0 1476 " [--mkdir <directory>] (make directory in image)\n".
michael@0 1477 " [--copy <source>[:<dest>]] (extra files to add)\n".
michael@0 1478 " [--symlink <source>[:<dest>]] (extra symlinks to add)\n".
michael@0 1479 " [--license <file>] (plain text license agreement)\n".
michael@0 1480 " [--resource <file>] (flat .r files to merge)\n".
michael@0 1481 " [--icon <icns-file>] (volume icon)\n".
michael@0 1482 " [--attribute <a>:<file>] (set file attributes)\n".
michael@0 1483 " [--idme] (make Internet-enabled image)\n".
michael@0 1484 " [--sourcefile] (treat --source as a file)\n".
michael@0 1485 " [--verbosity <level>] (0, 1, 2; default=2)\n".
michael@0 1486 " [--dry-run] (print what would be done)\n");
michael@0 1487 return;
michael@0 1488 }

mercurial