Skip to content

Instantly share code, notes, and snippets.

@joevt
Last active June 13, 2024 10:22
Show Gist options
  • Save joevt/a4ef4cd2e1485d0dd595893007bdfd6a to your computer and use it in GitHub Desktop.
Save joevt/a4ef4cd2e1485d0dd595893007bdfd6a to your computer and use it in GitHub Desktop.
Commands for kexts
#!/bin/bash
# joevt Jun 12, 2024
#10.3 Panther to 10.13 High Sierra
kextload=kextload
# Later macOS versions
command -v kextutil > /dev/null 2>&1 && kextload=kextutil
getkextidentifier () {
local thekextpath="$1"
if [[ ! -f "$thekextpath/Contents/Info.plist" ]]; then
echo "# Missing Info.plist." 1>&2
return 1
fi
# Mac OS X 10.4 doesn't have PlistBuddy so just use perl
perl -0777 -n -e 'if (m|<key>CFBundleIdentifier</key>\s*<string>(.+)</string>|) { print $1 }' "$thekextpath/Contents/Info.plist"
}
checkkext () {
local thekextpath="$1"
if [[ -d "$thekextpath" ]]; then
if [[ -f "$thekextpath/Contents/Info.plist" ]]; then
local kextname
kextname="$(basename "$thekextpath")"
if grep -q -E "\.kext$" <<< "$kextname"; then
local kextidentifier=""
kextidentifier="$(getkextidentifier "$thekextpath")"
local theerr=$?
if (( theerr == 0 )); then
return 0
else
echo "# Missing CFBundleIdentifier." 1>&2
return 1
fi
else
echo "# \"$kextname\" not a kext." 1>&2
return 1
fi
else
echo "# Missing Info.plist." 1>&2
return 1
fi
else
echo "# Expected a kext package directory." 1>&2
return 1
fi
}
unloadkext () {
local thekextpath="$1"
checkkext "$thekextpath" || return 1
local kextname
kextname="$(basename "$thekextpath")"
local kextidentifier
kextidentifier="$(getkextidentifier "$thekextpath")"
echo "# kextname: $kextname"
echo "# kextidentifier: $kextidentifier"
if ( kextstat 2> /dev/null | grep -q "$kextidentifier" ); then
while : ; do
echo "# Unloading ${kextname}."
sudo kextunload -b "$kextidentifier"
( kextstat 2> /dev/null | grep -q "$kextidentifier" ) || break
sleep 1
done
echo "# Unloaded ${kextname}."
else
echo "# ${kextname} is not loaded."
fi
}
checkkextpermissions () {
local thekextpath="$1"
checkkext "$thekextpath" || return 1
badpermissions="$(find "$thekextpath" -exec ls -ld {} \; | sed -E '/drwxr-xr-x.* root *wheel /d ; /-rw.r-.r-..* root *wheel /d')"
if (( $(printf "%s" "$badpermissions" | wc -l) == 0 )); then
return 0
fi
echo "# Permissions are not correct for the following:" 1>&2
echo "$badpermissions" 1>&2
return 1
}
fixkextpermissions () {
local thekextpath="$1"
checkkext "$thekextpath" || return 1
if checkkextpermissions "$thekextpath" 2> /dev/null ; then
echo "# Permissions are good. No changes were required."
return 0
fi
sudo chown -R root:wheel "$thekextpath"
sudo find "$thekextpath" -type d -exec /bin/chmod 0755 {} \;
sudo find "$thekextpath" -type f -exec /bin/chmod 0644 {} \;
if checkkextpermissions "$thekextpath" 2> /dev/null ; then
echo "# Fixed permissions."
return 0
fi
echo "# Could not fix permissions." 1>&2
return 1
}
loadkext () {
local thekextpath="$1"
checkkext "$thekextpath" || return 1
local kextname
kextname="$(basename "$thekextpath")"
local kextloadpath
if fixkextpermissions "$thekextpath" > /dev/null 2>&1 ; then
kextloadpath="$thekextpath"
else
echo "# Cannot fix permissions. Will load kext from temporary directory." 1>&2
local kexttmpdir
kexttmpdir="$(mktemp -d /tmp/KextUtil_XXXXXXX)"
kextloadpath="$kexttmpdir/$kextname"
sudo cp -R "$thekextpath" "$kextloadpath"
if ! fixkextpermissions "$kextloadpath" ; then
return 1
fi
fi
unloadkext "$kextloadpath"
if ( sudo "$kextload" "$kextloadpath" ); then
echo "# Loaded ${kextname}."
return 0
else
echo "# $kextname was not loaded." 1>&2
return 1
fi
}
mountsystemrw () {
mount | grep ' on / ' | grep -q 'read-only' && sudo mount -uw /
}
installkext () {
local doload=0
local dosystem=0
while (( $# )); do
if [[ "$1" == "-l" ]]; then
doload=1
elif [[ "$1" == "-s" ]]; then
dosystem=1
else
break
fi
shift 1
done
local thekextpath="$1"
checkkext "$thekextpath" || return 1
local kextname
kextname="$(basename "$thekextpath")"
local kextinstalldir="/Library/Extensions"
if ((dosystem)) || [[ ! -d /Library/Extensions ]] ; then
kextinstalldir="/System/Library/Extensions"
dosystem=1
fi
local kextidentifier
kextidentifier="$(getkextidentifier "$thekextpath")"
if ((doload)); then
# unloadkext will do these echo lines for us:
# echo "# kextname: $kextname"
# echo "# kextidentifier: $kextidentifier"
unloadkext "$thekextpath"
else
echo "# kextname: $kextname"
echo "# kextidentifier: $kextidentifier"
fi
local kexttmpdir
kexttmpdir="$(mktemp -d /tmp/KextUtil_XXXXXXX)"
kextloadpath="$kexttmpdir/$kextname"
sudo cp -R "$thekextpath" "$kextloadpath"
if ! fixkextpermissions "$kextloadpath" ; then
return 1
fi
if [[ -d "/System/Library/Extensions/$kextname" ]]; then
mountsystemrw
sudo rm -R "/System/Library/Extensions/$kextname"
fi
if [[ -d "/Library/Extensions/$kextname" ]]; then
sudo rm -R "/Library/Extensions/$kextname"
fi
if ((dosystem)); then
mountsystemrw
fi
sudo mv "$kextloadpath" "$kextinstalldir"
echo "# Installed ${kextname}."
if ((doload)); then
sudo "$kextload" "$kextinstalldir/$kextname"
kextstat 2> /dev/null | grep "$kextidentifier"
echo "# Loaded ${kextname}."
fi
}
removekext () {
local thekextpath="$1"
checkkext "$thekextpath" || return 1
local kextname
kextname="$(basename "$thekextpath")"
if [[ "$thekextpath" == /System/Library/* ]] ; then
mountsystemrw
fi
sudo rm -R "$thekextpath" && echo "# Removed ${kextname}." || echo "# Error removing ${kextname}."
}
rebuildkextcache () {
sudo kextcache -i /
}
@joevt
Copy link
Author

joevt commented Dec 8, 2022

Usage

Load the commands into the terminal like this:
source KextUtil.sh

Utility Functions

These utility functions are used by the main functions listed below.

getkextidentifier kextpath

  • Outputs the CFBundleIdentifier for the kext. This can be used to find the kext in kextstat or to unload the kext.

checkkext kextpath

  • Returns status 0 if the kext is ok (it's a kext directory and contains an Info.plist that has a CFBundleIdentifier).
  • Returns status 1 otherwise. The problem is output to stderr.
  • All commands below will use checkkext before attempting to do anything.

checkkextpermissions kextpath

  • Returns status 0 if the kext's permissions are ok.
  • Returns status 1 otherwise. The problem files are listed to stderr.
  • This is used by fixkextpermissions to see if the permissions need to be fixed.

fixkextpermissions kextpath

  • Fixes the permissions of the files and directories of the kext.
  • Returns status 0 if successful. It will report if the permissions were good already or if they were fixed.
  • Returns status 1 otherwise.
  • This is used by loadkext, unloadkext, and installkext.

Main Functions

loadkext kextpath

  • Loads a kext.
  • Returns status 0 if successful.
  • Returns status 1 otherwise.
  • Before loading, it will:
  1. Fix permissions if necessary. If the permissions cannot be fixed (for example, when it is on a volume that ignores permissions), then the kext is copied to a temporary directory where the permissions can be fixed.
  2. Unload previously loaded kext if necessary.

unloadkext kextpath

  • Unloads the kext. It will report if the kext was not loaded in the first place. It will try to unload the kext once per second until it is unloaded. The kextpath is not necessarily the same version of the kext that was loaded. The CFBundleIdentifier is used to unload the kext.

installkext [-s] [-l] kextpath

  • Installs a kext to /Library/Extensions. if -s is specified then the install location is /System/Library/Extensions. Do not use -s for Big Sur and later.
  • Loads the kext if -l is specified. It will first unload previously loaded kext if necessary.
  • Before installing the kext it will:
  1. Copy the kext to a staging location (a temporary directory) so no changes are done to the original.
  2. Fix permissions if necessary.
  3. Remove kext of the same name from /System/Library/Extensions if it exists there. It will mount the root volume as read/write if necessary (works in the case of Catalina; for later macOS it is expected that the kext will not be in /System).
  4. Remove kext of the same name from /Library/Extensions if it exists there.
  5. Move the kext from the staging location to the install location.

removekext kextpath

  • Removes the kext if it exists and is a kext.
  • Will mount the root volume as read/write if necessary (works in the case of Catalina; for later macOS it is expected that the kext will not be in /System).

Updates

Jun 12, 2024

  • Suppress error message when checking existence of kextutil.

Feb 10, 2024

  • Don't install to /Library/Extensions if it doesn't exist (as in Mac OS X 10.4 Tiger).

May 9, 2023

  • Corrections to changes made yesterday for loadkext and installkext.
  • Improved logging in checkkext, installkext, and removekext.

May 8, 2023

  • Minor updates to log and error messages.
  • Minor cleanup.
  • Remove 1 second delay for first attempt of unloadkext.
  • Added checkkextpermissions so that chown and chmod are not used unnecessarily.
  • fixkextpermissions now returns a status if the permissions needed to be fixed but could not be fixed.
  • loadkext will now load a kext from its current location if possible. It may modify permissions if necessary. It will revert to using a temporary directory if necessary.
  • Added mountsystemrw for mounting the / root volume as read write. This is useful for Catalina.
  • removekext now mounts the root volume as read write if necessary.

Jan 31, 2023

  • Add -s option for installkext to install to /System/Library/Extensions. This is required to load unsigned kexts in OS X 10.9 Mavericks.

Jan 6, 2023

  • Fixed kext staging location so that it's temporary and allows changing permissions.

Dec 8, 2022

  • Added compatibility with Mac OS X 10.4.

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