Daemon - Client IPC with Unix sockets

2019-06-12 09:49发布

问题:

I have a launch daemon which I want to ask for status information from a user app. I implemented a client-server model (with the daemon as server) using unix sockets as described here: OS X - Communication between launch daemon and launch agent

In fact it works well, when I run the daemon as a user process (for debugging), but it will fail when it is actually launched as root.

I have read the TN on Daemons and Agents and the Daemon & Services Programming Guide. However, I could not find decent information how the socket must be used in a launch daemon.

I am confused by several things:

  • Must I specify the socket in the launch daemon plist file? And how?

  • If the socket is specified in the plist, does that change the way I need to create the socket in code?

  • What path would be good for the unix socket? The Technical Note recommends /var/run but I guess a user process may not write there, or can it?

  • Is there maybe a easier way to do IPC between daemon and client?

  • What is the best way to log the daemon output. I tried NSLog but it seems not work...


I am also unsure if my socket code is correct. Maybe someone more experienced can tell me if I'm on the right track here. I have the following code in the daemon to initialize the unix socket:

#define SOCKETNAME  "/var/run/com.company.myApp.socket"

- (void) startServer {
    //remove any prev socket
    unlink(SOCKETNAME);
    CFSocketContext CTX = { 0, (__bridge void *)(self), NULL, NULL, NULL };

    CFSocketRef unixSocket = CFSocketCreate(NULL, PF_UNIX, SOCK_STREAM, 0,
                               kCFSocketAcceptCallBack, (CFSocketCallBack)AcceptCallBack, &CTX);
    if (unixSocket == NULL) {/*log and return*/} 

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, SOCKETNAME);
    addr.sun_len = strlen(addr.sun_path) + sizeof (addr.sun_family);

    NSData *address = [ NSData dataWithBytes: &addr length: sizeof(addr) ];
    if (CFSocketSetAddress(unixSocket, (__bridge CFDataRef) address) != kCFSocketSuccess) {
        NSLog(@"CFSocketSetAddress() failed\n");
        CFRelease(unixSocket);
    }

    CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, unixSocket, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
    CFRelease(sourceRef);    
    CFRunLoopRun();
}


void AcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
    CTServerController* selfServerController = (__bridge CTServerController*) info;
    //NSLog(@"acceptCallBack");   
    //...
}

回答1:

  • you should not specify the socket in the plist, you know the concrete place, the server knows, the client knows, that should be enought
  • the socket specified in the plist is for launching the deamon/agent ondemand, you do not need that in a basic case
  • you can use any path, if your daemon starts first (usually the case) it has all the privilege to set the correct rights on the socket to let anybody (or a given user, group) read, read and write or any given rights you wish, i'm sure you just forgot to let the client write/read the unix socket
  • i think on OSX the unix socket IPC is a perfect, easy solution (you have many other choices too of course, xpc, mach messages, etc.)
  • you can define where to go the stdout and stderr of a daemon in it's plist (StandardOutPath, StandardErrorPath keys)