Skip to content

Instantly share code, notes, and snippets.

@joevt
Last active May 11, 2023 06:34
Show Gist options
  • Save joevt/6d7a0ede45106345a39bdfa0ac10ffd6 to your computer and use it in GitHub Desktop.
Save joevt/6d7a0ede45106345a39bdfa0ac10ffd6 to your computer and use it in GitHub Desktop.
macOS disk labels, mounting partitions
#!/bin/bash
# by joevt Aug 5, 2021
directblesscmd="/Volumes/Work/Programming/XcodeProjects/bless/bless-204.40.27 joevt/DerivedData/bless/Build/Products/Debug/bless"
usedirectbless=0
if [[ -d /System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS ]]; then
if [[ ! -f "$directbless" ]]; then
echo "# Download and build bless from https://github.com/joevt/bless , then update the path of directbless defined in DiskUtil.sh"
else
usedirectbless=1
fi
fi
if ((usedirectbless)); then
directbless=$directblesscmd
alias directbless='"$directbless"'
else
directbless=bless
alias directbless=bless
fi
# dump disk_label file
dump_label () { local contents; contents=$(xxd -p -c99999 "$1"); echo "${contents:10}" | perl -pe "s/(..)/\1 /g;s/00/../g;s/ //g;s/(.{$((0x${contents:2:4}*2))})/\1\n/g" ; }
# mount a partition
mountpartition() {
local mounttype=$1
local slice=$2
local volume=$3
local mountpoint=""
mountpoint=$(mount | sed -n -E "/\/dev\/$slice on (.*) \(.*/s//\1/p")
if [[ -n "$mountpoint" ]]; then
echo -n "$mountpoint"
return 0
fi
local i=0
local startmountpoint="/Volumes/$volume"
mountpoint="$startmountpoint"
while ((1)); do
sudo mkdir "$mountpoint" 2> /dev/null && break
if ((i++ > 1000)); then
echo "Could not create mountpoint" 1>&2
return 1
fi
mountpoint="$startmountpoint$i"
done
sudo "mount$mounttype" "/dev/$slice" "$mountpoint" && echo -n "$mountpoint"
return $?
}
makemultilinedisklabel () {
local folder="$1"
local lines="$2"
local alllines=""
local alllines2x=""
local tempfolder=""
tempfolder="$(mktemp -d)"
IFS=$'\n'
local linenum=0
local theline=""
local theoneline=""
for theline in $(echo "$lines"); do
sudo bless --folder "$tempfolder" --label "$theline" 2> /dev/null
if (( linenum )); then
alllines+="$(dump_label "$tempfolder/.disk_label" | sed '1,/[^.]/ {/^[.]*$/d; }')"$'\n'
alllines2x+="$(dump_label "$tempfolder/.disk_label_2x" | sed '1,/[^.]/ {/^[.]*$/d; }')"$'\n'
theoneline+=" $theline"
else
alllines+="$(dump_label "$tempfolder/.disk_label")"$'\n'
alllines2x+="$(dump_label "$tempfolder/.disk_label_2x")"$'\n'
theoneline="$theline"
fi
((linenum++))
done
local suffix=""
local thelines=""
for suffix in "" "_2x"; do
[[ -z $suffix ]] && thelines="$alllines" || thelines="$alllines2x"
local linewidths=""
local maxlinewidth=""
linewidths="$(echo "${thelines}" | tr '0-9a-f' '.' | sort -ur)"
maxlinewidth=$(echo "$linewidths" | sed -n '1p')
linewidths=$(echo "$linewidths" | sed '1d')
local centercommands=""
local linewidth=""
for linewidth in $(echo "$linewidths"); do
local left=${maxlinewidth:0:(${#maxlinewidth}-${#linewidth})/4*2}
local right=${maxlinewidth:0:(${#maxlinewidth}-${#linewidth}-${#left})}
centercommands+="s/^(${linewidth})$/$left\1$right/; "
done
thelines=$(echo "$thelines" | sed -E "$centercommands s/\./0/g")
printf "01%04x%04x$thelines" $((${#maxlinewidth} / 2)) "$(echo "$thelines" | wc -l)" | xxd -p -r > "$tempfolder/disk_label$suffix"
[[ -f "$folder/.disk_label$suffix" ]] && sudo chflags noschg "$folder/.disk_label$suffix"
sudo cp "$tempfolder/disk_label$suffix" "$folder/.disk_label$suffix"
sudo chflags schg "$folder/.disk_label$suffix"
done
[[ -f "$folder/.disk_label.contentDetails" ]] && sudo chflags noschg "$folder/.disk_label.contentDetails"
theoneline="${theoneline/🄳/ [D]}"
theoneline="${theoneline/🄿/ [P]}"
theoneline="${theoneline/🅁/ [R]}"
theoneline="${theoneline/🄱/ [B]}"
theoneline="${theoneline// / }"
printf "%s" "$theoneline" > "$tempfolder/disk_label.contentDetails"
sudo cp "$tempfolder/disk_label.contentDetails" "$folder/.disk_label.contentDetails"
sudo chflags schg "$folder/.disk_label.contentDetails"
}
unsetall () {
local thevarprefix="$1" # prefix for variable names
if [[ -n $thevarprefix ]]; then
eval "$(set | sed -nE '/^('"$1"'[^=]*)=.*/s//unset \1/p')"
fi
}
fixapfsbooter () {
# Use Catalina for best results.
theoneapfsmountpoint="$1"
theline1="$2"
if [[ -z $theoneapfsmountpoint ]]; then
echo "#Error: missing parameters"
return 1
fi
if [[ -z $theline1 ]]; then
echo "#Error: missing second parameter"
return 1
fi
local diskutil_fab1_Error=""
local diskutil_fab1_APFSContainerReference=""
local diskutil_fab1_APFSVolumeGroupID=""
local diskutil_fab1_MountPoint=""
local diskutil_fab1_VolumeUUID=""
getdiskinfo "$theoneapfsmountpoint" "diskutil_fab1_"
if [[ -n $diskutil_fab1_Error ]]; then
echo "# Error: $diskutil_fab1_Error"
return 1
fi
mkdir -p /tmp/disklabeltemp
local iconsource="$theoneapfsmountpoint/.VolumeIcon.icns"
if [[ -f "$iconsource" ]]; then
echo "# Got icon at $iconsource"
if [[ -L "$iconsource" ]]; then
echo "# Making icon not a sym link"
cp -p "$iconsource" /tmp/disklabeltemp
sudo rm "$iconsource"
sudo cp -p /tmp/disklabeltemp/.VolumeIcon.icns "$iconsource"
fi
else
echo "# Did not find icon at $iconsource"
fi
local Recovery_MountPoint=""
local patAll="^(_|${diskutil_fab1_APFSVolumeGroupID}_System|${diskutil_fab1_APFSVolumeGroupID}_Data|_Preboot|_Recovery)="
local patSys="^_="
local patSys2="^${diskutil_fab1_APFSVolumeGroupID}_System="
local patData="^${diskutil_fab1_APFSVolumeGroupID}_Data="
local patPreboot="^_Preboot="
local patRecovery="^_Recovery="
local System_UUID="$diskutil_fab1_APFSVolumeGroupID"
if [[ -z $System_UUID ]]; then
System_UUID="$diskutil_fab1_VolumeUUID"
fi
for thepass in 0 1; do
for thedisk in $(getapfsroles "$diskutil_fab1_APFSContainerReference"); do
if [[ $thedisk =~ $patAll ]]; then
echo "#================"
echo "# Doing $thedisk, pass $(( thepass+1 ))"
local thedevice="${thedisk#*=}"
diskutil mount "$thedevice" > /dev/null 2>&1
getdiskinfo "$thedevice" "diskutil_fab2_"
if [[ -z "$diskutil_fab2_MountPoint" ]]; then
echo "# Could not mount"
else
local issource=0
if [[ "$diskutil_fab2_MountPoint" == "$diskutil_fab1_MountPoint" ]]; then
issource=1
fi
local theletter=""
local thedest=""
if [[ $thedisk =~ $patSys ]]; then
if ((issource)); then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
fi
elif [[ $thedisk =~ $patSys2 ]]; then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
elif [[ $thedisk =~ $patData ]]; then
theletter="🄳"
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
elif [[ $thedisk =~ $patPreboot ]]; then
theletter="🄿"
thedest="$diskutil_fab2_MountPoint/$System_UUID/System/Library/CoreServices"
if [[ ! -d $thedest ]]; then
thedest="$diskutil_fab2_MountPoint/$diskutil_fab1_VolumeUUID/System/Library/CoreServices"
fi
elif [[ $thedisk =~ $patRecovery ]]; then
theletter="🅁"
thedest="$diskutil_fab2_MountPoint/$System_UUID"
if [[ ! -d $thedest ]]; then
thedest="$diskutil_fab2_MountPoint/$diskutil_fab1_VolumeUUID"
fi
Recovery_MountPoint="$diskutil_fab2_MountPoint"
fi
if ((thepass == 0)); then
if ((issource)); then
echo "# Skipping icon copy"
elif [[ -f "$iconsource" ]]; then
echo "# Doing icon copy"
sudo cp "$iconsource" "$diskutil_fab2_MountPoint" || echo "# Error copying icon"
else
echo "# No icon to copy"
fi
fi
if [[ -d $thedest ]]; then
echo "# Destination at $thedest"
if ((thepass == 0)); then
local version=""
local theversionfile="$thedest/SystemVersion.plist"
if [[ -f "$theversionfile" ]]; then
version=$(/usr/libexec/PlistBuddy -c 'Print :ProductVersion' "$theversionfile")
fi
local theline2=""
if [[ -n "$version" ]]; then
theline2="${version}${theletter}"
else
theline2="${theletter}"
fi
makemultilinedisklabel "/tmp/disklabeltemp" "${theline1}"$'\n'"${theline2}"
echo "# Setting the label to: [${theline1}][${theline2}]"
sudo find "$thedest" -name '.disk_label*' -exec chflags noschg {} \;
sudo cp /tmp/disklabeltemp/.disk_label* "$thedest"
sudo find "$thedest" -name '.disk_label*' -exec chflags schg {} \;
else
if [[ -f "$thedest/boot.efi" ]]; then
echo "# Blessing $thedest/boot.efi"
if ((usedirectbless)); then
sudo "$directbless" --folder "$thedest" --file "$thedest/boot.efi"
else
if [[ -z $theletter ]]; then
# Blessing the system volume will bless Preboot, so temporarily change it to Recovery
# but first must change Recovery to none. This only works in Catalina
diskutil apfs changeVolumeRole "$Recovery_MountPoint" clear || echo "# ERROR: Unexpected error attempting to change role of Recovery volume"
diskutil apfs changeVolumeRole "$diskutil_fab2_MountPoint" R || echo "# ERROR: Retry this from a different macOS Catalina system"
fi
sudo bless --folder "$thedest" --file "$thedest/boot.efi"
if [[ -z $theletter ]]; then
diskutil apfs changeVolumeRole "$diskutil_fab2_MountPoint" clear || echo "# ERROR: Retry this from a different macOS Catalina system"
diskutil apfs changeVolumeRole "$Recovery_MountPoint" R || echo "# ERROR: Unexpected error attempting to change role of Recovery volume"
fi
fi
fi
fi
else
echo "# Destination does not exist $thedest"
fi
fi # if mount
fi # if the disk
done # for thedisk
done # for thepass
echo "#================"
echo "# Done!"
}
getvolumeproperty () {
local slice="$1"
local property="$2"
eval "$(diskutil info -plist "$slice" | plutil -p - | sed -nE '/^ *"'"$property"'" => (.*)/s//echo \1/p')"
}
getvolumename () {
getvolumeproperty "$1" "VolumeName"
}
mountEFIpartitions () {
IFS=$'\n'
local diskinfo=""
for diskinfo in $(diskutil list | LANG=C sed -nE $'/^ *[0-9]+: +EFI (\xe2\x81\xa8)?(.*[^ \xa9])?(\xe2\x81\xa9)? +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/''s//\2_\4/p'); do
local slice="${diskinfo##*_}"
local volume=""
volume="$(getvolumename "$slice")"
mountpartition "_msdos" "$slice" "$volume" > /dev/null
done
}
mountRecoveryHDpartitions() {
IFS=$'\n'
for diskinfo in $(diskutil list | LANG=C sed -nE $'/^ *[0-9]+: +Apple_Boot (\xe2\x81\xa8)?(Recovery HD)(\xe2\x81\xa9)? +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/''s//\2_\4/p'); do
local slice="${diskinfo##*_}"
local volume=""
volume="$(getvolumename "$slice")"
mountpartition "_hfs" "$slice" "$volume" > /dev/null
done
}
mountRecoveryPartitions() {
IFS=$'\n'
for diskinfo in $(diskutil list | LANG=C sed -nE $'/^ *[0-9]+: +APFS Volume (\xe2\x81\xa8)?(Recovery)(\xe2\x81\xa9)? +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/''s//\2_\4/p'); do
local slice="${diskinfo##*_}"
local volume=""
volume="$(getvolumename "$slice")"
mountpartition "_apfs" "$slice" "$volume" > /dev/null
done
}
mountPrebootPartitions() {
IFS=$'\n'
for diskinfo in $(diskutil list | LANG=C sed -nE $'/^ *[0-9]+: +APFS Volume (\xe2\x81\xa8)?(Preboot)(\xe2\x81\xa9)? +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/''s//\2_\4/p'); do
local slice="${diskinfo##*_}"
local volume=""
volume="$(getvolumename "$slice")"
mountpartition "_apfs" "$slice" "$volume" > /dev/null
done
}
getblessinfo () {
local mountpoint="$1"
local thevarprefix="$2"
unsetall "$thevarprefix"
eval "$(directbless -plist --info "$mountpoint" | plutil -convert json -o - - | perl -e '
use JSON::PP; my $f = decode_json (<>)->{"Finder Info"};
printf("'"$thevarprefix"'theblessfolder=\"%s\"\n'"$thevarprefix"'theblessfile=\"%s\"\n'"$thevarprefix"'theblessopen=\"%s\"\n", $f->[0]->{"Path"}, $f->[1]->{"Path"}, $f->[2]->{"Path"})')"
}
dumpAllDiskLabels () {
IFS=$'\n'
for theslice in $(diskutil list | sed -nE '/.*(disk[0-9]+s[0-9]+)$/s//\1/p'); do
#echo "$theslice"
sudo diskutil mount "$theslice" > /dev/null 2>&1
local diskutil_dadl_DeviceIdentifier=""
getdiskinfo "$theslice" "diskutil_dadl_"
if [[ -n "$diskutil_dadl_MountPoint" ]]; then
echo "#==========="
echo "$diskutil_dadl_DeviceIdentifier $diskutil_dadl_MountPoint"
local theblessfolder=""
local theblessfile=""
local theblessopen=""
getblessinfo "$diskutil_dadl_MountPoint"
if [[ -n $theblessfile ]]; then
theblessfolder="$(dirname "$theblessfile")"
fi
for thefolder in $( { find "$diskutil_dadl_MountPoint" -maxdepth 2 -name "boot.efi" 2> /dev/null | sed -E '/\/boot.efi$/s///'; ls -d "$diskutil_dadl_MountPoint/"*"/System/Library/CoreServices"; ls -d "$theblessfolder" "$diskutil_dadl_MountPoint/System/Library/CoreServices" "$diskutil_dadl_MountPoint/EFI/BOOT" ; } 2> /dev/null | sort -u ); do
local version=""
local theversionfile="$thefolder/SystemVersion.plist"
if [[ -f "$theversionfile" ]]; then
version=$(/usr/libexec/PlistBuddy -c 'Print :ProductVersion' "$theversionfile")
fi
echo "${thefolder} ($version)"
[[ -f $thefolder/.disk_label.contentDetails ]] && echo "contentDetails: $(cat "$thefolder/.disk_label.contentDetails")" || echo " missing .disk_label.contentDetails"
[[ -f $thefolder/.disk_label_2x ]] && dump_label "$thefolder/.disk_label_2x" || echo " missing .disk_label_2x"
[[ -f $thefolder/.disk_label ]] && dump_label "$thefolder/.disk_label" || echo " missing .disk_label"
done
fi
done
}
makeLabelCommands () {
# Partitions need to be mounted first.
# Only includes Apple_HFS and Apple_Boot partitions.
IFS=$'\n'
local thefolder=""
for diskinfo in $(diskutil list | LANG=C sed -nE $'/^ *[0-9]+: +Apple_[HFSBoot]* (\xe2\x81\xa8)?([^ ].*[^ \xa9])(\xe2\x81\xa9)? +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/''s//\2_\4/p'); do
local slice="${diskinfo##*_}"
local volume=""
volume="$(getvolumename "$slice")"
local version=""
local mountpoint=""
mountpoint=$(mountpartition "_hfs" "$slice" "$volume")
local theblessfolder=""
local theblessfile=""
local theblessopen=""
getblessinfo "$mountpoint"
if [[ -n $theblessfile ]]; then
thefolder="$(dirname "$theblessfile")"
if [[ "$thefolder" != /Volumes/*/System/Library/CoreServices ]]; then
local theversionfile="$thefolder/SystemVersion.plist"
if [[ -f "$theversionfile" ]]; then
version=$(/usr/libexec/PlistBuddy -c 'Print :ProductVersion' "$theversionfile")
fi
printf 'makemultilinedisklabel "%s" "%s"$%c\\n%c"%s"\n' "$thefolder" "${volume/Recovery HD/Recovery}" "'" "'" "$version"
fi
fi
done
# Scan the usual places first
IFS=$'\n'
for thefolder in $(find /Volumes/*/System/Library/CoreServices /Volumes/*/EFI/BOOT -maxdepth 0); do
local volume=""
local version=""
volume="${thefolder/\/System\/Library\/CoreServices/}"
volume="${volume/\/EFI\/BOOT/}"
volume="$(getvolumename "$volume")"
local theversionfile="$thefolder/SystemVersion.plist"
if [[ -f "$theversionfile" ]]; then
version=$(/usr/libexec/PlistBuddy -c 'Print :ProductVersion' "$theversionfile")
fi
printf 'makemultilinedisklabel "%s" "%s"$%c\\n%c"%s"\n' "$thefolder" "${volume/Recovery HD/Recovery}" "'" "'" "$version"
done
}
clearOpenFoldersAll () {
# Clear open folder list of hfs partitions (finderinfo[2] in bless --info).
# If you have an HFS+ disk that has folders that open when you mount them (such as an installer), then this will stop that from happening.
clearOpenFolders $(getallhfsdisks)
}
clearOpenFolders () {
while (($#)); do
local slice="$1"
shift
local volume=""
volume="$(getvolumename "$slice")"
local mountpoint=""
mountpoint=$(mountpartition "_hfs" "$slice" "$volume")
echo "#$slice=$mountpoint,$volume ($version)"
local theblessfolder=""
local theblessfile=""
local theblessopen=""
getblessinfo "$mountpoint"
if [[ -n $theblessopen ]]; then
echo "Clearing open folder list"
if [[ -n $theblessfolder ]]; then
if [[ -n $theblessfile ]]; then
sudo "$directbless" --folder "$theblessfolder" --file "$theblessfile"
else
sudo "$directbless" --folder "$theblessfolder"
fi
else
if [[ -n $theblessfile ]]; then
sudo "$directbless" --mount "$mountpoint" --file "$theblessfile" --openfolder ""
else
sudo "$directbless" --unbless "$mountpoint"
fi
fi
fi
done
}
getdiskinfo () {
local thedisk="$1" # the disk you want info for (either a mountpoint like "/" or a device "disk0s1")
local thevarprefix="$2" # prefix for variable names
# note: if thevarprefix is empty then you should clear any variables you want to use from this result beforehand
unsetall "$thevarprefix"
eval "$(showdiskinfo "$thedisk" "$thevarprefix")"
}
showdiskinfo () {
local thedisk="$1" # the disk you want info for (either a mountpoint like "/" or a device "disk0s1")
local thevarprefix="$2" # prefix for variable names
diskutil info -plist "$thedisk" | plutil -p - | perl -nE 'if (s/ *"([^"]+)" => "?(.*?)"?$/'"$thevarprefix"'\1=\2/) { s/"/\\"/g; s/([^=]+=)(.*)/\1"\2"/; print $_ }'
}
getapfsroles () {
# $1 = the apfs container
local patVolumeGroup="^(Data|System)="
for theapfsvolume in $(
diskutil apfs list -plist "$1" | plutil -convert json -o - - | perl -e '
use JSON::PP; my $apfs = decode_json (<>);
foreach my $volume (@{$apfs->{Containers}[0]{Volumes}}) {print $volume->{Roles}[0] . "=" . $volume->{DeviceIdentifier} . "\n";}
'
); do
diskutil_role_APFSVolumeGroupID=""
if [[ $theapfsvolume =~ $patVolumeGroup ]]; then
getdiskinfo "${theapfsvolume#*=}" "diskutil_role_"
fi
echo "${diskutil_role_APFSVolumeGroupID}_${theapfsvolume}"
done
}
setbootername () {
local thedisk="$1"
local NewBooterName="$2"
local diskutil_sbn_Root_BooterDeviceIdentifier=""
getdiskinfo "$thedisk" diskutil_sbn_Root_
if [[ -n $diskutil_sbn_Root_BooterDeviceIdentifier ]]; then
diskutil mount "$diskutil_sbn_Root_BooterDeviceIdentifier"
local diskutil_sbn_Root_VolumeUUID=""
local diskutil_sbn_Root_APFSVolumeGroupID=""
getdiskinfo "$diskutil_sbn_Root_BooterDeviceIdentifier" "diskutil_sbn_Preboot_"
if [[ -n $diskutil_sbn_Preboot_MountPoint ]]; then
local theUUID=""
if [[ -d $diskutil_sbn_Preboot_MountPoint/$diskutil_sbn_Root_VolumeUUID ]]; then
theUUID=$diskutil_sbn_Root_VolumeUUID
echo "using VolumeUUID"
elif [[ -d $diskutil_sbn_Preboot_MountPoint/$diskutil_sbn_Root_APFSVolumeGroupID ]]; then
theUUID=$diskutil_sbn_Root_APFSVolumeGroupID
echo "using APFSVolumeGroupID"
else
echo "unknown UUID in booter"
ls -l "$diskutil_sbn_Preboot_MountPoint"
return 1
fi
local thefile="$diskutil_sbn_Preboot_MountPoint/$theUUID/System/Library/CoreServices/.disk_label.contentDetails"
if [[ -f "$thefile" ]]; then
echo "$thefile already exists: $(cat "$thefile")"
else
sudo bash -c "echo \"$NewBooterName\" > \"$thefile\""
echo "$thefile is created"
fi
else
echo "$diskutil_sbn_Root_BooterDeviceIdentifier is not mounted"
fi
else
echo "No booter device"
fi
}
doperlondisks () {
local thefilter="$1"
local theperl="$2"
{
if [[ -z $thefilter ]]; then
diskutil list -plist
else
diskutil list -plist "$thefilter"
fi
} | plutil -convert json -o - - | perl -e '
use JSON::PP; my $disks = decode_json (<>)->{AllDisksAndPartitions};
foreach my $disk (@$disks) {
foreach my $type ("APFSVolumes","Partitions") {
foreach my $part (@{$disk->{$type}}) {
'"$theperl"'
}
}
}
'
}
doperlonmounteddisks () {
doperlondisks "$1" '
if (exists $part->{MountPoint}) {
'"$2"'
}
'
}
getallmounteddisks () {
# not all disks are mounted at /Volumes so use diskutil to find them
if [[ "$1" == "-d" ]]; then
doperlonmounteddisks "" 'print $part->{DeviceIdentifier} . ":" . $part->{MountPoint} . "\n";'
else
doperlonmounteddisks "" 'print $part->{MountPoint} . "\n";'
fi
}
getalldisks () {
doperlondisks "$1" 'print $part->{DeviceIdentifier} . "\n";'
}
getallhfsdisks () {
doperlondisks "$1" '
if ($part->{Content} eq "Apple_HFS") {
print $part->{DeviceIdentifier} . "\n";
}
'
}
getallefidisks () {
doperlondisks "$1" '
if ($part->{Content} eq "EFI") {
print $part->{DeviceIdentifier} . "\n";
}
'
}
setfinderinfoflag () {
local thepath="$1"
local thechar="$2"
local thebit="$3"
local thevalue="$4"
local thefinderinfo=""
local newfinderinfo=""
local thedescription=""
thefinderinfo=$(xattr -px com.apple.FinderInfo "$thepath" 2> /dev/null | xxd -p -r | xxd -p -c 32)
if [[ -z "$thefinderinfo" ]]; then
thefinderinfo="0000000000000000000000000000000000000000000000000000000000000000"
thedescription="nonexistant "
fi
newfinderinfo=${thefinderinfo:0:$thechar}$( printf "%x" $(( ${thefinderinfo:$thechar:1} & (~(1<<(thebit))) | (thevalue<<(thebit)) )) )${thefinderinfo:$((thechar+1))}
if [[ "$newfinderinfo" != "$thefinderinfo" ]]; then
echo "# modifying ${thedescription}FinderInfo $newfinderinfo $thepath"
xattr -wx com.apple.FinderInfo "$newfinderinfo" "$thepath"
else
echo "# unchanged ${thedescription}FinderInfo $newfinderinfo $thepath"
fi
}
getfinderinfoflag () {
local thepath="$1"
local thechar="$2"
local thebit="$3"
local thefinderinfo=""
thefinderinfo=$(xattr -px com.apple.FinderInfo "$thepath" 2> /dev/null | xxd -p -r | xxd -p -c 32)
echo $(( ( 0x0${thefinderinfo:$thechar:1} >> thebit ) & 1 ))
}
setfoldercustomicon () {
# if the folder is a mount point then the icon should be in datafork of ".VolumeIcon.icns"
# otherwise the icon should be in a icns resource in "Icon\n"
setfinderinfoflag "$1" 17 2 1
}
isfileinvisible () {
getfinderinfoflag "$1" 16 2
}
setfilevisible () {
setfinderinfoflag "$1" 16 2 0
}
setfileinvisible () {
setfinderinfoflag "$1" 16 2 1
}
fixcustomiconflagofallvolumes () {
IFS=$'\n'
for themount in $(getallmounteddisks); do
if [[ -f "$themount/.VolumeIcon.icns" ]]; then
setfoldercustomicon "$themount"
fi
done
}
@joevt
Copy link
Author

joevt commented Sep 30, 2020

Download

curl -L https://gist.github.com/joevt/6d7a0ede45106345a39bdfa0ac10ffd6/raw -o ~/Downloads/DiskUtil.sh

Install (temporarily, only for the current terminal window)

source ~/Downloads/DiskUtil.sh

Test

# Mount all EFI partitions.
mountEFIpartitions

# Mount all Recovery HD partitions.
mountRecoveryHDpartitions

# Mount all Recovery partitions.
mountRecoveryPartitions

# Mount all Preboot partitions.
mountPrebootPartitions

Make a list of commands that can be edited to create multi-line disk label files.
makeLabelCommands

Make a multi-line disk label file in a specific folder. Example:
makemultilinedisklabel "/Volumes/EFISATA/EFI/BOOT" "Ubuntu"$'\n'"22.04 LTS"

Changes

May 10, 2023

  • Added dolockdisklabels setting.
  • Fix makemultilinedisklabel so it creates the temp folder in /tmp/
  • For FAT partitions, schg is changed to uchg, so both need to be undone to make changes to .contentDetails files.
  • Fix unsetall so it doesn't complain about non-utf8 characters.
  • Fix fixapfsbooter and unlockapfsbooter so they log System Volume UUID, No APFS Volume Group, or when it is assuming the System Volume UUID.
  • Fix dumpAllDiskLabels so it checks /System/Library/CoreServices of root file system.

May 23, 2022

  • Added unlockapfsbooter to unlock the disk label files created by fixapfsbooter which might cause problems for macOS updaters.

Dec 24, 2021

  • Added some Mac OS X 10.4 compatibility.

Nov 30, 2021

  • Silenced errors in getblessinfo (fixes issue with NTFS disks).
  • Added argument checks to makemultilinedisklabel. Note that most commands expect you to enter correct arguments.

Both of those commands do not output results to stdout.
getblessinfo and other commands output results to variables (similar to eval "$(stat -s somepath)" instead of to stdout.
sudo does not work with shell function variables such as these commands or built in shell commands like if.

@joevt
Copy link
Author

joevt commented Dec 22, 2020

Big Sur changed the output format of the diskutil list command so it includes invisible surround characters before and after the volume name. I've updated the script to account for that.

I should probably move away from using diskutil list and use diskutil list -plist. The plist can be parsed as-is using plutil or PlistBuddy. Piping the plist to plutil -p - creates something that looks like perl hashes/arrays but it's missing commas and the arrays look like hashes but those two issues are simple to fix. The problem is that man plutil says the -p format is not stable and not designed for machine parsing. As an alternative, the plutil command can convert to JSON. Perl can convert JSON to hashes/arrays using the JSON::parse function.

I used the plutil with perl json method in the getallmounteddisks command to loop through all the disks.

@MacNB
Copy link

MacNB commented Oct 13, 2021

Thanks for adding convert_label function.
It did not work for me as mentioned on insanelymac forum thread.
There's an error from tr :
tr: Illegal byte sequence

@joevt
Copy link
Author

joevt commented Oct 13, 2021

@MacNB fixed.

@MacNB
Copy link

MacNB commented Oct 13, 2021

Perfect. Many thanks.

@startergo
Copy link

For Big Sur and later to write the labels to disk you may need to mount the system writable:
https://github.com/fxgst/writeable_root

@joevt
Copy link
Author

joevt commented Dec 1, 2021

True, but only the labels in the Preboot volume (and maybe the Recovery volume) actually matter and those are always writable I think.
The Data volume is not bootable (doesn't have boot.efi in /System/Library/CoreServices)
The System volume has a boot.efi but I don't think it's bootable.

I'll look into this one day when I get around to installing Big Sur and Monterey to my Mac Pro 2008.

One issue is that the .VolumeIcon.icns file on the System volume is a firm link to the icon on the Data volume. This makes the icon not viewable when booted into other macOS partitions so I'll probably want to fix that.

@startergo
Copy link

Here is the log:

mountPrebootPartitions
mbp113@MacBook-Pro ~ % dumpAllDiskLabels > dumpAllDiskLabels.txt
Can't get device for /Volumes/BOOTCAMP
malformed JSON string, neither array, object, number, string or atom, at character offset 0 (before "<stdin>: Property Li...") at -e line 2.
mbp113@MacBook-Pro ~ % makeLabelCommands > makeLabelCommands.txt
mbp113@MacBook-Pro ~ % makemultilinedisklabel > makemultilinedisklabel.txt
cp: /.disk_label: Read-only file system
chflags: /.disk_label: No such file or directory
cp: /.disk_label_2x: Read-only file system
chflags: /.disk_label_2x: No such file or directory
cp: /.disk_label.contentDetails: Read-only file system
chflags: /.disk_label.contentDetails: No such file or directory
mbp113@MacBook-Pro ~ % sudo makemultilinedisklabel > makemultilinedisklabel.txt
sudo: makemultilinedisklabel: command not found
mbp113@MacBook-Pro ~ % getblessinfo > getblessinfo.txt
Can't get device for 
malformed JSON string, neither array, object, number, string or atom, at character offset 0 (before "<stdin>: Property Li...") at -e line 2.
mbp113@MacBook-Pro ~ % dumpAllDiskLabels > dumpAllDiskLabels1.txt
Can't get device for /Volumes/BOOTCAMP
malformed JSON string, neither array, object, number, string or atom, at character offset 0 (before "<stdin>: Property Li...") at -e line 2.

Archive zip

Remove .png and rename to .zip the attachment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment