init commit
Signed-off-by: Jess Frazelle <jess@mesosphere.com>
This commit is contained in:
commit
fec7085140
13 changed files with 4205 additions and 0 deletions
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
###Go###
|
||||||
|
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.swo
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
|
||||||
|
|
||||||
|
###OSX###
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must ends with two \r.
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear on external disk
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
|
||||||
|
binctr
|
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Jess Frazelle
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
56
Makefile
Normal file
56
Makefile
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
.PHONY: clean all fmt vet lint build test install static
|
||||||
|
PREFIX?=$(shell pwd)
|
||||||
|
BUILDTAGS=seccomp apparmor
|
||||||
|
|
||||||
|
PROJECT := github.com/jfrazelle/binctr
|
||||||
|
VENDOR := vendor
|
||||||
|
|
||||||
|
# Variable to get the current version.
|
||||||
|
VERSION := $(shell cat VERSION)
|
||||||
|
|
||||||
|
# Variable to set the current git commit.
|
||||||
|
GITCOMMIT := $(shell git rev-parse --short HEAD)
|
||||||
|
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
|
GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no)
|
||||||
|
ifneq ($(GITUNTRACKEDCHANGES),)
|
||||||
|
GITCOMMIT := $(GITCOMMIT)-dirty
|
||||||
|
endif
|
||||||
|
|
||||||
|
LDFLAGS := ${LDFLAGS} \
|
||||||
|
-X $(PROJECT)/main.GITCOMMIT=${GITCOMMIT} \
|
||||||
|
-X $(PROJECT)/main.VERSION=${VERSION} \
|
||||||
|
|
||||||
|
all: clean build fmt lint test vet install
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "+ $@"
|
||||||
|
go build -tags "$(BUILDTAGS)" -ldflags "${LDFLAGS}" .
|
||||||
|
|
||||||
|
static:
|
||||||
|
@echo "+ $@"
|
||||||
|
CGO_ENABLED=1 go build -tags "$(BUILDTAGS) cgo static_build" \
|
||||||
|
-ldflags "-w -extldflags -static ${LDFLAGS}" -o binctr .
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@echo "+ $@"
|
||||||
|
@gofmt -s -l . | grep -v $(VENDOR) | tee /dev/stderr
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@echo "+ $@"
|
||||||
|
@golint ./... | grep -v $(VENDOR) | tee /dev/stderr
|
||||||
|
|
||||||
|
test: fmt lint vet
|
||||||
|
@echo "+ $@"
|
||||||
|
@go test -v -tags "$(BUILDTAGS) cgo" $(shell go list ./... | grep -v $(VENDOR))
|
||||||
|
|
||||||
|
vet:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go vet $(shell go list ./... | grep -v $(VENDOR))
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "+ $@"
|
||||||
|
@$(RM) binctr
|
||||||
|
|
||||||
|
install:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go install .
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0.1.0
|
15
circle.yml
Normal file
15
circle.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
machine:
|
||||||
|
environment:
|
||||||
|
GO15VENDOREXPERIMENT: 1
|
||||||
|
dependencies:
|
||||||
|
post:
|
||||||
|
# install golint
|
||||||
|
- go get github.com/golang/lint/golint
|
||||||
|
|
||||||
|
test:
|
||||||
|
pre:
|
||||||
|
- go vet $(go list ./... | grep -v vendor)
|
||||||
|
- test -z "$(golint ./... | grep -v vendor | tee /dev/stderr)"
|
||||||
|
- test -z "$(gofmt -s -l . | grep -v vendor | tee /dev/stderr)"
|
||||||
|
override:
|
||||||
|
- go test $(go list ./... | grep -v vendor)
|
120
main.go
Normal file
120
main.go
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BANNER is what is printed for help/info output.
|
||||||
|
BANNER = ` _ _ _
|
||||||
|
| |__ (_)_ __ ___| |_ _ __
|
||||||
|
| '_ \| | '_ \ / __| __| '__|
|
||||||
|
| |_) | | | | | (__| |_| |
|
||||||
|
|_.__/|_|_| |_|\___|\__|_|
|
||||||
|
|
||||||
|
Fully static self-contained container including the rootfs.
|
||||||
|
Version: %s
|
||||||
|
GitCommit: %s
|
||||||
|
`
|
||||||
|
|
||||||
|
defaultRoot = "/run/binctr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
console = os.Getenv("console")
|
||||||
|
containerID string
|
||||||
|
root string
|
||||||
|
|
||||||
|
debug bool
|
||||||
|
version bool
|
||||||
|
|
||||||
|
// GITCOMMIT is git commit the binary was compiled against.
|
||||||
|
GITCOMMIT = ""
|
||||||
|
|
||||||
|
// VERSION is the binary version.
|
||||||
|
VERSION = "v0.1.0"
|
||||||
|
|
||||||
|
// DATA is the rootfs tar that is added at compile time.
|
||||||
|
DATA = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Parse flags
|
||||||
|
flag.StringVar(&containerID, "id", "jessiscool", "container ID")
|
||||||
|
flag.StringVar(&console, "console", console, "the pty slave path for use with the container")
|
||||||
|
flag.StringVar(&root, "root", defaultRoot, "root directory of container state, should be tmpfs")
|
||||||
|
flag.BoolVar(&version, "version", false, "print version and exit")
|
||||||
|
flag.BoolVar(&version, "v", false, "print version and exit (shorthand)")
|
||||||
|
flag.BoolVar(&debug, "d", false, "run in debug mode")
|
||||||
|
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Fprint(os.Stderr, fmt.Sprintf(BANNER, VERSION, GITCOMMIT))
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if version {
|
||||||
|
fmt.Printf("%s, commit: %s", VERSION, GITCOMMIT)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set log level
|
||||||
|
if debug {
|
||||||
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "init" {
|
||||||
|
runInit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unpackRootfs(); err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := startContainer(spec, containerID)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit with the container's exit status
|
||||||
|
os.Exit(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackRootfs() error {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(DATA)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
if err := os.Mkdir("container", 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return archive.Untar(r, "container", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInit() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "init" {
|
||||||
|
runtime.GOMAXPROCS(1)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
factory, _ := libcontainer.New("")
|
||||||
|
if err := factory.StartInitialization(); err != nil {
|
||||||
|
// as the error is sent back to the parent there is no need to log
|
||||||
|
// or write it to stderr because the parent process will handle this
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
panic("libcontainer: container init failed to exec")
|
||||||
|
}
|
||||||
|
}
|
49
rlimit_linux.go
Normal file
49
rlimit_linux.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const (
|
||||||
|
RLIMIT_CPU = iota // CPU time in sec
|
||||||
|
RLIMIT_FSIZE // Maximum filesize
|
||||||
|
RLIMIT_DATA // max data size
|
||||||
|
RLIMIT_STACK // max stack size
|
||||||
|
RLIMIT_CORE // max core file size
|
||||||
|
RLIMIT_RSS // max resident set size
|
||||||
|
RLIMIT_NPROC // max number of processes
|
||||||
|
RLIMIT_NOFILE // max number of open files
|
||||||
|
RLIMIT_MEMLOCK // max locked-in-memory address space
|
||||||
|
RLIMIT_AS // address space limit
|
||||||
|
RLIMIT_LOCKS // maximum file locks held
|
||||||
|
RLIMIT_SIGPENDING // max number of pending signals
|
||||||
|
RLIMIT_MSGQUEUE // maximum bytes in POSIX mqueues
|
||||||
|
RLIMIT_NICE // max nice prio allowed to raise to
|
||||||
|
RLIMIT_RTPRIO // maximum realtime priority
|
||||||
|
RLIMIT_RTTIME // timeout for RT tasks in us
|
||||||
|
)
|
||||||
|
|
||||||
|
var rlimitMap = map[string]int{
|
||||||
|
"RLIMIT_CPU": RLIMIT_CPU,
|
||||||
|
"RLIMIT_FSIZE": RLIMIT_FSIZE,
|
||||||
|
"RLIMIT_DATA": RLIMIT_DATA,
|
||||||
|
"RLIMIT_STACK": RLIMIT_STACK,
|
||||||
|
"RLIMIT_CORE": RLIMIT_CORE,
|
||||||
|
"RLIMIT_RSS": RLIMIT_RSS,
|
||||||
|
"RLIMIT_NPROC": RLIMIT_NPROC,
|
||||||
|
"RLIMIT_NOFILE": RLIMIT_NOFILE,
|
||||||
|
"RLIMIT_MEMLOCK": RLIMIT_MEMLOCK,
|
||||||
|
"RLIMIT_AS": RLIMIT_AS,
|
||||||
|
"RLIMIT_LOCKS": RLIMIT_LOCKS,
|
||||||
|
"RLIMIT_SIGPENDING": RLIMIT_SIGPENDING,
|
||||||
|
"RLIMIT_MSGQUEUE": RLIMIT_MSGQUEUE,
|
||||||
|
"RLIMIT_NICE": RLIMIT_NICE,
|
||||||
|
"RLIMIT_RTPRIO": RLIMIT_RTPRIO,
|
||||||
|
"RLIMIT_RTTIME": RLIMIT_RTTIME,
|
||||||
|
}
|
||||||
|
|
||||||
|
func strToRlimit(key string) (int, error) {
|
||||||
|
rl, ok := rlimitMap[key]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("Wrong rlimit value: %s", key)
|
||||||
|
}
|
||||||
|
return rl, nil
|
||||||
|
}
|
1652
seccomp.go
Normal file
1652
seccomp.go
Normal file
File diff suppressed because it is too large
Load diff
1623
seccomp.json
Executable file
1623
seccomp.json
Executable file
File diff suppressed because it is too large
Load diff
116
signals.go
Normal file
116
signals.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/system"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const signalBufferSize = 2048
|
||||||
|
|
||||||
|
// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
|
||||||
|
// while still forwarding all other signals to the process.
|
||||||
|
func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler {
|
||||||
|
if enableSubreaper {
|
||||||
|
// set us as the subreaper before registering the signal handler for the container
|
||||||
|
if err := system.SetSubreaper(1); err != nil {
|
||||||
|
logrus.Warn(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ensure that we have a large buffer size so that we do not miss any signals
|
||||||
|
// incase we are not processing them fast enough.
|
||||||
|
s := make(chan os.Signal, signalBufferSize)
|
||||||
|
// handle all signals for the process.
|
||||||
|
signal.Notify(s)
|
||||||
|
return &signalHandler{
|
||||||
|
tty: tty,
|
||||||
|
signals: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit models a process exit status with the pid and
|
||||||
|
// exit status.
|
||||||
|
type exit struct {
|
||||||
|
pid int
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
type signalHandler struct {
|
||||||
|
signals chan os.Signal
|
||||||
|
tty *tty
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward handles the main signal event loop forwarding, resizing, or reaping depending
|
||||||
|
// on the signal received.
|
||||||
|
func (h *signalHandler) forward(process *libcontainer.Process) (int, error) {
|
||||||
|
// make sure we know the pid of our main process so that we can return
|
||||||
|
// after it dies.
|
||||||
|
pid1, err := process.Pid()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
// perform the initial tty resize.
|
||||||
|
h.tty.resize()
|
||||||
|
for s := range h.signals {
|
||||||
|
switch s {
|
||||||
|
case syscall.SIGWINCH:
|
||||||
|
h.tty.resize()
|
||||||
|
case syscall.SIGCHLD:
|
||||||
|
exits, err := h.reap()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
for _, e := range exits {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"pid": e.pid,
|
||||||
|
"status": e.status,
|
||||||
|
}).Debug("process exited")
|
||||||
|
if e.pid == pid1 {
|
||||||
|
// call Wait() on the process even though we already have the exit
|
||||||
|
// status because we must ensure that any of the go specific process
|
||||||
|
// fun such as flushing pipes are complete before we return.
|
||||||
|
process.Wait()
|
||||||
|
return e.status, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logrus.Debugf("sending signal to process %s", s)
|
||||||
|
if err := syscall.Kill(pid1, s.(syscall.Signal)); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reap runs wait4 in a loop until we have finished processing any existing exits
|
||||||
|
// then returns all exits to the main event loop for further processing.
|
||||||
|
func (h *signalHandler) reap() (exits []exit, err error) {
|
||||||
|
var (
|
||||||
|
ws syscall.WaitStatus
|
||||||
|
rus syscall.Rusage
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
pid, err := syscall.Wait4(-1, &ws, syscall.WNOHANG, &rus)
|
||||||
|
if err != nil {
|
||||||
|
if err == syscall.ECHILD {
|
||||||
|
return exits, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pid <= 0 {
|
||||||
|
return exits, nil
|
||||||
|
}
|
||||||
|
exits = append(exits, exit{
|
||||||
|
pid: pid,
|
||||||
|
status: utils.ExitStatus(ws),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
133
spec.go
Normal file
133
spec.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spec = &specs.Spec{
|
||||||
|
Version: specs.Version,
|
||||||
|
Platform: specs.Platform{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
},
|
||||||
|
Root: specs.Root{
|
||||||
|
Path: "rootfs",
|
||||||
|
Readonly: true,
|
||||||
|
},
|
||||||
|
Process: specs.Process{
|
||||||
|
Terminal: true,
|
||||||
|
User: specs.User{},
|
||||||
|
Args: []string{
|
||||||
|
"sh",
|
||||||
|
},
|
||||||
|
Env: []string{
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"TERM=xterm",
|
||||||
|
},
|
||||||
|
Cwd: "/",
|
||||||
|
NoNewPrivileges: true,
|
||||||
|
Capabilities: []string{
|
||||||
|
"CAP_AUDIT_WRITE",
|
||||||
|
"CAP_KILL",
|
||||||
|
"CAP_NET_BIND_SERVICE",
|
||||||
|
},
|
||||||
|
Rlimits: []specs.Rlimit{
|
||||||
|
{
|
||||||
|
Type: "RLIMIT_NOFILE",
|
||||||
|
Hard: uint64(1024),
|
||||||
|
Soft: uint64(1024),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Hostname: "ctr",
|
||||||
|
Mounts: []specs.Mount{
|
||||||
|
{
|
||||||
|
Destination: "/proc",
|
||||||
|
Type: "proc",
|
||||||
|
Source: "proc",
|
||||||
|
Options: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev",
|
||||||
|
Type: "tmpfs",
|
||||||
|
Source: "tmpfs",
|
||||||
|
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev/pts",
|
||||||
|
Type: "devpts",
|
||||||
|
Source: "devpts",
|
||||||
|
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev/shm",
|
||||||
|
Type: "tmpfs",
|
||||||
|
Source: "shm",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/dev/mqueue",
|
||||||
|
Type: "mqueue",
|
||||||
|
Source: "mqueue",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/sys",
|
||||||
|
Type: "sysfs",
|
||||||
|
Source: "sysfs",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/sys/fs/cgroup",
|
||||||
|
Type: "cgroup",
|
||||||
|
Source: "cgroup",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Linux: specs.Linux{
|
||||||
|
MaskedPaths: []string{
|
||||||
|
"/proc/kcore",
|
||||||
|
"/proc/latency_stats",
|
||||||
|
"/proc/timer_stats",
|
||||||
|
"/proc/sched_debug",
|
||||||
|
},
|
||||||
|
ReadonlyPaths: []string{
|
||||||
|
"/proc/asound",
|
||||||
|
"/proc/bus",
|
||||||
|
"/proc/fs",
|
||||||
|
"/proc/irq",
|
||||||
|
"/proc/sys",
|
||||||
|
"/proc/sysrq-trigger",
|
||||||
|
},
|
||||||
|
Resources: &specs.Resources{
|
||||||
|
Devices: []specs.DeviceCgroup{
|
||||||
|
{
|
||||||
|
Allow: false,
|
||||||
|
Access: sPtr("rwm"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Namespaces: []specs.Namespace{
|
||||||
|
{
|
||||||
|
Type: "pid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "ipc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "uts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "mount",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Seccomp: defaultSeccompProfile,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
126
tty.go
Normal file
126
tty.go
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setup standard pipes so that the TTY of the calling runc process
|
||||||
|
// is not inherited by the container.
|
||||||
|
func createStdioPipes(p *libcontainer.Process, rootuid int) (*tty, error) {
|
||||||
|
i, err := p.InitializeIO(rootuid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t := &tty{
|
||||||
|
closers: []io.Closer{
|
||||||
|
i.Stdin,
|
||||||
|
i.Stdout,
|
||||||
|
i.Stderr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// add the process's io to the post start closers if they support close
|
||||||
|
for _, cc := range []interface{}{
|
||||||
|
p.Stdin,
|
||||||
|
p.Stdout,
|
||||||
|
p.Stderr,
|
||||||
|
} {
|
||||||
|
if c, ok := cc.(io.Closer); ok {
|
||||||
|
t.postStart = append(t.postStart, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
io.Copy(i.Stdin, os.Stdin)
|
||||||
|
i.Stdin.Close()
|
||||||
|
}()
|
||||||
|
t.wg.Add(2)
|
||||||
|
go t.copyIO(os.Stdout, i.Stdout)
|
||||||
|
go t.copyIO(os.Stderr, i.Stderr)
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
|
||||||
|
defer t.wg.Done()
|
||||||
|
io.Copy(w, r)
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTty(p *libcontainer.Process, rootuid int, consolePath string) (*tty, error) {
|
||||||
|
if consolePath != "" {
|
||||||
|
if err := p.ConsoleFromPath(consolePath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tty{}, nil
|
||||||
|
}
|
||||||
|
console, err := p.NewConsole(rootuid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
go io.Copy(console, os.Stdin)
|
||||||
|
go io.Copy(os.Stdout, console)
|
||||||
|
|
||||||
|
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
||||||
|
}
|
||||||
|
return &tty{
|
||||||
|
console: console,
|
||||||
|
state: state,
|
||||||
|
closers: []io.Closer{
|
||||||
|
console,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tty struct {
|
||||||
|
console libcontainer.Console
|
||||||
|
state *term.State
|
||||||
|
closers []io.Closer
|
||||||
|
postStart []io.Closer
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosePostStart closes any fds that are provided to the container and dup2'd
|
||||||
|
// so that we no longer have copy in our process.
|
||||||
|
func (t *tty) ClosePostStart() error {
|
||||||
|
for _, c := range t.postStart {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all open fds for the tty and/or restores the orignal
|
||||||
|
// stdin state to what it was prior to the container execution
|
||||||
|
func (t *tty) Close() error {
|
||||||
|
// ensure that our side of the fds are always closed
|
||||||
|
for _, c := range t.postStart {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
// wait for the copy routines to finish before closing the fds
|
||||||
|
t.wg.Wait()
|
||||||
|
for _, c := range t.closers {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
if t.state != nil {
|
||||||
|
term.RestoreTerminal(os.Stdin.Fd(), t.state)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tty) resize() error {
|
||||||
|
if t.console == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ws, err := term.GetWinsize(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return term.SetWinsize(t.console.Fd(), ws)
|
||||||
|
}
|
245
utils.go
Normal file
245
utils.go
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/specconv"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// startContainer starts the container. Returns the exit status or -1 and an
|
||||||
|
// error. Signals sent to the current process will be forwarded to container.
|
||||||
|
func startContainer(spec *specs.Spec, id string) (int, error) {
|
||||||
|
// create the libcontainer config
|
||||||
|
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
|
||||||
|
CgroupName: id,
|
||||||
|
Spec: spec,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(config.Rootfs); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return -1, fmt.Errorf("rootfs (%q) does not exist", config.Rootfs)
|
||||||
|
}
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
factory, err := loadFactory()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr, err := factory.Create(id, config)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &runner{
|
||||||
|
enableSubreaper: true,
|
||||||
|
shouldDestroy: true,
|
||||||
|
container: ctr,
|
||||||
|
console: console,
|
||||||
|
detach: false,
|
||||||
|
pidFile: "",
|
||||||
|
listenFDs: []*os.File{},
|
||||||
|
}
|
||||||
|
return r.run(&spec.Process)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadFactory returns the configured factory instance for execing containers.
|
||||||
|
func loadFactory() (libcontainer.Factory, error) {
|
||||||
|
abs, err := filepath.Abs(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cgroupManager := libcontainer.Cgroupfs
|
||||||
|
return libcontainer.New(abs, cgroupManager, func(l *libcontainer.LinuxFactory) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newProcess returns a new libcontainer Process with the arguments from the
|
||||||
|
// spec and stdio from the current process.
|
||||||
|
func newProcess(p specs.Process) (*libcontainer.Process, error) {
|
||||||
|
lp := &libcontainer.Process{
|
||||||
|
Args: p.Args,
|
||||||
|
Env: p.Env,
|
||||||
|
// TODO: fix libcontainer's API to better support uid/gid in a typesafe way.
|
||||||
|
User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
|
||||||
|
Cwd: p.Cwd,
|
||||||
|
Capabilities: p.Capabilities,
|
||||||
|
Label: p.SelinuxLabel,
|
||||||
|
NoNewPrivileges: &p.NoNewPrivileges,
|
||||||
|
AppArmorProfile: p.ApparmorProfile,
|
||||||
|
}
|
||||||
|
for _, rlimit := range p.Rlimits {
|
||||||
|
rl, err := createLibContainerRlimit(rlimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lp.Rlimits = append(lp.Rlimits, rl)
|
||||||
|
}
|
||||||
|
return lp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dupStdio(process *libcontainer.Process, rootuid int) error {
|
||||||
|
process.Stdin = os.Stdin
|
||||||
|
process.Stdout = os.Stdout
|
||||||
|
process.Stderr = os.Stderr
|
||||||
|
for _, fd := range []uintptr{
|
||||||
|
os.Stdin.Fd(),
|
||||||
|
os.Stdout.Fd(),
|
||||||
|
os.Stderr.Fd(),
|
||||||
|
} {
|
||||||
|
if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func destroy(container libcontainer.Container) {
|
||||||
|
if err := container.Destroy(); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupIO sets the proper IO on the process depending on the configuration
|
||||||
|
// If there is a nil error then there must be a non nil tty returned
|
||||||
|
func setupIO(process *libcontainer.Process, rootuid int, console string, createTTY, detach bool) (*tty, error) {
|
||||||
|
// detach and createTty will not work unless a console path is passed
|
||||||
|
// so error out here before changing any terminal settings
|
||||||
|
if createTTY && detach && console == "" {
|
||||||
|
return nil, fmt.Errorf("cannot allocate tty if runc will detach")
|
||||||
|
}
|
||||||
|
if createTTY {
|
||||||
|
return createTty(process, rootuid, console)
|
||||||
|
}
|
||||||
|
if detach {
|
||||||
|
if err := dupStdio(process, rootuid); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tty{}, nil
|
||||||
|
}
|
||||||
|
return createStdioPipes(process, rootuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createPidFile creates a file with the processes pid inside it atomically
|
||||||
|
// it creates a temp file with the paths filename + '.' infront of it
|
||||||
|
// then renames the file
|
||||||
|
func createPidFile(path string, process *libcontainer.Process) error {
|
||||||
|
pid, err := process.Pid()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
tmpDir = filepath.Dir(path)
|
||||||
|
tmpName = filepath.Join(tmpDir, fmt.Sprintf(".%s", filepath.Base(path)))
|
||||||
|
)
|
||||||
|
f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintf(f, "%d", pid)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(tmpName, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type runner struct {
|
||||||
|
enableSubreaper bool
|
||||||
|
shouldDestroy bool
|
||||||
|
detach bool
|
||||||
|
listenFDs []*os.File
|
||||||
|
pidFile string
|
||||||
|
console string
|
||||||
|
container libcontainer.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
|
process, err := newProcess(*config)
|
||||||
|
if err != nil {
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
if len(r.listenFDs) > 0 {
|
||||||
|
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
|
||||||
|
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
|
||||||
|
}
|
||||||
|
rootuid, err := r.container.Config().HostUID()
|
||||||
|
if err != nil {
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
tty, err := setupIO(process, rootuid, r.console, config.Terminal, r.detach)
|
||||||
|
if err != nil {
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
handler := newSignalHandler(tty, r.enableSubreaper)
|
||||||
|
if err := r.container.Start(process); err != nil {
|
||||||
|
r.destroy()
|
||||||
|
tty.Close()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
if err := tty.ClosePostStart(); err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
tty.Close()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
if r.pidFile != "" {
|
||||||
|
if err := createPidFile(r.pidFile, process); err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
tty.Close()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.detach {
|
||||||
|
tty.Close()
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
status, err := handler.forward(process)
|
||||||
|
if err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
}
|
||||||
|
r.destroy()
|
||||||
|
tty.Close()
|
||||||
|
return status, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) destroy() {
|
||||||
|
if r.shouldDestroy {
|
||||||
|
destroy(r.container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runner) terminate(p *libcontainer.Process) {
|
||||||
|
p.Signal(syscall.SIGKILL)
|
||||||
|
p.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sPtr(s string) *string { return &s }
|
||||||
|
|
||||||
|
func createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) {
|
||||||
|
rl, err := strToRlimit(rlimit.Type)
|
||||||
|
if err != nil {
|
||||||
|
return configs.Rlimit{}, err
|
||||||
|
}
|
||||||
|
return configs.Rlimit{
|
||||||
|
Type: rl,
|
||||||
|
Hard: uint64(rlimit.Hard),
|
||||||
|
Soft: uint64(rlimit.Soft),
|
||||||
|
}, nil
|
||||||
|
}
|
Loading…
Reference in a new issue