-
Notifications
You must be signed in to change notification settings - Fork 261
Expand file tree
/
Copy pathBundle.swift
More file actions
146 lines (125 loc) · 4.79 KB
/
Bundle.swift
File metadata and controls
146 lines (125 loc) · 4.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//===----------------------------------------------------------------------===//
// 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
}
}