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
# by joevt Sep 30, 2020
# dump disk_label file
dump_label () { local 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=$(mount | sed -n -E "/\/dev\/$slice on (.*) \(.*/s//\1/p")
if [ -z $mountpoint ]; then
i=0
local startmountpoint="/Volumes/$volume"
mountpoint="$startmountpoint"
while [ -d "$mountpoint" ]; do
((i++))
mountpoint="$startmountpoint$i"
done
fi
if [ ! -d "$mountpoint" ]; then
sudo mkdir "$mountpoint" 2> /dev/null
sudo mount$mounttype "/dev/$slice" "$mountpoint"
fi
echo -n "$mountpoint"
}
makemultilinedisklabel () {
local folder="$1"
local lines="$2"
local alllines=""
local alllines2x=""
[[ -d "/tmp/$folder" ]] || mkdir -p "/tmp/$folder"
IFS=$'\n'
local linenum=0
local theline=""
for theline in $(echo "$lines"); do
#echo "doing line: \"$theline\""
#echo sudo bless --folder "$folder" --label "$theline"
sudo bless --folder "/tmp/$folder" --label "$theline" 2> /dev/null
if (( $linenum )); then
alllines+="$(dump_label "/tmp/$folder/.disk_label" | sed '1,/[^.]/ {/^[.]*$/d; }')"$'\n'
alllines2x+="$(dump_label "/tmp/$folder/.disk_label_2x" | sed '1,/[^.]/ {/^[.]*$/d; }')"$'\n'
else
alllines+="$(dump_label "/tmp/$folder/.disk_label")"$'\n'
alllines2x+="$(dump_label "/tmp/$folder/.disk_label_2x")"$'\n'
fi
((linenum++))
done
local suffix=""
local thelines=""
for suffix in "" "_2x"; do
[[ -z $suffix ]] && thelines="$alllines" || thelines="$alllines2x"
local linewidths="$(echo "${thelines}" | tr '0-9a-f' '.' | sort -ur)"
local 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 > "/tmp/$folder/disk_label$suffix"
[[ -f "$folder/.disk_label$suffix" ]] && sudo chflags noschg "$folder/.disk_label$suffix"
sudo cp "/tmp/$folder/disk_label$suffix" "$folder/.disk_label$suffix"
sudo chflags schg "$folder/.disk_label$suffix"
[[ -f $folder/.disk_label.contentDetails ]] && sudo touch $folder/.disk_label.contentDetails
#sudo bless --folder "$folder" --labelfile "/tmp/$folder/disk_label$suffix"
#dump_label "$folder/.disk_label$suffix"
done
}
fixapfsbooter () {
local theosmountpoint="$1"
local theline1="$2"
local theline2="$3"
if [[ -d "$theosmountpoint/System/Library/CoreServices" ]]; then
mkdir -p /tmp/disklabeltemp
makemultilinedisklabel "/tmp/disklabeltemp" "${theline1}"$'\n'"${theline2}"
#open /tmp/disklabeltemp
#dump_label /private/tmp/disklabeltemp/.disk_label_2x
sudo cp /tmp/disklabeltemp/.disk_label* "$theosmountpoint/System/Library/CoreServices"
[[ -f "$theosmountpoint/System/Library/CoreServices/.disk_label.contentDetails" ]] && sudo rm "$theosmountpoint/System/Library/CoreServices/.disk_label.contentDetails"
#sudo echo -n "${theline1} ${theline2}" > "$theosmountpoint/System/Library/CoreServices/.disk_label.contentDetails"
#open "$theosmountpoint/System/Library/CoreServices"
local DiskUUID=""
local APFSContainerReference=""
local BooterDeviceIdentifier=""
local RecoveryDeviceIdentifier=""
getdiskinfo $theosmountpoint
local theDiskUUID=$DiskUUID
local theBooterDeviceIdentifier="$BooterDeviceIdentifier"
local theRecoveryDeviceIdentifier="$RecoveryDeviceIdentifier"
local theDataDeviceIdentifier=""
local theAPFSContainerReference="$APFSContainerReference"
if [[ -n $theAPFSContainerReference ]]; then
local theDataDeviceIdentifier=$(diskutil apfs list $theAPFSContainerReference | perl -nE '/^[| ]*APFS Volume Disk \(Role\): *(disk\d+s\d+) *\(Data\)/ && print $1')
fi
if [[ -L "$theosmountpoint/.VolumeIcon.icns" ]]; then
echo "Icon is sym link"
cp -p "$theosmountpoint/.VolumeIcon.icns" /tmp/disklabeltemp
sudo rm "$theosmountpoint/.VolumeIcon.icns"
sudo cp -p /tmp/disklabeltemp/.VolumeIcon.icns "$theosmountpoint"
fi
for theitem in "P:$theBooterDeviceIdentifier" "R:$theRecoveryDeviceIdentifier" "D:$theDataDeviceIdentifier"; do
local theletter="${theitem:0:1}"
local thedevice="${theitem:2}"
#echo $theletter
#echo $thedevice
if [[ -n $thedevice ]]; then
diskutil mount $thedevice > /dev/null
local MountPoint=""
getdiskinfo $thedevice
#echo $MountPoint
if [[ -n $MountPoint ]]; then
#open "$MountPoint"
[[ -f "$theosmountpoint/.VolumeIcon.icns" ]] && sudo cp "$theosmountpoint/.VolumeIcon.icns" "$MountPoint" || echo "No volume icon to copy to $theitem"
makemultilinedisklabel "/tmp/disklabeltemp" "${theline1} (${theletter})"$'\n'"${theline2}"
#open "/tmp/disklabeltemp"
#dump_label /tmp/disklabeltemp/.disk_label_
local thedest=""
if [[ $theletter == 'P' ]] && [[ -d "$MountPoint/$theDiskUUID/System/Library/CoreServices" ]]; then
thedest="$MountPoint/$theDiskUUID/System/Library/CoreServices"
elif [[ $theletter == 'D' ]] && [[ -d "$MountPoint/System/Library/CoreServices" ]]; then
thedest="$MountPoint/System/Library/CoreServices"
elif [[ $theletter == 'R' ]] && [[ -f "$MountPoint/$theDiskUUID/boot.efi" ]]; then
thedest="${MountPoint}/${theDiskUUID}"
if [[ -f "$thedest/boot.efi" ]]; then
echo "# Blessing $thedest/boot.efi"
sudo bless --folder "$thedest" --file "$thedest/boot.efi"
fi
fi
if [[ -n "$thedest" ]]; then
echo "# ${theline1} ${theline2}: Destination for ${theletter} $thedevice $thedest"
sudo find "$thedest" -name '.disk_label*' -not -name '*.contentDetails' -exec chflags noschg {} \;
sudo cp /tmp/disklabeltemp/.disk_label* "$thedest"
sudo find "$thedest" -name '.disk_label*' -not -name '*.contentDetails' -exec chflags schg {} \;
[[ -f "$thedest/.disk_label.contentDetails" ]] && sudo rm "$thedest/.disk_label.contentDetails"
#sudo echo -n "${theline1} (${theletter}) ${theline2}" > "$thedest/.disk_label.contentDetails"
else
echo "# ${theline1} ${theline2}: No destination for ${theletter} $thedevice $MountPoint"
fi
fi
fi
done
fi
}
getvolumename () {
local slice="$1"
eval "$(diskutil info -plist /dev/$slice | plutil -p - | sed -nE '/^ *"VolumeName" => (.*)/s//echo \1/p')"
}
mountEFIpartitions () {
IFS=$'\n'
local diskinfo=""
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +EFI (.*[^ ]) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
mountpartition "_msdos" "$slice" "$volume" > /dev/null
done
}
mountRecoveryHDpartitions() {
IFS=$'\n'
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +Apple_Boot (Recovery HD) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
mountpartition "_hfs" "$slice" "$volume" > /dev/null
done
}
mountRecoveryPartitions() {
IFS=$'\n'
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +APFS Volume (Recovery) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
mountpartition "_apfs" "$slice" "$volume" > /dev/null
done
}
mountPrebootPartitions() {
IFS=$'\n'
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +APFS Volume (Preboot) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
mountpartition "_apfs" "$slice" "$volume" > /dev/null
done
}
dumpAllDiskLabels () {
# Partitions need to be mounted first.
# Only includes Apple_HFS and Apple_Boot partitions.
IFS=$'\n'
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +Apple_[HFSBoot]* ([^ ].*[^ ]) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
local version=""
local mountpoint=$(mountpartition "_hfs" "$slice" "$volume")
local theblessinfo="$(bless --info "$mountpoint")"
local thefolder=""
local thefile=""
local theopen=""
eval $(echo "$theblessinfo" | sed -nE '/^finderinfo\[0\]: +[0-9]+ => Blessed System Folder is (.*)/s//thefolder="\1"/p; /^finderinfo\[1\]: +[0-9]+ => Blessed System File is (.*)/s//thefile="\1"/p; /^finderinfo\[2\]: +[0-9]+ => 1st dir in open-folder list is (.*)/s//theopen="\1"/p; ')
if [[ -n $thefile ]]; then
thefolder="$(dirname "$thefile")"
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
echo "#$slice=$thefolder, $volume ($version)"
[[ -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"
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/\/Volumes\//}"
volume="${volume/\/System\/Library\/CoreServices/}"
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_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
}
makeLabelCommands () {
# Partitions need to be mounted first.
# Only includes Apple_HFS and Apple_Boot partitions.
IFS=$'\n'
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +Apple_[HFSBoot]* ([^ ].*[^ ]) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
local version=""
local mountpoint=$(mountpartition "_hfs" "$slice" "$volume")
local theblessinfo="$(bless --info "$mountpoint")"
local thefolder=""
local thefile=""
local theopen=""
eval $(echo "$theblessinfo" | sed -nE '/^finderinfo\[0\]: +[0-9]+ => Blessed System Folder is (.*)/s//thefolder="\1"/p; /^finderinfo\[1\]: +[0-9]+ => Blessed System File is (.*)/s//thefile="\1"/p; /^finderinfo\[2\]: +[0-9]+ => 1st dir in open-folder list is (.*)/s//theopen="\1"/p; ')
if [[ -n $thefile ]]; then
thefolder="$(dirname "$thefile")"
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
echo "#$slice=$thefolder, $volume ($version)"
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/\/Volumes\//}"
volume="${volume/\/System\/Library\/CoreServices/}"
local theversionfile="$thefolder/SystemVersion.plist"
if [[ -f "$theversionfile" ]]; then
version=$(/usr/libexec/PlistBuddy -c 'Print :ProductVersion' "$theversionfile")
fi
echo "#$thefolder ($version)"
printf 'makemultilinedisklabel "%s" "%s"$%c\\n%c"%s"\n' "$thefolder" "${volume/Recovery HD/Recovery}" "'" "'" "$version"
done
}
clearOpenFolders () {
# 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'
for diskinfo in $(diskutil list | sed -nE '/^ *[0-9]+: +Apple_HFS (.*[^ ]) +[0-9.]+ [^ ]?B +(disk[0-9]+s[0-9]+)$/s//\1_\2/p'); do
local slice="${diskinfo##*_}"
local volume="$(getvolumename $slice)"
local mountpoint=$(mountpartition "_hfs" "$slice" "$volume")
echo "#$slice=$mountpoint,$volume ($version)"
local theblessinfo="$(bless --info "$mountpoint")"
local thefolder=""
local thefile=""
local theopen=""
eval $(echo "$theblessinfo" | sed -nE '/^finderinfo\[0\]: +[0-9]+ => Blessed System Folder is (.*)/s//thefolder="\1"/p; /^finderinfo\[1\]: +[0-9]+ => Blessed System File is (.*)/s//thefile="\1"/p; /^finderinfo\[2\]: +[0-9]+ => 1st dir in open-folder list is (.*)/s//theopen="\1"/p; ')
if [[ -n $theopen ]]; then
echo "Clearing open folder list"
sudo bless --folder "$thefolder" --file "$thefile"
fi
done
}
getdiskinfo () {
# $1 = the disk you want info for (either a mountpoint like "/" or a device "disk0s1")
# $2 = prefix for variable names
# note: you should clear any variables you want to use from this result beforehand
eval $(diskutil info -plist $1 | plutil -p - | perl -nE 'if (s/ *"([^"]+)" => "?(.*?)"?$/'$2'\1=\2/) { s/"/\\"/g; s/([^=]+=)(.*)/\1"\2"/; print $_ }')
}
setbootername () {
local thedisk="$1"
local NewBooterName="$2"
getdiskinfo "$thedisk" Root
if [[ -n $RootBooterDeviceIdentifier ]]; then
diskutil mount $RootBooterDeviceIdentifier
getdiskinfo $RootBooterDeviceIdentifier Preboot
if [[ -n $PrebootMountPoint ]]; then
local theUUID=""
if [[ -d $PrebootMountPoint/$RootVolumeUUID ]]; then
theUUID=$RootVolumeUUID
echo "using VolumeUUID"
elif [[ -d $PrebootMountPoint/$RootAPFSVolumeGroupID ]]; then
theUUID=$RootAPFSVolumeGroupID
echo "using APFSVolumeGroupID"
else
echo "unknown UUID in booter"
ls -l "$PrebootMountPoint"
return 1
fi
thefile="$PrebootMountPoint/$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 "$RootBooterDeviceIdentifier is not mounted"
fi
else
echo "No booter device"
fi
}
@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