KoblentsBlog Photography
Contact About
Ches
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
$target_disk
is going to look something like
mmc0s1
, and
$whole_disk
will be something like
mmc0
.
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
$dir
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
 
« Newer Older »
© Copyright Koblents.com, 2012-2024