-
-
Save joevt/6d7a0ede45106345a39bdfa0ac10ffd6 to your computer and use it in GitHub Desktop.
#!/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 | |
} |
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
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.
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.
Remove .png and rename to .zip the attachment.
Perfect. Many thanks.