Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify that a helper tool is installed

I'm writing a macOS app in Swift that needs a privileged helper tool -- wish the elevation wasn't necessary, but it looks like it is.

I found this excellent example application especially tailored for this scenario. I've managed to port its code to my own app, but I'm stuck at the point where I need to check if the helper tool is installed, and if it isn't, use SMJobBless() and friends to install it.

When running the example app, if the helper tool is not installed, the app stays stuck at the following screen:

enter image description here

To be clear, from reading the code, I thought it was supposed to update the label to "Helper Installed: No" at some point, but that doesn't seem to happen.

If I click "Install Helper", this is the result.

enter image description here

From now on, unless I manually remove the helper tool, rerunning the app will display this screen with "Helper Installed: Yes".

This behavior might be OK in this example situation where the user has to manually click on the "Install Helper" button. However, in my app, I would like it to automatically request installation of the helper tool if it's not installed yet. If it's already installed, I don't want to waste the user's time requesting their password again.

I thought this would be simple enough: if the helper tool is not available, somewhere along the process of connecting to it, an error would happen, which is the trigger for me to request installation of the tool. If no errors happen, it's assumed that the tool was already installed.

Here is the hacked together code that I wrote for connecting to the helper tool via XPC:

var helperConnection: NSXPCConnection?
var xpcErrorHandler: ((Error) -> Void)?
var helper: MyServiceProtocol?

// ...

helperConnection = NSXPCConnection(machServiceName: MyServiceName, options: .privileged)
helperConnection?.remoteObjectInterface = NSXPCInterface(with: MyServiceProtocol.self)
helperConnection?.resume()

helperConnection?.interruptionHandler = {
    // Handle interruption
    NSLog("interruptionHandler()")
}

helperConnection?.invalidationHandler = {
    // Handle invalidation
    NSLog("invalidationHandler()")
}

xpcErrorHandler = { error in
   NSLog("xpcErrorHandler: \(error.localizedDescription)")
}

guard
    let errorHandler = xpcErrorHandler,
    let helperService = helperConnection?.remoteObjectProxyWithErrorHandler(errorHandler) as? MyServiceProtocol
    else {
        return
}

helper = helperService

If the helper tool is not installed, running this code produces no errors or NSLog() output. If, afterwards, I call a function via XPC (using helper?.someFunction(...)), nothing happens -- I might as well be talking to /dev/null.

Now I'm left scratching my head in search of a technique to detect if the tool is installed. The example applications's solution to the problem is to add a getVersion() method; if it returns something, "Install Helper" is grayed out and the label changes to "Helper Installed: Yes".

I thought about extending this idea a bit by writing a simple function in my tool that returns instantly, and use a timeout in the main app -- if I don't get a result until the code times out, the helper tool is likely not installed. I find this a hacky solution -- what if, for instance, the helper tool (which is launched on demand) takes a little too long to launch , say because the computer is old and the user is running something CPU-intensive?

I see other alternatives such as peeking around the file system in the expected places (/Library/PrivilegedHelperTools and /Library/LaunchDaemons), but again this solution feels unsatisfactory to me.

My question: Is there a way to unambigously detect if a privileged XPC helper tool is listening at the other end?

My environment: macOS Mojave 10.14.2, Xcode 10.1, Swift 4.2.

like image 954
swineone Avatar asked Mar 04 '23 16:03

swineone


1 Answers

I would check the file system if the binary exists (in /Library/PrivilegedHelperTools and if the plist exists in /Library/LaunchDaemons). Then you might contact the XPC service and call kind of a ping function which answers if the service is up and running.

just my 2 cts,

Robert

like image 58
Robert Avatar answered Mar 09 '23 06:03

Robert