OpenCorePkg/Utilities/LogoutHook/Launchd.command

768 lines
23 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
#
# Copyright © 2022 Mike Beaton. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
#
# Examine log at /var/log/org.acidanthera.nvramhook.launchd/launchd.log.
#
# Script can run immediately or be installed as daemon or logout hook. Installs as
# launch daemon on Yosemite and above, which is the only supported approach that on
# shutdown reads NVRAM after macOS installer vars are set, in more recent macOS
# installers (e.g. Monterey).
#
# Non-installed script can be run directly as 'daemon', 'agent' or 'logout' hook by
# specifying one of those options on the command line. For 'logout' this saves NVRAM
# immediately, for 'daemon' or 'agent' this waits to be terminated e.g. CTRL+C before
# saving.
#
# Credits:
# - Rodion Shingarev (with additional contributions by vit9696 and PMheart) for
# original LogoutHook.command
# - Rodion Shingarev for nvramdump command
# - Mike Beaton for this script
#
usage() {
echo "Usage: ${SELFNAME} [install|uninstall|status] [logout|daemon|agent|both]"
echo " - [install|uninstall] without type uses recommended type for macOS version"
echo " - 'status' shows status of agent, daemon and logout hook"
echo " - 'status daemon' or 'status agent' give additional detail"
echo ""
}
# Non-sudo log only if agent will use it, so logfile has been chmod-ed at install.
# If only daemon or logout hook will use it, explicit sudo required at install
# time, but harmless when running.
doLog() {
if [ ! "$AGENT" = "1" ] ; then
# macOS recreates this for daemon at reboot, but preferable
# to continue logging immediately after any log cleardown,
# also never recreated except for this with logout hook
sudo mkdir -p "${LOGDIR}"
# 'sudo tee' to write to log file as root (could also 'sh -c') for installation steps;
# will be root anyway when running installed as daemon or logout hook
if [ ! "${LOG_PREFIX}" = "" ] ; then
sudo tee -a "${LOGFILE}" > /dev/null << EOF
$(date +"${DATEFORMAT}") (${LOG_PREFIX}) ${1}
EOF
else
sudo tee -a "${LOGFILE}" > /dev/null << EOF
${1}
EOF
fi
else
# non-sudo copy, could simplify
if [ ! "${LOG_PREFIX}" = "" ] ; then
tee -a "${LOGFILE}" > /dev/null << EOF
$(date +"${DATEFORMAT}") (${LOG_PREFIX}) ${1}
EOF
else
tee -a "${LOGFILE}" > /dev/null << EOF
${1}
EOF
fi
fi
if [ ! "$INSTALLED" = "1" ] ; then
echo "${1}"
fi
}
abort() {
doLog "Fatal error: ${1}"
exit 1
}
# abort without logging or requiring sudo
earlyAbort() {
echo "Fatal error: ${1}" > /dev/stderr
exit 1
}
getDarwinMajorVersion() {
darwin_ver=$(uname -r)
# Cannot add -r in earlier macOS
# shellcheck disable=SC2162
IFS="." read -a darwin_ver_array <<< "$darwin_ver"
echo "${darwin_ver_array[0]}"
}
installLog() {
if [ ! -d "${LOGDIR}" ] ; then
# We intentionally don't use -p as something is probably
# weird if parent dirs don't exist here
sudo mkdir "${LOGDIR}" || abort "Failed to mkdir for logfile!"
fi
if [ ! -f "${LOGFILE}" ] ; then
sudo touch "${LOGFILE}" || abort "Failed to touch logfile!"
fi
if [ "$AGENT" = "1" ] ; then
sudo chmod 666 "${LOGFILE}" || abort "Failed to chmod logfile!"
fi
}
install() {
FAIL="Failed to install!"
if [ ! -d "${PRIVILEGED_HELPER_TOOLS}" ] ; then
sudo mkdir "${PRIVILEGED_HELPER_TOOLS}" || abort "${FAIL}"
fi
# Copy executable files into place.
sudo cp "${SELFNAME}" "${HELPER}" || abort "${FAIL}"
sudo cp nvramdump "${NVRAMDUMP}" || abort "${FAIL}"
# Install logout hook.
if [ "$LOGOUT" = "1" ] ; then
sudo defaults write com.apple.loginwindow LogoutHook "${HELPER}" || abort "${FAIL}"
fi
if [ "$DAEMON" = "1" ] ; then
# Make customised Launchd.command.plist for daemon.
if [ "$BOTH" = "1" ] ; then
LAUNCHFILE_DAEMON="${LAUNCHFILE_DAEMON} (1 of 2)"
fi
if [ -f "$LAUNCHFILE_DAEMON" ] ; then
sudo rm "$LAUNCHFILE_DAEMON" || abort "${FAIL}"
fi
sudo ln -s "$(which sh)" "$LAUNCHFILE_DAEMON" || abort "${FAIL}"
sed "s/\$LABEL/${ORG}.daemon/g;s/\$HELPER/$(sed 's/\//\\\//g' <<< "$HELPER")/g;s/\$PARAM/${DAEMON_PARAMS}/g;s/\$LOGFILE/$(sed 's/\//\\\//g' <<< "$LOGFILE")/g;s/\$LAUNCHFILE/$(sed 's/\//\\\//g' <<< "$LAUNCHFILE_DAEMON")/g" "Launchd.command.plist" > "/tmp/Launchd.command.plist" || abort "${FAIL}"
sudo cp "/tmp/Launchd.command.plist" "${DAEMON_PLIST}" || abort "${FAIL}"
rm -f /tmp/Launchd.command.plist
# Launch already installed daemon.
sudo launchctl load "${DAEMON_PLIST}" || abort "${FAIL}"
fi
if [ "$AGENT" = "1" ] ; then
# Make customised Launchd.command.plist for agent.
if [ "$BOTH" = "1" ] ; then
LAUNCHFILE_AGENT="${LAUNCHFILE_AGENT} (2 of 2)"
fi
if [ -f "$LAUNCHFILE_AGENT" ] ; then
sudo rm "$LAUNCHFILE_AGENT" || abort "${FAIL}"
fi
sudo ln -s "$(which sh)" "$LAUNCHFILE_AGENT" || abort "${FAIL}"
sed "s/\$LABEL/${ORG}.agent/g;s/\$HELPER/$(sed 's/\//\\\//g' <<< "$HELPER")/g;s/\$PARAM/${AGENT_PARAMS}/g;s/\$LOGFILE/$(sed 's/\//\\\//g' <<< "$LOGFILE")/g;s/\$LAUNCHFILE/$(sed 's/\//\\\//g' <<< "$LAUNCHFILE_AGENT")/g" "Launchd.command.plist" > "/tmp/Launchd.command.plist" || abort "${FAIL}"
sudo cp "/tmp/Launchd.command.plist" "${AGENT_PLIST}" || abort "${FAIL}"
rm -f /tmp/Launchd.command.plist
# Launch already installed agent.
sudo launchctl bootstrap gui/501 "${AGENT_PLIST}" || abort "${FAIL}"
fi
echo "Installed."
}
uninstall() {
UNINSTALLED=1
if [ "$DAEMON" = "1" ] || [ "$AGENT" = "1" ] ; then
if [ "$DAEMON_PID" = "" ] ; then
UNINSTALLED=2
if [ ! "$INSTALL" = "1" ] && [ "$DAEMON" = "1" ] ; then
echo "Daemon was not installed!" > /dev/stderr
fi
else
# Place special value in saved device node so that nvram.plist is not updated at uninstall
sudo /usr/sbin/nvram "${BOOT_NODE}=null" || abort "Failed to save null boot device!"
sudo launchctl unload "${DAEMON_PLIST}" || UNINSTALLED=0
DAEMON_PID=
fi
sudo rm -f "${DAEMON_PLIST}"
sudo rm -f "${LAUNCHFILE_DAEMON}"*
fi
if [ "$AGENT" = "1" ] || [ "$DAEMON" = "1" ] ; then
if [ "$AGENT_PID" = "" ] ; then
UNINSTALLED=2
if [ ! "$INSTALL" = "1" ] && [ "$AGENT" = "1" ] ; then
echo "Agent was not installed!" > /dev/stderr
fi
else
# Place special value in saved device node so that nvram.plist is not updated at uninstall
sudo /usr/sbin/nvram "${BOOT_NODE}=null" || abort "Failed to save null boot device!"
sudo launchctl bootout "${AGENT_ID}" || UNINSTALLED=0
AGENT_PID=
fi
sudo rm -f "${AGENT_PLIST}"
sudo rm -f "${LAUNCHFILE_AGENT}"*
fi
if [ "$LOGOUT" = "1" ] ; then
if [ "$LOGOUT_HOOK" = "" ] ; then
UNINSTALLED=2
echo "Logout hook was not installed!" > /dev/stderr
else
sudo defaults delete com.apple.loginwindow LogoutHook || UNINSTALLED=0
LOGOUT_HOOK=
fi
fi
if [ "$DAEMON_PID" = "" ] &&
[ "$AGENT_PID" = "" ] &&
[ "$LOGOUT_HOOK" = "" ] ; then
sudo rm -f "${HELPER}"
sudo rm -f "${NVRAMDUMP}"
fi
if [ ! "$INSTALL" = "1" ] ; then
if [ "$UNINSTALLED" = "0" ] ; then
echo "Could not uninstall!"
elif [ "$UNINSTALLED" = "1" ] ; then
echo "Uninstalled."
fi
fi
}
status() {
if [ "$DAEMON" = "1" ] ; then
# Detailed info on daemon.
sudo launchctl print "$DAEMON_ID"
elif [ "$AGENT" = "1" ] ; then
# Detailed info on agent.
sudo launchctl print "$AGENT_ID"
else
echo "Daemon pid = ${DAEMON_PID}"
echo "Agent pid = ${AGENT_PID}"
if [ ! "$BOTH" = "1" ] ; then
echo "LogoutHook = ${LOGOUT_HOOK}"
fi
fi
}
# On earlier macOS (at least Mojave in VMWare) there is an intermittent
# problem where msdos/FAT kext is occasionally not available when we try
# to mount the drive on daemon exit; mounting and unmounting on daemon
# start here fixes this.
# Doing this introduces a different problem, since this early mount sometimes
# fails initially, however (as with 'diskutil info' above) in that case the
# script gets restarted by launchd after ~5s, and eventually either succeeds or
# finds the drive already mounted (when applicable, i.e. not marked as ESP).
#
# On later macOS (Sonoma) this problem recurs, and the strategy of co-ordinated
# daemon and user agent was introduced to handle this (see saveNvram()):
#
# At startup:
# - daemon waits to see agent
# - daemon mounts ESP
# - daemon kills agent, then waits 1s
# - agent:
# o traps kill
# o touches nvram.plist (which forces kext to be loaded)
# o exits
# - after 1s wait daemon unmounts ESP
#
# At shutdown:
# - daemon mounts ESP and writes nvram.plist
# o this only works if the above palaver at startup has happened recently
#
earlyMount() {
mount_path=$(mount | sed -n "s:${node} on \(.*\) (.*$:\1:p")
if [ ! "${mount_path}" = "" ] ; then
doLog "Early mount not needed, already mounted at ${mount_path}"
else
sudo /usr/sbin/diskutil mount "${node}" 1>/dev/null || abort "Early mount failed!"
sleep 1 & wait
sudo /usr/sbin/diskutil unmount "${node}" 1>/dev/null || abort "Early unmount failed!"
doLog "Early mount/unmount succeeded"
fi
}
# Save some diskutil info in emulated NVRAM for use at daemon shutdown:
# - While we can access diskutil normally at agent startup and at logout hook;
# - We cannot use diskutil at daemon shutdown, because:
# "Unable to run because unable to use the DiskManagement framework.
# Common reasons include, but are not limited to, the DiskArbitration
# framework being unavailable due to being booted in single-user mode."
# - At daemon startup, diskutil works but the device may not be ready
# immediately, but macOS restarts us quickly (~5s) and then we can run.
# Note that saving any info for use at process shutdown if not running as
# daemon (sudo) would have to go into e.g. a file not nvram.
#
# $1 = 0 - Set node var, but do not save in emulated NVRAM
# $1 = 1 - Set node var and save in emulated NVRAM
#
saveMount() {
UUID="$(/usr/sbin/nvram "${BOOT_PATH}" | sed 's/.*GPT,\([^,]*\),.*/\1/')"
if [ "$(printf '%s' "${UUID}" | /usr/bin/wc -c)" -eq 36 ] && [ -z "$(echo "${UUID}" | sed 's/[-0-9A-F]//g')" ] ; then
node="$(/usr/sbin/diskutil info "${UUID}" | sed -n 's/.*Device Node: *//p')"
# This may randomly fail initially, if so the script gets restarted by
# launchd and eventually succeeds.
if [ "${node}" = "" ] ; then
abort "Cannot access device node!"
fi
doLog "Found boot device at ${node}"
if [ "${1}" = "1" ] ; then
if [ "$BOTH" = "1" ] &&
[ "$DAEMON" = "1" ]; then
doLog "Using both agent and daemon, delaying early mount"
else
earlyMount
fi
# Use hopefully emulated NVRAM as temporary storage for the boot
# device node discovered with diskutil.
# If we are in emulated NVRAM, should not appear at next boot as
# nvramdump does not write values from OC GUID back to nvram.plist.
sudo /usr/sbin/nvram "${BOOT_NODE}=${node}" || abort "Failed to store boot device!"
fi
else
abort "Missing or invalid ${BOOT_PATH} value!"
fi
}
# $1 = 0 - Use existing node var, do not fetch from emulated NVRAM
# $1 = 1 - Use node var saved in emulated NVRAM
#
# $2 = 0 - Just touch nvram.plist
# $2 = 1 - Really save NVRAM state to nvram.plist
# $2 = 2 - Just pre-mount
#
saveNvram() {
if [ "${1}" = "1" ] ; then
# . matches tab, note that \t for tab cannot be used in earlier macOS (e.g Mojave)
node=$(/usr/sbin/nvram "$BOOT_NODE" | sed -n "s/${BOOT_NODE}.//p")
if [ "$2" = "1" ]; then
if [ ! "$INSTALLED" = "1" ] ; then
# don't trash saved value if daemon or agent is live
launchctl print "$DAEMON_ID" 2>/dev/null 1>/dev/null || \
launchctl print "$AGENT_ID" 2>/dev/null 1>/dev/null || \
sudo /usr/sbin/nvram -d "$BOOT_NODE"
else
sudo /usr/sbin/nvram -d "$BOOT_NODE"
fi
fi
fi
if [ "${node}" = "" ] ; then
abort "Cannot access saved device node!"
elif [ "${node}" = "null" ] ; then
if [ ! "$AGENT" = "1" ] ; then
sudo /usr/sbin/nvram "${BOOT_NODE}=" || abort "Failed to remove boot node variable!"
fi
doLog "Uninstalling…"
return
fi
if [ "$INSTALLED" = "1" ] &&
[ "$AGENT" = "1" ] ; then
if /usr/sbin/nvram "${DAEMON_WAITING}" 1>/dev/null 2>/dev/null ; then
echo -n
else
doLog "Daemon is not waiting, stopping..."
return
fi
fi
mount_path=$(mount | sed -n "s:${node} on \(.*\) (.*$:\1:p")
if [ ! "${mount_path}" = "" ] ; then
doLog "Found mounted at ${mount_path}"
if [ "$DAEMON" = "1" ] &&
[ "$BOTH" = "1" ] &&
[ ! "$2" = "2" ] &&
[ "$(getDarwinMajorVersion)" -ge ${LAUNCHD_BOTH} ] ; then
doLog "WARNING: User-mounted ESP may not save NVRAM successfully"
fi
else
if [ "$INSTALLED" = "1" ] &&
[ "$AGENT" = "1" ] ; then
# This should have been pre-mounted for us by daemon
abort "Agent cannot mount ESP!"
fi
# use reasonably assumed unique path
mount_path="/Volumes/${UNIQUE_DIR}"
sudo mkdir -p "${mount_path}" || abort "Failed to make directory!"
sudo mount -t msdos "${node}" "${mount_path}" || abort "Failed to mount!"
doLog "Mounted at ${mount_path}"
MOUNTED=1
fi
if [ "${NVRAM_DIR}" = "" ] ; then
nvram_dir="${mount_path}"
else
nvram_dir="${mount_path}/${NVRAM_DIR}"
if [ ! -d "${nvram_dir}" ] ; then
mkdir "${nvram_dir}" || abort "Failed to make directory ${nvram_dir}"
fi
fi
if [ "${2}" = "2" ] ; then
doLog "Killing agent…"
sudo /usr/sbin/nvram "${DAEMON_WAITING}=1" || abort "Failed to set daemon-waiting variable!"
kill "$3" || abort "Cannot kill agent!"
if [ "$BOTH" = "1" ] ; then
sleep 1 & wait
sudo /usr/sbin/nvram "${DAEMON_WAITING}=" || abort "Failed to remove daemon-waiting variable!"
fi
elif [ ! "${2}" = "1" ] ; then
# Just touch nvram.plist
touch "${nvram_dir}/nvram.plist" || abort "Failed to touch nvram.plist!"
doLog "Touched nvram.plist"
else
# Really save NVRAM
rm -f /tmp/nvram.plist
${USE_NVRAMDUMP} || abort "failed to save nvram.plist!"
if [ -f "${nvram_dir}/nvram.plist" ] ; then
cp "${nvram_dir}/nvram.plist" "${nvram_dir}/nvram.fallback" || abort "Failed to create nvram.fallback!"
doLog "Copied nvram.fallback"
fi
cp /tmp/nvram.plist "${nvram_dir}/nvram.plist" || abort "Failed to copy nvram.plist!"
doLog "Saved nvram.plist"
rm -f /tmp/nvram.plist
if [ -f "${nvram_dir}/nvram.used" ] ; then
rm "${nvram_dir}/nvram.used" || abort "Failed to delete nvram.used!"
doLog "Deleted nvram.used"
fi
fi
# We would like to always unmount here if we made the mount point, but at exit of
# installed daemon umount fails with "Resource busy" and diskutil is not available.
# This should not cause any problem except that the boot drive will be left mounted
# at the unique path if the daemon process gets killed (the process would then be
# restarted by macOS and NVRAM should still be saved at exit).
while [ "$MOUNTED" = "1" ] ;
do
# For logout hook or if running in immediate mode, we can clean up.
if [ "$LOGOUT" = "1" ] ||
[ ! "$INSTALLED" = "1" ] ||
[ "$2" = "2" ] ; then
# Sometimes needed after directory access...
# # if [ ! "$2" = "2" ] ; then
# # sleep 1 & wait
# # fi
# Depending on how installed and macOS version, either unmount may be needed.
# (On Lion failure of 'diskutil unmount' may be to say volume is already unmounted when it is not.)
MOUNTED=0
sudo diskutil unmount "${node}" 1>/dev/null 2>/dev/null || MOUNTED=1
if [ "$MOUNTED" = "0" ] ; then
doLog "Unmounted with diskutil"
else
sudo umount "${node}" && MOUNTED=0
if [ "$MOUNTED" = "0" ] ; then
sudo rmdir "${mount_path}" || abort "Failed to remove directory!"
doLog "Unmounted with umount"
else
if [ "$INSTALLED" = "1" ] ; then
abort "Failed to unmount!"
else
doLog "Retrying…"
fi
fi
fi
fi
done
}
onComplete() {
doLog "Trap ${1}"
if [ "$DAEMON" = "1" ] ; then
saveNvram 1 1
elif [ "$AGENT" = "1" ] ; then
saveNvram 1 0
fi
doLog "Ended."
# Required if running directly (launchd kills any orphaned child processes by default).
# ShellCheck ref: https://github.com/koalaman/shellcheck/issues/648#issuecomment-208306771
# shellcheck disable=SC2046
kill $(jobs -p)
exit 0
}
if [ ! -x /usr/bin/dirname ] ||
[ ! -x /usr/bin/basename ] ||
[ ! -x /usr/bin/wc ] ||
[ ! -x /usr/sbin/diskutil ] ||
[ ! -x /usr/sbin/nvram ] ; then
earlyAbort "Unix environment is broken!"
fi
NVRAM_DIR="NVRAM"
OCNVRAMGUID="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"
# Darwin version from which we can wake a sleeping daemon, corresponds to Yosemite and upwards.
LAUNCHD_DARWIN=14
# Darwin version for which we need both agent and daemon, corresponds to Sonoma and upwards.
LAUNCHD_BOTH=23
# OC generated NVRAM var
BOOT_PATH="${OCNVRAMGUID}:boot-path"
# temp storage vars for this script
BOOT_NODE="${OCNVRAMGUID}:boot-node"
DAEMON_WAITING="${OCNVRAMGUID}:daemon-waiting"
# re-use as unique directory name for mount point when needed
UNIQUE_DIR="${BOOT_NODE}"
PRIVILEGED_HELPER_TOOLS="/Library/PrivilegedHelperTools"
ORG="org.acidanthera.nvramhook"
NVRAMDUMP="${PRIVILEGED_HELPER_TOOLS}/${ORG}.nvramdump.helper"
DAEMON_ID="system/${ORG}.daemon"
DAEMON_PLIST="/Library/LaunchDaemons/${ORG}.daemon.plist"
DAEMON_PARAMS="daemon"
AGENT_ID="gui/501/${ORG}.agent"
AGENT_PLIST="/Library/LaunchAgents/${ORG}.agent.plist"
AGENT_PARAMS="agent"
HELPER="${PRIVILEGED_HELPER_TOOLS}/${ORG}.helper"
LAUNCHFILE_DAEMON="${PRIVILEGED_HELPER_TOOLS}/${ORG} System Daemon"
LAUNCHFILE_AGENT="${PRIVILEGED_HELPER_TOOLS}/${ORG} User Agent"
LOGDIR="/var/log/${ORG}.launchd"
# launchd .plist configuration is set in install() to redirect stdout and stderr (useful) to LOGFILE anyway.
LOGFILE="${LOGDIR}/launchd.log"
SELFDIR="$(/usr/bin/dirname "${0}")"
SELFNAME="$(/usr/bin/basename "${0}")"
# -R sets this, on newer macOS only; date format is required because root from user sudo and root
# when running daemons or logout hooks are picking up different date formats.
DATEFORMAT="%a, %d %b %Y %T %z"
# Detect whether we're running renamed, i.e. installed copy, or else presumably from distribution directory.
if [ ! "$SELFNAME" = "Launchd.command" ] ; then
USE_NVRAMDUMP="${NVRAMDUMP}"
INSTALLED=1
else
cd "${SELFDIR}" || earlyAbort "Failed to enter working directory!"
if [ ! -x ./nvramdump ] ; then
earlyAbort "Compiled nvramdump is not found!"
fi
USE_NVRAMDUMP="./nvramdump"
fi
for arg;
do
case $arg in
install )
INSTALL=1
;;
uninstall )
UNINSTALL=1
;;
daemon )
DAEMON=1
;;
"daemon both" )
DAEMON=1
BOTH=1
;;
agent )
AGENT=1
;;
"agent both" )
AGENT=1
BOTH=1
;;
both )
BOTH=1
;;
logout )
LOGOUT=1
;;
status )
STATUS=1
;;
# Usage for invalid param.
# Note script called as logout hook gets passed name of user logging out as a param.
* )
if [ ! "$INSTALLED" = "1" ] ; then
usage
exit 0
fi
;;
esac
done
# Get root permissions early if immediate mode.
if [ "$INSTALL" = "1" ] ||
[ "$UNINSTALL" = "1" ] ||
[ "$STATUS" = "1" ] ; then
if [ "$(whoami)" = "root" ] ; then
earlyAbort "Do not run this script as root!"
fi
sudo echo -n || earlyAbort "Could not obtain sudo!"
if [ "$INSTALL" = "1" ] ; then
UNINSTALL=1
fi
if [ "$UNINSTALL" = "1" ] ||
[ "$STATUS" = "1" ] ; then
# For agent/daemon pid prefer 'launchctl list' as it works on older macOS where 'launchtl print' does not.
DAEMON_PID="$(sudo launchctl list | sed -n "s/\([0-9\-]*\).*${ORG}.daemon/\1/p" | sed 's/-/Failed to start!/')"
AGENT_PID="$(launchctl list | sed -n "s/\([0-9\-]*\).*${ORG}.agent/\1/p" | sed 's/-/Failed to start!/')"
LOGOUT_HOOK="$(sudo defaults read com.apple.loginwindow LogoutHook 2>/dev/null)"
fi
fi
# If not told what to do, work it out.
# When running as daemon, 'daemon' is specified as a param.
if [ ! "$DAEMON" = "1" ] &&
[ ! "$AGENT" = "1" ] &&
[ ! "$BOTH" = "1" ] &&
[ ! "$LOGOUT" = "1" ] ; then
if [ "$INSTALL" = "1" ] ||
[ "$UNINSTALL" = "1" ] ; then
# When not specified, choose to (un)install daemon or logout hook depending on macOS version.
if [ "$(getDarwinMajorVersion)" -ge ${LAUNCHD_BOTH} ] ; then
echo "Darwin $(uname -r) >= ${LAUNCHD_BOTH}, using both agent and daemon"
BOTH=1
AGENT=1
DAEMON=1
elif [ "$(getDarwinMajorVersion)" -ge ${LAUNCHD_DARWIN} ] ; then
echo "Darwin $(uname -r) >= ${LAUNCHD_DARWIN}, using daemon"
DAEMON=1
else
echo "Darwin $(uname -r) < ${LAUNCHD_DARWIN}, using logout hook."
LOGOUT=1
fi
else
if [ "$INSTALLED" = "1" ] ; then
# If installed with no type as a param, we are installed as logout hook.
LOGOUT=1
elif [ ! "$STATUS" = "1" ] ; then
# Usage for no params.
usage
exit 0
fi
fi
fi
if [ ! "$INSTALLED" = "1" ] &&
[ "$BOTH" = "1" ] ; then
LOG_PREFIX="Both"
AGENT=1
DAEMON=1
AGENT_PARAMS="${AGENT_PARAMS} both"
DAEMON_PARAMS="${DAEMON_PARAMS} both"
elif [ "$DAEMON" = "1" ] ; then
LOG_PREFIX="Daemon"
elif [ "$AGENT" = "1" ] ; then
LOG_PREFIX="Agent"
elif [ "$LOGOUT" = "1" ] ; then
LOG_PREFIX="Logout"
fi
if [ ! "$INSTALLED" = "1" ] ; then
LOG_PREFIX="${LOG_PREFIX}-Immediate"
fi
if [ "$INSTALL" = "1" ] &&
[ "$AGENT" = "1" ] &&
[ ! "$DAEMON" = "1" ] ; then
earlyAbort "Standalone agent install is not supported!"
fi
if [ "$UNINSTALL" = "1" ] ; then
msg="$(uninstall)"
if [ ! "${msg}" = "" ] ; then
doLog "${msg}"
fi
if [ ! "$INSTALL" = "1" ] ; then
exit 0
fi
fi
if [ "$INSTALL" = "1" ] ; then
installLog
# Save NVRAM immediately, this will become the fallback NVRAM after the first shutdown.
# Do not install if this fails, since this indicates that required boot path from OC is
# not available, or other fatal error.
if [ "$DAEMON" = "1" ] ||
[ "$LOGOUT" = "1" ] ; then
doLog "Installing…"
doLog "Saving initial nvram.plist…"
saveMount 0
if [ "$DAEMON" = "1" ] ; then
# daemon
saveNvram 0 1
else
# logout hook
saveNvram 0 1
fi
fi
install
exit 0
fi
if [ "$STATUS" = "1" ] ; then
status
exit 0
fi
if [ "${LOGOUT}" = "1" ] ; then
saveMount 0
saveNvram 0 1
exit 0
fi
# Useful for trapping all signals to see what we get.
#for s in {1..31} ;do trap "onComplete $s" $s ;done
# Trap CTRL+C for testing for immediate mode, and trap normal or forced daemon
# or agent termination. Separate trap commands so we can log which was caught.
trap "onComplete SIGINT" SIGINT
trap "onComplete SIGTERM" SIGTERM
doLog "Starting…"
if [ "$DAEMON" = "1" ] ; then
saveMount 1
fi
if [ "$DAEMON" = "1" ] &&
[ "$BOTH" = "1" ] ; then
agent_pid=""
doLog "Waiting for agent…"
while [ "$agent_pid" = "" ] ;
do
agent_pid="$(pgrep -f "$(echo "${HELPER} agent" | sed "s/\./\\\./g")")"
if [ "$agent_pid" = "" ] ; then
# https://apple.stackexchange.com/a/126066/113758
# Only works from Yosemite upwards.
sleep 5 & wait
fi
done
doLog "Found agent…"
saveNvram 1 2 "$agent_pid"
fi
while true
do
doLog "Running…"
# https://apple.stackexchange.com/a/126066/113758
# Only works from Yosemite upwards.
sleep $RANDOM & wait
done