Troubleshooting the launch of local user XPC Launch Agent

This is a follow up to the thread, Troubleshooting the launch of local user XPC Launch Agent, in the original (and now archived) Dev Forums.


Based on further tests, I assume there must be some configuration that I'm not setting up correctly, as opposed to the code.


I started from scratch again with a fresh Xcode Project. This time, I based the code mostly on the example code posted by Apple Engineer eskimo1 in this thread (which uses an privileged Launch Daemon) with a modification or two based on the code posted by Apple Engineer dsorresso in this thread. As I assume that their code can be expected to work (i.e.- to help rule out a coding error on my part). Except that I modified the code so that it works with/as an XPC Service Launch Agent without the privileged aspect and which resides inside of the user's home directory.


The following was all done on OS X 10.10.3 (14D136) Yosemite and built with Xcode 6.3.2 (6D2105).


* I first started off both the command line interface program and the XPC Service Launch Agent without Code Signing or Sandboxing. When the command line interface program was run at the command line, I got output such as the following. Which more or less resembles the behaviour I was seeing with my own test code.

connection event
error Connection invalid

* Then I code signed both items. No change in the output.


* Next, I Sandboxed both. While there was no change in the output at the command line, some entries containing 'deny mach-lookup' began to appear in the System Console logs -> in asl -> with the AUX prefix. e.g.


CLIHelloToLaunch(6103) deny mach-lookup com.test.XPCLA


* Later, I added a com.apple.security.temporary-exception.mach-lookup.global-name key of Type Array containing the Bundle Identifier of the XPC Service Launch Agent in the Entitlements files for both Targets. No change in the command line output and 'deny mach-lookup' entries still appeared in System Console logs -> asl -> AUX.

* As well, I also added an App Group to the Entitlements files for both Targets. No change in the command line output and 'deny mach-lookup' entries still appeared in System Console logs -> asl -> AUX.

What could be the cause of the XPC connection errors? I presume that the 'deny mach-lookup' entries are related. How can I fix this?

This is the contents of the launchd plist file for the XPC Service Launch Agent. It resides in ~/Library/LaunchAgents:


<?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>com.test.XPCLA</string>
  <key>MachService</key>
  <dict>
  <key>com.test.XPCLA</key>
  <true/>
  </dict>
  <key>ProgramArguments</key>
  <array>
  <string>~/Library/Application Support/com.test.XPCLA.xpc</string>
  </array>
</dict>
</plist>


This is the code being used in the XPC Service Launch Agent. Specifically, in it's main.m file. After building, I put the .xpc Launch Agent in ~/Library/Application Support for testing.


#import <Foundation/Foundation.h>
#include <xpc/xpc.h>

static void SetupConnection(xpc_connection_t connection)
{
    xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
        if ( xpc_get_type(object) == XPC_TYPE_ERROR ) {
            fprintf(stderr, "connection error %s\n", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
            xpc_connection_cancel(connection);
        } else if ( xpc_get_type(object) == XPC_TYPE_DICTIONARY ) {
            const char *    name;
     
            fprintf(stderr, "connection message\n");
     
            name = xpc_dictionary_get_string(object, "name");
            if (name == NULL) {
                fprintf(stderr, "no name\n");
                xpc_connection_cancel(connection);
            } else {
                xpc_object_t    response;
                char *          responseStr;
         
                fprintf(stderr, "name is '%s'\n", name);
         
                (void) asprintf(&responseStr, "hello %s", name);
         
                response = xpc_dictionary_create(NULL, NULL, 0);
                assert(response != NULL);
         
                xpc_dictionary_set_string(response, "greeting", responseStr);
         
                xpc_connection_send_message(connection, response);
         
                //                xpc_release(response);
            }
        } else {
            assert(false);
        }
    });
    xpc_connection_resume(connection);
}


int main(int argc, char** argv)
{
    xpc_connection_t    listener;

    fprintf(stderr, "XPC Helper start\n");

    listener = xpc_connection_create_mach_service("com.test.XPCLA", NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
    assert(listener != NULL);

    xpc_connection_set_event_handler(listener, ^(xpc_object_t object) {
        if ( xpc_get_type(object) == XPC_TYPE_ERROR ) {
            fprintf(stderr, "listener error\n"); // XPC_ERROR_KEY_DESCRIPTION
        } else if ( xpc_get_type(object) == XPC_TYPE_CONNECTION ) {
            fprintf(stderr, "listener connection\n");
            SetupConnection(object);
        } else {
            assert(false);
        }
    });

    xpc_connection_resume(listener);

    dispatch_main();

    return EXIT_SUCCESS;
}


This is the code in the command line interface program's main.m file. After building, I put the program into ~/Applications.


#import <Foundation/Foundation.h>
#include <xpc/xpc.h>

static BOOL Test(void)
{
    BOOL                success;
    xpc_connection_t    connection;
    xpc_object_t        request;
    xpc_object_t        response;

    response = NULL;

    connection = xpc_connection_create_mach_service("com.test.XPCLA", NULL, 0);
    assert(connection != NULL);

    request = xpc_dictionary_create(NULL, NULL, 0);
    assert(request != NULL);

    xpc_dictionary_set_string(request, "name", "Hello service, this is the program speaking, are you out there?");

    xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
        fprintf(stderr, "connection event\n");
        if ( xpc_get_type(object) == XPC_TYPE_ERROR ) {
            fprintf(stderr, "error %s\n", xpc_dictionary_get_string(object, XPC_ERROR_KEY_DESCRIPTION));
        } else if ( xpc_get_type(object) == XPC_TYPE_DICTIONARY ) {
            const char *    greeting;
       
            fprintf(stderr, "response\n");
            greeting = xpc_dictionary_get_string(object, "greeting");
            fprintf(stderr, "greeting is '%s'\n", greeting);
        } else {
            fprintf(stderr, "something else\n");
        }
    });
    xpc_connection_resume(connection);
    xpc_connection_send_message(connection, request);
    dispatch_main();

    if (request != NULL) {
        xpc_release(request);
    }
    if (response != NULL) {
        xpc_release(response);
    }

    return success;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%@", @"About to run test of XPC Service.");
         Test();
    }
    return 0;
}


Here are the command line program's Entitlements.


<?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>com.apple.security.app-sandbox</key>
  <true/>
  <key>com.apple.security.temporary-exception.mach-lookup.global-name </key>
  <array>
  <string>com.test.XPCLA</string>
  </array>
  <key>com.apple.security.application-groups</key>
  <array>
  <string>$(TeamIdentifierPrefix)IPC</string>
  </array>
</dict>
</plist>


The XPC Service Launch Agent's Entitlements.


<?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>com.apple.security.app-sandbox</key>
  <true/>
  <key>com.apple.security.application-groups</key>
  <array>
  <string>$(TeamIdentifierPrefix)IPC</string>
  </array>
  <key>com.apple.security.temporary-exception.mach-lookup.global-name </key>
  <array>
  <string>com.test.XPCLA</string>
  </array>
</dict>
</plist>


Additional notes, if it's relevant:

* The command line interface program has an embedded info.plist since it's a single file executable.

* Command line interface program was created via Xcode 6.3.2's New Target -> Command Line Tool template

* XPC Service was created via Xcode 6.3.2's New Target -> XPC Service template.

I'm very interested in the answer for this as well. Actually, I had just posted a similar question earlier today but now it's gone.


I thought the XPC Service template within xcode was only for XPC Services hosted within a parent application - produces ".xpc" files. I'm pretty sure those don't work as standalone XPC services like you describe above.


Wish I had more to add. Please share any progress you make on this.

wjens wrote:

> I thought the XPC Service template within xcode was only for XPC Services hosted within a parent application - produces ".xpc" files. I'm pretty sure those don't work as standalone XPC services like you describe above.


Thanks for your input.


I thought that this might be the case as well. In a variant of my prior test code, not shown here, I tried it with an Launch Agent based on the .app AppKit template but this didn't really change the broken connection behaviour. Which leads me to believe it's some kind of configuration issue.


Although, I haven't tried the code of the Launch Agent, as posted above, as an Mac App created with the .app AppKit Xcode template but with the GUI stuff stripped out or with the Command Line Tool template.


I forgot to mention in my previous post that debugging print statements in the Launch Agent's main.m, like the fprintf() calls in the example code or NSLog() or asl_log() calls in my original test code, do not appear anywhere in the command line output or in the System Console logs even if placed at the very beginning of the main() function. Which suggests that the Launch Agent never executed.


Also, the XPC Launch Agent doesn't appear in the Activity Monitor when the command line program is invoked.


On the other hand, along the way of doing the previously mentioned steps I ran some launchtl diagnostic like commands which seem contradictory. The results appear to indicate that launchd isn't seeing any errors as a zero exit status is reported.


For instance, this command...

launchctl list | grep XPCLA


...Outputs:

- 0 com.test.XPCLA


Similarly, this command...

launchctl list com.test.XPCLA


... indicates a zero exit status (success from launchd's perspetive?):

{
  "LimitLoadToSessionType" = "Aqua";
  "Label" = "com.test.XPCLA";
  "TimeOut" = 30;
  "OnDemand" = true;
  "LastExitStatus" = 0;
  "Program" = "~/Library/Application Support/com.test.XPCLA.xpc";
  "ProgramArguments" = (
  "~/Library/Application Support/com.test.XPCLA.xpc";
  );
};


While...

launchctl print gui/UID_ofCurrentlyLoggedInUser/com.test.XPCLA


...contains a couple lines which seem to indicate that while there isn't an error, it 'never exited':

state = waiting
last exit code = (never exited)


On the other hand, this command...

launchctl blame gui/UID_ofCurrentlyLoggedInUser/com.test.XPCLA


...displays output which seems to indicate that the Launch Agent process quit:

(not running)

I think your problem is mostly in your launchd plist file.


In my working Launch Agent I have no entitlements. There are no entitlements related to the Launch Agent target or the command line tool target. So just to get something working I'd recommend removing entitlements from the equation right now and adding them back in afterwards.


The path to the program in the Program arguments array is to the executable within the LaunchAgent bundle, not just to the Launch Agent itself. I'm pretty certain this is needed.


I do not use ~ but fully resolved paths to the Launch Agent as well. My installer updates the launchd plist file to point to the location of the Launch Agent at install time. I think it is possible that the ~ will not be resolved. I can't find documentation either way but in the first instance if I was you I would hard code in the full path until you have it working. Once working you can undo and check. Actually I've just gone and modified my launchd plist file with a ~ and the LaunchAgent doesn't start, returning to a full path and it works again. So don't use ~. I had to use "launchctl unload/load" for launchd to recognize that the launchd plist file had been modified.


If that still doesn't work for you. Then the one last thing from my experience for you to try is that you might need to prefix your service name with your application id identifier. Mine looks like: U6TV63RN87


So instead of "com.yvs.MovingImagesAgent" I end up with "U6TV63RN87.com.yvs.MovingImagesAgent"


I got this identifier from my "Xcode Mac Wildcard App ID" from the sub section App IDs of Identifiers under MacApps on Apple's dev center.


This prefix is added in lots of places. It is the mach service being advertized, it is the label in the launchd plist file, it prefixes the name of the launchd plist file itself. It prefixes the service being advertized within your launch agent, and what you call that service when accessing it from your command line tool. This is what my current plist looks like.




If none of that works then I might be out of ideas.


Kevin


//Edit: Not sure why the image is not being included in the message.

Thanks for your suggestions, Kevin.


ktam wrote:

> So just to get something working I'd recommend removing entitlements from the equation right now and adding them back in afterwards.


While I haven't tried this with the code I posted above I did try that later with my original test code and that didn't resolve the invalid connection issue.


> The path to the program in the Program arguments array is to the executable within the LaunchAgent bundle, not just to the Launch Agent itself. I'm pretty certain this is needed.


I've corrected that issue later in both the code above and with my original test code.


> I do not use ~ but fully resolved paths to the Launch Agent as well.


The later versions of the code posted above and my original test code use full/absolute paths to the Launch Agent in the launchd plist.


> I had to use "launchctl unload/load" for launchd to recognize that the launchd plist file had been modified.


I've been doing this in-between every change.


> you might need to prefix your service name with your application id identifier. Mine looks like: U6TV63RN87


> So instead of "com.yvs.MovingImagesAgent" I end up with "U6TV63RN87.com.yvs.MovingImagesAgent"


> I got this identifier from my "Xcode Mac Wildcard App ID" from the sub section App IDs of Identifiers under MacApps on Apple's dev center.


> This prefix is added in lots of places. It is the mach service being advertized, it is the label in the launchd plist file, it prefixes the name of the launchd plist file itself. It prefixes the service being advertized within your launch agent, and what you call that service when accessing it from your command line tool. This is what my current plist looks like.


I tried this with the code posted above but not with my original test code in which I've recently removed the Sandboxing and Code Signing.


> If none of that works then I might be out of ideas.


About the only thing I haven't tried is all of your above suggestions above in combination with the application id identifier/Team ID prefix and removing all entitlements. In other words, so far I've tried either...


A) With the application id identifier/Team ID prefix and Sandboxing/Entitlements.


B) Or without any Sandboxing, Code Signing (but without the application id identifier/Team ID prefix).

My LaunchAgent is code signed, no sandboxing etc. has the application id identifier prefix added. So I would redo your A, but removing Sandboxing/Entitlements but leaving the LaunchAgent code signed.


Good luck.

I tried this (application id identifier prefix added, Sandboxing/Entitlements removed, and with leaving the LaunchAgent code signed) with the above posted code along with the previously mentioned modifications but there's still a connection error.


Example command line output:

About to run test of XPC Service.
connection event
error Connection invalid

I think I have your code working. When I run the client, the server kicks off automatically and I get:


2015-06-23 14:32:26.955 2N3A97G84X.com.pagepath.myXpcClient[57090:3316499] About to run test of XPC Service.

connection event

response

greeting is 'hello Hello service, this is the program speaking, are you out there?'


You can get my project at:

https://drive.google.com/file/d/0B6GSVnh1xLTteVZpSEpwb0ZtWGc/view?usp=sharing


You'll need to update wherever you see "2N3A97G84X.com.pagepath.myXpcServer" to use your identifier in both the code and project settings. I'm not sure if they all require the team identifier, but this is working for me so I'm moving forward for now.


Plus I think the launchd plsit under /Library/LauchAgents needs to be owned by root.


Another thing that might help is "RB App Checker Lite" (free in the App Store). It can tell you if you've signed your unix exec's correctly.


Hope this helps,

William

Thanks for your feedback. I downloaded your example project and changed the team identifier part as appropriate.


wjens wrote:

> Plus I think the launchd plsit under /Library/LauchAgents needs to be owned by root.


Actually, I'm attempting to get this working with both the command line program and the Launch Agent it's attempting to communicate with in the user's home directory. In other words, ~/Library/Application Support, ~/Library/LaunchAgents, etc..


When I changed your example project to put the pair of executables in ~/Library/Application Support and the launchd plist in ~/Library/LaunchAgents I got an invalid connection error in the output at the command line.


On the other hand, if the items are placed in the System's top level /Library/Application Support and /Library/LaunchAgents it does appear work, with the output as you described.


A question comes to mind, do the XPC C level Mach Service API's (connection_create_mach_service()) not work if the items are located in the user's home directory '~' as opposed to the relevant top level System directories outside of /Users on Mac OS X 10.10.3 (14D136)?

A follow up. The following combination also works:


  • Installing the XPC Launch Agent in /usr/local/. With the user ownership permissions set to a local, non-admin, non-root user and the mode set to 0700.
  • Installing the command line program which communicates with it via XPC in either the local user's ~/Applications or in /usr/local/. With the user permissions set to be owned by that user and the mode set to 0700.
  • Installing the launchd.plist in the local user's ~/Library/LaunchAgents. With the user permissions set to be owned by that user.


Which further seems to suggest that the XPC C level Mach Service API's (connection_create_mach_service()) will only work if the XPC Launch Agent's executable is located outside of the user's home directory '~'. Even if one only wishes to have the XPC Launch Agent just work with the particular local user and no other users.

try running the Key Access built in app.

Troubleshooting the launch of local user XPC Launch Agent
 
 
Q