-
-
Save joevt/6d7a0ede45106345a39bdfa0ac10ffd6 to your computer and use it in GitHub Desktop.
| #!/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 | |
| } |
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.
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
@MacNB fixed.
Perfect. Many thanks.
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.

Download
curl -L https://gist.github.com/joevt/6d7a0ede45106345a39bdfa0ac10ffd6/raw -o ~/Downloads/DiskUtil.shInstall (temporarily, only for the current terminal window)
Test
Make a list of commands that can be edited to create multi-line disk label files.
makeLabelCommandsMake 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
makemultilinedisklabelso it creates the temp folder in /tmp/unsetallso it doesn't complain about non-utf8 characters.fixapfsbooterandunlockapfsbooterso they log System Volume UUID, No APFS Volume Group, or when it is assuming the System Volume UUID.dumpAllDiskLabelsso it checks /System/Library/CoreServices of root file system.May 23, 2022
unlockapfsbooterto unlock the disk label files created byfixapfsbooterwhich might cause problems for macOS updaters.Dec 24, 2021
Nov 30, 2021
getblessinfo(fixes issue with NTFS disks).makemultilinedisklabel. Note that most commands expect you to enter correct arguments.Both of those commands do not output results to stdout.
getblessinfoand other commands output results to variables (similar toeval "$(stat -s somepath)"instead of to stdout.sudodoes not work with shell function variables such as these commands or built in shell commands likeif.