diff --git a/Sources/Containerization/FileMount.swift b/Sources/Containerization/FileMount.swift index af539d79..c10b860e 100644 --- a/Sources/Containerization/FileMount.swift +++ b/Sources/Containerization/FileMount.swift @@ -18,6 +18,7 @@ import ContainerizationError import ContainerizationOCI +import ContainerizationOS import Foundation /// Manages single-file mounts by transforming them into virtiofs directory shares @@ -132,8 +133,14 @@ extension FileMountContext { mount: Mount, runtimeOptions: [String] ) throws -> PreparedMount { - let resolvedSource = URL(fileURLWithPath: mount.source).resolvingSymlinksInPath() let sourceURL = URL(fileURLWithPath: mount.source) + let resolvedSource: URL + if sourceURL.isSymlink, let rawTarget = sourceURL.rawSymlinkTarget() { + resolvedSource = URL(fileURLWithPath: rawTarget, relativeTo: sourceURL.deletingLastPathComponent()) + .standardized + } else { + resolvedSource = sourceURL + } let filename = sourceURL.lastPathComponent let tempDir = FileManager.default.temporaryDirectory diff --git a/Sources/Containerization/Hash.swift b/Sources/Containerization/Hash.swift index b46f576d..99c65bd5 100644 --- a/Sources/Containerization/Hash.swift +++ b/Sources/Containerization/Hash.swift @@ -18,11 +18,18 @@ import Crypto import ContainerizationError +import ContainerizationOS import Foundation public func hashMountSource(source: String) throws -> String { - // Resolve symlinks so different paths to the same directory get the same hash. - let resolvedSource = URL(fileURLWithPath: source).resolvingSymlinksInPath().path + let sourceURL = URL(fileURLWithPath: source) + let resolvedSource: String + if sourceURL.isSymlink, let rawTarget = sourceURL.rawSymlinkTarget() { + resolvedSource = URL(fileURLWithPath: rawTarget, relativeTo: sourceURL.deletingLastPathComponent()) + .standardized.path + } else { + resolvedSource = sourceURL.standardizedFileURL.path + } guard let data = resolvedSource.data(using: .utf8) else { throw ContainerizationError(.invalidArgument, message: "\(source) could not be converted to Data") } diff --git a/Sources/ContainerizationArchive/ArchiveWriter.swift b/Sources/ContainerizationArchive/ArchiveWriter.swift index a79a105b..305964da 100644 --- a/Sources/ContainerizationArchive/ArchiveWriter.swift +++ b/Sources/ContainerizationArchive/ArchiveWriter.swift @@ -15,6 +15,7 @@ //===----------------------------------------------------------------------===// import CArchive +import ContainerizationOS import Foundation /// A class responsible for writing archives in various formats. @@ -302,13 +303,18 @@ extension ArchiveWriter { } size = Int64(_size) } else if type == .symbolicLink { - let target = fileURL.resolvingSymlinksInPath().absoluteString - let root = dir.absoluteString - guard target.hasPrefix(root) else { + guard let rawTarget = fileURL.rawSymlinkTarget() else { continue } - let linkTarget = target.dropFirst(root.count + 1) - entry.symlinkTarget = String(linkTarget) + let resolvedTarget = URL( + fileURLWithPath: rawTarget, + relativeTo: fileURL.deletingLastPathComponent() + ).standardized + let rootPath = dir.standardizedFileURL.path + guard resolvedTarget.path.hasPrefix(rootPath) else { + continue + } + entry.symlinkTarget = rawTarget } guard let created = resourceValues.creationDate else { diff --git a/Sources/ContainerizationOS/URL+Extensions.swift b/Sources/ContainerizationOS/URL+Extensions.swift index 787344df..d11ef3b4 100644 --- a/Sources/ContainerizationOS/URL+Extensions.swift +++ b/Sources/ContainerizationOS/URL+Extensions.swift @@ -50,4 +50,13 @@ extension URL { public var isSymlink: Bool { (try? resourceValues(forKeys: [.isSymbolicLinkKey]))?.isSymbolicLink == true } + + #if os(macOS) + public func rawSymlinkTarget() -> String? { + var buf = [CChar](repeating: 0, count: Int(PATH_MAX)) + let len = Darwin.readlink(self.path(percentEncoded: false), &buf, buf.count - 1) + guard len > 0 else { return nil } + return String(decoding: buf[0..