forked from apple/containerization
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExecCommand.swift
More file actions
128 lines (106 loc) · 4.44 KB
/
Copy pathExecCommand.swift
File metadata and controls
128 lines (106 loc) · 4.44 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
//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved.
//
// 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 ArgumentParser
import ContainerizationOCI
import Foundation
import LCShim
import Logging
import Musl
struct ExecCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "exec",
abstract: "Exec in a container"
)
@Option(name: .long, help: "path to an OCI runtime spec process configuration")
var processPath: String
@Option(name: .long, help: "pid of the init process for the container")
var parentPid: Int
func run() throws {
LoggingSystem.bootstrap(App.standardError)
let log = Logger(label: "vmexec")
let src = URL(fileURLWithPath: processPath)
let processBytes = try Data(contentsOf: src)
let process = try JSONDecoder().decode(
ContainerizationOCI.Process.self,
from: processBytes
)
try execInNamespaces(process: process, log: log)
}
static func enterNS(path: String, nsType: Int32) throws {
let fd = open(path, O_RDONLY)
if fd <= 0 {
throw App.Errno(stage: "open(ns)")
}
defer { close(fd) }
guard setns(fd, nsType) == 0 else {
throw App.Errno(stage: "setns(fd)")
}
}
private func execInNamespaces(
process: ContainerizationOCI.Process,
log: Logger
) throws {
// CLOEXEC the pipe fd that signals process readiness.
let syncfd = FileHandle(fileDescriptor: 3)
if fcntl(3, F_SETFD, FD_CLOEXEC) == -1 {
throw App.Errno(stage: "cloexec(syncfd)")
}
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/cgroup", nsType: CLONE_NEWCGROUP)
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/pid", nsType: CLONE_NEWPID)
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/uts", nsType: CLONE_NEWUTS)
try Self.enterNS(path: "/proc/\(self.parentPid)/ns/mnt", nsType: CLONE_NEWNS)
let childPipe = Pipe()
try childPipe.setCloexec()
let processID = fork()
guard processID != -1 else {
try? childPipe.fileHandleForReading.close()
try? childPipe.fileHandleForWriting.close()
try? syncfd.close()
throw App.Errno(stage: "fork")
}
if processID == 0 { // child
try childPipe.fileHandleForReading.close()
try syncfd.close()
guard setsid() != -1 else {
throw App.Errno(stage: "setsid()")
}
// Apply O_CLOEXEC to all file descriptors except stdio.
// This ensures that all unwanted fds we may have accidentally
// inherited are marked close-on-exec so they stay out of the
// container.
try App.applyCloseExecOnFDs()
try App.setRLimits(rlimits: process.rlimits)
// set uid, gid, and supplementary groups
try App.setPermissions(user: process.user)
if process.terminal {
guard ioctl(0, UInt(TIOCSCTTY), 0) != -1 else {
throw App.Errno(stage: "setctty()")
}
}
try App.exec(process: process)
} else { // parent process
try childPipe.fileHandleForWriting.close()
// wait until the pipe is closed then carry on.
_ = try childPipe.fileHandleForReading.readToEnd()
try childPipe.fileHandleForReading.close()
// send our child's pid to our parent before we exit.
var childPid = processID
let data = Data(bytes: &childPid, count: MemoryLayout.size(ofValue: childPid))
try syncfd.write(contentsOf: data)
try syncfd.close()
}
}
}