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 May 10, 2023
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 ]]; then
if [[ ! -f "$directblesscmd" ]]; 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
# Set this to 1 since some macOS versions will modify disk labels without user action.
# But you need to run unlockapfsbooter before doing OS updates.
dolockdisklabels=1
# 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" ; }
# convert 4 byte number to little-endian
le () { local n; n=$(printf "%08x" $(($1))); printf "%s" "${n:6:2}${n:4:2}${n:2:2}${n:0:2}"; }
#convert disk_lbel to tiff file
convert_label () {
local thelabel="$1"
local thedest="$2"
if [[ ! -f $thelabel ]]; then
printf '# Disk label "%s" does not exist.\n' "$thelabel"
return 1
fi
if [[ -z $thedest ]]; then
thedest="$thelabel.tiff"
fi
local contents=""
contents=$(xxd -p "$thelabel")
local width=$((0x${contents:2:4}))
local height=$((0x${contents:6:4}))
echo "
4949 2a00 1800 0000 d002 0000 0a00 0000 d002 0000 0a00 0000 0d00 fe00 0400 0100
0000 0000 0000 0001 0400 0100 0000 $(le width) 0101 0400 0100 0000 $(le height) 0201
0300 0100 0000 0800 0000 0301 0300 0100 0000 0100 0000 0601 0300 0100 0000 0000
0000 1101 0400 0100 0000 ba00 0000 1501 0300 0100 0000 0100 0000 1601 0400 0100
0000 $(le width) 1701 0400 0100 0000 $(le $((width * height))) 1a01 0500 0100 0000 0800 0000 1b01
0500 0100 0000 1000 0000 2801 0300 0100 0000 0200 0000 0000 0000
$(echo "${contents:10}" | xxd -p -r | LANG=C tr -C \
'\0\366\367\52\370\371\125\372\373\200\374\375\253\376\377\326' '\0' | LANG=C tr \
'\0\366\367\52\370\371\125\372\373\200\374\375\253\376\377\326' \
'\0\021\042\63\104\125\146\167\210\231\252\273\314\335\356\377' | xxd -p)
" | xxd -p -r > "$thedest"
}
# 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 () {
if (( $# != 2 )); then
echo "# error: not enough arguments" 1>&2
echo "# usage: makemultilinedisklabel destinationpath disklabellines" 1>&2
return 1
fi
local folder="$1"
if [[ ! -d $folder ]]; then
echo '# error: "'"$folder"' is not a folder' 1>&2
echo "# usage: makemultilinedisklabel destinationpath disklabellines" 1>&2
return 1
fi
local lines="$2"
local alllines=""
local alllines2x=""
local tempfolder0=""
local tempfolder=""
tempfolder0="$(mktemp -d /tmp/makemultilinedisklabel.XXX)"
# do the blessing on a temporary disk image so it doesn't do apfs stuff like copy the disk label to preboot of the current system
tempfolder="$(hdiutil create -volname disklabeltmp -size 600k -fs HFS+ -attach "$tempfolder0/tmp.dmg" | perl -nE 'if (/\/dev\/disk[0-9]+s[0-9]+[^\t]*\tApple_HFS[^\t]*\t(.*)/) { print $1 }')"
if [[ -z $tempfolder ]]; then
echo "# error creating temporary disk image" 1>&2
return 1
fi
if [[ ! -d $tempfolder ]]; then
echo "# error getting temporary disk image mount point: $tempfolder" 1>&2
return 1
fi
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"
# EFI file system converts schg flag to uchg flag, so we set both to no
[[ -f "$folder/.disk_label$suffix" ]] && {
sudo chflags noschg,nouchg "$folder/.disk_label$suffix"
}
sudo cp "$tempfolder/disk_label$suffix" "$folder/.disk_label$suffix"
((dolockdisklabels)) && sudo chflags schg "$folder/.disk_label$suffix"
done
[[ -f "$folder/.disk_label.contentDetails" ]] && {
sudo chflags noschg,nouchg "$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"
((dolockdisklabels)) && sudo chflags schg "$folder/.disk_label.contentDetails"
hdiutil detach -quiet "${tempfolder}"
}
unsetall () {
local thevarprefix="$1" # prefix for variable names
if [[ -n $thevarprefix ]]; then
eval "$(set | LANG=C sed -nE '/^('"$1"'[^=]*)=.*/s//unset \1/p')"
fi
}
patmatch () {
# substitute for [[ =~ ]] for Mac OS X 10.4
perl -0777 -ne '<>; exit !( $_ =~ /'"$1"'/ )'
}
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 roles=""
roles=$(getapfsroles "$diskutil_fab1_APFSContainerReference")
local System_UUID=""
if [[ -n $diskutil_fab1_APFSVolumeGroupID ]]; then
echo "# APFS Volume Group is $diskutil_fab1_APFSVolumeGroupID"
IFS=$'\n'
for thedisk in $(echo "$roles"); do
if patmatch "$patSys2" <<< "$thedisk"; then
local thedevice="${thedisk#*=}"
local diskutil_fabsys_VolumeUUID=""
getdiskinfo "$thedevice" "diskutil_fabsys_"
System_UUID="$diskutil_fabsys_VolumeUUID"
echo "# System Volume UUID is $System_UUID"
fi
done
else
echo "# No APFS Volume Group"
fi
if [[ -z $System_UUID ]]; then
System_UUID="$diskutil_fab1_VolumeUUID"
echo "# Assuming System Volume UUID is $System_UUID"
fi
for thepass in 0 1; do
IFS=$'\n'
for thedisk in $(echo "$roles"); do
if patmatch "$patAll" <<< "$thedisk" ; 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 patmatch "$patSys" <<< "$thedisk"; then
if ((issource)); then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
fi
elif patmatch "$patSys2" <<< "$thedisk"; then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
elif patmatch "$patData" <<< "$thedisk"; then
theletter="🄳"
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
elif patmatch "$patPreboot" <<< "$thedisk"; 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 patmatch "$patRecovery" <<< "$thedisk"; 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
if [[ -L "$diskutil_fab2_MountPoint/.VolumeIcon.icns" ]]; then
echo "# Removing icon sym link"
sudo rm "$diskutil_fab2_MountPoint/.VolumeIcon.icns"
fi
echo "# Doing icon copy"
sudo cp -p "$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,nouchg {} \;
sudo cp /tmp/disklabeltemp/.disk_label* "$thedest"
((dolockdisklabels)) && 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!"
}
unlockapfsbooter () {
# If you use fixapfsbooter to create disk labels for an APFS system, you should probably
# use this to unlock the disk label files in case the installer doesn't like them.
theoneapfsmountpoint="$1"
if [[ -z $theoneapfsmountpoint ]]; then
echo "#Error: missing parameters"
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
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 roles=""
roles=$(getapfsroles "$diskutil_fab1_APFSContainerReference")
local System_UUID=""
if [[ -n $diskutil_fab1_APFSVolumeGroupID ]]; then
echo "# APFS Volume Group is $diskutil_fab1_APFSVolumeGroupID"
IFS=$'\n'
for thedisk in $(echo "$roles"); do
if patmatch "$patSys2" <<< "$thedisk"; then
local thedevice="${thedisk#*=}"
local diskutil_fabsys_VolumeUUID=""
getdiskinfo "$thedevice" "diskutil_fabsys_"
System_UUID="$diskutil_fabsys_VolumeUUID"
echo "# System Volume UUID is $System_UUID"
fi
done
else
echo "# No APFS Volume Group"
fi
if [[ -z $System_UUID ]]; then
System_UUID="$diskutil_fab1_VolumeUUID"
echo "# Assuming System Volume UUID is $System_UUID"
fi
IFS=$'\n'
for thedisk in $(echo "$roles"); do
if patmatch "$patAll" <<< "$thedisk" ; then
echo "#================"
echo "# Doing $thedisk"
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 thedest=""
if patmatch "$patSys" <<< "$thedisk"; then
if ((issource)); then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
fi
elif patmatch "$patSys2" <<< "$thedisk"; then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
elif patmatch "$patData" <<< "$thedisk"; then
thedest="$diskutil_fab2_MountPoint/System/Library/CoreServices"
elif patmatch "$patPreboot" <<< "$thedisk"; then
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 patmatch "$patRecovery" <<< "$thedisk"; then
thedest="$diskutil_fab2_MountPoint/$System_UUID"
if [[ ! -d $thedest ]]; then
thedest="$diskutil_fab2_MountPoint/$diskutil_fab1_VolumeUUID"
fi
fi
if [[ -d $thedest ]]; then
echo "# Unlocking these files:"
find "$thedest" -name '.disk_label*'
sudo find "$thedest" -name '.disk_label*' -exec chflags noschg,nouchg {} \;
else
echo "# Destination does not exist $thedest"
fi
fi # if mount
fi # if the disk
done # for thedisk
echo "#================"
echo "# Done!"
}
getvolumeproperty () {
local slice="$1"
local property="$2"
local duinfo=""
duinfo="$(diskutil info -plist "$slice" 2> /dev/null)"
eval "$(
{
for (( i=0 ; i < 2 ; i++ )); do
if patmatch "Disk Utility Tool" <<< "$duinfo"; then
diskutil info "$slice" | perl -ne 'while (<>) { if (/ +([^:]+): +"?(.*?)"?$/) { $name = $1; $value = $2; $name =~ s/ //g; print "\"" . $name . "\" => \"" . $value . "\"\n" } }'
break
else
printf "%s" "$duinfo" | plutil -p - 2> /dev/null && break || duinfo="Disk Utility Tool"
fi
done
} | 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.]+ [^ ]?[Bi] +(disk[0-9]+s[0-9]+)$/''s//\2_\4/p'); do
local slice="${diskinfo##*_}"
local volume=""
volume="$(getvolumename "$slice")"
if [[ -z $volume ]]; then
volume="EFI" # Mac OS X 10.4.11 doesn't load EFI volume name
fi
#echo mountpartition "_msdos" "$slice" "$volume"
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.]+ [^ ]?[Bi] +(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.]+ [^ ]?[Bi] +(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.]+ [^ ]?[Bi] +(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
}
mounthfsimage () {
# macOS doesn't support mounting HFS anymore. To get around that,
# this command uses https://github.com/joevt/fusehfs.
# Useful for mounting SheepShaver.app disk image.
local theimage="$1"
theresult=$(hdiutil attach "$theimage" -readonly -nomount -noverify -noautofsck -plist)
if [[ -n $theresult ]]; then
local device=""
local volume_kind=""
eval "$(
plutil -convert json -o - - <<< "$theresult" | perl -e '
use JSON::PP; my $f = decode_json (<>)->{"system-entities"};
printf("device=\"%s\"\nvolume_kind=\"%s\"\n", $f->[0]->{"dev-entry"}, $f->[0]->{"volume-kind"})
'
)"
if [[ -z $volume_kind ]] || [[ $volume_kind = "hfs" ]]; then
local volume=""
local slice="${device/\/dev\//}"
volume="$(getvolumename "$slice")"
mountpartition "_fusefs_hfs" "$slice" "$volume" # > /dev/null
else
echo "# unexpected volume kind '$volume_kind'"
fi
fi
}
getblessinfo () {
local mountpoint="$1"
local thevarprefix="$2"
unsetall "$thevarprefix"
eval "$(
directbless -plist --info "$mountpoint" 2> /dev/null | 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"})
' 2> /dev/null
)"
}
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 "$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.]+ [^ ]?[Bi] +(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 -maxdepth 0 || true ; find /Volumes/*/EFI/BOOT -maxdepth 0 || true ; } 2> /dev/null); 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.
IFS=$'\n'
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" 2> /dev/null | 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 patmatch "$patVolumeGroup" <<< "$theapfsvolume"; 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"
sudo 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