Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vminitd/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ let package = Package(
.executableTarget(
name: "vminitd",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Logging", package: "swift-log"),
.product(name: "Containerization", package: "containerization"),
.product(name: "ContainerizationNetlink", package: "containerization"),
Expand Down
83 changes: 57 additions & 26 deletions vminitd/Sources/vminitd/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,44 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

import ArgumentParser
import ContainerizationOS
import Foundation
import Logging

@main
struct Application {
static func main() async throws {
LoggingSystem.bootstrap(StreamLogHandler.standardError)

// Parse command line arguments
let args = CommandLine.arguments
let command = args.count > 1 ? args[1] : "init"
struct Application: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "vminitd",
abstract: "Virtual machine init daemon",
version: "0.1.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be the version of the previous tag + commit info if not at a tagged commit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, I'm not too worried about the version at the moment

subcommands: [
InitCommand.self,
PauseCommand.self,
],
defaultSubcommand: InitCommand.self
)

switch command {
case "pause":
let log = Logger(label: "pause")

log.info("Running pause command")
try PauseCommand.run(log: log)
case "init":
fallthrough
default:
let log = Logger(label: "vminitd")
static func main() async throws {
// Swift has issues spawning threads if /proc isn't mounted,
// so we do this synchronously before any async code runs.
try mountProc()

log.info("Running init command")
try Self.mountProc(log: log)
try await InitCommand.run(log: log)
var command = try parseAsRoot()
if let asyncCommand = command as? AsyncParsableCommand {
nonisolated(unsafe) var unsafeCommand = asyncCommand
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need to be an unsafe variable?

try await unsafeCommand.run()
} else {
try command.run()
}
}

// Swift seems like it has some fun issues trying to spawn threads if /proc isn't around, so we
// do this before calling our first async function.
static func mountProc(log: Logger) throws {
private static func mountProc() throws {
// Is it already mounted (would only be true in debug builds where we re-exec ourselves)?
if isProcMounted() {
return
}

log.info("mounting /proc")

let mnt = ContainerizationOS.Mount(
type: "proc",
source: "proc",
Expand All @@ -63,7 +61,7 @@ struct Application {
try mnt.mount(createWithPerms: 0o755)
}

static func isProcMounted() -> Bool {
private static func isProcMounted() -> Bool {
guard let data = try? String(contentsOfFile: "/proc/mounts", encoding: .utf8) else {
return false
}
Expand All @@ -81,3 +79,36 @@ struct Application {
return false
}
}

struct LogLevelOption: ParsableArguments {
@Option(name: .long, help: "Set the log level (trace, debug, info, notice, warning, error, critical)")
var logLevel: String = "info"

func resolvedLogLevel() -> Logger.Level {
switch logLevel.lowercased() {
case "trace":
return .trace
case "debug":
return .debug
case "info":
return .info
case "notice":
return .notice
case "warning":
return .warning
case "error":
return .error
case "critical":
return .critical
default:
return .info
}
}
}

func makeLogger(label: String, level: Logger.Level) -> Logger {
LoggingSystem.bootstrap(StreamLogHandler.standardError)
var log = Logger(label: label)
log.logLevel = level
return log
}
30 changes: 18 additions & 12 deletions vminitd/Sources/vminitd/InitCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

import ArgumentParser
import Cgroup
import Containerization
import ContainerizationError
Expand All @@ -28,12 +29,19 @@ import Musl
import LCShim
#endif

struct InitCommand {
struct InitCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "init",
abstract: "Run the init daemon"
)

private static let foregroundEnvVar = "FOREGROUND"
private static let vsockPort = 1024

static func run(log: Logger) async throws {
var log = log
@OptionGroup var options: LogLevelOption

mutating func run() async throws {
let log = makeLogger(label: "vminitd", level: options.resolvedLogLevel())
try Self.adjustLimits(log)

// when running under debug mode, launch vminitd as a sub process of pid1
Expand All @@ -43,11 +51,11 @@ struct InitCommand {
log.info("DEBUG mode active, checking FOREGROUND env var")
let environment = ProcessInfo.processInfo.environment
let foreground = environment[Self.foregroundEnvVar]
log.info("checking for shim var \(foregroundEnvVar)=\(String(describing: foreground))")
log.info("checking for shim var \(Self.foregroundEnvVar)=\(String(describing: foreground))")

if foreground == nil {
try runInForeground(log)
exit(0)
try Self.runInForeground(log, logLevel: options.logLevel)
_exit(0)
}

log.info("FOREGROUND is set, running as subprocess, setting subreaper")
Expand All @@ -57,8 +65,6 @@ struct InitCommand {
CZ_set_sub_reaper()
#endif

log.logLevel = .debug

signal(SIGPIPE, SIG_IGN)

log.info("vminitd booting")
Expand Down Expand Up @@ -137,7 +143,7 @@ struct InitCommand {

do {
log.info("serving vminitd API")
try await server.serve(port: vsockPort)
try await server.serve(port: Self.vsockPort)
log.info("vminitd API returned, syncing filesystems")

#if os(Linux)
Expand All @@ -150,14 +156,14 @@ struct InitCommand {
Musl.sync()
#endif

exit(1)
_exit(1)
}
}

private static func runInForeground(_ log: Logger) throws {
private static func runInForeground(_ log: Logger, logLevel: String) throws {
log.info("running vminitd under pid1")

var command = Command("/sbin/vminitd")
var command = Command("/sbin/vminitd", arguments: ["init", "--log-level", logLevel])
command.attrs = .init(setsid: true)
command.stdin = .standardInput
command.stdout = .standardOutput
Expand Down
14 changes: 12 additions & 2 deletions vminitd/Sources/vminitd/PauseCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

import ArgumentParser
import Dispatch
import Logging
import Musl

struct PauseCommand {
static func run(log: Logger) throws {
struct PauseCommand: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "pause",
abstract: "Run the pause container"
)

@OptionGroup var options: LogLevelOption

mutating func run() throws {
let log = makeLogger(label: "pause", level: options.resolvedLogLevel())

if getpid() != 1 {
log.warning("pause should be the first process")
}
Expand Down