From df20c6c42387e05101d11361a6c79ee5d5be8530 Mon Sep 17 00:00:00 2001 From: vspader Date: Sun, 25 Feb 2007 22:17:05 +0000 Subject: [PATCH] Added DiskImage scripts, for easier (LOL) image creation. --- DiskImage/dmg-cog.scpt | Bin 0 -> 6562 bytes DiskImage/get-dsstore.sh | 24 + DiskImage/howto | 23 + DiskImage/make-diskimage.sh | 83 ++ DiskImage/make-release.sh | 67 ++ DiskImage/pkg-dmg | 1447 +++++++++++++++++++++++++++++++++++ DiskImage/template.dmg.bz2 | Bin 0 -> 4007047 bytes 7 files changed, 1644 insertions(+) create mode 100644 DiskImage/dmg-cog.scpt create mode 100755 DiskImage/get-dsstore.sh create mode 100644 DiskImage/howto create mode 100755 DiskImage/make-diskimage.sh create mode 100755 DiskImage/make-release.sh create mode 100755 DiskImage/pkg-dmg create mode 100644 DiskImage/template.dmg.bz2 diff --git a/DiskImage/dmg-cog.scpt b/DiskImage/dmg-cog.scpt new file mode 100644 index 0000000000000000000000000000000000000000..07ca5975eab74e5bc66d17e3e7030c5d05182e1f GIT binary patch literal 6562 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 kE@qXnlP?Show View Options. Ensure "This window only" is selected. Use shift+apple+G to type in the path to the background. + d) Set custom icons. + e) Hide the toolbar and set the size. + +5) Eject the image very carefully. + +6) Custom icons are copied through applescript in dmg-cog.scrpt. Make sure all files with custom icons have them copied. + +7) Run make-release.sh, and hope his noodly appendage looks favourably upon you. + +8) When it fails, maybe it'd be easier to just make the image manually. + diff --git a/DiskImage/make-diskimage.sh b/DiskImage/make-diskimage.sh new file mode 100755 index 000000000..d07a72983 --- /dev/null +++ b/DiskImage/make-diskimage.sh @@ -0,0 +1,83 @@ +#!/bin/sh +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the make-diskimage script. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 2002 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Brian Ryner +# Jean-Jacques Enser +# Arthur Wiebe +# Mark Mentovai +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +# Create a read-only disk image of the contents of a folder +# +# Usage: make-diskimage +# +# +# +# <.dsstore_file> +# +# +# tip: use '-null-' for if you only want to +# provide <.dsstore_file> and + +DMG_PATH=$1 +SRC_FOLDER=$2 +VOLUME_NAME=$3 + +# optional arguments +EULA_RSRC=$4 +DMG_DSSTORE=$5 +DMG_BKGND_IMG=$6 + +EXTRA_ARGS= + +if test -n "$EULA_RSRC" && test "$EULA_RSRC" != "-null-" ; then + EXTRA_ARGS="--resource $EULA_RSRC" +fi + +if test -n "$DMG_DSSTORE" ; then + EXTRA_ARGS="$EXTRA_ARGS --copy $DMG_DSSTORE:/.DS_Store" +fi + +if test -n "$DMG_BKGND_IMG" ; then + EXTRA_ARGS="$EXTRA_ARGS --mkdir /.background --copy $DMG_BKGND_IMG:/.background" +fi + +echo `dirname $0`/pkg-dmg --target "$DMG_PATH" --source "$SRC_FOLDER" \ + --volname "$VOLUME_NAME" $EXTRA_ARGS + +`dirname $0`/pkg-dmg --target "$DMG_PATH" --source "$SRC_FOLDER" \ + --volname "$VOLUME_NAME" $EXTRA_ARGS + +exit $? diff --git a/DiskImage/make-release.sh b/DiskImage/make-release.sh new file mode 100755 index 000000000..628005947 --- /dev/null +++ b/DiskImage/make-release.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +# Create a read-only disk image of the contents of a folder +# +# Usage: make-release + +set -e; + +VERSION=0.06 + +APP_NAME=Cog +DEST_FOLDER=Release +APPLESCRIPT=dmg-cog.scpt + +TEMPLATE_DMG=template.dmg +DSSTORE=cog.dsstore + +APP_PATH=../build/Release/Cog.app +LICENSE_PATH=../COPYING +CHANGES_PATH=../ChangeLog +README_PATH=../README + +#Make dest +echo "Creating destination..." +mkdir ${DEST_FOLDER} + +echo "Copying app..." +cp -R ${APP_PATH} ${DEST_FOLDER} + +echo "Copying misc files..." +cp ${LICENSE_PATH} ${DEST_FOLDER}/License.txt +cp ${CHANGES_PATH} ${DEST_FOLDER}/Changes.txt +cp ${README_PATH} ${DEST_FOLDER}/Readme.txt + +echo "Creating Applications alias..." +ln -s /Applications ${DEST_FOLDER}/Applications + + +echo "Mounting template..." +TEMP_DIR=temp +BACKGROUND=${TEMP_DIR}/.background/background.png + +bunzip2 -k ${TEMPLATE_DMG}.bz2 + +mkdir -p ${TEMP_DIR} + +hdiutil attach "${TEMPLATE_DMG}" -noautoopen -quiet -mountpoint "${TEMP_DIR}" + +echo "Copying .DS_Store" +cp ${TEMP_DIR}/.DS_Store ${DSSTORE} + +echo "Launching applescript..." +osascript ${APPLESCRIPT} ${DEST_FOLDER} ${TEMP_DIR} + +echo "Creating image..." +./make-diskimage.sh ${APP_NAME}-${VERSION}.dmg ${DEST_FOLDER} "${APP_NAME} ${VERSION}" -null- ${DSSTORE} ${BACKGROUND} + +echo "Unmounting template..." +DMG_DEV=`hdiutil info | grep "${TEMP_DIR}" | grep "Apple_HFS" | awk '{print $1}'` +hdiutil detach ${DMG_DEV} -quiet -force + +rm -r ${TEMP_DIR} + +rm ${TEMPLATE_DMG} +rm ${DSSTORE} + +rm -r ${DEST_FOLDER} diff --git a/DiskImage/pkg-dmg b/DiskImage/pkg-dmg new file mode 100755 index 000000000..a3338d91b --- /dev/null +++ b/DiskImage/pkg-dmg @@ -0,0 +1,1447 @@ +#!/usr/bin/perl +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is pkg-dmg, a Mac OS X disk image (.dmg) packager +# +# The Initial Developer of the Original Code is +# Mark Mentovai . +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +use strict; +use warnings; + +=pod + +=head1 NAME + +B - Mac OS X disk image (.dmg) packager + +=head1 SYNOPSIS + +B +B<--source> I +B<--target> I +[B<--format> I] +[B<--volname> I] +[B<--tempdir> I] +[B<--mkdir> I] +[B<--copy> I[:I]] +[B<--license> I] +[B<--resource> I] +[B<--icon> I] +[B<--attribute> I:I[:I...] +[B<--idme>] +[B<--sourcefile>] +[B<--verbosity> I] +[B<--dry-run>] + +=head1 DESCRIPTION + +I takes a directory identified by I and transforms +it into a disk image stored as I. The disk image will +occupy the least space possible for its format, or the least space that the +authors have been able to figure out how to achieve. + +=head1 OPTIONS + +=over 5 + +==item B<--source> I + +Identifies the directory that will be packaged up. This directory is not +touched, a copy will be made in a temporary directory for staging purposes. +See B<--tempdir>. + +==item B<--target> I + +The disk image to create. If it exists and is not in use, it will be +overwritten. If I already contains a suitable extension, +it will be used unmodified. If no extension is present, or the extension +is incorrect for the selected format, the proper extension will be added. +See B<--format>. + +==item B<--format> I + +The format to create the disk image in. Valid values for I are: + - UDZO - zlib-compressed, read-only; extension I<.dmg> + - UDRW - read-write; extension I<.dmg> + - UDSP - read-write, sparse; extension I<.sparseimage> + +UDZO is the default format. + +See L for a description of these formats. + +=item B<--volname> I + +The name of the volume in the disk image. If not specified, I +defaults to the name of the source directory from B<--source>. + +=item B<--tempdir> I + +A temporary directory to stage intermediate files in. I must +have enough space available to accommodate twice the size of the files +being packaged. If not specified, defaults to the same directory that +the I is to be placed in. B will remove any +temporary files it places in I. + +=item B<--mkdir> I + +Specifies a directory that should be created in the disk image. +I and any ancestor directories will be created. This is +useful in conjunction with B<--copy>, when copying files to directories +that may not exist in I. B<--mkdir> may appear multiple +times. + +=item B<--copy> I[:I] + +Additional files to copy into the disk image. If I is +specified, I is copied to the location I identifies, +otherwise, I is copied to the root of the new volume. B<--copy> +provides a way to package up a I by adding files to it +without modifying the original I. B<--copy> may appear +multiple times. + +This option is useful for adding .DS_Store files and window backgrounds +to disk images. + +=item B<--license> I + +A plain text file containing a license agreement to be displayed before +the disk image is mounted. English is the only supported language. To +include license agreements in other languages, in multiple languages, +or to use formatted text, prepare a resource and use L<--resource>. + +=item B<--resource> I + +A resource file to merge into I. If I is UDZO, +the disk image will be flattened to a single-fork file that contains +the resource but may be freely transferred without any special encodings. +I must be in a format suitable for L. See L for a +description of the format, and L for a discussion on flattened +disk images. B<--resource> may appear multiple times. + +This option is useful for adding license agreements and other messages +to disk images. + +=item B<--icon> I + +Specifies an I file that will be used as the icon for the root of +the volume. This file will be copied to the new volume and the custom +icon attribute will be set on the root folder. + +=item B<--attribute> I:I[:I...] + +Sets the attributes of I to the attribute list in I. See +L + +=item B<--idme> + +Enable IDME to make the disk image "Internet-enabled." The first time +the image is mounted, if IDME processing is enabled on the system, the +contents of the image will be copied out of the image and the image will +be placed in the trash with IDME disabled. + +=item B<--sourcefile> + +If this option is present, I is treated as a file, and is +placed as a file within the volume's root folder. Without this option, +I is treated as the volume root itself. + +=item B<--verbosity> I + +Adjusts the level of loudness of B. The possible values for +I are: + 0 - Only error messages are displayed. + 1 - Print error messages and command invocations. + 2 - Print everything, including command output. + +The default I is 2. + +=item B<--dry-run> + +When specified, the commands that would be executed are printed, without +actually executing them. When commands depend on the output of previous +commands, dummy values are displayed. + +=back + +=head1 NON-OPTIONS + +=over 5 + +=item + +Resource forks aren't copied. + +=item + +The root folder of the created volume is designated as the folder +to open when the volume is mounted. See L. + +=item + +All files in the volume are set to be world-readable, only writable +by the owner, and world-executable when appropriate. All other +permissions bits are cleared. + +=item + +When possible, disk images are created without any partition tables. This +is what L refers to as I<-layout NONE>, and saves a handful of +kilobytes. The alternative, I, contains a partition table that +is not terribly handy on disk images that are not intended to represent any +physical disk. + +=item + +Read-write images are created with journaling off. Any read-write image +created by this tool is expected to be transient, and the goal of this tool +is to create images which consume a minimum of space. + +=back + +=head1 EXAMPLE + +pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg + --sourcefile --volname DeerPark --icon ~/DeerPark.icns + --mkdir /.background + --copy DeerParkBackground.png:/.background/background.png + --copy DeerParkDSStore:/.DS_Store + +=head1 REQUIREMENTS + +I has been tested with Mac OS X releases 10.2 ("Jaguar") +through 10.4 ("Panther"). Certain adjustments to behavior are made +depending on the host system's release. Mac OS X 10.3 ("Panther") or +later are recommended. + +=head1 LICENSE + +MPL 1.1/GPL 2.0/LGPL 2.1. Your choice. + +=head1 AUTHOR + +Mark Mentovai + +=head1 SEE ALSO + +L, L, L, L, L, +L, L + +=cut + +use Fcntl; +use POSIX; +use Getopt::Long; + +sub argumentEscape(@); +sub cleanupDie($); +sub command(@); +sub commandInternal($@); +sub commandInternalVerbosity($$@); +sub commandOutput(@); +sub commandOutputVerbosity($@); +sub commandVerbosity($@); +sub diskImageMaker($$$$$$$$); +sub giveExtension($$); +sub hdidMountImage($@); +sub licenseMaker($$); +sub pathSplit($); +sub setAttributes($@); +sub trapSignal($); +sub usage(); + +# Variables used as globals +my(@gCleanup, %gConfig, $gDarwinMajor, $gDryRun, $gVerbosity); + +# Use the commands by name if they're expected to be in the user's +# $PATH (/bin:/sbin:/usr/bin:/usr/sbin). Otherwise, go by absolute +# path. These may be overridden with --config. +%gConfig = ('cmd_bless' => 'bless', + 'cmd_chmod' => 'chmod', + 'cmd_diskutil' => 'diskutil', + 'cmd_du' => 'du', + 'cmd_hdid' => 'hdid', + 'cmd_hdiutil' => 'hdiutil', + 'cmd_mkdir' => 'mkdir', + 'cmd_mktemp' => 'mktemp', + 'cmd_Rez' => '/Developer/Tools/Rez', + 'cmd_rm' => 'rm', + 'cmd_rsync' => 'rsync', + 'cmd_SetFile' => '/Developer/Tools/SetFile', + + # create_directly indicates whether hdiutil create supports + # -srcfolder and -srcdevice. It does on >= 10.3 (Panther). + # This is fixed up for earlier systems below. If false, + # hdiutil create is used to create empty disk images that + # are manually filled. + 'create_directly' => 1, + + # If hdiutil attach -mountpoint exists, use it to avoid + # mounting disk images in the default /Volumes. This reduces + # the likelihood that someone will notice a mounted image and + # interfere with it. Only available on >= 10.3 (Panther), + # fixed up for earlier systems below. + # + # This is presently turned off for all systems, because there + # is an infrequent synchronization problem during ejection. + # diskutil eject might return before the image is actually + # unmounted. If pkg-dmg then attempts to clean up its + # temporary directory, it could remove items from a read-write + # disk image or attempt to remove items from a read-only disk + # image (or a read-only item from a read-write image) and fail, + # causing pkg-dmg to abort. This problem is experienced + # under Tiger, which appears to eject asynchronously where + # previous systems treated it as a synchronous operation. + # Using hdiutil attach -mountpoint didn't always keep images + # from showing up on the desktop anyway. + 'hdiutil_mountpoint' => 0, + + # hdiutil makehybrid results in optimized disk images that + # consume less space and mount more quickly. Use it when + # it's available, but that's only on >= 10.3 (Panther). + # If false, hdiutil create is used instead. Fixed up for + # earlier systems below. + 'makehybrid' => 1, + + # hdiutil create doesn't allow specifying a folder to open + # at volume mount time, so those images are mounted and + # their root folders made holy with bless -openfolder. But + # only on >= 10.3 (Panther). Earlier systems are out of luck. + # Even on Panther, bless refuses to run unless root. + # Fixed up below. + 'openfolder_bless' => 1, + + # It's possible to save a few more kilobytes by including the + # partition only without any partition table in the image. + # This is a good idea on any system, so turn this option off. + # + # Except it's buggy. "-layout NONE" seems to be creating + # disk images with more data than just the partition table + # stripped out. You might wind up losing the end of the + # filesystem - the last file (or several) might be incomplete. + 'partition_table' => 1, + + # To create a partition table-less image from something + # created by makehybrid, the hybrid image needs to be + # mounted and a new image made from the device associated + # with the relevant partition. This requires >= 10.4 + # (Tiger), presumably because earlier systems have + # problems creating images from devices themselves attached + # to images. If this is false, makehybrid images will + # have partition tables, regardless of the partition_table + # setting. Fixed up for earlier systems below. + 'recursive_access' => 1); + +# --verbosity +$gVerbosity = 2; + +# --dry-run +$gDryRun = 0; + +# %gConfig fix-ups based on features and bugs present in certain releases. +my($ignore, $uname_r, $uname_s); +($uname_s, $ignore, $uname_r, $ignore, $ignore) = POSIX::uname(); +if($uname_s eq 'Darwin') { + ($gDarwinMajor, $ignore) = split(/\./, $uname_r, 2); + + # $major is the Darwin major release, which for our purposes, is 4 higher + # than the interesting digit in a Mac OS X release. + if($gDarwinMajor <= 6) { + # <= 10.2 (Jaguar) + # hdiutil create does not support -srcfolder or -srcdevice + $gConfig{'create_directly'} = 0; + # hdiutil attach does not support -mountpoint + $gConfig{'hdiutil_mountpoint'} = 0; + # hdiutil mkhybrid does not exist + $gConfig{'makehybrid'} = 0; + } + if($gDarwinMajor <= 7) { + # <= 10.3 (Panther) + # Can't mount a disk image and then make a disk image from the device + $gConfig{'recursive_access'} = 0; + # bless does not support -openfolder on 10.2 (Jaguar) and must run + # as root under 10.3 (Panther) + $gConfig{'openfolder_bless'} = 0; + } +} +else { + # If it's not Mac OS X, just assume all of those good features are + # available. They're not, but things will fail long before they + # have a chance to make a difference. + # + # Now, if someone wanted to document some of these private formats... + print STDERR ($0.": warning, not running on Mac OS X, ". + "this could be interesting.\n"); +} + +# Non-global variables used in Getopt +my(@attributes, @copyFiles, $iconFile, $idme, $licenseFile, @makeDirs, + $outputFormat, @resourceFiles, $sourceFile, $sourceFolder, $targetImage, + $tempDir, $volumeName); + +# --format +$outputFormat = 'UDZO'; + +# --idme +$idme = 0; + +# --sourcefile +$sourceFile = 0; + +# Leaving this might screw up the Apple tools. +delete $ENV{'NEXT_ROOT'}; + +# This script can get pretty messy, so trap a few signals. +$SIG{'INT'} = \&trapSignal; +$SIG{'HUP'} = \&trapSignal; +$SIG{'TERM'} = \&trapSignal; + +Getopt::Long::Configure('pass_through'); +GetOptions('source=s' => \$sourceFolder, + 'target=s' => \$targetImage, + 'volname=s' => \$volumeName, + 'format=s' => \$outputFormat, + 'tempdir=s' => \$tempDir, + 'mkdir=s' => \@makeDirs, + 'copy=s' => \@copyFiles, + 'license=s' => \$licenseFile, + 'resource=s' => \@resourceFiles, + 'icon=s' => \$iconFile, + 'attribute=s' => \@attributes, + 'idme' => \$idme, + 'sourcefile' => \$sourceFile, + 'verbosity=i' => \$gVerbosity, + 'dry-run' => \$gDryRun, + 'config=s' => \%gConfig); # "hidden" option not in usage() + +if(@ARGV) { + # All arguments are parsed by Getopt + usage(); + exit(1); +} + +if($gVerbosity<0 || $gVerbosity>2) { + usage(); + exit(1); +} + +if(!defined($sourceFolder) || $sourceFolder eq '' || + !defined($targetImage) || $targetImage eq '') { + # --source and --target are required arguments + usage(); + exit(1); +} + +# Make sure $sourceFolder doesn't contain trailing slashes. It messes with +# rsync. +while(substr($sourceFolder, -1) eq '/') { + chop($sourceFolder); +} + +if(!defined($volumeName)) { + # Default volumeName is the name of the source directory. + my(@components); + @components = pathSplit($sourceFolder); + $volumeName = pop(@components); +} + +my(@tempDirComponents, $targetImageFilename); +@tempDirComponents = pathSplit($targetImage); +$targetImageFilename = pop(@tempDirComponents); + +if(defined($tempDir)) { + @tempDirComponents = pathSplit($tempDir); +} +else { + # Default tempDir is the same directory as what is specified for + # targetImage + $tempDir = join('/', @tempDirComponents); +} + +# Ensure that the path of the target image has a suitable extension. If +# it didn't, hdiutil would add one, and we wouldn't be able to find the +# file. +# +# Note that $targetImageFilename is not being reset. This is because it's +# used to build other names below, and we don't need to be adding all sorts +# of extra unnecessary extensions to the name. +my($originalTargetImage, $requiredExtension); +$originalTargetImage = $targetImage; +if($outputFormat eq 'UDSP') { + $requiredExtension = '.sparseimage'; +} +else { + $requiredExtension = '.dmg'; +} +$targetImage = giveExtension($originalTargetImage, $requiredExtension); + +if($targetImage ne $originalTargetImage) { + print STDERR ($0.": warning: target image extension is being added\n"); + print STDERR (' The new filename is '. + giveExtension($targetImageFilename,$requiredExtension)."\n"); +} + +# Make a temporary directory in $tempDir for our own nefarious purposes. +my(@output, $tempSubdir, $tempSubdirTemplate); +$tempSubdirTemplate=join('/', @tempDirComponents, + 'pkg-dmg.'.$$.'.XXXXXXXX'); +if(!(@output = commandOutput($gConfig{'cmd_mktemp'}, '-d', + $tempSubdirTemplate)) || $#output != 0) { + cleanupDie('mktemp failed'); +} + +if($gDryRun) { + (@output)=($tempSubdirTemplate); +} + +($tempSubdir) = @output; + +push(@gCleanup, + sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempSubdir);}); + +my($tempMount, $tempRoot, @tempsToMake); +$tempRoot = $tempSubdir.'/stage'; +$tempMount = $tempSubdir.'/mount'; +push(@tempsToMake, $tempRoot); +if($gConfig{'hdiutil_mountpoint'}) { + push(@tempsToMake, $tempMount); +} + +if(command($gConfig{'cmd_mkdir'}, @tempsToMake) != 0) { + cleanupDie('mkdir tempRoot/tempMount failed'); +} + +# This cleanup object is not strictly necessary, because $tempRoot is inside +# of $tempSubdir, but the rest of the script relies on this object being +# on the cleanup stack and expects to remove it. +push(@gCleanup, + sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempRoot);}); + +# If $sourceFile is true, it means that $sourceFolder is to be treated as +# a file and placed as a file within the volume root, as opposed to being +# treated as the volume root itself. rsync will do this by default, if no +# trailing '/' is present. With a trailing '/', $sourceFolder becomes +# $tempRoot, instead of becoming an entry in $tempRoot. +if(command($gConfig{'cmd_rsync'}, '-a', '--links', '-E', + $sourceFolder.($sourceFile?'':'/'),$tempRoot) != 0) { + cleanupDie('rsync failed'); +} + +if(@makeDirs) { + my($makeDir, @tempDirsToMake); + foreach $makeDir (@makeDirs) { + if($makeDir =~ /^\//) { + push(@tempDirsToMake, $tempRoot.$makeDir); + } + else { + push(@tempDirsToMake, $tempRoot.'/'.$makeDir); + } + } + if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) { + cleanupDie('mkdir failed'); + } +} + +my($copyFile); +foreach $copyFile (@copyFiles) { + my($copySource, $copyDestination); + ($copySource, $copyDestination) = split(/:/, $copyFile); + if(!defined($copyDestination)) { + $copyDestination = $tempRoot; + } + elsif($copyDestination =~ /^\//) { + $copyDestination = $tempRoot.$copyDestination; + } + else { + $copyDestination = $tempRoot.'/'.$copyDestination; + } + if(command($gConfig{'cmd_rsync'}, '-a', '--links', '-E', + $copySource, $copyDestination) != 0) { + cleanupDie('rsync failed for item copy'); + } +} + +if($gConfig{'create_directly'}) { + # If create_directly is false, the contents will be rsynced into a + # disk image and they would lose their attributes. + setAttributes($tempRoot, @attributes); +} + +if(defined($iconFile)) { + if(command($gConfig{'cmd_rsync'}, '-a', '--links', '-E', $iconFile, + $tempRoot.'/.VolumeIcon.icns') != 0) { + cleanupDie('rsync failed for volume icon'); + } + + # It's pointless to set the attributes of the root when diskutil create + # -srcfolder is being used. In that case, the attributes will be set + # later, after the image is already created. + if($outputFormat eq 'UDZO' && + (command($gConfig{'cmd_SetFile'}, '-a', 'C', $tempRoot) != 0)) { + cleanupDie('SetFile failed'); + } +} + +if(command($gConfig{'cmd_chmod'}, '-R', 'a+rX,a-st,u+w,go-w', + $tempRoot) != 0) { + cleanupDie('chmod failed'); +} + +my($unflattenable); +if($outputFormat eq 'UDZO') { + $unflattenable = 1; +} +else { + $unflattenable = 0; +} + +diskImageMaker($tempRoot, $targetImage, $outputFormat, $volumeName, + $tempSubdir, $tempMount, $targetImageFilename, defined($iconFile)); + +if(defined($licenseFile) && $licenseFile ne '') { + my($licenseResource); + $licenseResource = $tempSubdir.'/license.r'; + if(!licenseMaker($licenseFile, $licenseResource)) { + cleanupDie('licenseMaker failed'); + } + push(@resourceFiles, $licenseResource); + # Don't add a cleanup object because licenseResource is in tempSubdir. +} + +if(@resourceFiles) { + # Add resources, such as a license agreement. + + # Only unflatten read-only and compressed images. It's not supported + # on other image times. + if($unflattenable && + (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) { + cleanupDie('hdiutil unflatten failed'); + } + # Don't push flatten onto the cleanup stack. If we fail now, we'll be + # removing $targetImage anyway. + + # Type definitions come from Carbon.r. + if(command($gConfig{'cmd_Rez'}, 'Carbon.r', @resourceFiles, '-a', '-o', + $targetImage) != 0) { + cleanupDie('Rez failed'); + } + + # Flatten. This merges the resource fork into the data fork, so no + # special encoding is needed to transfer the file. + if($unflattenable && + (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) { + cleanupDie('hdiutil flatten failed'); + } +} + +# $tempSubdir is no longer needed. It's buried on the stack below the +# rm of the fresh image file. Splice in this fashion is equivalent to +# pop-save, pop, push-save. +splice(@gCleanup, -2, 1); +# No need to remove licenseResource separately, it's in tempSubdir. +if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) { + cleanupDie('rm -rf tempSubdir failed'); +} + +if($idme) { + if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes', + $targetImage) != 0) { + cleanupDie('hdiutil internet-enable failed'); + } +} + +# Done. + +exit(0); + +# argumentEscape(@arguments) +# +# Takes a list of @arguments and makes them shell-safe. +sub argumentEscape(@) { + my(@arguments); + @arguments = @_; + my($argument, @argumentsOut); + foreach $argument (@arguments) { + $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g; + push(@argumentsOut, $argument); + } + return @argumentsOut; +} + +# cleanupDie($message) +# +# Displays $message as an error message, and then runs through the +# @gCleanup stack, performing any cleanup operations needed before +# exiting. Does not return, exits with exit status 1. +sub cleanupDie($) { + my($message); + ($message) = @_; + print STDERR ($0.': '.$message.(@gCleanup?' (cleaning up)':'')."\n"); + while(@gCleanup) { + my($subroutine); + $subroutine = pop(@gCleanup); + &$subroutine; + } + exit(1); +} + +# command(@arguments) +# +# Runs the specified command at the verbosity level defined by $gVerbosity. +# Returns nonzero on failure, returning the exit status if appropriate. +# Discards command output. +sub command(@) { + my(@arguments); + @arguments = @_; + return commandVerbosity($gVerbosity,@arguments); +} + +# commandInternal($command, @arguments) +# +# Removes the files specified by @arguments with a verbosity level specified +# by $gVerbosity. +sub commandInternal($@) { + my(@arguments, $command); + ($command, @arguments) = @_; + return commandInternalVerbosity($gVerbosity, $command, @arguments); +} + +# commandInternalVerbosity($verbosity, $command, @arguments) +# +# Run an internal command, printing a bogus command invocation message if +# $verbosity is true. +# +# If $command is unlink: +# Removes the files specified by @arguments. Wraps unlink. +# +# If $command is mkdir: +# Creates the directory specified by @arguments, with an optional mask +# argument, wrapping mkdir. +sub commandInternalVerbosity($$@) { + my(@arguments, $command, $verbosity); + ($verbosity, $command, @arguments) = @_; + if($command eq 'unlink') { + if($verbosity || $gDryRun) { + print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n"); + } + if($gDryRun) { + return $#arguments+1; + } + return unlink(@arguments); + } +} + +# commandOutput(@arguments) +# +# Runs the specified command at the verbosity level defined by $gVerbosity. +# Output is returned in an array of lines. undef is returned on failure. +# The exit status is available in $?. +sub commandOutput(@) { + my(@arguments); + @arguments = @_; + return commandOutputVerbosity($gVerbosity, @arguments); +} + +# commandOutputVerbosity($verbosity, @arguments) +# +# Runs the specified command at the verbosity level defined by the +# $verbosity argument. Output is returned in an array of lines. undef is +# returned on failure. The exit status is available in $?. +# +# If an error occurs in fork or exec, an error message is printed to +# stderr and undef is returned. +# +# If $verbosity is 0, the command invocation is not printed, and its +# stdout is not echoed back to stdout. +# +# If $verbosity is 1, the command invocation is printed. +# +# If $verbosity is 2, the command invocation is printed and the output +# from stdout is echoed back to stdout. +# +# Regardless of $verbosity, stderr is left connected. +sub commandOutputVerbosity($@) { + my(@arguments, $verbosity); + ($verbosity, @arguments) = @_; + my($pid); + if($verbosity || $gDryRun) { + print(join(' ', argumentEscape(@arguments))."\n"); + } + if($gDryRun) { + return(1); + } + if (!defined($pid = open(*COMMAND, '-|'))) { + printf STDERR ($0.': fork: '.$!."\n"); + return undef; + } + elsif ($pid) { + # parent + my(@lines); + while(!eof(*COMMAND)) { + my($line); + chop($line = ); + if($verbosity > 1) { + print($line."\n"); + } + push(@lines, $line); + } + close(*COMMAND); + if ($? == -1) { + printf STDERR ($0.': fork: '.$!."\n"); + return undef; + } + elsif ($? & 127) { + printf STDERR ($0.': exited on signal '.($? & 127). + ($? & 128 ? ', core dumped' : '')."\n"); + return undef; + } + return @lines; + } + else { + # child; this form of exec is immune to shell games + if(!exec {$arguments[0]} (@arguments)) { + printf STDERR ($0.': exec: '.$!."\n"); + exit(-1); + } + } +} + +# commandVerbosity($verbosity, @arguments) +# +# Runs the specified command at the verbosity level defined by the +# $verbosity argument. Returns nonzero on failure, returning the exit +# status if appropriate. Discards command output. +sub commandVerbosity($@) { + my(@arguments, $verbosity); + ($verbosity, @arguments) = @_; + if(!defined(commandOutputVerbosity($verbosity, @arguments))) { + return -1; + } + return $?; +} + +# diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount, +# $baseName, $setRootIcon) +# +# Creates a disk image in $destination of format $format corresponding to the +# source directory $source. $name is the volume name. $tempDir is a good +# place to write temporary files, which should be empty (aside from the other +# things that this script might create there, like stage and mount). +# $tempMount is a mount point for temporary disk images. $baseName is the +# name of the disk image, and is presently unused. $setRootIcon is true if +# a volume icon was added to the staged $source and indicates that the +# custom volume icon bit on the volume root needs to be set. +sub diskImageMaker($$$$$$$$) { + my($baseName, $destination, $format, $name, $setRootIcon, $source, + $tempDir, $tempMount); + ($source, $destination, $format, $name, $tempDir, $tempMount, + $baseName, $setRootIcon) = @_; + if($format eq 'UDZO') { + my($uncompressedImage); + + if($gConfig{'makehybrid'}) { + my($hybridImage); + $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg'); + + if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs', + '-hfs-volume-name', $name, '-hfs-openfolder', $source, '-ov', + $source, '-o', $hybridImage) != 0) { + cleanupDie('hdiutil makehybrid failed'); + } + + $uncompressedImage = $hybridImage; + + # $source is no longer needed and will be removed before anything + # else can fail. splice in this form is the same as pop/push. + splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $hybridImage);}); + + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + cleanupDie('rm -rf failed'); + } + + if(!$gConfig{'partition_table'} && $gConfig{'recursive_access'}) { + # Even if we do want to create disk images without partition tables, + # it's impossible unless recursive_access is set. + my($rootDevice, $partitionDevice, $partitionMountPoint); + + if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = + hdidMountImage($tempMount, '-readonly', $hybridImage))) { + cleanupDie('hdid mount failed'); + } + + push(@gCleanup, sub {commandVerbosity(0, + $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); + + my($udrwImage); + $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); + + if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', 'UDRW', + '-ov', '-srcdevice', $partitionDevice, $udrwImage) != 0) { + cleanupDie('hdiutil create failed'); + } + + $uncompressedImage = $udrwImage; + + # Going to eject before anything else can fail. Get the eject off + # the stack. + pop(@gCleanup); + + # $hybridImage will be removed soon, but until then, it needs to + # stay on the cleanup stack. It needs to wait until after + # ejection. $udrwImage is staying around. Make it appear as + # though it's been done before $hybridImage. + # + # splice in this form is the same as popping one element to + # @tempCleanup and pushing the subroutine. + my(@tempCleanup); + @tempCleanup = splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $udrwImage);}); + push(@gCleanup, @tempCleanup); + + if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { + cleanupDie('diskutil eject failed'); + } + + # Pop unlink of $uncompressedImage + pop(@gCleanup); + + if(commandInternal('unlink', $hybridImage) != 1) { + cleanupDie('unlink hybridImage failed: '.$!); + } + } + } + else { + # makehybrid is not available, fall back to making a UDRW and + # converting to UDZO. It ought to be possible to do a UDZO directly, + # but those come out far too large (journaling?) and need to be + # read-write to fix up the volume icon anyway. Luckily, we can + # take advantage of a single call back into this function. + my($udrwImage); + $udrwImage = giveExtension($tempDir.'/udrw', '.dmg'); + + diskImageMaker($source, $udrwImage, 'UDRW', $name, $tempDir, + $tempMount, $baseName, $setRootIcon); + + # The call back into diskImageMaker already removed $source. + + $uncompressedImage = $udrwImage; + } + + # The uncompressed disk image is now in its final form. Compress it. + # Jaguar doesn't support hdiutil convert -ov, but it always allows + # overwriting. + if(command($gConfig{'cmd_hdiutil'}, 'convert', '-format', 'UDZO', + '-imagekey', 'zlib-level=9', + (defined($gDarwinMajor) && $gDarwinMajor <= 6 ? () : ('-ov')), + $uncompressedImage, '-o', $destination) != 0) { + cleanupDie('hdiutil convert failed'); + } + + # $uncompressedImage is going to be unlinked before anything else can + # fail. splice in this form is the same as pop/push. + splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $destination);}); + + if(commandInternal('unlink', $uncompressedImage) != 1) { + cleanupDie('unlink uncompressedImage failed: '.$!); + } + + # At this point, the only thing that the UDZO block has added to the + # cleanup stack is the removal of $destination. $source has already + # been removed, and its cleanup entry has been removed as well. + } + elsif($format eq 'UDRW' || $format eq 'UDSP') { + my(@extraArguments); + if(!$gConfig{'partition_table'}) { + @extraArguments = ('-layout', 'NONE'); + } + + if($gConfig{'create_directly'}) { + # Use -fs HFS+ to suppress the journal. + if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format, + @extraArguments, '-fs', 'HFS+', '-volname', $name, + '-ov', '-srcfolder', $source, $destination) != 0) { + cleanupDie('hdiutil create failed'); + } + + # $source is no longer needed and will be removed before anything + # else can fail. splice in this form is the same as pop/push. + splice(@gCleanup, -1, 1, + sub {commandInternalVerbosity(0, 'unlink', $destination);}); + + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + cleanupDie('rm -rf failed'); + } + } + else { + # hdiutil create does not support -srcfolder or -srcdevice, it only + # knows how to create blank images. Figure out how large an image + # is needed, create it, and fill it. This is needed for Jaguar. + + # Use native block size for hdiutil create -sectors. + delete $ENV{'BLOCKSIZE'}; + + my(@duOutput, $ignore, $sizeBlocks, $sizeOverhead, $sizeTotal, $type); + if(!(@output = commandOutput($gConfig{'cmd_du'}, '-s', $tempRoot)) || + $? != 0) { + cleanupDie('du failed'); + } + ($sizeBlocks, $ignore) = split(' ', $output[0], 2); + + # The filesystem itself takes up 152 blocks of its own blocks for the + # filesystem up to 8192 blocks, plus 64 blocks for every additional + # 4096 blocks or portion thereof. + $sizeOverhead = 152 + 64 * POSIX::ceil( + (($sizeBlocks - 8192) > 0) ? (($sizeBlocks - 8192) / (4096 - 64)) : 0); + + # The number of blocks must be divisible by 8. + my($mod); + if($mod = ($sizeOverhead % 8)) { + $sizeOverhead += 8 - $mod; + } + + # sectors is taken as the size of a disk, not a filesystem, so the + # partition table eats into it. + if($gConfig{'partition_table'}) { + $sizeOverhead += 80; + } + + # That was hard. Leave some breathing room anyway. Use 1024 sectors + # (512kB). These read-write images wouldn't be useful if they didn't + # have at least a little free space. + $sizeTotal = $sizeBlocks + $sizeOverhead + 1024; + + # Minimum sizes - these numbers are larger on Jaguar than on later + # systems. Just use the Jaguar numbers, since it's unlikely to wind + # up here on any other release. + if($gConfig{'partition_table'} && $sizeTotal < 8272) { + $sizeTotal = 8272; + } + if(!$gConfig{'partition_table'} && $sizeTotal < 8192) { + $sizeTotal = 8192; + } + + # hdiutil create without -srcfolder or -srcdevice will not accept + # -format. It uses -type. Fortunately, the two supported formats + # here map directly to the only two supported types. + if ($format eq 'UDSP') { + $type = 'SPARSE'; + } + else { + $type = 'UDIF'; + } + + if(command($gConfig{'cmd_hdiutil'}, 'create', '-type', $type, + @extraArguments, '-fs', 'HFS+', '-volname', $name, + '-ov', '-sectors', $sizeTotal, $destination) != 0) { + cleanupDie('hdiutil create failed'); + } + + push(@gCleanup, + sub {commandInternalVerbosity(0, 'unlink', $destination);}); + + # The rsync will occur shortly. + } + + my($mounted, $rootDevice, $partitionDevice, $partitionMountPoint); + + $mounted=0; + if(!$gConfig{'create_directly'} || $gConfig{'openfolder_bless'} || + $setRootIcon) { + # The disk image only needs to be mounted if: + # create_directly is false, because the content needs to be copied + # openfolder_bless is true, because bless -openfolder needs to run + # setRootIcon is true, because the root needs its attributes set. + if(!(($rootDevice, $partitionDevice, $partitionMountPoint) = + hdidMountImage($tempMount, $destination))) { + cleanupDie('hdid mount failed'); + } + + $mounted=1; + + push(@gCleanup, sub {commandVerbosity(0, + $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);}); + } + + if(!$gConfig{'create_directly'}) { + # Couldn't create and copy directly in one fell swoop. Now that + # the volume is mounted, copy the files. --links is + # unnecessary since it was used to copy everything to the staging + # area. There can be no more unsafe links. + if(command($gConfig{'cmd_rsync'}, '-a', '-E', + $source.'/',$partitionMountPoint) != 0) { + cleanupDie('rsync to new volume failed'); + } + + # We need to get the rm -rf of $source off the stack, because it's + # being cleaned up here. There are two items now on top of it: + # removing the target image and, above that, ejecting it. Splice it + # out. + my(@tempCleanup); + @tempCleanup = splice(@gCleanup, -2); + # The next splice is the same as popping once and pushing @tempCleanup. + splice(@gCleanup, -1, 1, @tempCleanup); + + if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) { + cleanupDie('rm -rf failed'); + } + } + + if($gConfig{'openfolder_bless'}) { + # On Tiger, the bless docs say to use --openfolder, but only + # --openfolder is accepted on Panther. Tiger takes it with a single + # dash too. Jaguar is out of luck. + if(command($gConfig{'cmd_bless'}, '-openfolder', + $partitionMountPoint) != 0) { + cleanupDie('bless failed'); + } + } + + setAttributes($partitionMountPoint, @attributes); + + if($setRootIcon) { + # When "hdiutil create -srcfolder" is used, the root folder's + # attributes are not copied to the new volume. Fix up. + + if(command($gConfig{'cmd_SetFile'}, '-a', 'C', + $partitionMountPoint) != 0) { + cleanupDie('SetFile failed'); + } + } + + if($mounted) { + # Pop diskutil eject + pop(@gCleanup); + + if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) { + cleanupDie('diskutil eject failed'); + } + } + + # End of UDRW/UDSP section. At this point, $source has been removed + # and its cleanup entry has been removed from the stack. + } + else { + cleanupDie('unrecognized format'); + print STDERR ($0.": unrecognized format\n"); + exit(1); + } +} + +# giveExtension($file, $extension) +# +# If $file does not end in $extension, $extension is added. The new +# filename is returned. +sub giveExtension($$) { + my($extension, $file); + ($file, $extension) = @_; + if(substr($file, -length($extension)) ne $extension) { + return $file.$extension; + } + return $file; +} + +# hdidMountImage($mountPoint, @arguments) +# +# Runs the hdid command with arguments specified by @arguments. +# @arguments may be a single-element array containing the name of the +# disk image to mount. Returns a three-element array, with elements +# corresponding to: +# - The root device of the mounted image, suitable for ejection +# - The device corresponding to the mounted partition +# - The mounted partition's mount point +# +# If running on a system that supports easy mounting at points outside +# of the default /Volumes with hdiutil attach, it is used instead of hdid, +# and $mountPoint is used as the mount point. +# +# The root device will differ from the partition device when the disk +# image contains a partition table, otherwise, they will be identical. +# +# If hdid fails, undef is returned. +sub hdidMountImage($@) { + my(@arguments, @command, $mountPoint); + ($mountPoint, @arguments) = @_; + my(@output); + + if($gConfig{'hdiutil_mountpoint'}) { + @command=($gConfig{'cmd_hdiutil'}, 'attach', @arguments, + '-mountpoint', $mountPoint); + } + else { + @command=($gConfig{'cmd_hdid'}, @arguments); + } + + if(!(@output = commandOutput(@command)) || + $? != 0) { + return undef; + } + + if($gDryRun) { + return('/dev/diskX','/dev/diskXsY','/Volumes/'.$volumeName); + } + + my($line, $restOfLine, $rootDevice); + + foreach $line (@output) { + my($device, $mountpoint); + if($line !~ /^\/dev\//) { + # Consider only lines that correspond to /dev entries + next; + } + ($device, $restOfLine) = split(' ', $line, 2); + + if(!defined($rootDevice) || $rootDevice eq '') { + # If this is the first device seen, it's the root device to be + # used for ejection. Keep it. + $rootDevice = $device; + } + + if($restOfLine =~ /(\/.*)/) { + # The first partition with a mount point is the interesting one. It's + # usually Apple_HFS and usually the last one in the list, but beware of + # the possibility of other filesystem types and the Apple_Free partition. + # If the disk image contains no partition table, the partition will not + # have a type, so look for the mount point by looking for a slash. + $mountpoint = $1; + return($rootDevice, $device, $mountpoint); + } + } + + # No mount point? This is bad. If there's a root device, eject it. + if(defined($rootDevice) && $rootDevice ne '') { + # Failing anyway, so don't care about failure + commandVerbosity(0, $gConfig{'cmd_diskutil'}, 'eject', $rootDevice); + } + + return undef; +} + +# licenseMaker($text, $resource) +# +# Takes a plain text file at path $text and creates a license agreement +# resource containing the text at path $license. English-only, and +# no special formatting. This is the bare-bones stuff. For more +# intricate license agreements, create your own resource. +# +# ftp://ftp.apple.com/developer/Development_Kits/SLAs_for_UDIFs_1.0.dmg +sub licenseMaker($$) { + my($resource, $text); + ($text, $resource) = @_; + if(!sysopen(*TEXT, $text, O_RDONLY)) { + print STDERR ($0.': licenseMaker: sysopen text: '.$!."\n"); + return 0; + } + if(!sysopen(*RESOURCE, $resource, O_WRONLY|O_CREAT|O_EXCL)) { + print STDERR ($0.': licenseMaker: sysopen resource: '.$!."\n"); + return 0; + } + print RESOURCE << '__EOT__'; +// See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs. +data 'LPic' (5000) { + // Default language ID, 0 = English + $"0000" + // Number of entries in list + $"0001" + + // Entry 1 + // Language ID, 0 = English + $"0000" + // Resource ID, 0 = STR#/TEXT/styl 5000 + $"0000" + // Multibyte language, 0 = no + $"0000" +}; + +resource 'STR#' (5000, "English") { + { + // Language (unused?) = English + "English", + // Agree + "Agree", + // Disagree + "Disagree", +__EOT__ + # This stuff needs double-quotes for interpolations to work. + print RESOURCE (" // Print, ellipsis is 0xC9\n"); + print RESOURCE (" \"Print\xc9\",\n"); + print RESOURCE (" // Save As, ellipsis is 0xC9\n"); + print RESOURCE (" \"Save As\xc9\",\n"); + print RESOURCE (' // Descriptive text, curly quotes are 0xD2 and 0xD3'. + "\n"); + print RESOURCE (' "If you agree to the terms of this license '. + "agreement, click \xd2Agree\xd3 to access the software. If you ". + "do not agree, press \xd2Disagree.\xd3\"\n"); +print RESOURCE << '__EOT__'; + }; +}; + +// Beware of 1024(?) byte (character?) line length limitation. Split up long +// lines. +// If straight quotes are used ("), remember to escape them (\"). +// Newline is \n, to leave a blank line, use two of them. +// 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly +// single quotes ('), 0xD5 is also the apostrophe. +data 'TEXT' (5000, "English") { +__EOT__ + + while(!eof(*TEXT)) { + my($line); + chop($line = ); + + while(defined($line)) { + my($chunk); + + # Rez doesn't care for lines longer than (1024?) characters. Split + # at less than half of that limit, in case everything needs to be + # backwhacked. + if(length($line)>500) { + $chunk = substr($line, 0, 500); + $line = substr($line, 500); + } + else { + $chunk = $line; + $line = undef; + } + + if(length($chunk) > 0) { + # Unsafe characters are the double-quote (") and backslash (\), escape + # them with backslashes. + $chunk =~ s/(["\\])/\\$1/g; + + print RESOURCE ' "'.$chunk.'"'."\n"; + } + } + print RESOURCE ' "\n"'."\n"; + } + close(*TEXT); + + print RESOURCE << '__EOT__'; +}; + +data 'styl' (5000, "English") { + // Number of styles following = 1 + $"0001" + + // Style 1. This is used to display the first two lines in bold text. + // Start character = 0 + $"0000 0000" + // Height = 16 + $"0010" + // Ascent = 12 + $"000C" + // Font family = 1024 (Lucida Grande) + $"0400" + // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline + // 0x10=shadow 0x20=condensed 0x40=extended + $"00" + // Style, unused? + $"02" + // Size = 12 point + $"000C" + // Color, RGB + $"0000 0000 0000" +}; +__EOT__ + close(*RESOURCE); + + return 1; +} + +# pathSplit($pathname) +# +# Splits $pathname into an array of path components. +sub pathSplit($) { + my($pathname); + ($pathname) = @_; + return split(/\//, $pathname); +} + +# setAttributes($root, @attributeList) +# +# @attributeList is an array, each element of which must be in the form +# :. is a list of attributes, per SetFile. is a file +# which is taken as relative to $root (even if it appears as an absolute +# path.) SetFile is called to set the attributes on each file in +# @attributeList. +sub setAttributes($@) { + my(@attributes, $root); + ($root, @attributes) = @_; + my($attribute); + foreach $attribute (@attributes) { + my($attrList, $file, @fileList, @fixedFileList); + ($attrList, @fileList) = split(/:/, $attribute); + if(!defined($attrList) || !@fileList) { + cleanupDie('--attribute requires :'); + } + @fixedFileList=(); + foreach $file (@fileList) { + if($file =~ /^\//) { + push(@fixedFileList, $root.$file); + } + else { + push(@fixedFileList, $root.'/'.$file); + } + } + if(command($gConfig{'cmd_SetFile'}, '-a', $attrList, @fixedFileList)) { + cleanupDie('SetFile failed to set attributes'); + } + } + return; +} + +sub trapSignal($) { + my($signalName); + ($signalName) = @_; + cleanupDie('exiting on SIG'.$signalName); +} + +sub usage() { + print STDERR ( +"usage: pkg-dmg --source \n". +" --target \n". +" [--format ] (default: UDZO)\n". +" [--volname ] (default: same name as source)\n". +" [--tempdir ] (default: same dir as target)\n". +" [--mkdir ] (make directory in image)\n". +" [--copy [:]] (extra files to add)\n". +" [--license ] (plain text license agreement)\n". +" [--resource ] (flat .r files to merge)\n". +" [--icon ] (volume icon)\n". +" [--attribute :] (set file attributes)\n". +" [--idme] (make an Internet-enabled image)\n". +" [--sourcefile] (treat --source as a file)\n". +" [--verbosity ] (0, 1, 2; default=2)\n". +" [--dry-run] (print what would be done)\n"); + return; +} diff --git a/DiskImage/template.dmg.bz2 b/DiskImage/template.dmg.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..072e3b47bb40e5080c4c93c09458fea592b059f1 GIT binary patch literal 4007047 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