Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Make a .dmg from any directory UNIX
A recent post to the java-dev mailing list had a great script I thought I'd pass along. It allows you to make OS X disk images (.dmg) from any directory with a simple command in the terminal, like so:

mkdmg <volname> <version> <srcdir>

To see the script, read the rest of this story:
#!/bin/sh
#
# Creates a disk image (dmg) on Mac OS X from the command line.
# usage:
# mkdmg <volname> <vers> <srcdir>
#
# Where <volname> is the name to use for the mounted image, <vers> is the version
# number of the volume and <srcdir> is where the contents to put on the dmg are.
#
# The result will be a file called <volname>-<vers>.dmg

if [ $# != 3 ]; then
 echo "usage: mkdmg.sh volname vers srcdir"
 exit 0
fi

VOL="$1"
VER="$2"
FILES="$3"

DMG="tmp-$VOL.dmg"

# create temporary disk image and format, ejecting when done
SIZE=`du -sk ${FILES} | sed -n '/^[0-9]*/s/([0-9]*).*/1/p'`
SIZE=$((${SIZE}/1000+1))
hdiutil create "$DMG" -megabytes ${SIZE} -ov -type UDIF
DISK=`hdid "$DMG" | sed -ne ' /Apple_partition_scheme/ s|^/dev/([^ ]*).*$|1|p'`
newfs_hfs -v "$VOL" /dev/r${DISK}s2
hdiutil eject $DISK

# mount and copy files onto volume
hdid "$DMG"
cp -R "${FILES}"/* "/Volumes/$VOL"
hdiutil eject $DISK
#osascript -e "tell application "Finder" to eject disk "$VOL"" &&

# convert to compressed image, delete temp image
rm -f "${VOL}-${VER}.dmg"
hdiutil convert "$DMG" -format UDZO -o "${VOL}-${VER}.dmg"
rm -f "$DMG"

    •    
  • Currently 2.63 / 5
  You rated: 3 / 5 (8 votes cast)
 
[63,166 views]  

Make a .dmg from any directory | 21 comments | Create New Account
Click here to return to the 'Make a .dmg from any directory' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Re: Make a .dmg from any directory
Authored by: sjk on Mar 11, '02 10:18:06PM
Probably want to use ditto instead of cp to copy files. And do some checking before running newfs_hfs to make sure it doesn't clobber some other filesystem (ouch!). Thinking about how to do this without copying files to a temporary volume...

[ Reply to This | # ]
Script has some bugs
Authored by: Jayce on Mar 12, '02 01:17:34AM

There is some problems in the script :
- The use of cp instead of ditto implies losing resource-forks.
- the SIZE calculated from the size of the folder is not good. SIZE must be the size of the folder + x Mo. The more the folder is big, the more x must be big. (it's for catalog informations).
- The name of the disk linked to the dmg is not always $VOL. The only sure way to get the name of the volume is to use the -verbose option of hdiutil and to grep the result.



[ Reply to This | # ]
Script has some bugs
Authored by: sharumpe on Mar 12, '02 11:31:30AM

Good point (both of you) about ditto - I will hem/haw a bit by saying that I didn't write the original. ;)

However, in looking at the ditto man page to try to use it instead, it looks like it has to be run as root. I don't want to be responsible for people who don't normally use root enabling it just for this. ;) If you know enough to use root safely, you can make the change from cp to ditto. Here's my compromise: NOTE: THIS DOES NOT COPY RESOURCE FORKS! <grin>

As far as the size goes, right now it takes the size reported by du -sk. I get what you're saying (before it had a static 5MB size) but I'm not aware of a command that will give me that information. We're probably stuck with a guesstimate. I've increased the 'fudge factor,' currently at 1MB, to 3MB in my copy.

It took me a bit to figure out what you were getting at with $VOL, but if I understand correctly, the reason that $VOL will not always be correct is that the system will sometimes give you foo-1 if foo is already mounted, correct? In that case, how do you know which one you want? It could be the last one, but that's not necessarily going to be the case. :)

In any case, the script is a good starting point, and illustrates one of the cool things that can happen when you bring Unix to the Mac OS.

Mr. Sharumpe



[ Reply to This | # ]
Script has some bugs
Authored by: a1291762 on Mar 12, '02 04:55:20PM

Ditto has to run as root?

Is it a simple Unix executable?

You could do this to make it run as root easily:
sudo chown root.admin ditto
sudo chmod 4755 ditto

This makes it owned by root and group admin. The 4755 makes it setuid and executable. Thus anyone can run it *without* having to use sudo and it will automatically gain root privilages.

I'll go look at the ditto page and suggest this to the author I think...



[ Reply to This | # ]
D'oh!
Authored by: a1291762 on Mar 12, '02 05:27:30PM

D'oh! I just looked at an OS X system...

I didn't realise ditto was an Apple supplied command :)

I just tried the setuid bit and it works except for one small thing... It creates the destination file as root.

So if you do "ditto foo bar" then bar is owned by root and you'll need to sudo chown <yourname>.[admin|staff] and possibly reset the permissions on it.

Oh well... maybe a wrapper program could sort that out...



[ Reply to This | # ]
Script has some bugs
Authored by: sharumpe on Mar 13, '02 01:34:41AM

It is generally a bad idea to run anything as root, and even less of a good idea to set programs to setuid. If the program turns out to have some sort of problem, it may become possible to gain root access.

Not to mention, of course, little things like the fact that the files are created as root. cp has a flag that maintains permissions, I wonder if ditto does?

Mr. Sharumpe



[ Reply to This | # ]
Ditto does _not_ have to run as root
Authored by: sabi on Mar 13, '02 03:54:51AM

The man page is out of date. I've reported this bug (such as it is) to
Apple, they've fixed it already.

Ditto is a wonderful tool since it's the only one that supports
duplicating including metadata, preserving modification dates,
symbolic links, etc. It cuts across the Mac and Unix sides perfectly.



[ Reply to This | # ]
Script has some bugs
Authored by: Jayce on Mar 17, '02 01:48:05AM

>However, in looking at the ditto man page to try to use it instead, it looks >like it has to be run as root.

You didn't need root privileges to use ditto... You only need root privileges to copy the system tree...

>As far as the size goes, right now it takes the size reported by du -sk. I get >what you're saying (before it had a static 5MB size) but I'm not aware of a >command that will give me that information. We're probably stuck with a >guesstimate. I've increased the 'fudge factor,' currently at 1MB, to 3MB in my >copy.

I had a problem with this with a script of mine... And, the only solution I had found is to make my diskimage 20% larger of my folder... Here is the command :
sudo hdiutil create -sectors $(expr $(echo $(sudo du -sHx "$Folder")|cut -f1 -d' ') * 12 / 10) $ImageFile

>It took me a bit to figure out what you were getting at with $VOL, but if I >understand correctly, the reason that $VOL will not always be correct is that >the system will sometimes give you foo-1 if foo is already mounted, correct? In >that case, how do you know which one you want? It could be the last one, but >that's not necessarily going to be the case. :)

The only way is to use hdiutil to mount the image :
MOUNT_POINT=$(sudo hdiutil mount -verbose $ImageFile | grep "/Volumes" |sed -e "s#</{0,1}string>##g")

>In any case, the script is a good starting point, and illustrates one of the >cool things that can happen when you bring Unix to the Mac OS.

Sure, it is a good start...



[ Reply to This | # ]
Revised script...
Authored by: jg on Mar 24, '02 11:01:43AM
Here's a moderately modified version of the same script. First of all, when running the script the user will be prompted for an administrator's password. I have also changed the calculations to allow for an overhead of 5 per cent above the source directory's size. The copy command has been changed from using cp to using ditto because of resource forks. Bear careful though, as ditto does not preserve unix permissions, in particular group information. The script could be made a little cleaner and newbie friendlier, but I think that if the user is not familiar with the command line, Disk Copy becomes a better proposition any way. ;-)
#!/bin/sh
#
#12/03/02- Modified script, originally posted by Mr. Sharumpe to Mac OS X Hints - JG
#
# Creates a disk image (dmg) on Mac OS X from the command line.
# usage:
# mkdmg
#
# Where is the name to use for the mounted image, is the version
# number of the volume and is where the contents to put on the dmg are.
#
# The result will be a file called -.dmg
#
if [ $# != 3 ]; then
echo "Usage: mkdmg volname vers srcdir"
exit 0
fi
VOL="$1"
VER="$2"
SRC="$3"
DMG="tmp-$VOL.dmg"

## The newfs command can only be run as root, so we need to prompt for an administrative
## password. By default, the admin group is listed in /etc/sudoers give its memebers the
## right to run commands as root, albeit still having to authenticate.
#
echo ">"
echo ""
echo ""
sudo -v

## Create temporary disk image and format, ejecting when done
#
echo ""
echo ">"

## The total size of the disk image is calculated in here to be 5% percent more than the
## total data size. This allows for the partition tables overhead (64 sectors, 512 bytes each)
## plus some spare.
#
SIZE=`du -sk ${SRC} | awk '{printf "%.3gn",($1*10.5)/(10240)}'`
echo "..................................................................................................."
echo ""
echo ">"
echo ""
hdiutil create "$DMG" -megabytes ${SIZE} -ov -type UDIF

## Create /dev entries but do not mount it, else the Finder will prompt to initialize the disk
#
DISK=`hdid -nomount "$DMG" |awk '/scheme/ {print substr ($1, 6, length)}'`

## Make a new filesystem and eject the disk
#
sudo newfs_hfs -v "$VOL" /dev/r${DISK}s2

hdiutil eject $DISK

## Mount and copy files onto volume, making sure that there are no locked files before running ditto
#
hdid "$DMG"
echo "..................................................................................................."
echo ""
chflags -R nouchg,noschg "${SRC}"
ditto -rsrcFork -v "${SRC}" "/Volumes/${VOL}" && echo ">"
echo ""
echo "..................................................................................................."
hdiutil eject $DISK

## Convert to compressed image, delete temp image if it exists
#
if [ -e "${VOL}-${VER}.dmg" ]; then
rm -f "${VOL}-${VER}.dmg"
fi
hdiutil convert "$DMG" -format UDZO -o "${VOL}-${VER}.dmg"
rm -f "$DMG"


[ Reply to This | # ]
Revised script...
Authored by: flumignan on Sep 25, '02 02:37:17PM

This seems to have broken under 10.2; disk images aren't being ejected like they used to. Any tips?



[ Reply to This | # ]
how to set the compression level, plus an updated script
Authored by: mblakele on Oct 27, '02 02:58:39PM

According to 'man hdiutil', UDZO defaults to zlib level 1 of 1-9, where 1 is fastest and 9 produces the smallest output. The default for gzip is 6.

Since I'm usually using a script like this to archive folders for backup or distribution, I use level 9 - it's worth a little extra time on the front-end to have smaller images.

hdiutil convert $TMPFILE -format UDZO -imagekey zlib-level=9 -o "$FILE"

Some people have have problems with the original script - here's the one I use:

#!/bin/sh
#
FOLDER="$1"
if [ -z "$FOLDER" ]; then
echo
echo "usage: $0 <folder>"
echo
exit 1
fi

if [ ! -d "$FOLDER" ]; then
echo $FOLDER does not exist
exit 2
fi

SIZE=`du -s "$FOLDER" | awk '{ print $1 }'`
# allow space for partition map and directory structure
SIZE=`echo 1024 + $SIZE \* 1.1 / 1 | bc`
NAME=`basename "$FOLDER"`
FILE=$NAME.dmg
TMP=${TMP:-/tmp}

if [ $SIZE -lt 9216 ]; then
SIZE=9216
fi

if [ -e "$FILE" ] ; then
echo $FILE already exists!
exit 3
fi

TMPFILE=$TMP/$$.dmg

echo Creating $TMPFILE from $FOLDER, $SIZE sectors...
hdiutil create $TMPFILE -sectors $SIZE -ov
if [ $? -ne 0 ] ; then
rm -f $TMPFILE
exit 4
fi
echo

DEVICES=`hdid -nomount $TMPFILE`
DEVMASTER=`echo $DEVICES| awk '{ print $1 }'`
DEVHFS=`echo $DEVICES| awk '{ print $5 }'`
echo Creating HFS partition $NAME on $TMPFILE at $DEVHFS
newfs_hfs -v "$NAME" $DEVHFS
if [ $? -ne 0 ] ; then
rm -f $TMPFILE
exit 5
fi
hdiutil eject $DEVMASTER
if [ $? -ne 0 ] ; then
rm -f $TMPFILE
exit 6
fi
DEVICES=`hdid $TMPFILE`
if [ $? -ne 0 ] ; then
rm -f $TMPFILE
exit 7
fi

DEVMASTER=`echo $DEVICES| awk '{ print $1 }'`
DEVHFS=`echo $DEVICES| awk '{ print $5 }'`
echo Copying $FOLDER to /Volumes/$NAME on $DEVMASTER
sudo ditto -rsrcFork "$FOLDER" "/Volumes/$NAME"
if [ $? -ne 0 ]; then
hdiutil eject $DEVMASTER
rm -f $TMPFILE
exit 8
fi

hdiutil eject $DEVMASTER
if [ $? -ne 0 ]; then
#rm -f $TMPFILE
exit 9
fi

echo "Compressing $NAME to $FILE"
#hdiutil convert $TMPFILE -format UDZO -o "$FILE"
hdiutil convert $TMPFILE -format UDZO -imagekey zlib-level=9 -o "$FILE"
if [ $? -ne 0 ]; then
rm -f "$FILE" $TMPFILE
exit 10
fi

rm -f $TMPFILE

# end



[ Reply to This | # ]
how to set the compression level, plus an updated script
Authored by: cactus on May 05, '03 05:14:55PM
Here is a script I wrote which creates a backup, then compresses and encrypts the image. It performs some sanity checks and prints copious output. Note that the tar I'm using is from the Fink distribution, in /sw/bin. Without this version, you need to adjust the pathname (I haven't tested this).
#!/usr/bin/perl

use Getopt::Std;
use Cwd;
use File::Basename;

$DF='/bin/df';
$DU='/usr/bin/du';
$HDIUTIL='/usr/bin/hdiutil';
$HDID='/usr/bin/hdid';
$DITTO='/usr/bin/ditto';
$DISKUTIL='/usr/sbin/diskutil';
$DISKTOOL='/usr/sbin/disktool';
$MOUNT='/sbin/mount';
$UMOUNT='/sbin/umount';
$ID='/usr/bin/id';
$STTY='/bin/stty';
$TAR='/sw/bin/tar';
$SUDO = '/usr/bin/sudo';
$NEWFS_HFS = '/sbin/newfs_hfs';
$CHFLAGS = '/usr/bin/chflags';

$MAX_DU_LEN = 160;
$MIN_SLACK  = 32;
$SLACK_FACTOR = 0.05;
$MIN_HFS = 4 * 1024 + 64;

sub checkexist(@);
sub getpasswd($);
sub getinput($);
sub diskusage(@);
sub changeflags(@);
sub addslack($);
sub setuser();
sub makevolume($$);
sub attach($);
sub makehfs($$);
sub mount($$);
sub ditto($@);
sub unmount($);
sub detach($);
sub convert2cryptcompressed($$$);
sub attachcrypt($$);
sub mountreadonly($$);
sub ducom($);
sub debugdu($$$);
sub ditto_link($$);
sub ditto_dir($$);
sub ditto_file($$);
sub ditto_path($$);
sub docommand($);
sub real_docommand($$);

############################################################################

%opts = ();

getopts('i:m:p:v:', \%opts) or
    die("usage:\n" .
        "   $0 [-i] [-m] [-p] " .
        "[-v] node1 [...]\n");

checkexist(@ARGV) || die("$0: checkexist() failed\n");

$mypass = ($opts{"p"}) ? $opts{"p"} : getpasswd("Passwd");
$myvolume = ($opts{"v"}) ? $opts{"v"} : getinput("Volume Name");

$imagename = ($opts{"i"}) ? $opts{"i"} : getinput("Image Filename");
unless ($imagename =~ /^.+\.dmg$/)
{
    $imagename =~ s/$/.dmg/;
}
$tmpimage = $imagename;
unless ($tmpimage =~ s/^(.+)(\.dmg)$/\1.TEMP\2/)
{
    die("$0: trying to create temp filename from '${imagename}'\n" .
        "    '${tmpimage}'\n");
}
if (-e $tmpimage)
{
    die("$0: temporary image filename '${tmpimage}' exists\n");
}

$mountdir = ($opts{"m"}) ? $opts{"m"} : getinput("Mount Directory");
unless (-d $mountdir)
{
    die("$0: directory '${mountdir}' doesn't exist\n");
}

($dulist, $origsize) = diskusage(@ARGV);
changeflags(@ARGV);
$totsize = addslack($origsize);
setuser();
makevolume($totsize, $tmpimage);
$diskname = attach($tmpimage);
makehfs($diskname, $myvolume);
mount($diskname, $mountdir);
$targetlist = ditto($mountdir, @ARGV);
unmount($mountdir);
detach($diskname);
convert2cryptcompressed($tmpimage, $imagename, $mypass);
print("@@ unlink(${tmpimage})\n");
unlink($tmpimage) ||
    warn("$0: couldn't remove temp image '${tmpimage}': $!\n");

$diskname = attachcrypt($imagename, $mypass);
mountreadonly($diskname, $mountdir);
($imgdulist, $imgsize) = diskusage(@$targetlist);
unmount($mountdir);
detach($diskname);

print("\n\n");
print("original disk usage = ${origsize} K\n");
print("image disk usage    = ${imgsize} K\n");
print("volume name         = '${myvolume}'\n");
print("image file          = '${imagename}'\n");
print("====================================================\n");
debugdu($dulist, $imgdulist, \*STDOUT);
print("\n");



############################################################################
############################################################################
############################################################################

sub checkexist(@)
{
    my @nodes = @_;
    my $node;
    my $retval = 1;

    foreach $node (@nodes)
    {
        unless (-r $node)
        {
            warn("$0: checkexist(): `$node' doesn't exist or isn't readable\n");
            $retval = 0;
        }
    }

    return($retval);
}

sub getpasswd($)
{
    my ($prompt) = @_;
    my $word;
    my $command;

    open(TTY, ">/dev/tty") ||
        die("$0: getpasswd(): couldn't open /dev/tty: $!\n");

    $command = "${STTY} -echo";

    unless (docommand($command))
    {
        docommand("${STTY} echo");
        die("$0: bailing\n" .
            "'${command}'\n");
    }
    print TTY ("$prompt: ");
    chomp($word = );
    print TTY ("\n");

    $command = "${STTY} echo";
    docommand($command) ||
        die("$0: bailing\n" .
            "'${command}'\n");
    close(TTY);

    return($word);
}

sub getinput($)
{
    my ($prompt) = @_;
    my $word;

    open(TTY, ">/dev/tty") ||
        die("$0: getvolname(): couldn't open /dev/tty: $!\n");;
    print TTY ("$prompt: ");
    chomp($word = );
    close(TTY);

    return($word);
}

sub diskusage(@)
{
    my @nodes = @_;
    my ($node, $size, $totk, $fullist);

    $totk = 0;
    $fulllist = [ ];

    foreach $node (@nodes)
    {
        $size = ducom($node);
        $totk += $size;
        push(@$fulllist, [ $node, $size ]);
    }

    return($fulllist, $totk);
}

sub changeflags(@)
{
    my $command;
    my $node;

    foreach $node (@_)
    {
        $command = qq(${CHFLAGS} -R nouchg,noschg "$node");

        docommand($command) ||
            die("$0: changeflags(): command failed\n" .
                "'${command}'\n");
    }
}

sub addslack($)
{
    my ($original) = @_;
    my $slack;

    $slack = int(0.5 + $original * $SLACK_FACTOR);
    $slack = ($slack > $MIN_SLACK) ? $slack : $MIN_SLACK;

    return ($original + $slack);
}

sub setuser()
{
    my $command;

    $command = "${DISKTOOL} -c $;
    print $line;
    unless ($line =~ m{^/dev/disk\d+\s+Apple_partition_scheme\s*$})
    {
        die("$0: attach(): unexpected output (line 1) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless ($line =~ m{^/dev/disk\d+s1\s+Apple_partition_map\s*$})
    {
        die("$0: attach(): unexpected output (line 2) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless (($diskname) = ($line =~ m{^/dev/(disk\d+)s2\s+Apple_HFS\s*$}))
    {
        die("$0: attach(): unexpected output (line 3) from command\n" .
            "'${command}'\n");
    }

    close(HDID);

    return($diskname);
}

sub makehfs($$)
{
    my ($diskname, $volname) = @_;
    my $command;

    $command = qq(${SUDO} ${NEWFS_HFS} -v "${volname}" /dev/${diskname}s2);

    docommand($command) ||
        die("$0: makehfs(): command failed\n" .
            "'${command}'\n");
}

sub mount($$)
{
    my ($diskname, $mountdir) = @_;
    my $command;

    $command = qq(${MOUNT} -thfs /dev/${diskname}s2 "${mountdir}");

    docommand($command) ||
        die("$0: mount(): command failed\n" .
            "'${command}'\n");
}

sub ditto($@)
{
    my $mountdir = shift(@_);
    my ($node, $targetlist, $target);

    $targetlist = [ ];
    foreach $node (@_)
    {
        if (-l $node)
        {
            $target = ditto_link($mountdir, $node);
        }
        elsif ((-f $node) || (-b $node) || (-c $node))
        {
            $target = ditto_file($mountdir, $node);
        }
        elsif (-d $node)
        {
            $target = ditto_dir($mountdir, $node);
        }
        else
        {
            warn("$0: ditto(): skipping pipe/socket/unknown:\n  '$node'\n");
        }
        push(@$targetlist, $target);
    }

    return($targetlist);
}

sub unmount($)
{
    my ($mountdir) = @_;
    my $command;

    $command = qq(${UMOUNT} "${mountdir}");

    docommand($command) ||
        die("$0: unmount(): command failed\n" .
            "'${command}'\n");
}

sub detach($)
{
    my ($diskname) = @_;
    my $command;

    $command = "${HDIUTIL} detach ${diskname}";

    docommand($command) ||
        die("$0: detach(): command failed\n" .
            "'${command}'\n");
}

sub convert2cryptcompressed($$$)
{
    my ($oldimage, $newimage, $password) = @_;
    my ($command, $printcommand);

    $command = qq(${HDIUTIL} convert -encryption -passphrase "${password}" ) .
               qq(-format 'UDZO UDIF' -tgtimagekey zlib-level=9 "${oldimage}" ) .
               qq(-o "${newimage}");

    $printcommand = $command;
    $printcommand =~ s/-passphrase "${password}"/-passphrase "********"/;

    real_docommand($command, $printcommand) ||
        die("$0: convert2cryptcompressed(): command failed\n" .
            "'${command}'\n");
}

sub attachcrypt($$)
{
    my ($image, $password) = @_;
    my ($command, $line, $diskname, $printcommand);

    $command = qq(${HDID} -encryption -passphrase "${mypass}" -nomount "$image");

    $printcommand = $command;
    $printcommand =~ s/-passphrase "${password}"/-passphrase "********"/;

    print("@ ${printcommand}\n");
    open(HDID, "${command} |") ||
        die("$0: attachcrypt(): command failed: $!\n'${command}'\n");

    $line = ;
    print $line;
    unless ($line =~ m{^/dev/disk\d+\s+Apple_partition_scheme\s*$})
    {
        die("$0: attachcrypt(): unexpected output (line 1) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless ($line =~ m{^/dev/disk\d+s1\s+Apple_partition_map\s*$})
    {
        die("$0: attachcrypt(): unexpected output (line 2) from command\n" .
            "'${command}'\n");
    }

    $line = ;
    print $line;
    unless (($diskname) = ($line =~ m{^/dev/(disk\d+)s2\s+Apple_HFS\s*$}))
    {
        die("$0: attachcrypt(): unexpected output (line 3) from command\n" .
            "'${command}'\n");
    }

    close(HDID);

    return($diskname);
}

sub mountreadonly($$)
{
    my ($diskname, $mountdir) = @_;
    my $command;

    $command = qq(${MOUNT} -r -thfs /dev/${diskname}s2 "${mountdir}");

    docommand($command) ||
        die("$0: mountreadonly(): command failed\n" .
            "'${command}'\n");
}

sub ducom($)
{
    my ($node) = @_;
    my ($dulist, $command, $line, $expectnode, $nodename,
        $size, $totsize);

    $command = qq(${DU} -sk "${node}");

    print("@ ${command}\n");
    open(DU, "${command} |") ||
        die("$0: ducom(): command failed : $!\n" .
            "'${command}'\n");

    chomp($line = );
    print("$line\n");
    close(DU);

    unless (($size, $nodename) = ($line =~ /^(\d+)\s+($node)\s*$/))
    {
        die("$0: ducom(): parse error checking for " .
            "'$node' in line:\n'$line'\n");
    }

    return($size);
}

sub debugdu($$$)
{
    my ($dulist_orig, $dulist_new, $fh) = @_;
    my $maxlen = 0;
    my $maxsize = 0;
    my ($item, $name, $size, $mylen, $mysizelen, $i, $matchall);
    my ($nname, $nsize);

    foreach $item (@$dulist_orig)
    {
        ($name, $size) = @$item;
        $mylen = length($name);
        $mysizelen = length($size);
        $maxlen = ($maxlen [$i]};
        ($nname, $nsize) = @{$dulist_new->[$i]};
        ($size == $nsize) or
            $matchall = 0;
        printf $fh ("%-*s : %*d\n",
                    $maxlen, $name,
                    $maxsize, $size);
        printf $fh ("%-*s : %*d\n",
                    $maxlen, $nname,
                    $maxsize, $nsize);
    }

    print $fh "\n";

    if ($matchall)
    {
        print $fh "Disk usage matches\n";
    }
    else
    {
        print $fh "ERROR: DISK USAGE DOES NOT MATCH\n";
    }
}

sub ditto_link($$)
{
    my ($mountdir, $srcnode) = @_;
    my ($srcdir, $destdir, $command);
    my ($srcname, $path, $srcpath, $orig_wd, $target);

    unless (-l $srcnode)
    {
        die("$0: ditto_link(): invalid argument (not a link):\n" .
            "    '${srcnode}'\n");
    }

    ($srcname, $path) = fileparse($srcnode);
    $srcpath = &Cwd::realpath($path);

    unless (-d $srcpath)
    {
        die("$0: ditto_link(): missing directory:\n" .
            "    '${srcpath}'\n" .
            "resolved from link name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    unless ($srcpath =~ m{^/})
    {
        die("$0: ditto_link(): srcpath missing leading /:\n" .
            "    '${srcpath}'\n" .
            "resolved from link name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    $destdir = "${mountdir}${srcpath}";
    $target = "${destdir}/${srcname}";

    unless (-d $destdir)
    {
        ditto_path(${srcpath}, ${destdir});
    }

    $orig_wd = &Cwd::getcwd;
    chdir(${srcpath}) ||
        die("$0: ditto_link(): couldn't chdir to '${srcpath}': $!\n");

    unless (-l ${srcname})
    {
        die("$0: ditto_link(): link '${srcname}' not found in '${srcpath}'\n");
    }

    $command = qq[${TAR} cf - "${srcname}" | ( cd "${destdir}" ; ${TAR} xf - )];

    docommand($command) ||
        die("$0: ditto_link(): command failed\n" .
            "'${command}'\n");

    chdir(${orig_wd}) ||
        die("$0: ditto_link(): couldn't chdir back to '${orig_wd}': $!\n");

    unless (-l $target)
    {
        die("$0: ditto_link(): target not created:\n" .
            "    source = '${srcnode}'\n" .
            "    target = '${target}'\n");
    }

    return($target);
}

sub ditto_dir($$)
{
    my ($mountdir, $srcdir) = @_;
    my ($srcpath, $destdir, $target);

    unless (-d $srcdir)
    {
        die("$0: ditto_dir(): invalid argument (not a directory):\n" .
            "    '${srcdir}'\n");
    }

    $srcpath = &Cwd::realpath($srcdir);

    unless (-d $srcpath)
    {
        die("$0: ditto_dir(): missing directory:\n" .
            "    '${srcpath}'\n" .
            "resolved from directory name:\n" .
            "    '${srcdir}'\n");
    }

    $destdir = "${mountdir}${srcpath}";
    $target = $destdir;

    $command = qq(${DITTO} -v -rsrcFork "${srcpath}" "${destdir}");

    docommand($command) ||
        die("$0: ditto_dir(): command failed\n" .
            "'${command}'\n");

    unless (-d $target)
    {
        die("$0: ditto_dir(): target not created:\n" .
            "    source = '${srcnode}'\n" .
            "    target = '${target}'\n");
    }

    return($target);
}

sub ditto_file($$)
{
    my ($mountdir, $srcnode) = @_;
    my ($srcdir, $destdir, $command);
    my ($srcname, $path, $srcpath, $orig_wd, $target);

    unless ((-f $srcnode) || (-c $srcnode) || (-b $srcnode))
    {
        die("$0: ditto_file(): invalid argument (not a file/char/block):\n" .
            "    '${srcnode}'\n");
    }

    ($srcname, $path) = fileparse($srcnode);
    $srcpath = &Cwd::realpath($path);

    unless (-d $srcpath)
    {
        die("$0: ditto_file(): missing directory:\n" .
            "    '${srcpath}'\n" .
            "resolved from file name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    unless ($srcpath =~ m{^/})
    {
        die("$0: ditto_file(): srcpath missing leading /:\n" .
            "    '${srcpath}'\n" .
            "resolved from file name:\n" .
            "    '${srcnode}'\n" .
            "  = ('${path}', '${name}')\n");
    }

    $destdir = "${mountdir}${srcpath}";
    $target = "${destdir}/${srcname}";

    unless (-d $destdir)
    {
        ditto_path(${srcpath}, ${destdir});
    }

    $command = qq(${DITTO} -v -rsrcFork "${srcpath}/${srcname}" ) .
               qq("${target}");

    docommand($command) ||
        die("$0: ditto_file(): command failed\n" .
            "'${command}'\n");

    unless ((-f $target) || (-c $target) || (-b $target))
    {
        die("$0: ditto_file(): target not created:\n" .
            "    source = '${srcnode}'\n" .
            "    target = '${target}'\n");
    }

    return($target);
}

sub ditto_path($$)
{
    my ($srcpath, $destpath) = @_;
    my ($tmpdir, $tmpnum, $command, $dir, $srctemp, $desttemp);

    $tmpnum = 0;
    $tmpdir = sprintf("TEMPDIR.%04d.$$", $tmpnum);
    $srctemp = "${srcpath}/${tmpdir}";
    $desttemp = "${destpath}/${tmpdir}";
    until ((! -e $srctemp) && (! -e $desttemp))
    {
        if ($tmpnum > 9999)
        {
            die("$0: ditto_path(): could not find available tmpname:\n" .
                "    '${srctemp}'\n" .
                "    '${desttemp}'\n");
        }

        $tmpnum++;
        $tmpdir = sprintf("TEMPDIR.%04d.$$", $tmpnum);
        $srctemp = "${srcpath}/${tmpdir}";
        $desttemp = "${destpath}/${tmpdir}";
    }

    if (-d $desttemp)
    {
        die("$0: ditto_path(): I was going to copy '${srctemp}'\n" .
            "   to '${desttemp}' but it already exists!\n");
    }

    print("@@ mkdir(${srctemp}, 0700)\n");
    mkdir($srctemp, 0700) ||
        die("$0: ditto_path(): mkdir('${srctemp}', 0700) failed: $!\n");

    $command = qq(${DITTO} -v "${srctemp}" "${desttemp}");

    docommand($command) ||
        die("$0: ditto_path(): command failed\n" .
            "'${command}'\n");

    $dir = "${srctemp}";
    print("@@ rmdir(${dir})\n");
    rmdir($dir) ||
        die("$0: ditto_path(): rmdir('${dir}') failed: $!\n");

    $dir = "${desttemp}";
    print("@@ rmdir(${dir})\n");
    rmdir($dir) ||
        die("$0: ditto_path(): rmdir('${dir}') failed: $!\n");
}

sub docommand($)
{
    my ($command) = @_;

    real_docommand($command, $command);
}

sub real_docommand($$)
{
    my ($command, $printcommand) = @_;
    my $rc;

    print("@ '${printcommand}'\n");

    $rc = 0xffff & system($command);
    if ($rc == 0)
    {
        return(1);
    }
    if ($rc == 0xff00)
    {
        warn("command failed: $!\n'$command'\n");
    }
    elsif ($rc > 0x80)
    {
        $rc >>= 8;
        warn("non-zero exit status $rc\n'$command'\n");
    }
    else
    {
        $core = ($rc & 0x80);
        if ($core)
        {
            $rc &= ~0x80;
        }
        warn("signal $rc");
        if ($core)
        {
            warn(" (core dump)");
        }
        warn("\n'$command'\n");
    }

    return(0);
}


[ Reply to This | # ]
Make a .dmg from any directory
Authored by: morgen on May 12, '03 07:02:26PM

Is there any way to avoid having to "sudo"? I'm trying to create a .dmg in an unattended build script and not having to type a password each time woud be a bonus.

Thanks!



[ Reply to This | # ]
Make a .dmg from any directory
Authored by: Impatient1 on May 13, '03 03:59:09AM

A minor addition to '/etc/sudoers' would allow eliminating the password prompt. See man 5 sudoers for examples on how to achieve this change.
Just be careful how much power you give yourself.



[ Reply to This | # ]
Make a .dmg from any directory
Authored by: purecanesugar on Sep 15, '03 07:00:56PM

There is one small bug in the 'mkdmg' script if you have a different 'du' installed, i.e. one from fink or somewhere else. 'du -s' does different things on different versions of 'du'. To make sure you are using the Apple 'du', change:

SIZE=`du -s "$FOLDER" | awk '{ print $1 }'`

to:

SIZE=`/usr/bin/du -s "$FOLDER" | awk '{ print $1 }'`



[ Reply to This | # ]
Make a .dmg from any directory
Authored by: omnivector on May 12, '04 09:56:23PM
here's an even simpler and more safe script:
#!/usr/bin/env ruby

# usage error
if ARGV.length < 1 or ARGV.length > 2 then
    $stderr.puts( "Usage: #{File.basename( $0 )} directory [cd name]" )
    exit( 1 )
end

# get variables
dir = ARGV.shift.sub( /\/$/, "" )
volname = if ARGV.length == 0 then
        File.basename( File.expand_path( dir ) )
    else
        ARGV.shift
    end
output = "#{dir}.dmg"

# dmg the folder
`hdiutil create -fs HFS+ -srcfolder "#{dir}" -volname "#{volname}" "#{output}"`

---
- Tristan

[ Reply to This | # ]

Make a .dmg from any directory
Authored by: ov on Nov 05, '04 05:23:21PM

And here is an AppleScript version,
Just copy paste the following text into "Script Editor" and save it as an application
this script is a droplet, you only have to drop a folder onto the applescript application icon

[code]
on open (list_of_aliases)
set itemStr to (first item of list_of_aliases) as string
if (itemStr ends with ":") then
-- removes the ending ":" character
set itemStr to characters 1 through ((length of itemStr) - 1) of itemStr as string
createDMG(itemStr)
else
display dialog "The dropped element must be a folder and not a file" buttons {"Exit"} default button "Exit"
end if
end open

on createDMG(x)
set srcName to (POSIX path of x)
set targetName to srcName & ".dmg"
set srcName to quoted form of srcName
set targetName to quoted form of targetName

set scriptStr to "hdiutil create -fs HFS+ -srcFolder " & srcName & " " & targetName & " 2>&1 &"
say "Creating disk image from folder " & srcName
say "Please be patient ..."
display dialog "Please be patient ..." giving up after 2
do shell script scriptStr
set res to the result
display dialog res
end createDMG

[/code]


Regards
Olivier



[ Reply to This | # ]
Make a .dmg from any directory
Authored by: sbussard on Jan 02, '08 10:58:13PM

Tristan it might be easier to use if the path is absolute and not relative.

Good interpretation of what the script should be though, now all that is needed is a simple GUI ;)



[ Reply to This | # ]
Make a .dmg from any directory
Authored by: cekim on Nov 30, '07 01:21:53PM
Have you tried creating a DMG with iArchiver? I'm a developer and use this app to distribute my Mac software.

[ Reply to This | # ]
Make a .dmg from any directory
Authored by: sbussard on Jan 02, '08 10:49:40PM

I didn't read through all the way to see if someone replied to the "sudo" question or not, but I discovered a very cool trick while using ubuntu linux.

I simply use "sudo su" in the command line. See if it works

Best of luck,
Stephen



[ Reply to This | # ]
Make a .dmg from any directory
Authored by: falcn on Aug 01, '12 04:52:38AM

As of 10.8 Mountain Lion, you can choose "File->New->Disk Image from Folder" in Disk Utility



[ Reply to This | # ]