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