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: 7 additions & 2 deletions Sources/Containerization/Hash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@

import Crypto
import ContainerizationError
import Foundation

func hashMountSource(source: String) throws -> String {
guard let data = source.data(using: .utf8) else {
throw ContainerizationError(.invalidArgument, message: "\(source) could not be converted to Data")
let resolvedSource = URL(fileURLWithPath: source)
.resolvingSymlinksInPath()
.path

guard let data = resolvedSource.data(using: .utf8) else {
throw ContainerizationError(.invalidArgument, message: "\(resolvedSource) could not be converted to Data")
}
return String(SHA256.hash(data: data).encoded.prefix(36))
}
Expand Down
9 changes: 8 additions & 1 deletion Sources/Containerization/Mount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public struct Mount: Sendable {
#if os(macOS)

extension Mount {
func configure(config: inout VZVirtualMachineConfiguration) throws {
func configure(config: inout VZVirtualMachineConfiguration, usedVirtioFSTags: inout Set<String>) throws {
switch self.runtimeOptions {
case .virtioblk(let options):
let device = try VZDiskImageStorageDeviceAttachment.mountToVZAttachment(mount: self, options: options)
Expand All @@ -144,6 +144,13 @@ extension Mount {
}

let name = try hashMountSource(source: self.source)

// Skip creating VZVirtioFileSystemDeviceConfiguration if tag already exists.
if usedVirtioFSTags.contains(name) {
return
}

usedVirtioFSTags.insert(name)
let urlSource = URL(fileURLWithPath: source)

let device = VZVirtioFileSystemDeviceConfiguration(tag: name)
Expand Down
12 changes: 8 additions & 4 deletions Sources/Containerization/VZVirtualMachineInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ struct VZVirtualMachineInstance: VirtualMachineInstance, Sendable {
public var initialFilesystem: Mount?
/// File path to store the sandbox boot logs.
public var bootlog: URL?
/// Set of virtiofs tags that have already been configured to avoid duplicates.
var usedVirtioFSTags: Set<String> = []

init() {
self.cpus = 4
Expand All @@ -65,6 +67,7 @@ struct VZVirtualMachineInstance: VirtualMachineInstance, Sendable {
self.nestedVirtualization = false
self.mounts = []
self.interfaces = []
self.usedVirtioFSTags = []
}
}

Expand All @@ -86,6 +89,7 @@ struct VZVirtualMachineInstance: VirtualMachineInstance, Sendable {
}

init(group: MultiThreadedEventLoopGroup, config: Configuration, logger: Logger?) throws {
var mutableConfig = config
self.config = config
self.group = group
self.lock = .init()
Expand All @@ -95,7 +99,7 @@ struct VZVirtualMachineInstance: VirtualMachineInstance, Sendable {
self.timeSyncer = .init(logger: logger)

self.vm = VZVirtualMachine(
configuration: try config.toVZ(),
configuration: try mutableConfig.toVZ(),
queue: self.queue
)
}
Expand Down Expand Up @@ -243,7 +247,7 @@ extension VZVirtualMachineInstance.Configuration {
return [c]
}

func toVZ() throws -> VZVirtualMachineConfiguration {
mutating func toVZ() throws -> VZVirtualMachineConfiguration {
var config = VZVirtualMachineConfiguration()

config.cpuCount = self.cpus
Expand Down Expand Up @@ -301,9 +305,9 @@ extension VZVirtualMachineInstance.Configuration {
loader.commandLine = kernel.linuxCommandline(initialFilesystem: initialFilesystem)
config.bootLoader = loader

try initialFilesystem.configure(config: &config)
try initialFilesystem.configure(config: &config, usedVirtioFSTags: &usedVirtioFSTags)
for mount in self.mounts {
try mount.configure(config: &config)
try mount.configure(config: &config, usedVirtioFSTags: &usedVirtioFSTags)
}

let platform = VZGenericPlatformConfiguration()
Expand Down
1 change: 1 addition & 0 deletions Sources/Integration/Suite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ struct IntegrationSuite: AsyncParsableCommand {
"container hostname": testHostname,
"container hosts": testHostsFile,
"container mount": testMounts,
"container mount duplicate": testDuplicateMount,
"nested virt": testNestedVirtualizationEnabled,
"container manager": testContainerManagerCreate,
]
Expand Down
37 changes: 36 additions & 1 deletion Sources/Integration/VMTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ extension IntegrationSuite {

let bs = try await bootstrap()
let buffer = BufferWriter()
let directory = try createMountDirectory()
defer { try? FileManager.default.removeItem(at: directory) }

let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
let directory = try createMountDirectory()
config.process.arguments = ["/bin/cat", "/mnt/hi.txt"]
config.mounts.append(.share(source: directory.path, destination: "/mnt"))
config.process.stdout = buffer
Expand Down Expand Up @@ -121,6 +123,39 @@ extension IntegrationSuite {
}
}

func testDuplicateMount() async throws {
let id = "test-duplicate-mounts"

let bs = try await bootstrap()
let buffer = BufferWriter()
let directory = try createMountDirectory()
defer { try? FileManager.default.removeItem(at: directory) }

let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
config.process.arguments = ["/bin/sh", "-c", "cat /mnt1/hi.txt && cat /mnt2/hi.txt"]
// Mount the same directory to two different destinations
config.mounts.append(.share(source: directory.path, destination: "/mnt1"))
config.mounts.append(.share(source: directory.path, destination: "/mnt2"))
config.process.stdout = buffer
}

try await container.create()
try await container.start()

let status = try await container.wait()
try await container.stop()

guard status == 0 else {
throw IntegrationError.assert(msg: "process status \(status) != 0")
}

let value = String(data: buffer.data, encoding: .utf8)
guard value == "hellohello" else {
throw IntegrationError.assert(
msg: "process should have returned 'hellohello' != '\(String(data: buffer.data, encoding: .utf8)!)'")
}
}

private func createMountDirectory() throws -> URL {
let dir = FileManager.default.uniqueTemporaryDirectory(create: true)
try "hello".write(to: dir.appendingPathComponent("hi.txt"), atomically: true, encoding: .utf8)
Expand Down