@@ -50,6 +50,9 @@ public final class LinuxPod: Sendable {
5050 public var virtualization : Bool = false
5151 /// Optional file path to store serial boot logs.
5252 public var bootLog : BootLog ?
53+ /// Whether containers in the pod should share a PID namespace.
54+ /// When enabled, all containers can see each other's processes.
55+ public var shareProcessNamespace : Bool = false
5356
5457 public init ( ) { }
5558 }
@@ -103,6 +106,7 @@ public final class LinuxPod: Sendable {
103106 private struct State : Sendable {
104107 var phase : Phase
105108 var containers : [ String : PodContainer ]
109+ var pauseProcess : LinuxProcess ?
106110 }
107111
108112 private enum Phase : Sendable {
@@ -173,7 +177,7 @@ public final class LinuxPod: Sendable {
173177 try configuration ( & config)
174178
175179 self . config = config
176- self . state = AsyncMutex ( State ( phase: . initialized, containers: [ : ] ) )
180+ self . state = AsyncMutex ( State ( phase: . initialized, containers: [ : ] , pauseProcess : nil ) )
177181 }
178182
179183 private static func createDefaultRuntimeSpec( _ containerID: String , podID: String ) -> Spec {
@@ -303,9 +307,64 @@ extension LinuxPod {
303307
304308 do {
305309 let containers = state. containers
310+ let shareProcessNamespace = self . config. shareProcessNamespace
311+ let pauseProcessHolder = Mutex < LinuxProcess ? > ( nil )
312+
306313 try await vm. withAgent { agent in
307314 try await agent. standardSetup ( )
308315
316+ // Create pause container if PID namespace sharing is enabled
317+ if shareProcessNamespace {
318+ let pauseID = " pause- \( self . id) "
319+ let pauseRootfsPath = " /run/container/ \( pauseID) /rootfs "
320+
321+ // Bind mount /sbin into the pause container rootfs.
322+ // This is where the guest agent lives.
323+ try await agent. mount (
324+ ContainerizationOCI . Mount (
325+ type: " " ,
326+ source: " /sbin " ,
327+ destination: " \( pauseRootfsPath) /sbin " ,
328+ options: [ " bind " ]
329+ ) )
330+
331+ var pauseSpec = Self . createDefaultRuntimeSpec ( pauseID, podID: self . id)
332+ pauseSpec. process? . args = [ " /sbin/vminitd " , " pause " ]
333+ pauseSpec. hostname = " "
334+ pauseSpec. mounts = LinuxContainer . defaultMounts ( ) . map {
335+ ContainerizationOCI . Mount (
336+ type: $0. type,
337+ source: $0. source,
338+ destination: $0. destination,
339+ options: $0. options
340+ )
341+ }
342+ pauseSpec. linux? . namespaces = [
343+ LinuxNamespace ( type: . cgroup) ,
344+ LinuxNamespace ( type: . ipc) ,
345+ LinuxNamespace ( type: . mount) ,
346+ LinuxNamespace ( type: . pid) ,
347+ LinuxNamespace ( type: . uts) ,
348+ ]
349+
350+ // Create LinuxProcess for pause container
351+ let process = LinuxProcess (
352+ pauseID,
353+ containerID: pauseID,
354+ spec: pauseSpec,
355+ io: LinuxProcess . Stdio ( stdin: nil , stdout: nil , stderr: nil ) ,
356+ ociRuntimePath: nil ,
357+ agent: agent,
358+ vm: vm,
359+ logger: self . logger
360+ )
361+
362+ try await process. start ( )
363+ pauseProcessHolder. withLock { $0 = process }
364+
365+ self . logger? . debug ( " Pause container started " , metadata: [ " pid " : " \( process. pid) " ] )
366+ }
367+
309368 // Mount all container rootfs
310369 for (_, container) in containers {
311370 guard let attachments = vm. mounts [ container. id] , let rootfsAttachment = attachments. first else {
@@ -353,6 +412,8 @@ extension LinuxPod {
353412 }
354413 }
355414
415+ state. pauseProcess = pauseProcessHolder. withLock { $0 }
416+
356417 // Transition all containers to created state
357418 for id in state. containers. keys {
358419 state. containers [ id] ? . state = . created
@@ -394,6 +455,33 @@ extension LinuxPod {
394455 let containerMounts = createdState. vm. mounts [ containerID] ?? [ ]
395456 spec. mounts = containerMounts. dropFirst ( ) . map { $0. to }
396457
458+ // Configure namespaces for the container
459+ var namespaces : [ LinuxNamespace ] = [
460+ LinuxNamespace ( type: . cgroup) ,
461+ LinuxNamespace ( type: . ipc) ,
462+ LinuxNamespace ( type: . mount) ,
463+ LinuxNamespace ( type: . uts) ,
464+ ]
465+
466+ // Either join pause container's pid ns or create a new one
467+ if self . config. shareProcessNamespace, let pausePID = state. pauseProcess? . pid {
468+ let nsPath = " /proc/ \( pausePID) /ns/pid "
469+
470+ self . logger? . debug (
471+ " Container joining pause PID namespace " ,
472+ metadata: [
473+ " container " : " \( containerID) " ,
474+ " pausePID " : " \( pausePID) " ,
475+ " nsPath " : " \( nsPath) " ,
476+ ] )
477+
478+ namespaces. append ( LinuxNamespace ( type: . pid, path: nsPath) )
479+ } else {
480+ namespaces. append ( LinuxNamespace ( type: . pid) )
481+ }
482+
483+ spec. linux? . namespaces = namespaces
484+
397485 let stdio = IOUtil . setup (
398486 portAllocator: self . hostVsockPorts,
399487 stdin: container. config. process. stdin,
0 commit comments