From d4f6443f55347bec56be0101ac69fff9ab4dedfa Mon Sep 17 00:00:00 2001 From: Kabir Oberai Date: Thu, 10 Sep 2026 17:23:15 -0400 Subject: [PATCH] sdk: fix permission issue when copying clang resources (#255) ## What does this PR do? Fixes #254 by using `cp -R` instead of `FileManager.copyItem`. The latter tries to preserve the original owner of files instead of using the caller, which fails if you're non-root and the original files are root-owned. ## How was it tested? I was able to repro the original issue by installing an SDK as a non-root user in an xtool Docker container. Applied this patch, tried `xtool sdk install` again, and it worked. ## AI tool usage How much of this PR was AI-assisted? (check one) - [ ] **0** - No AI was used to write code - [x] **1** - I was assisted by AI. I reviewed the finished result. - [ ] **2** - I set the AI going and left it to it; nobody has read the result - no review, or AI review only --- Sources/XToolSupport/SDKCommand.swift | 4 ++-- Sources/XUtils/Foundation+Utils.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/XToolSupport/SDKCommand.swift b/Sources/XToolSupport/SDKCommand.swift index cdb0bfc..2ac5484 100644 --- a/Sources/XToolSupport/SDKCommand.swift +++ b/Sources/XToolSupport/SDKCommand.swift @@ -171,7 +171,7 @@ struct DarwinSDK { let hostClangResources = URL(filePath: output.trimmingCharacters(in: .whitespacesAndNewlines)) let hostInclude = hostClangResources.appending(path: "include") let sdkInclude = sdk.appending(path: "Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/clang/include") - try FileManager.default.copyItem(at: hostInclude, to: sdkInclude) + try await FileManager.default.copyItem(at: hostInclude, to: sdkInclude, preserveOwner: false) } static func current() async throws -> DarwinSDK? { @@ -264,7 +264,7 @@ struct InstallSDKOperation { if path.hasSuffix(".xtoolsdk") { print("Installing prebuilt SDK...") - try FileManager.default.copyItem(at: URL(filePath: path), to: sdkPath) + try await FileManager.default.copyItem(at: URL(filePath: path), to: sdkPath, preserveOwner: false) } else { // validate input before removing existing SDK let input = try SDKBuilder.Input(path: path) diff --git a/Sources/XUtils/Foundation+Utils.swift b/Sources/XUtils/Foundation+Utils.swift index 50d02df..3067a86 100644 --- a/Sources/XUtils/Foundation+Utils.swift +++ b/Sources/XUtils/Foundation+Utils.swift @@ -1,4 +1,7 @@ import Foundation +#if canImport(Subprocess) +import Subprocess +#endif extension Data { // AsyncBytes is Darwin-only :/ @@ -20,6 +23,30 @@ extension Data { } } +extension FileManager { + /// Like `copyItem(at:to:)` but allows opting out of preserving the owner + package func copyItem( + at srcURL: URL, + to dstURL: URL, + preserveOwner: Bool, + ) async throws { + // why this is needed: https://github.com/xtool-org/xtool/issues/254 + + #if canImport(Subprocess) + if !preserveOwner { + try await Subprocess.run( + .name("cp"), + arguments: ["-R", srcURL.path, dstURL.path], + output: .discarded, + ).checkSuccess() + return + } + #endif + + try self.copyItem(at: srcURL, to: dstURL) + } +} + package func stderrPrint(_ message: String, terminator: String = "\n") { try? FileHandle.standardError.write(contentsOf: Data("\(message)\(terminator)".utf8)) }