Run a script only at shutdown (not log off or restart) on Mac OS X

强颜欢笑 提交于 2019-11-27 14:26:11

Few days ago I published on github a configuration/script able to be executed at boot/shutdown.

Basically on Mac OS X you could/should use a System wide and per-user daemon/agent configuration file (plist) in conjunction with a bash script file. This is a sample of the plist file you could use:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>boot.shutdown.script.name</string>

<key>ProgramArguments</key>
<array>
  <string>SCRIPT_PATH/boot-shutdown.sh</string>
</array>

<key>RunAtLoad</key>
<true/>

<key>StandardOutPath</key>
<string>LOG_PATH/boot-shutdown.log</string>

<key>StandardErrorPath</key>
<string>LOG_PATH/boot-shutdown.err</string>

</dict>
</plist>

You can place this file into /Library/LaunchDaemons. There are many directories where the plist file could be placed, it depends from what you need, the rights of the process and so on.

~/Library/LaunchAgents         Per-user agents provided by the user.
/Library/LaunchAgents          Per-user agents provided by the administrator.
/Library/LaunchDaemons         System wide daemons provided by the administrator.
/System/Library/LaunchAgents   Mac OS X Per-user agents.
/System/Library/LaunchDaemons  Mac OS X System wide daemons.

This script boot-shutdown.sh will be loaded and executed at every boot/shutdown.

#!/bin/bash
function shutdown()
{

  # INSERT HERE THE COMMAND YOU WANT EXECUTE AT SHUTDOWN OR SERVICE UNLOAD

  exit 0
}

function startup()
{

  # INSERT HERE THE COMMAND YOU WANT EXECUTE AT STARTUP OR SERVICE LOAD

  tail -f /dev/null &
  wait $!
}

trap shutdown SIGTERM
trap shutdown SIGKILL

startup;

Then call launchctl command which load and unload daemons/agents.

sudo launchctl load -w /Library/LaunchDaemons/boot-shutdown-script.plist

It looks like the most straightforward way would be to write a small C++ application that would run as a daemon with launchctl, catch the shutdown notification but ignore the reboot notification (see below) and then call whatever is given to it as arguments, e.g. a shell script. It does not look like Apple provides libraries to catch those notifications in any other language.

From the "Kernel Programming" manual https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/KernelProgramming.pdf from Apple, page 150:

"Although OS X does not have traditional BSD-style shutdown hooks, the I/O Kit provides equivalent functionality in recent versions. Since the I/O Kit provides this functionality, you must call it from C++ code."

"To register for notification, you call registerSleepWakeInterest (described in IOKit/RootDomain.h) and register for sleep notification. If the system is about to be shut down, your handler is called with the message type kIOMessageSystemWillPowerOff. If the system is about to reboot, your handler gets the message type kIOMessageSystemWillRestart. If the system is about to reboot, your handler gets the message type kIOMessageSystemWillSleep."

As you can see there is a different message for reboot, so you can handle the shutdown case exclusively.

Here is a way that does work (I just tested it) but it is quite technical and not for inexperienced people... I put a wrapper around /sbin/shutdown. This will work even if you shutdown your Mac from the Apple menu in the GUI.

Basically, you need to su to root, like this, and rename the existing, Apple-supplied shutdown binary to shutdown.orig.

su -
cd /sbin
mv shutdown shutdown.orig

Then you create a bash script called shutdown that does what you want first, then execs the original Apple-supplied shutdown binary.

#!/bin/bash
Do something you want done before shutdown
exec /sbin/shutdown.orig "$@"

There are three things to watch out for...

1. Make all the permissions the same on shutdown as shutdown.orig
2. Parse the parameters to the originl shutdown and see if `-r` is one of them as this means it is a `restart` shutdown. You will also have to pass through the other parameters that Apple calls the script with - if any.
3. Apple may feel at liberty to overwrite your lovely, shiny, new `shutdown` script when updating OSX, so maybe abstract out the bulk of your personal shutdown script into another place so that you can easily re-insert a single-line call to it if/when Apple overwrites it at some point.

Be careful! And make a backup first!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!