Files
wehub-resource-sync 680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
chore: import upstream snapshot with attribution
2026-07-13 12:25:30 +08:00

147 lines
4.8 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerizationError
import Foundation
#if canImport(Musl)
import Musl
private let _mount = Musl.mount
private let _umount = Musl.umount2
#elseif canImport(Glibc)
import Glibc
private let _mount = Glibc.mount
private let _umount = Glibc.umount2
#endif
/// `Bundle` represents an OCI runtime spec bundle for running
/// a container.
public struct Bundle: Sendable {
/// The path to the bundle.
public let path: URL
/// The path to the OCI runtime spec config.json file.
public var configPath: URL {
self.path.appending(path: "config.json")
}
/// The path to a rootfs mount inside the bundle.
public var rootfsPath: URL {
self.path.appending(path: "rootfs")
}
/// Create the OCI bundle.
///
/// - Parameters:
/// - path: A URL pointing to where to create the bundle on the filesystem.
/// - spec: A data blob that should contain an OCI runtime spec. This will be written
/// to the bundle as a "config.json" file.
public static func create(path: URL, spec: Data) throws -> Bundle {
try self.init(path: path, spec: spec)
}
/// Create the OCI bundle.
///
/// - Parameters:
/// - path: A URL pointing to where to create the bundle on the filesystem.
/// - spec: An OCI runtime spec that will be written to the bundle as a "config.json"
/// file.
public static func create(path: URL, spec: ContainerizationOCI.Spec) throws -> Bundle {
try self.init(path: path, spec: spec)
}
/// Load an OCI bundle from the provided path.
///
/// - Parameters:
/// - path: A URL pointing to where to load the bundle from on the filesystem.
public static func load(path: URL) throws -> Bundle {
try self.init(path: path)
}
private init(path: URL) throws {
let fm = FileManager.default
if !fm.fileExists(atPath: path.path) {
throw ContainerizationError(.invalidArgument, message: "no bundle at \(path.path)")
}
self.path = path
}
// This constructor does not do any validation that data is actually a
// valid OCI spec.
private init(path: URL, spec: Data) throws {
self.path = path
let fm = FileManager.default
try fm.createDirectory(
atPath: self.path.appending(component: "rootfs").path,
withIntermediateDirectories: true
)
try spec.write(to: self.configPath)
}
private init(path: URL, spec: ContainerizationOCI.Spec) throws {
self.path = path
let fm = FileManager.default
try fm.createDirectory(
atPath: self.path.appending(component: "rootfs").path,
withIntermediateDirectories: true
)
let specData = try JSONEncoder().encode(spec)
try specData.write(to: self.configPath)
}
/// Delete the OCI bundle from the filesystem.
public func delete() throws {
// Unmount, and then blow away the dir.
#if os(Linux)
let rootfs = self.rootfsPath
if Self.isMountpoint(rootfs) {
guard _umount(rootfs.path, 0) == 0 else {
throw POSIXError.fromErrno()
}
}
#endif
// removeItem is recursive so should blow away the rootfs dir inside as well.
let fm = FileManager.default
try fm.removeItem(at: self.path)
}
/// Load and return the OCI runtime spec written to the bundle.
public func loadConfig() throws -> ContainerizationOCI.Spec {
let data = try Data(contentsOf: self.configPath)
return try JSONDecoder().decode(ContainerizationOCI.Spec.self, from: data)
}
private static func isMountpoint(_ path: URL) -> Bool {
var st = stat()
var parent_st = stat()
guard stat(path.path, &st) == 0 else {
return false
}
let parentPath = path.deletingLastPathComponent()
guard stat(parentPath.path, &parent_st) == 0 else {
return false
}
return st.st_dev != parent_st.st_dev
}
}