Launching Unix tools from command-line apps

I’m trying to write a utility to help automate some things with simctl. My thought was to write a quick command-line application in Swift, using the Task API to launch things, but it appears that due to App Sandboxing (I’m on 10.15) I can’t actually launch an executable in /usr/bin:


Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'launch path not accessible'


I tried creating an Entitlements file to disable the sandbox, but this didn’t work:


<?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>
  <false/>
</dict>
</plist>


What I’m trying to write is a developer tool with no plans of distribution. Is there a way to write a Swift app that launches software like this locally, or has this been completely locked down with this iteration of macOS?

Accepted Reply

Last reply, because I’m an ***** and didn’t see the exectuableURL property the first time through:


let process = Process()
process.executableURL = url

process.arguments = ["test"]

process.launch()

process.waitUntilExit()


This works fine, and crucially lets me use Process, so I can terminate the running process.

Replies

Maybe post code that described how you are launching this task. I do this hundreds of times from my sandboxed app.

I’m trying to write a utility to help automate some things with

simctl
… I can’t actually launch an executable in
/usr/bin
simctl
isn’t in
/usr/bin
:
% which simctl
simctl not found

I suspect you’re using

xcrun
:
% which xcrun
/usr/bin/xcrun

This is a trampoline that bounces to the default Xcode (selected using

xcode-select
).

How you should proceed here depends on your deploy strategy:

  • If you’re just building this for yourself, disable App Sandbox and move on.

    Note Setting

    com.apple.security.app-sandbox
    to false is not the right way to disable the App Sandbox. If you’re using Xcode, remove the App Sandbox slice from the Signing & Capabilities editor. If you’re not using Xcode, remove the
    com.apple.security.app-sandbox
    entitlement entirely.
  • If you’re deploying to the Mac App Store, I don’t think you’ll be able to make this work. For a start, the user can place the default Xcode anywhere on disk. Second,

    simctl
    uses IPC to the simulator infrastructure to do its job, and it’s likely that’ll be blocked by the sandbox.
  • If you’re deploying to a wide range of users outside of the Mac App Store, you can choose to enable the sandbox or not. If you do enable the sandbox, it’s likely that you can add enough temporary exceptions to get things working, but I suspect that your solution is going to end up being very brittle.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the reply. You’re right, I’m using xcrun to launch simctl. Here’s a simple example of how I’m trying to launch the process, from a Swift command-line utility. The app sandbox is not in the Signing & Capabilities section of Xcode, and there are no entitlements:


import Foundation

let process = Process()
process.arguments = ["/bin/echo", "Hello, World!"]

process.launch()

process.waitUntilExit()


This generates the above error about reading from that location.

I’ve also tried using NSUserUnixTask, but this seems to quit immediately:


let url = URL(fileURLWithPath: "/bin/echo")

do {
    let task = try NSUserUnixTask(url: url)
    
    task.standardOutput = FileHandle(forWritingAtPath: "/Users/jeff/Desktop/test")
    
    task.execute(withArguments: ["test"]) { error in
        if let error = error {
            fatalError(error.localizedDescription)
        }
        
        print("Hello")
    }
}
catch {
    fatalError(error.localizedDescription)
}


My print statement never prints, and the file is not written. If I create the file, it does not receive any contents.

OK, more progress. This works—apparently I just wasn’t letting the task live long enough:


let url = URL(fileURLWithPath: "/bin/echo")

do {
    let task = try NSUserUnixTask(url: url)
    
    task.standardOutput = FileHandle(forWritingAtPath: "/Users/jeff/Desktop/test")
    
    task.execute(withArguments: ["test"]) { error in
        if let error = error {
            fatalError(error.localizedDescription)
        }
        
        print("Hello")
        
        exit(0)
    }
}
catch {
    fatalError(error.localizedDescription)
}

dispatchMain()

Last reply, because I’m an ***** and didn’t see the exectuableURL property the first time through:


let process = Process()
process.executableURL = url

process.arguments = ["test"]

process.launch()

process.waitUntilExit()


This works fine, and crucially lets me use Process, so I can terminate the running process.