Skip to content

Commit d07cd83

Browse files
saehejkangjglogan
andcommitted
update filesystem operation to run in new namespace
Co-authored-by: John Logan <john_logan@apple.com>
1 parent 5796abe commit d07cd83

8 files changed

Lines changed: 127 additions & 16 deletions

File tree

Sources/Containerization/LinuxContainer.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,8 +1134,7 @@ extension LinuxContainer {
11341134
guard let vminitd = agent as? Vminitd else {
11351135
throw ContainerizationError(.unsupported, message: "filesystemOperation requires Vminitd agent")
11361136
}
1137-
let guestPath = URL(filePath: Self.guestRootfsPath(self.id)).appending(path: path).path
1138-
try await vminitd.filesystemOperation(operation: operation, path: guestPath)
1137+
try await vminitd.filesystemOperation(operation: operation, path: path, containerID: self.id)
11391138
}
11401139
}
11411140
}

Sources/Containerization/LinuxPod.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,8 +1285,7 @@ extension LinuxPod {
12851285
guard let vminitd = agent as? Vminitd else {
12861286
throw ContainerizationError(.unsupported, message: "filesystemOperation requires Vminitd agent")
12871287
}
1288-
let guestPath = URL(filePath: Self.guestRootfsPath(containerID)).appending(path: path).path
1289-
try await vminitd.filesystemOperation(operation: operation, path: guestPath)
1288+
try await vminitd.filesystemOperation(operation: operation, path: path, containerID: containerID)
12901289
}
12911290
}
12921291
}

Sources/Containerization/SandboxContext/SandboxContext.pb.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,15 @@ public nonisolated struct Com_Apple_Containerization_Sandbox_V3_FilesystemOperat
11541154
set {operation = .thaw(newValue)}
11551155
}
11561156

1157+
public var containerID: String {
1158+
get {_containerID ?? String()}
1159+
set {_containerID = newValue}
1160+
}
1161+
/// Returns true if `containerID` has been explicitly set.
1162+
public var hasContainerID: Bool {self._containerID != nil}
1163+
/// Clears the value of `containerID`. Subsequent reads from it will return its default value.
1164+
public mutating func clearContainerID() {self._containerID = nil}
1165+
11571166
public var unknownFields = SwiftProtobuf.UnknownStorage()
11581167

11591168
public nonisolated enum OneOf_Operation: Equatable, Sendable {
@@ -1164,6 +1173,8 @@ public nonisolated struct Com_Apple_Containerization_Sandbox_V3_FilesystemOperat
11641173
}
11651174

11661175
public init() {}
1176+
1177+
fileprivate var _containerID: String? = nil
11671178
}
11681179

11691180
public nonisolated struct Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse: Sendable {
@@ -3453,7 +3464,7 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_FiTrimResult: SwiftP
34533464

34543465
nonisolated extension Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
34553466
public static let protoMessageName: String = _protobuf_package + ".FilesystemOperationRequest"
3456-
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}trim\0\u{1}freeze\0\u{1}thaw\0")
3467+
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}path\0\u{1}trim\0\u{1}freeze\0\u{1}thaw\0\u{1}containerID\0")
34573468

34583469
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
34593470
while let fieldNumber = try decoder.nextFieldNumber() {
@@ -3501,6 +3512,7 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_FilesystemOperationR
35013512
self.operation = .thaw(v)
35023513
}
35033514
}()
3515+
case 5: try { try decoder.decodeSingularStringField(value: &self._containerID) }()
35043516
default: break
35053517
}
35063518
}
@@ -3529,12 +3541,16 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_FilesystemOperationR
35293541
}()
35303542
case nil: break
35313543
}
3544+
try { if let v = self._containerID {
3545+
try visitor.visitSingularStringField(value: v, fieldNumber: 5)
3546+
} }()
35323547
try unknownFields.traverse(visitor: &visitor)
35333548
}
35343549

35353550
public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest, rhs: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest) -> Bool {
35363551
if lhs.path != rhs.path {return false}
35373552
if lhs.operation != rhs.operation {return false}
3553+
if lhs._containerID != rhs._containerID {return false}
35383554
if lhs.unknownFields != rhs.unknownFields {return false}
35393555
return true
35403556
}

Sources/Containerization/SandboxContext/SandboxContext.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ message FilesystemOperationRequest {
315315
FiFreezeParams freeze = 3;
316316
FiThawParams thaw = 4;
317317
}
318+
optional string containerID = 5;
318319
}
319320

320321
message FilesystemOperationResponse {

Sources/Containerization/VirtualMachineAgent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public protocol VirtualMachineAgent: Sendable {
4141
/// Close any resources held by the agent.
4242
func close() async throws
4343
// Perform a filesystem operation on the given path.
44-
func filesystemOperation(operation: FilesystemOperation, path: String) async throws
44+
func filesystemOperation(operation: FilesystemOperation, path: String, containerID: String?) async throws
4545

4646
// POSIX-y
4747
func getenv(key: String) async throws -> String

Sources/Containerization/Vminitd.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,14 @@ extension Vminitd: VirtualMachineAgent {
213213
}
214214

215215
/// Perform a filesystem operation on a path inside the sandbox's environment.
216-
public func filesystemOperation(operation: FilesystemOperation, path: String) async throws {
216+
public func filesystemOperation(operation: FilesystemOperation, path: String, containerID: String? = nil) async throws {
217217
_ = try await client.filesystemOperation(
218218
.with {
219219
$0.operation = operation.toProtoOperation()
220220
$0.path = path
221+
if let containerID {
222+
$0.containerID = containerID
223+
}
221224
})
222225
}
223226

vminitd/Sources/LCShim/include/syscall.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
// CLONE_* flags
3030
#ifndef CLONE_NEWNS
31+
#define CLONE_FS 0x00000200
32+
#endif
33+
#ifndef CLONE_NEWNS
3134
#define CLONE_NEWNS 0x00020000
3235
#endif
3336
#ifndef CLONE_NEWCGROUP

vminitd/Sources/VminitdCore/Server+GRPC.swift

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import ContainerizationOS
2727
import Foundation
2828
import GRPCCore
2929
import GRPCProtobuf
30+
import LCShim
3031
import Logging
3132
import NIOCore
3233
import NIOPosix
@@ -722,18 +723,95 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ
722723
async throws -> Com_Apple_Containerization_Sandbox_V3_FilesystemOperationResponse
723724
{
724725
let path = FilePath(request.path)
726+
if !request.hasContainerID {
727+
throw ContainerizationError(
728+
.invalidArgument,
729+
message: "containerID is required"
730+
)
731+
}
732+
733+
guard let operation = request.operation else {
734+
throw ContainerizationError(
735+
.invalidArgument,
736+
message: "operation is required"
737+
)
738+
}
739+
740+
let container = try await state.get(container: request.containerID)
741+
guard let containerPid = await container.pid else {
742+
throw ContainerizationError(
743+
.invalidArgument,
744+
message: "container pid is not present"
745+
)
746+
}
725747

726748
log.debug(
727749
"filesystemOperation",
728750
metadata: [
729-
"operation": "\(String(describing: request.operation))",
751+
"containerID": "\(request.containerID)",
752+
"containerPid": "\(containerPid)",
753+
"operation": "\(operation)",
730754
"path": "\(path)",
731755
])
732756

733757
if !path.isAbsolute {
734758
throw RPCError(code: .invalidArgument, message: "path must be absolute")
735759
}
736760

761+
let selfMountFd = open("/proc/self/ns/mount", O_RDONLY | O_DIRECTORY | O_CLOEXEC)
762+
if selfMountFd < 0 {
763+
let error = swiftErrno("open")
764+
throw RPCError(code: .internalError, message: "failed to open self mount namespace", cause: error)
765+
}
766+
767+
defer { close(selfMountFd) }
768+
769+
let containerMountFd = open("/proc/\(containerPid)/ns/mount", O_RDONLY | O_DIRECTORY | O_CLOEXEC)
770+
if containerMountFd < 0 {
771+
let error = swiftErrno("open")
772+
throw RPCError(code: .internalError, message: "failed to open container mount namespace", cause: error)
773+
}
774+
775+
defer { close(containerMountFd) }
776+
777+
var finfo = _stat_struct()
778+
let selfMountStat = fstat(selfMountFd, &finfo)
779+
if selfMountStat != 0 {
780+
let error = swiftErrno("fstat")
781+
throw RPCError(code: .internalError, message: "failed to stat self mount namespace", cause: error)
782+
}
783+
let selfInode = finfo.st_ino
784+
785+
let containerMountStat = fstat(containerMountFd, &finfo)
786+
if containerMountStat != 0 {
787+
let error = swiftErrno("fstat")
788+
throw RPCError(code: .internalError, message: "failed to stat container mount namespace", cause: error)
789+
}
790+
let containerInode = finfo.st_ino
791+
792+
if selfInode == containerInode {
793+
try doFilesystemOperation(path: path, operation: operation)
794+
} else {
795+
try await self.runOnDedicatedThread {
796+
if unshare(CLONE_FS) != 0 {
797+
let error = self.swiftErrno("unshare(CLONE_FS)")
798+
throw RPCError(code: .internalError, message: "failed to unshare filesystem namespace", cause: error)
799+
}
800+
if setns(containerMountFd, CLONE_NEWNS) != 0 {
801+
let error = self.swiftErrno("setns(CLONE_NEWNS)")
802+
throw RPCError(code: .internalError, message: "failed to enter container mount namespace", cause: error)
803+
}
804+
try self.doFilesystemOperation(path: path, operation: operation)
805+
}
806+
}
807+
808+
return .init()
809+
}
810+
811+
private func doFilesystemOperation(
812+
path: FilePath,
813+
operation: Com_Apple_Containerization_Sandbox_V3_FilesystemOperationRequest.OneOf_Operation
814+
) throws {
737815
var finfo = _stat_struct()
738816
let rc = _stat(path.string, &finfo)
739817
if rc != 0 {
@@ -753,20 +831,18 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ
753831
defer { close(fd) }
754832

755833
do {
756-
switch request.operation {
757-
case .freeze:
834+
switch operation {
835+
case .freeze(_):
758836
try freezeFilesystem(fd: fd)
759-
case .thaw:
837+
case .thaw(_):
760838
try thawFilesystem(fd: fd)
761839
case .trim(let params):
762840
switch params.schedule {
763-
case .oneShot:
841+
case .oneShot(_):
764842
try trimFilesystem(fd: fd)
765843
case .none:
766844
throw RPCError(code: .invalidArgument, message: "trim schedule must be specified")
767845
}
768-
case .none:
769-
throw RPCError(code: .invalidArgument, message: "invalid operation")
770846
}
771847
} catch {
772848
log.error(
@@ -776,8 +852,6 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ
776852
])
777853
throw RPCError(code: .internalError, message: "filesystemOperation", cause: error)
778854
}
779-
780-
return .init()
781855
}
782856

783857
private func freezeFilesystem(fd: Int32) throws {
@@ -1650,6 +1724,22 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ
16501724
return error
16511725
}
16521726

1727+
private func runOnDedicatedThread<T: Sendable>(
1728+
_ work: @escaping @Sendable () throws -> T
1729+
) async throws -> T {
1730+
try await withCheckedThrowingContinuation { continuation in
1731+
let thread = Thread {
1732+
do {
1733+
let result = try work()
1734+
continuation.resume(returning: result)
1735+
} catch {
1736+
continuation.resume(throwing: error)
1737+
}
1738+
}
1739+
thread.start()
1740+
}
1741+
}
1742+
16531743
// NOTE: This is just crummy. It works because today the assumption is
16541744
// every NIC in the root net namespace is for the container(s), but if we
16551745
// ever supported individual containers having their own NICs/IPs then this

0 commit comments

Comments
 (0)