Fix XPC connection failure when home directory is on external volume#1724
Fix XPC connection failure when home directory is on external volume#1724majidfeiz wants to merge 2 commits into
Conversation
launchd cannot register Mach services from plists located on external or removable volumes. When the user's home directory (and therefore the default ApplicationRoot) resides on such a volume, `launchctl bootstrap` silently fails, causing the XPC connection to be reported as invalid. Introduce ServiceRoot, a new root type whose default path resolves to a subdirectory of FileManager.temporaryDirectory — which macOS always places on the internal system volume regardless of where the home directory lives. ServiceRoot follows the same pattern as ApplicationRoot, InstallRoot, and LogRoot: it can be overridden via CONTAINER_SERVICE_ROOT or the new --service-root CLI flag on `container system start`. - Add Sources/ContainerPlugin/ServiceRoot.swift - Write apiserver.plist under serviceRoot instead of appRoot in SystemStart - Propagate CONTAINER_SERVICE_ROOT through the plist environment so the daemon's PluginLoader also writes plugin plists to the system volume - Add serviceRoot parameter to PluginLoader.init (nil = read env var) - Add ServiceRootTests and update PluginLoaderTest to cover the separation Fixes apple#1721
kasc0206
left a comment
There was a problem hiding this comment.
审查意见 / Review
This is an excellent contribution. The root cause analysis is thorough, and the solution is clean and well-isolated.
优点 / Strengths
- Root cause analysis: Correctly identifies that
launchctl bootstrapsilently fails on external volumes due to Gatekeeper/AMFI restrictions - Solution design: Introducing
ServiceRootas a new root type follows the existingApplicationRoot/InstallRoot/LogRootpattern perfectly — consistency with the codebase is excellent - Minimal changes: Only 5 files changed, with clear separation of concerns
- Test coverage: Both unit tests (
RootPathTests,PluginLoaderTest) properly validate the behavior - Backward compatibility:
serviceRoot: URL? = nildefaults mean existing callers are unaffected - Documentation: The doc comments on
ServiceRootclearly explain why the separation exists
小建议 / Nitpicks
- Consider adding a fallback or warning when
NSTemporaryDirectory()itself returns nil or an unexpected path, though this is extremely rare on macOS - The
plistDirconstructionserviceRoot.appending("apiserver")vs thePluginLoaderusingplugin-statesubdirectory is intentional for audit separation — consider noting this in a comment
已验证 / Verified
- Code compiles with
swift build✅ - All existing
ContainerPluginTestspass ✅ - No breaking API changes to
PluginLoader.init(new parameter is optional with default nil)
Great work!
Code Review Summary / 代码审查摘要Strengths / 优点
Nitpicks / 小建议
Verification / 验证
Great contribution! / 优秀的贡献! |
…comment - ServiceRoot.defaultPath: convert to a closure-based static let so a precondition fires if NSTemporaryDirectory() ever returns an empty path, making the safety assumption explicit and catchable at startup rather than silently producing a broken path. - SystemStart: expand the comment around plistDir construction to make the intentional appRoot/serviceRoot separation explicit, noting both why the split exists (external-volume restriction) and how CONTAINER_SERVICE_ROOT propagates to the daemon without additional wiring.
|
Hi @kasc0206, Thank you for the thorough review and the kind words — really appreciate you taking the time to go through the changes carefully. I've addressed both of your suggestions: 1. Defensive check for 2. Clearer comment around Both changes are in the latest commit. Let me know if anything else needs adjustment. |
Problem
When a user's home directory resides on an external or removable volume
(e.g.
/Volumes/ex-data/), runningcontainer system startresults in an"XPC connection invalid" error and the daemon never becomes reachable.
Reported in: #1721
Root cause:
launchctl bootstrap <domain> <plist-path>silently failswhen the plist file is located on an external or removable volume. macOS
(Gatekeeper / AMFI) does not allow Mach service registration from plists
that reside outside the internal system volume. Because
ApplicationRootdefaults to
~/.local/share/container, any user whose home directory is onan external volume ends up writing
apiserver.plistand plugin plists tothat volume — causing all subsequent XPC connections to fail.
Solution
Introduce
ServiceRoot: a new root type whose sole responsibility is toprovide a location for launchd plist files. Its default path is:
NSTemporaryDirectory()(i.e.FileManager.default.temporaryDirectory) isalways located on the internal system volume on macOS, regardless of where
the user's home directory lives. This guarantees that
launchctl bootstrapsucceeds in all configurations.
ServiceRootfollows the exact same pattern as the existingApplicationRoot,InstallRoot, andLogRoottypes:ApplicationRootCONTAINER_APP_ROOT~/.local/share/containerInstallRootCONTAINER_INSTALL_ROOT<binary>/../../LogRootCONTAINER_LOG_ROOTServiceRootCONTAINER_SERVICE_ROOT$TMPDIR/com.apple.containerCONTAINER_SERVICE_ROOTis propagated fromSystemStartinto thecontainer-apiserverplist environment, so the daemon'sPluginLoaderautomatically picks it up for plugin plist registration as well — no
changes are required at any other call site.
Changes
Sources/ContainerPlugin/ServiceRoot.swift(new)Defines
ServiceRootwithenvironmentName,defaultPath,path, andpathnamestatics, consistent with the other root types.Sources/ContainerCommands/System/SystemStart.swift--service-rootCLI option (overridesCONTAINER_SERVICE_ROOT).CONTAINER_SERVICE_ROOTinto the plist environment.apiserver.plistunderserviceRootinstead ofappRoot.Sources/ContainerPlugin/PluginLoader.swiftplistResourceRoot(system volume, for plists) frompluginResourceRoot(appRoot, for runtime state).serviceRoot: URL? = niltoinit— existing callers areunaffected;
nilfalls back to readingCONTAINER_SERVICE_ROOT.Tests/ContainerPluginTests/RootPathTests.swiftAdds
ServiceRootTests(3 tests): default path is absolute, ends withcom.apple.container, and is located under the temporary directory.Tests/ContainerPluginTests/PluginLoaderTest.swiftUpdates
testRegisterWithLaunchdPlistNotUnderAppRootto pass an explicitserviceRootdistinct fromappRoot, directly asserting the separation.Testing
All existing tests pass:
Manual verification path (for reviewers with an external-volume home):
container system start— previously failed with XPC connection invalid.To override the service root manually:
container system start --service-root /private/tmp/my-container-service # or via env var: CONTAINER_SERVICE_ROOT=/private/tmp/my-container-service container system start