Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Sources/Containerization/FileMount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import ContainerizationError
import ContainerizationOCI
import ContainerizationOS
import Foundation

/// Manages single-file mounts by transforming them into virtiofs directory shares
Expand Down Expand Up @@ -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
Expand Down
11 changes: 9 additions & 2 deletions Sources/Containerization/Hash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
16 changes: 11 additions & 5 deletions Sources/ContainerizationArchive/ArchiveWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//

import CArchive
import ContainerizationOS
import Foundation

/// A class responsible for writing archives in various formats.
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions Sources/ContainerizationOS/URL+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<len].map { UInt8(bitPattern: $0) }, as: UTF8.self)
}
#endif
}