ios: run qemu-img in a helper extension on iOS 26 - #7908
Merged
Merged
Conversation
QEMU tools cannot run in the app process on iOS: a process may load QEMU only once and qemu-img calls exit(). So disk snapshots of a stopped VM, reclaiming space, compression, resizing and conversion on import were macOS-only. iOS 26 brings ExtensionKit, so qemu-img now runs in an ExtensionKit extension (iOSHelper, and iOSHelper-SE for UTM SE since an extension's bundle identifier must be prefixed by its host's). The helper speaks the same QEMUHelperProtocol as the macOS XPC helper but loads the tool into its own process, one tool per process. UTMProcess accepts an injected connection so the remote path is shared with macOS, including file access through bookmarks, whose implicit security scope grants the resolving process access. UTMHelperProcess launches the helper and, because the system hands a process that is still exiting to a launch that comes too soon, tears it down with a handshake on the interruption callback and runs helpers one at a time. UTMQemuImage now reads the tool's output until it ends instead of racing the exit message, resumes its continuations off the XPC reply queue (a call made from that queue blocks in NSXPC), and stops the process explicitly since the connection's exported object kept it from ever being deallocated on macOS. The last stderr line becomes the error text when the tool gives none. Assisted-by: Claude:claude-fable-5-1
The iOS-SE scheme has run with Address Sanitizer since the schemes were first committed to set the architecture override, which was incidental. An ExtensionKit extension linked with the sanitizer does not start on a device, so every qemu-img operation of UTM SE failed when run from Xcode. Turn it off, add the schemes Xcode created for the helper targets so both can be built and debugged on their own, and take the Metal API validation setting Xcode 27 writes into the macOS scheme. Assisted-by: Claude:claude-fable-5-1
osy
force-pushed
the
feature/ios-plugin
branch
from
September 24, 2026 04:23
774540b to
a68e274
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
QEMU tools cannot run in the app process on iOS: a process may load QEMU only once and
qemu-imgcallsexit(). So disk snapshots of a stopped VM, Reclaim Space, Compress, Resize and qcow2 conversion on drive import were macOS-only.iOS 26 brings ExtensionKit, so
qemu-imgnow runs in an ExtensionKit extension embedded in the app (iOSHelper, andiOSHelper-SEfor UTM SE since an extension's bundle identifier must be prefixed by its host's). The helper speaks the sameQEMUHelperProtocolas the macOS XPC helper but loads the tool into its own process, one tool per process; the app launches a fresh helper for each operation and tears it down with a handshake on the system's interruption callback, because a launch that comes too soon otherwise gets a process that is still exiting. Files are passed as bookmarks, whose implicit security scope grants the resolving process access, so the file path is unchanged from macOS.The shared
UTMQemuImagecode also gets three fixes that surfaced while testing: it reads the tool's output to end-of-file instead of racing the exit message, resumes its continuations off the XPC reply queue (NSXPC makes a call issued from that queue synchronous, which deadlocked on macOS), and stops the process explicitly since the connection's exported object kept it from ever being deallocated. The last stderr line becomes the error text when the tool gives none. The Resize confirmation moved into the resize popover, as an alert requested while the sheet closes is dropped on iPhone.A second commit adds the helper schemes Xcode created, drops the incidental Address Sanitizer setting from the UTM SE scheme (a sanitized extension does not start on a device), and takes the Metal API validation setting Xcode 27 writes into the macOS scheme.
Release changes: signed iOS exports need a provisioning profile for the helper (
package.shtakes it as a new argument, the App Store job readsIOS_SE_HELPER_PROFILE_DATA/IOS_SE_HELPER_PROFILE_UUID), documented inRelease.md,iOSDevelopment.mdandCodeSigning.xcconfig.sample.Resolves #4774
Testing: Tested by a human on iOS 26.7, iPhone 16 Pro (UTM SE). The author acknowledges that this change has been tested and/or reviewed by a human in accordance with UTM's AI contribution guidelines.
Additional automated checks: iOS 26.1 and 27.0 simulators (snapshots, reclaim, resize through the UI), iOS 17.0 simulator (features hidden, app installs with the extension embedded), macOS 27 host and a macOS 12.6 guest (qemu-img through
QEMUHelper, snapshots and resize through the UI).