Wed, 31 Dec 2014 06:09:35 +0100
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 | } |