{"id":311,"date":"2016-12-23T14:41:02","date_gmt":"2016-12-23T06:41:02","guid":{"rendered":"https:\/\/www.zhuyanbin.com\/?p=311"},"modified":"2016-12-23T14:57:16","modified_gmt":"2016-12-23T06:57:16","slug":"mac-os-x-launchd-is-cool","status":"publish","type":"post","link":"https:\/\/www.yanbin888.com\/?p=311","title":{"rendered":"Mac OS X: Launchd Is Cool"},"content":{"rendered":"<p>One of the core components of Mac OS X is <a href=\"http:\/\/en.wikipedia.org\/wiki\/Launchd\"><b>launchd<\/b><\/a>, and it turns out it can do some cool things.<\/p>\n<p>I particularly like the idea of using <b>QueueDirectories<\/b> to monitor and act upon files dropped into a directory, without having to run any extra daemons. The files could be uploaded to S3, transcoded to a different video format, gzipped\u2026 anything.<\/p>\n<p>Anyway, I recently fell into the <b>launchd<\/b> documentation, and came out with this write-up. <a href=\"https:\/\/twitter.com\/pda\">Let me know<\/a> if you find it useful.<\/p>\n<h2>Overview<\/h2>\n<p>The first thing that the Mac OS kernel runs on boot is launchd, which bootstraps the rest of the system by loading and managing various daemons, agents, scripts and other processes. The <a href=\"https:\/\/developer.apple.com\/library\/mac\/#documentation\/Darwin\/Reference\/ManPages\/man8\/launchd.8.html\">launchd man page<\/a> clarifies the difference between a daemon and an agent:<\/p>\n<blockquote><p>In the launchd lexicon, a \u201cdaemon\u201d is, by definition, a system-wide service of which there is one instance for all clients. An \u201cagent\u201d is a service that runs on a per-user basis. Daemons should not attempt to display UI or interact directly with a user\u2019s login session. Any and all work that involves interacting with a user should be done through agents.<\/p><\/blockquote>\n<p>Daemons and agents are declared and configured by creating <b>.plist<\/b> files in various locations of the system:<\/p>\n<pre>~\/Library\/LaunchAgents         Per-user agents provided by the user.\r\n\/Library\/LaunchAgents          Per-user agents provided by the administrator.\r\n\/Library\/LaunchDaemons         System-wide daemons provided by the administrator.\r\n\/System\/Library\/LaunchAgents   Per-user agents provided by OS X.\r\n\/System\/Library\/LaunchDaemons  System-wide daemons provided by OS X.\r\n<\/pre>\n<p>Perhaps best of all, launchd is open source under the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Apache_License\">Apache License 2.0<\/a>. You can currently find the <a href=\"http:\/\/www.opensource.apple.com\/release\/mac-os-x-1081\/\">latest source code on the Apple Open Source site<\/a>.<\/p>\n<h2>launchd as cron<\/h2>\n<p>The <a href=\"https:\/\/developer.apple.com\/library\/mac\/#documentation\/Darwin\/Reference\/ManPages\/man1\/crontab.1.html\">Mac OS crontab<\/a> man page says:<\/p>\n<pre>Although cron(8) and crontab(5) are officially supported under Darwin,\r\ntheir functionality has been absorbed into launchd(8), which provides a\r\nmore flexible way of automatically executing commands.\r\n<\/pre>\n<p>Turns out <b>launchd<\/b> has a simple <b>StartInterval &lt;integer&gt;<\/b> property, which starts the job every N seconds. However the true cron-like power lies in <b>StartCalendarInterval<\/b>:<\/p>\n<pre>StartCalendarInterval &lt;dictionary of integers or array of dictionary of integers&gt;\r\n\r\nThis optional key causes the job to be started every calendar interval as\r\nspecified. Missing arguments are considered to be wildcard. The semantics\r\nare much like crontab(5).  Unlike cron which skips job invocations when the\r\ncomputer is asleep, launchd will start the job the next time the computer\r\nwakes up.  If multiple intervals transpire before the computer is woken,\r\nthose events will be coalesced into one event upon wake from sleep.\r\n\r\n     Minute &lt;integer&gt;\r\n     The minute on which this job will be run.\r\n\r\n     Hour &lt;integer&gt;\r\n     The hour on which this job will be run.\r\n\r\n     Day &lt;integer&gt;\r\n     The day on which this job will be run.\r\n\r\n     Weekday &lt;integer&gt;\r\n     The weekday on which this job will be run (0 and 7 are Sunday).\r\n\r\n     Month &lt;integer&gt;\r\n     The month on which this job will be run.\r\n<\/pre>\n<p>Lets find the shortest example of this in action:<\/p>\n<pre>pda@paulbook ~ &gt; grep -rl StartCalendarInterval \\\r\n                   \/Library\/Launch* \/System\/Library\/Launch* | \\\r\n                   xargs wc -l | sort -n | head -n1 | awk '{print $2}' | xargs cat\r\n\r\n&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n&lt;!DOCTYPE plist PUBLIC \"-\/\/Apple\/\/DTD PLIST 1.0\/\/EN\" \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\"&gt;\r\n&lt;plist version=\"1.0\"&gt;\r\n&lt;dict&gt;\r\n        &lt;key&gt;Label&lt;\/key&gt;\r\n        &lt;string&gt;com.apple.gkreport&lt;\/string&gt;\r\n        &lt;key&gt;ProgramArguments&lt;\/key&gt;\r\n        &lt;array&gt;\r\n                &lt;string&gt;\/usr\/libexec\/gkreport&lt;\/string&gt;\r\n        &lt;\/array&gt;\r\n        &lt;key&gt;StartCalendarInterval&lt;\/key&gt;\r\n        &lt;dict&gt;\r\n                &lt;key&gt;Minute&lt;\/key&gt;&lt;integer&gt;52&lt;\/integer&gt;\r\n                &lt;key&gt;Hour&lt;\/key&gt;&lt;integer&gt;3&lt;\/integer&gt;\r\n                &lt;key&gt;WeekDay&lt;\/key&gt;&lt;integer&gt;5&lt;\/integer&gt;\r\n        &lt;\/dict&gt;\r\n&lt;\/dict&gt;\r\n&lt;\/plist&gt;\r\n<\/pre>\n<p>Better than cron? Apart from better handling of skipped jobs after system wake, it also supports per-job environment variables, which can save writing wrapper scripts around your cron jobs:<\/p>\n<pre>EnvironmentVariables &lt;dictionary of strings&gt;\r\n\r\nThis optional key is used to specify additional environmental variables to\r\nbe set before running the job.\r\n<\/pre>\n<p>So, anything XML is obviously worse than <b>0 52 3 * 5 \/path\/to\/command<\/b>, but <b>launchd<\/b> is packing more features than cron, so it can pull it off.<\/p>\n<h2>launchd as a filesystem watcher<\/h2>\n<p>Apart from having an awesome daemon\/agent manager, Mac OS X also has an excellent Mail Transport Agent called <a href=\"http:\/\/en.wikipedia.org\/wiki\/Postfix_(software)\">postfix<\/a>. There\u2019s a good chance your ISP runs the same software to handle millions of emails every day. We\u2019ll be using it as an example of how <b>launchd<\/b> can start jobs based on filesystem changes.<\/p>\n<p>Because your laptop isn\u2019t, and shouldn\u2019t be, a mail server, you don\u2019t want postfix running all the time. But when messages are injected into it, e.g. by a script shelling out to <b>\/usr\/sbin\/sendmail<\/b> or <b>\/usr\/bin\/mail<\/b>, you want them to be delivered straight away.<\/p>\n<p>Here\u2019s how Mac OS X does it (<b>\/System\/Library\/LaunchDaemons\/org.postfix.master.plist<\/b>):<\/p>\n<pre>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\r\n&lt;!DOCTYPE plist PUBLIC \"-\/\/Apple Computer\/\/DTD PLIST 1.0\/\/EN\" \"http:\/\/www.apple.com\/DTDs\/PropertyList-1.0.dtd\"&gt;\r\n&lt;plist version=\"1.0\"&gt;\r\n&lt;dict&gt;\r\n    &lt;key&gt;Label&lt;\/key&gt;\r\n    &lt;string&gt;org.postfix.master&lt;\/string&gt;\r\n    &lt;key&gt;Program&lt;\/key&gt;\r\n    &lt;string&gt;\/usr\/libexec\/postfix\/master&lt;\/string&gt;\r\n    &lt;key&gt;ProgramArguments&lt;\/key&gt;\r\n    &lt;array&gt;\r\n        &lt;string&gt;master&lt;\/string&gt;\r\n        &lt;string&gt;-e&lt;\/string&gt;\r\n        &lt;string&gt;60&lt;\/string&gt;\r\n    &lt;\/array&gt;\r\n    &lt;key&gt;QueueDirectories&lt;\/key&gt;\r\n    &lt;array&gt;\r\n        &lt;string&gt;\/var\/spool\/postfix\/maildrop&lt;\/string&gt;\r\n    &lt;\/array&gt;\r\n    &lt;key&gt;AbandonProcessGroup&lt;\/key&gt;\r\n    &lt;true\/&gt;\r\n&lt;\/dict&gt;\r\n&lt;\/plist&gt;\r\n<\/pre>\n<p>We\u2019ll start with the simple part. <b>ProgramArguments<\/b> passes <b>-e 60<\/b> to postfix, <a href=\"http:\/\/www.postfix.org\/master.8.html\">described thusly<\/a>:<\/p>\n<pre>-e exit_time\r\n              Terminate the master process after exit_time seconds.\r\n              Child processes terminate at their convenience.\r\n<\/pre>\n<p>So postfix is told to exit after running for 60 seconds. The mystery (to me, earlier today, at least) is how it gets started. It could be on a cron-like schedule, but (a) it isn\u2019t, (b) that would suck, and (c) it would result in delayed mail delivery. It turns out the magic lies in <b>QueueDirectory<\/b>, which I initially overlooked thinking it was a postfix option. The <a href=\"https:\/\/developer.apple.com\/library\/mac\/#documentation\/Darwin\/Reference\/ManPages\/man5\/launchd.plist.5.html\">launchd.plist<\/a> man page says:<\/p>\n<pre>WatchPaths &lt;array of strings&gt;\r\nThis optional key causes the job to be started if any one of the listed\r\npaths are modified.\r\n\r\nQueueDirectories &lt;array of strings&gt;\r\nMuch like the WatchPaths option, this key will watch the paths for\r\nmodifications. The difference being that the job will only be started if\r\nthe path is a directory and the directory is not empty.<\/pre>\n<p>The <a href=\"http:\/\/en.wikipedia.org\/wiki\/Launchd\">Launchd Wikipedia page<\/a> actually goes into more detail:<\/p>\n<pre>QueueDirectories\r\nWatch a directory for new files. The directory must be empty to begin with,\r\nand must be returned to an empty state before QueueDirectories will launch\r\nits task again.\r\n<\/pre>\n<p>So <b>launchd<\/b> can monitor a directory for new files, and then trigger an agent\/daemon to consume them. In this case, the <a href=\"http:\/\/www.postfix.org\/sendmail.1.html\">postfix sendmail(1) man page<\/a> tells us that \u201cPostfix sendmail(1) relies on the postdrop(1) command to create a queue file in the maildrop directory\u201d, and the <a href=\"http:\/\/www.postfix.org\/postdrop.1.html\">man page for postdrop(1)<\/a> tells us that <b>\/var\/spool\/postfix\/maildrop<\/b> is the maildrop queue. <b>launchd<\/b> sees new mail there, fires up postfix, and then stops it after 60 seconds. This might cause deferred mail to stay deferred for quite some time, but again; your laptop isn\u2019t a mail server.<\/p>\n<h2>launchd as inetd<\/h2>\n<p>Tranditionally the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Inetd\">inetd<\/a> and later <a href=\"http:\/\/en.wikipedia.org\/wiki\/Xinetd\">xinetd<\/a> \u201csuper-server daemon\u201d were used to listen on various ports (e.g. FTP, telnet, \u2026) and launch daemons on-demand to handle in-bound connection, keeping them out of memory at other times. Sounds like something <b>launchd<\/b> could do\u2026<\/p>\n<p>Lets create a simple inetd-style server at <b>~\/Library\/LaunchAgents\/my.greeter.plist<\/b>:<\/p>\n<pre>&lt;plist version=\"1.0\"&gt;\r\n&lt;dict&gt;\r\n  &lt;key&gt;Label&lt;\/key&gt;&lt;string&gt;my.greeter&lt;\/string&gt;\r\n  &lt;key&gt;ProgramArguments&lt;\/key&gt;\r\n  &lt;array&gt;\r\n    &lt;string&gt;\/usr\/bin\/ruby&lt;\/string&gt;\r\n    &lt;string&gt;-e&lt;\/string&gt;\r\n    &lt;string&gt;puts \"Hi #{gets.match(\/(\\w+)\\W*\\z\/)[1]}, happy #{Time.now.strftime(\"%A\")}!\"&lt;\/string&gt;\r\n  &lt;\/array&gt;\r\n  &lt;key&gt;inetdCompatibility&lt;\/key&gt;&lt;dict&gt;&lt;key&gt;Wait&lt;\/key&gt;&lt;false\/&gt;&lt;\/dict&gt;\r\n  &lt;key&gt;Sockets&lt;\/key&gt;\r\n  &lt;dict&gt;\r\n    &lt;key&gt;Listeners&lt;\/key&gt;\r\n    &lt;dict&gt;\r\n      &lt;key&gt;SockServiceName&lt;\/key&gt;&lt;string&gt;13117&lt;\/string&gt;\r\n    &lt;\/dict&gt;\r\n  &lt;\/dict&gt;\r\n&lt;\/dict&gt;\r\n&lt;\/plist&gt;\r\n<\/pre>\n<p>Load it up and give it a shot:<\/p>\n<pre>pda@paulbook ~ &gt; launchctl load ~\/Library\/LaunchAgents\/my.greeter.plist\r\npda@paulbook ~ &gt; echo \"My name is Paul.\" | nc localhost 13117\r\nHi Paul, happy Friday!\r\n<\/pre>\n<h2>launchd as <a href=\"http:\/\/godrb.com\/\">god<\/a>!<\/h2>\n<p>You can use launchd to ensure a process stays alive forever using &lt;key&gt;KeepAlive&lt;\/key&gt;&lt;true\/&gt;, or stays alive under the following conditions.<\/p>\n<ul>\n<li>SuccessfulExit \u2014 the previous run exited successfully (or if false, unsuccessful exit).<\/li>\n<li>NetworkState \u2014 network (other than localhost) is up (or if false, down).<\/li>\n<li>PathState \u2014 list of file paths exists (or if false, do not exist).<\/li>\n<li>OtherJobEnabled \u2014 the other named job is enabled (or if false, disabled).<\/li>\n<\/ul>\n<p>These can be combined with various other properties, for example:<\/p>\n<ul>\n<li>WorkingDirectory<\/li>\n<li>EnvironmentVariables<\/li>\n<li>Umask<\/li>\n<li>ThrottleInterval<\/li>\n<li>StartOnMount<\/li>\n<li>StandardInPath<\/li>\n<li>StandardOutPath<\/li>\n<li>StandardErrorPath<\/li>\n<li>SoftResourceLimits and HardResourceLimits<\/li>\n<li>Nice<\/li>\n<\/ul>\n<h2>More?<\/h2>\n<p>There\u2019s some more <a href=\"http:\/\/developer.apple.com\/library\/mac\/#documentation\/MacOSX\/Conceptual\/BPSystemStartup\/Chapters\/CreatingLaunchdJobs.html\">information at developer.apple.com<\/a>, and the <a href=\"https:\/\/developer.apple.com\/library\/mac\/#documentation\/Darwin\/Reference\/ManPages\/man8\/launchd.8.html\">launchd<\/a> and <a href=\"https:\/\/developer.apple.com\/library\/mac\/#documentation\/Darwin\/Reference\/ManPages\/man5\/launchd.plist.5.html\">launchd.plist<\/a> man pages are worth reading.<\/p>\n<p>link:\u00a0<a href=\"http:\/\/paul.annesley.cc\/2012\/09\/mac-os-x-launchd-is-cool\/\">http:\/\/paul.annesley.cc\/2012\/09\/mac-os-x-launchd-is-cool\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the core components of Mac OS X is launchd, and  <span class=\"ellipsis\">&hellip;<\/span> <span class=\"more-link-wrap\"><a href=\"https:\/\/www.yanbin888.com\/?p=311\" class=\"more-link\"><span>Read More &rarr;<\/span><\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[6],"class_list":["post-311","post","type-post","status-publish","format-standard","hentry","category-mac","tag-mac-os-x"],"_links":{"self":[{"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=\/wp\/v2\/posts\/311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=311"}],"version-history":[{"count":9,"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=\/wp\/v2\/posts\/311\/revisions"}],"predecessor-version":[{"id":320,"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=\/wp\/v2\/posts\/311\/revisions\/320"}],"wp:attachment":[{"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.yanbin888.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}