Automate Drive (USB drive, SD / CF card) Cloning on MacOS
We recently needed to clone some SD cards - or, more precisely, copy a directory structure. Apparently SD card cloners are kind of expensive. We didn't really need a block-type copy, just a standard copy, so I whipped up a script. Details after the jump.
As a shortcut, I figured I'd target only 32-gigabyte drives, since that's what we needed. Check this thoroughly before you go accidentally wiping things!
1234567891011121314151617 | my $next_disk_cmd = "diskutil list | grep \"3\\d\\.\\d GB.*disk\\ds\" | grep -v $whole_disk";
my @next_disks = `$next_disk_cmd`; chomp(@next_disks);
if (scalar @next_disks == 0) { print "ERROR\tNo disk 3x.x GB non-$whole_disk.\n"; exit; }
if (scalar @next_disks > 1) { print "ERROR\tFound too many disks 3x.x GB.\n"; exit; }
$diskline = $next_disks[0];
if ($diskline =~ /3\d\.\d\s+GB\s*(disk\S+)/) {
$target_disk = $1;
print "OK\tTarget Disk '$target_disk'\n";
} else {
print "ERROR\tUnexpected format '$diskline'\n";
exit;
}
$whole_disk = $target_disk; $whole_disk =~ s/(disk\d+)s\d+/$1/;
|
Great, so now
is going to look something like
, and
will be something like
.
Now we need to ensure it's mounted read-write and in the right place:
12345678910111213141516171819202122232425262728293031 | $info_dump = `diskutil info $target_disk`;
$target_mounted = get_mounted($info_dump);
if ($target_mounted == 0) {
$target_mounted = mount_disk($whole_disk);
$info_dump = `diskutil info $target_disk`;
$target_mounted = get_mounted($info_dump);
}
if ($target_mounted == 0) { print "ERROR\tCould not mount $whole_disk\n"; exit; }
$target_read_only = get_read_only($info_dump);
if ($target_read_only == 1) {
unmount_disk($whole_disk);
$target_mounted = mount_disk($whole_disk);
$info_dump = `diskutil info $target_disk`;
$target_mounted = get_mounted($info_dump);
if ($target_mounted == 0) { print "ERROR\tCould not mount $whole_disk\n"; exit; }
$target_read_only = get_read_only($info_dump);
}
if ($target_read_only == 1) { print "ERROR\tCan not mount $whole_disk read only.\n"; exit; }
$target_mount_point = get_mount_point($info_dump);
print "OK\t$target_disk is read-write.\n";
print "OK\t$target_disk mounted '$target_mount_point'.\n";
|
We'll go over the helper functions later. Before we get there, here's the meat of the code that does the cloning:
12345678910111213141516171819202122232425262728 | print "\tFormatting: FAT32 LAVA_SD MBRFormat $whole_disk\n";
my $format_cmd = "diskutil eraseDisk FAT32 LAVA_SD MBRFormat $whole_disk";
print "\t\$ $format_cmd\n\n";
my $formatted = system($format_cmd);
print "\n\n";
$info_dump = `diskutil info $target_disk`;
$target_mount_point = get_mount_point($info_dump);
$target_read_only = get_read_only($info_dump);
if ($target_read_only == 1) { print "ERROR\tFormat mounted read-only.\n"; exit; }
printf "%-5s\tUnmount, format, mount $whole_disk\n", ($formatted == 0 ? "OK" : "ERROR");
if ($formatted != 0) { exit; }
print "\tCopy: $dir -> $target_mount_point\n";
my $copy_cmd = "rsync -a $dir/* $target_mount_point";
my $copied = system($copy_cmd);
printf "%-5s\tCopied $dir -> $target_mount_point\n", ($copied == 0 ? "OK" : "ERROR");
if ($copied != 0) { exit; }
unmount_disk($whole_disk);
print "Please remove target SD card.\n";
|
As long as
is set to your master directory, this should work fine. The wildcard glob won't select hidden files, so change that if you need it to.
The helper functions are really quite simple, just moved around to refactor and clean up the code.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 | sub get_mounted {
if (scalar @_ != 1) { return -1; }
my $info_dump = shift;
if ($info_dump =~ /^\s*Mounted:\s+(Yes|No)\s*$/m) {
return ($1 eq 'Yes');
}
print "ERROR\tCould not determine if mounted.\n\n$info_dump\n";
exit;
}
sub get_mount_point {
if (scalar @_ != 1) { return -1; }
my $info_dump = shift;
if ($info_dump =~ /^\s*Mount Point:\s+(.+?)\s*$/m) {
return $1;
}
print "ERROR\tCould not find mount point.\n\n$info_dump\n";
exit;
}
sub get_read_only {
if (scalar @_ != 1) { return -1; }
my $info_dump = shift;
if ($info_dump =~ /^\s*Read-Only Volume:\s+(Yes|No)\s*$/m) {
return ($1 eq 'Yes');
}
print "ERROR\tCould not find read-only volume.\n\n$info_dump\n";
exit;
}
sub mount_disk {
if (scalar @_ < 1) { return -1; }
my ($disk_name, $read_only) = @_;
if (! defined($read_only)) { $read_only = ""; }
elsif ($read_only ne "readOnly") {
print "ERROR\tUnknown mount type $read_only\n";
exit;
}
my $mount_cmd = "diskutil mountDisk $read_only $disk_name";
my $mounted = `$mount_cmd`;
if ($mounted =~ /^Volume.*mounted successfully/) {
print "OK\tMounted $disk_name $read_only\n";
return 1;
}
print "ERROR\tCould not mount $disk_name\n";
print "ERROR\t\$ $mount_cmd\n";
exit;
}
sub unmount_disk {
if (scalar @_ != 1) { return -1; }
my $disk_name = shift;
my $unmount_cmd = "diskutil unmountDisk $disk_name";
my $unmounted = `$unmount_cmd`;
if ($unmounted =~ /^Unmount of all volumes.*was successful/) {
print "OK\tUnmounted $disk_name\n";
return 1;
}
print "ERROR\tCould not unmount $disk_name\n";
print "ERROR\t\$ $unmount_cmd\n";
exit;
}
|
Ches Koblents
June 20, 2019