{"id":1463,"date":"2009-02-02T15:40:07","date_gmt":"2009-02-02T18:40:07","guid":{"rendered":"http:\/\/brockerhoff.net\/bb\/viewtopic.php?p=2647"},"modified":"2010-05-08T11:34:08","modified_gmt":"2010-05-08T14:34:08","slug":"cocoa-musings-pt-3","status":"publish","type":"post","link":"https:\/\/brockerhoff.net\/blog\/2009\/02\/02\/cocoa-musings-pt-3\/","title":{"rendered":"Cocoa musings pt.3"},"content":{"rendered":"<p>It&#8217;s been a while since the last post, but I&#8217;ve finally gotten past the boring parts of the new <a href=\"\/klicko\">Klicko<\/a> System Preference pane &amp; background process. The latter is actually a <a href=\"http:\/\/developer.apple.com\/technotes\/tn2005\/tn2083.html\">per-user Launch Agent<\/a>. I&#8217;d used <a href=\"http:\/\/launchd.macosforge.org\/\">launchd<\/a> before in the last <a href=\"\/quay\">Quay<\/a> implementation, but didn&#8217;t fully understand what I was doing.<\/p>\n<p>Well, I <em>still<\/em> can&#8217;t claim I fully understand all, but I&#8217;m definitely getting better at this. As soon as Klicko 1.2 is out and working (hm, maybe that should be the other way around?) I&#8217;ll back-port everything I learned to Quay.<\/p>\n<p>One important part of messing around with launchd agents is that you have to set everything up just so &#8211; permissions of the executable <em>and<\/em> of its containing folders, as well as of the controlling plist if it&#8217;s a global agent; and you have to run <a href=\"http:\/\/developer.apple.com\/documentation\/Darwin\/Reference\/ManPages\/man1\/launchctl.1.html\">launchctl<\/a> to load (register) the controlling plist with launchd. Only then, and if the conditions inside the plist are satisfied, launchd will run your agent for you.<\/p>\n<p>Uninstalling your agent, usually for putting an updated version in its place, must also be done carefully. You have to unload it if it&#8217;s loaded (even though it may not be running at the time) before swapping in the new executable and plist. Ideally, to avoid wasting time, there should be an easy way to test if your agent is properly loaded &#8211; the user might have unloaded it manually, or whatever. Unfortunately, the only &#8220;official&#8221; way to do so is to run launchctl again, passing in the &#8220;list&#8221; parameter, and parsing the output; not ideal.<\/p>\n<p>However, there&#8217;s a primitive API in place in &lt;launch.h&gt;, and in fact launchctl uses this API itself to communicate with launchd. Still, there&#8217;s no documentation about it; only some sample code. Also, there&#8217;s this recent reply on the <a href=\"http:\/\/lists.macosforge.org\/pipermail\/launchd-dev\/2009-February\/000483.html\">launchd-dev mailing list<\/a>:<\/p>\n<blockquote><p>Right now the &lt;launch.h&gt; API is only rated for<br \/>\ndaemons checking in with launchd (ala SampleD).<br \/>\nIt is not really designed for job management.<br \/>\nRather, we recommend that folks do their job<br \/>\nmanagement by fork\/exec of launchctl.<br \/>\n&#8230;<br \/>\nQuinn &#8220;The Eskimo!&#8221;<\/p><\/blockquote>\n<p>Still, I thought it would be interesting to use this API to check on the agent; after all, if something fails you&#8217;ll get an error back, and nothing will be messed up. So here&#8217;s the code for doing so:<\/p>\n<pre><code>#include &lt;launch.h&gt;\r\n\r\nstatic id GetFromLaunchData(launch_data_t obj);\r\n\r\nstatic void Launch_data_iterate(launch_data_t obj, const char *key, void* dict) {\r\n\r\n\u00a0 \u00a0if (obj) {\r\n\u00a0 \u00a0\u00a0 \u00a0id value = GetFromLaunchData(obj);\r\n\u00a0 \u00a0\u00a0 \u00a0if(value) {\r\n\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0[(NSMutableDictionary*)dict setObject:value forKey:[NSString stringWithUTF8String:key]];\r\n\u00a0 \u00a0\u00a0 \u00a0}\r\n\u00a0 \u00a0}\r\n}\r\n\r\nstatic NSDictionary* GetFromLaunchDictionary(launch_data_t dict) {\r\n\u00a0 \u00a0NSMutableDictionary* result = NULL;\r\n\u00a0 \u00a0if (launch_data_get_type(dict)==LAUNCH_DATA_DICTIONARY) {\r\n\u00a0 \u00a0\u00a0 \u00a0result = [NSMutableDictionary dictionary];\r\n\u00a0 \u00a0\u00a0 \u00a0launch_data_dict_iterate(dict, Launch_data_iterate, result);\r\n\u00a0 \u00a0}\r\n\u00a0 \u00a0return result;\r\n}\r\n\r\nstatic NSArray* GetFromLaunchArray(launch_data_t arr) {\r\n\u00a0 \u00a0NSMutableArray* result = NULL;\u00a0 \u00a0\r\n\u00a0 \u00a0if (launch_data_get_type(arr)==LAUNCH_DATA_ARRAY) {\r\n\u00a0 \u00a0\u00a0 \u00a0size_t count = launch_data_array_get_count(arr);\r\n\u00a0 \u00a0\u00a0 \u00a0result = [NSMutableArray arrayWithCapacity:count];\r\n\u00a0 \u00a0\u00a0 \u00a0for (size_t i=0;i&lt;count;i++) {\r\n\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0id obj = GetFromLaunchData(launch_data_array_get_index(arr, i));\r\n\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0if (obj) {\r\n\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0[result addObject:obj];\r\n\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0}\r\n\u00a0 \u00a0\u00a0 \u00a0}\r\n\u00a0 \u00a0}\r\n\u00a0 \u00a0return result;\r\n}\r\n\r\nstatic id GetFromLaunchData(launch_data_t obj) {\r\n\u00a0 \u00a0switch (launch_data_get_type(obj)) {\r\n\u00a0 \u00a0case LAUNCH_DATA_STRING:\r\n\u00a0 \u00a0\u00a0 \u00a0return [NSString stringWithUTF8String:launch_data_get_string(obj)];\r\n\u00a0 \u00a0case LAUNCH_DATA_INTEGER:\r\n\u00a0 \u00a0\u00a0 \u00a0return [NSNumber numberWithLongLong:launch_data_get_integer(obj)];\r\n\u00a0 \u00a0case LAUNCH_DATA_REAL:\r\n\u00a0 \u00a0\u00a0 \u00a0return [NSNumber numberWithDouble:launch_data_get_real(obj)];\r\n\u00a0 \u00a0case LAUNCH_DATA_BOOL:\r\n\u00a0 \u00a0\u00a0 \u00a0return [NSNumber numberWithBool:launch_data_get_bool(obj)?YES:NO];\r\n\u00a0 \u00a0case LAUNCH_DATA_ARRAY:\r\n\u00a0 \u00a0\u00a0 \u00a0return GetFromLaunchArray(obj);\r\n\u00a0 \u00a0case LAUNCH_DATA_DICTIONARY:\r\n\u00a0 \u00a0\u00a0 \u00a0return GetFromLaunchDictionary(obj);\r\n\u00a0 \u00a0case LAUNCH_DATA_FD:\r\n\u00a0 \u00a0\u00a0 \u00a0return [NSNumber numberWithInt:launch_data_get_fd(obj)];\r\n\u00a0 \u00a0case LAUNCH_DATA_MACHPORT:\r\n\u00a0 \u00a0\u00a0 \u00a0return [NSNumber numberWithInt:launch_data_get_machport(obj)];\r\n\u00a0 \u00a0default:\r\n\u00a0 \u00a0\u00a0 \u00a0break;\r\n\u00a0 \u00a0}\r\n\u00a0 \u00a0return nil;\r\n}\r\n\r\nstatic NSDictionary* GetFromJobLabel(NSString* job) {\r\n\u00a0 \u00a0NSDictionary* result = nil;\r\n\u00a0 \u00a0launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);\r\n\u00a0 \u00a0if (msg&amp;&amp;launch_data_dict_insert(msg, launch_data_new_string([job fileSystemRepresentation]), LAUNCH_KEY_GETJOB)) {\r\n\u00a0 \u00a0\u00a0 \u00a0launch_data_t response = launch_msg(msg);\r\n\u00a0 \u00a0\u00a0 \u00a0launch_data_free(msg);\r\n\u00a0 \u00a0\u00a0 \u00a0if (response&amp;&amp;(launch_data_get_type(response)==LAUNCH_DATA_DICTIONARY)) {\r\n\u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0result = GetFromLaunchDictionary(response);\r\n\u00a0 \u00a0\u00a0 \u00a0}\r\n\u00a0 \u00a0\u00a0 \u00a0launch_data_free(response);\r\n\u00a0 \u00a0}\r\n\u00a0 \u00a0return result;\r\n}<\/code><\/pre>\n<p>To use it, just call the last function like this:<\/p>\n<pre><code>\u00a0 \u00a0NSDictionary* dictionary = GetFromJobLabel(@\"com.yourcompany.agentlabel\");<\/code><\/pre>\n<p>and examine the dictionary. If it&#8217;s nil, some error happened, or your agent isn&#8217;t loaded into launchd.<\/p>\n<p>If you do get a dictionary back, it will be autoreleased, and the &#8220;Label&#8221; key&#8217;s object  should be equal to the string you passed in. There are two more keys that might be interesting to check: &#8220;PID&#8221; will contain your agent&#8217;s pid if it&#8217;s running, and will be missing if it&#8217;s not; and &#8220;LastExitStatus&#8221; will show you what your agent&#8217;s main() returned on its last run.<\/p>\n<p>Note that most of the code above is actually based on the <a href=\"http:\/\/launchd.macosforge.org\/\">launchd<\/a> source, converted to use Foundation objects instead of CFTypes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s been a while since the last post, but I&#8217;ve finally gotten past the boring parts of the new Klicko System Preference pane &amp; background process. The latter is actually a per-user Launch Agent. I&#8217;d used launchd before in the last Quay implementation, but didn&#8217;t fully understand what I was doing. Well, I still can&#8217;t [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[4,19],"tags":[26,28,27],"class_list":["post-1463","post","type-post","status-publish","format-standard","hentry","category-dev","category-software","tag-cocoa","tag-klicko","tag-quay"],"featured_image_src":null,"author_info":{"display_name":"Rainer Brockerhoff","author_link":"https:\/\/brockerhoff.net\/blog\/author\/rbrockerhoff\/"},"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1q3Zc-nB","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/posts\/1463","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/comments?post=1463"}],"version-history":[{"count":0,"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/posts\/1463\/revisions"}],"wp:attachment":[{"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/media?parent=1463"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/categories?post=1463"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/brockerhoff.net\/blog\/wp-json\/wp\/v2\/tags?post=1463"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}