mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-21 10:09:07 +00:00
226 lines
5.7 KiB
Go
226 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
runcBinary = "/usr/bin/runc"
|
|
logDirBase = "/run/log/"
|
|
varLogDir = "/var/log"
|
|
)
|
|
|
|
func dumpFile(w io.Writer, filePath string) error {
|
|
f, err := os.OpenFile(filePath, os.O_RDONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
_, err = io.Copy(w, f)
|
|
|
|
return err
|
|
}
|
|
|
|
func runcInit(rootPath, serviceType string) int {
|
|
// do nothing if the path does not exist
|
|
if _, err := os.Stat(rootPath); err != nil && os.IsNotExist(err) {
|
|
return 0
|
|
}
|
|
|
|
// get files; note ReadDir already sorts them
|
|
files, err := os.ReadDir(rootPath)
|
|
if err != nil {
|
|
log.Fatalf("Cannot read files in %s: %v", rootPath, err)
|
|
}
|
|
|
|
tmpdir, err := os.MkdirTemp("", filepath.Base(rootPath))
|
|
if err != nil {
|
|
log.Fatalf("Cannot create temporary directory: %v", err)
|
|
}
|
|
|
|
// need to set ourselves as a child subreaper or we cannot wait for runc as reparents to init
|
|
if err := setSubreaper(1); err != nil {
|
|
log.Fatalf("Cannot set as subreaper: %v", err)
|
|
}
|
|
|
|
status := 0
|
|
|
|
logDir := path.Join(logDirBase, serviceType)
|
|
varLogLink := path.Join(varLogDir, serviceType)
|
|
|
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
|
log.Fatalf("Cannot create log directory %s: %v", logDir, err)
|
|
}
|
|
|
|
logger := GetLog(logDir)
|
|
v2, err := isCgroupV2()
|
|
if err != nil {
|
|
log.Fatalf("Cannot determine cgroup version: %v", err)
|
|
}
|
|
msg := "cgroup v1"
|
|
if v2 {
|
|
msg = "cgroup v2"
|
|
}
|
|
log.Printf("Using %s", msg)
|
|
|
|
// did we choose to run in debug mode? If so, runc will be in debug, and all messages will go to stdout/stderr in addition to the log
|
|
var runcDebugMode, runcConsoleMode bool
|
|
dt, err := os.ReadFile("/proc/cmdline")
|
|
if err != nil {
|
|
log.Fatalf("error reading /proc/cmdline: %v", err)
|
|
}
|
|
|
|
debugLogger := log.New()
|
|
debugLogger.Level = log.InfoLevel
|
|
|
|
for _, s := range strings.Fields(string(dt)) {
|
|
if s == "linuxkit.runc_debug=1" {
|
|
runcDebugMode = true
|
|
debugLogger.Level = log.DebugLevel
|
|
}
|
|
if s == "linuxkit.runc_console=1" {
|
|
runcConsoleMode = true
|
|
}
|
|
}
|
|
|
|
for _, file := range files {
|
|
name := file.Name()
|
|
path := filepath.Join(rootPath, name)
|
|
log.Printf("%s %s: from %s", serviceType, name, path)
|
|
|
|
runtimeConfig := getRuntimeConfig(path)
|
|
|
|
if err := prepareFilesystem(path, runtimeConfig); err != nil {
|
|
log.Printf("Error preparing %s: %v", name, err)
|
|
status = 1
|
|
continue
|
|
}
|
|
debugLogger.Debugf("%s %s: creating", serviceType, name)
|
|
pidfile := filepath.Join(tmpdir, name)
|
|
cmdArgs := []string{"create", "--bundle", path, "--pid-file", pidfile, name}
|
|
if runcDebugMode {
|
|
cmdArgs = append([]string{"--debug"}, cmdArgs...)
|
|
}
|
|
cmd := exec.Command(runcBinary, cmdArgs...)
|
|
|
|
stdoutLog := serviceType + "." + name + ".out"
|
|
stdout, err := logger.Open(stdoutLog)
|
|
if err != nil {
|
|
log.Printf("Error opening stdout log connection: %v", err)
|
|
status = 1
|
|
continue
|
|
}
|
|
defer stdout.Close()
|
|
|
|
stderrLog := serviceType + "." + name
|
|
stderr, err := logger.Open(stderrLog)
|
|
if err != nil {
|
|
log.Printf("Error opening stderr log connection: %v", err)
|
|
status = 1
|
|
continue
|
|
}
|
|
defer stderr.Close()
|
|
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stderr
|
|
|
|
// if in console mode, send output to stdout/stderr instead of the log
|
|
// do not try io.MultiWriter(os.Stdout, stdout) as console messages will hang.
|
|
// it is not clear why, but since this is all for debugging anyways, it doesn't matter
|
|
// much.
|
|
if runcConsoleMode {
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
}
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
log.Printf("Error creating %s: %v", name, err)
|
|
status = 1
|
|
// skip cleanup on error for debug
|
|
continue
|
|
}
|
|
pf, err := os.ReadFile(pidfile)
|
|
if err != nil {
|
|
log.Printf("Cannot read pidfile: %v", err)
|
|
status = 1
|
|
continue
|
|
}
|
|
pid, err := strconv.Atoi(string(pf))
|
|
if err != nil {
|
|
log.Printf("Cannot parse pid from pidfile: %v", err)
|
|
status = 1
|
|
continue
|
|
}
|
|
|
|
debugLogger.Debugf("%s %s: preparing", serviceType, name)
|
|
if err := prepareProcess(pid, runtimeConfig); err != nil {
|
|
log.Printf("Cannot prepare process: %v", err)
|
|
status = 1
|
|
continue
|
|
}
|
|
|
|
waitFor := make(chan *os.ProcessState)
|
|
go func() {
|
|
// never errors in Unix
|
|
p, _ := os.FindProcess(pid)
|
|
state, err := p.Wait()
|
|
if err != nil {
|
|
log.Printf("Process wait error: %v", err)
|
|
}
|
|
waitFor <- state
|
|
}()
|
|
|
|
debugLogger.Debugf("%s %s: starting", serviceType, name)
|
|
cmdArgs = []string{"start", name}
|
|
if runcDebugMode {
|
|
cmdArgs = append([]string{"--debug"}, cmdArgs...)
|
|
}
|
|
cmd = exec.Command(runcBinary, cmdArgs...)
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
log.Printf("Error starting %s: %v", name, err)
|
|
status = 1
|
|
continue
|
|
}
|
|
|
|
debugLogger.Debugf("%s %s: waiting for completion", serviceType, name)
|
|
_ = <-waitFor
|
|
|
|
debugLogger.Debugf("%s %s: cleaning up", serviceType, name)
|
|
cleanup(path)
|
|
_ = os.Remove(pidfile)
|
|
|
|
// ideally we want to use io.MultiWriter here, sending one stream to stdout/stderr, another to the log
|
|
// however, this hangs if we do, due to a runc bug, see https://github.com/opencontainers/runc/issues/1721#issuecomment-366315563
|
|
// once that is fixed, this can be cleaned up
|
|
logger.Dump(stdoutLog)
|
|
logger.Dump(stderrLog)
|
|
debugLogger.Debugf("%s %s: complete", serviceType, name)
|
|
}
|
|
|
|
_ = os.RemoveAll(tmpdir)
|
|
|
|
// make sure the link exists from /var/log/onboot -> /run/log/onboot
|
|
logger.Symlink(varLogLink)
|
|
|
|
return status
|
|
}
|
|
|
|
// setSubreaper copied directly from https://github.com/opencontainers/runc/blob/b23315bdd99c388f5d0dd3616188729c5a97484a/libcontainer/system/linux.go#L88
|
|
// to avoid version and vendor conflict issues
|
|
func setSubreaper(i int) error {
|
|
return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
|
|
}
|