update vendor

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-09-25 12:27:46 -04:00
parent 19a32db84d
commit 94d1cfbfbf
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
10501 changed files with 2307943 additions and 29279 deletions

View file

@ -1,5 +1,6 @@
vendor/pkg
/runc
/runc-*
contrib/cmd/recvtty/recvtty
man/man8
release

View file

@ -32,4 +32,4 @@ before_install:
script:
- git-validation -run DCO,short-subject -v
- make BUILDTAGS="${BUILDTAGS}"
- make BUILDTAGS="${BUILDTAGS}" clean ci
- make BUILDTAGS="${BUILDTAGS}" clean ci cross

View file

@ -1,6 +1,10 @@
FROM golang:1.10-stretch
RUN apt-get update && apt-get install -y \
RUN dpkg --add-architecture armel \
&& dpkg --add-architecture armhf \
&& dpkg --add-architecture arm64 \
&& dpkg --add-architecture ppc64el \
&& apt-get update && apt-get install -y \
build-essential \
curl \
sudo \
@ -21,6 +25,8 @@ RUN apt-get update && apt-get install -y \
python-minimal \
uidmap \
kmod \
crossbuild-essential-armel crossbuild-essential-armhf crossbuild-essential-arm64 crossbuild-essential-ppc64el \
libseccomp-dev:armel libseccomp-dev:armhf libseccomp-dev:arm64 libseccomp-dev:ppc64el \
--no-install-recommends \
&& apt-get clean
@ -39,10 +45,14 @@ RUN cd /tmp \
&& rm -rf /tmp/bats
# install criu
ENV CRIU_VERSION v3.7
# For criu v3.10, a patch is needed for `@test "checkpoint --lazy-pages and restore"`.
# Starting with v3.11, the patch will no longer be needed.
# See https://github.com/opencontainers/runc/issues/1863#issuecomment-412074413
ENV CRIU_VERSION v3.10
RUN mkdir -p /usr/src/criu \
&& curl -sSL https://github.com/checkpoint-restore/criu/archive/${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1 \
&& cd /usr/src/criu \
&& curl https://github.com/checkpoint-restore/criu/commit/27034e7c64b00a1f2467afb5ebb1d5b9b1a06ce1.patch | patch -p1 \
&& make install-criu \
&& rm -rf /usr/src/criu

View file

@ -1,6 +1,7 @@
.PHONY: all shell dbuild man release \
localtest localunittest localintegration \
test unittest integration
test unittest integration \
cross localcross
GO := go
@ -39,14 +40,14 @@ contrib/cmd/recvtty/recvtty: $(SOURCES)
$(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
static: $(SOURCES)
CGO_ENABLED=1 $(GO) build $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo cgo static_build" -installsuffix netgo -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -o runc .
CGO_ENABLED=1 $(GO) build $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo cgo static_build" -installsuffix netgo -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
CGO_ENABLED=1 $(GO) build $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo cgo static_build" -installsuffix netgo -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -o runc .
CGO_ENABLED=1 $(GO) build $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo cgo static_build" -installsuffix netgo -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
release:
script/release.sh -r release/$(VERSION) -v $(VERSION)
dbuild: runcimage
docker run --rm -v $(CURDIR):/go/src/$(PROJECT) --privileged $(RUNC_IMAGE) make clean all
docker run ${DOCKER_RUN_PROXY} --rm -v $(CURDIR):/go/src/$(PROJECT) --privileged $(RUNC_IMAGE) make clean all
lint:
$(GO) vet $(allpackages)
@ -56,7 +57,7 @@ man:
man/md2man-all.sh
runcimage:
docker build -t $(RUNC_IMAGE) .
docker build ${DOCKER_BUILD_PROXY} -t $(RUNC_IMAGE) .
test:
make unittest integration rootlessintegration
@ -65,25 +66,25 @@ localtest:
make localunittest localintegration localrootlessintegration
unittest: runcimage
docker run -e TESTFLAGS -t --privileged --rm -v /lib/modules:/lib/modules:ro -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localunittest
docker run ${DOCKER_RUN_PROXY} -t --privileged --rm -v /lib/modules:/lib/modules:ro -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localunittest TESTFLAGS=${TESTFLAGS}
localunittest: all
$(GO) test -timeout 3m -tags "$(BUILDTAGS)" ${TESTFLAGS} -v $(allpackages)
integration: runcimage
docker run -e TESTFLAGS -t --privileged --rm -v /lib/modules:/lib/modules:ro -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localintegration
docker run ${DOCKER_RUN_PROXY} -t --privileged --rm -v /lib/modules:/lib/modules:ro -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localintegration TESTPATH=${TESTPATH}
localintegration: all
bats -t tests/integration${TESTFLAGS}
bats -t tests/integration${TESTPATH}
rootlessintegration: runcimage
docker run -e TESTFLAGS -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localrootlessintegration
docker run ${DOCKER_RUN_PROXY} -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localrootlessintegration
localrootlessintegration: all
tests/rootless.sh
shell: runcimage
docker run -e TESTFLAGS -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash
docker run ${DOCKER_RUN_PROXY} -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash
install:
install -D -m0755 runc $(BINDIR)/runc
@ -105,7 +106,7 @@ uninstall-man:
rm -f $(addprefix $(MAN_INSTALL_PATH),$(MAN_PAGES_BASE))
clean:
rm -f runc
rm -f runc runc-*
rm -f contrib/cmd/recvtty/recvtty
rm -rf $(RELEASE_DIR)
rm -rf $(MAN_DIR)
@ -117,6 +118,15 @@ validate:
ci: validate test release
cross: runcimage
docker run ${DOCKER_RUN_PROXY} -e BUILDTAGS="$(BUILDTAGS)" --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localcross
localcross:
CGO_ENABLED=1 GOARCH=arm GOARM=6 CC=arm-linux-gnueabi-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-armel .
CGO_ENABLED=1 GOARCH=arm GOARM=7 CC=arm-linux-gnueabihf-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-armhf .
CGO_ENABLED=1 GOARCH=arm64 CC=aarch64-linux-gnu-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-arm64 .
CGO_ENABLED=1 GOARCH=ppc64le CC=powerpc64le-linux-gnu-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-ppc64le .
# memoize allpackages, so that it's executed only once and only if used
_allpackages = $(shell $(GO) list ./... | grep -v vendor)
allpackages = $(if $(__allpackages),,$(eval __allpackages := $$(_allpackages)))$(__allpackages)

View file

@ -87,6 +87,18 @@ You can run a specific test case by setting the `TESTFLAGS` variable.
# make test TESTFLAGS="-run=SomeTestFunction"
```
You can run a specific integration test by setting the `TESTPATH` variable.
```bash
# make test TESTPATH="/checkpoint.bats"
```
You can run a test in your proxy environment by setting `DOCKER_BUILD_PROXY` and `DOCKER_RUN_PROXY` variables.
```bash
# make test DOCKER_BUILD_PROXY="--build-arg HTTP_PROXY=http://yourproxy/" DOCKER_RUN_PROXY="-e HTTP_PROXY=http://yourproxy/"
```
### Dependencies Management
`runc` uses [vndr](https://github.com/LK4D4/vndr) for dependencies management.

View file

@ -44,7 +44,11 @@ checkpointed.`,
return err
}
// XXX: Currently this is untested with rootless containers.
if isRootless() {
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc checkpoint requires root")
}
@ -121,7 +125,8 @@ var namespaceMapping = map[specs.LinuxNamespaceType]int{
}
func setEmptyNsMask(context *cli.Context, options *libcontainer.CriuOpts) error {
var nsmask int
/* Runc doesn't manage network devices and their configuration */
nsmask := unix.CLONE_NEWNET
for _, ns := range context.StringSlice("empty-ns") {
f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]

View file

@ -0,0 +1,238 @@
/*
* Copyright 2016 SUSE LLC
*
* 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
*
* http://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.
*/
package main
import (
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
"github.com/containerd/console"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/urfave/cli"
)
// version will be populated by the Makefile, read from
// VERSION file of the source code.
var version = ""
// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""
const (
usage = `Open Container Initiative contrib/cmd/recvtty
recvtty is a reference implementation of a consumer of runC's --console-socket
API. It has two main modes of operation:
* single: Only permit one terminal to be sent to the socket, which is
then hooked up to the stdio of the recvtty process. This is useful
for rudimentary shell management of a container.
* null: Permit as many terminals to be sent to the socket, but they
are read to /dev/null. This is used for testing, and imitates the
old runC API's --console=/dev/pts/ptmx hack which would allow for a
similar trick. This is probably not what you want to use, unless
you're doing something like our bats integration tests.
To use recvtty, just specify a socket path at which you want to receive
terminals:
$ recvtty [--mode <single|null>] socket.sock
`
)
func bail(err error) {
fmt.Fprintf(os.Stderr, "[recvtty] fatal error: %v\n", err)
os.Exit(1)
}
func handleSingle(path string) error {
// Open a socket.
ln, err := net.Listen("unix", path)
if err != nil {
return err
}
defer ln.Close()
// We only accept a single connection, since we can only really have
// one reader for os.Stdin. Plus this is all a PoC.
conn, err := ln.Accept()
if err != nil {
return err
}
defer conn.Close()
// Close ln, to allow for other instances to take over.
ln.Close()
// Get the fd of the connection.
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return fmt.Errorf("failed to cast to unixconn")
}
socket, err := unixconn.File()
if err != nil {
return err
}
defer socket.Close()
// Get the master file descriptor from runC.
master, err := utils.RecvFd(socket)
if err != nil {
return err
}
c, err := console.ConsoleFromFile(master)
if err != nil {
return err
}
console.ClearONLCR(c.Fd())
// Copy from our stdio to the master fd.
quitChan := make(chan struct{})
go func() {
io.Copy(os.Stdout, c)
quitChan <- struct{}{}
}()
go func() {
io.Copy(c, os.Stdin)
quitChan <- struct{}{}
}()
// Only close the master fd once we've stopped copying.
<-quitChan
c.Close()
return nil
}
func handleNull(path string) error {
// Open a socket.
ln, err := net.Listen("unix", path)
if err != nil {
return err
}
defer ln.Close()
// As opposed to handleSingle we accept as many connections as we get, but
// we don't interact with Stdin at all (and we copy stdout to /dev/null).
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go func(conn net.Conn) {
// Don't leave references lying around.
defer conn.Close()
// Get the fd of the connection.
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return
}
socket, err := unixconn.File()
if err != nil {
return
}
defer socket.Close()
// Get the master file descriptor from runC.
master, err := utils.RecvFd(socket)
if err != nil {
return
}
// Just do a dumb copy to /dev/null.
devnull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
// TODO: Handle this nicely.
return
}
io.Copy(devnull, master)
devnull.Close()
}(conn)
}
}
func main() {
app := cli.NewApp()
app.Name = "recvtty"
app.Usage = usage
// Set version to be the same as runC.
var v []string
if version != "" {
v = append(v, version)
}
if gitCommit != "" {
v = append(v, fmt.Sprintf("commit: %s", gitCommit))
}
app.Version = strings.Join(v, "\n")
// Set the flags.
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "mode, m",
Value: "single",
Usage: "Mode of operation (single or null)",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "Path to write daemon process ID to",
},
}
app.Action = func(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 1 {
return fmt.Errorf("need to specify a single socket path")
}
path := ctx.Args()[0]
pidPath := ctx.String("pid-file")
if pidPath != "" {
pid := fmt.Sprintf("%d\n", os.Getpid())
if err := ioutil.WriteFile(pidPath, []byte(pid), 0644); err != nil {
return err
}
}
switch ctx.String("mode") {
case "single":
if err := handleSingle(path); err != nil {
return err
}
case "null":
if err := handleNull(path); err != nil {
return err
}
default:
return fmt.Errorf("need to select a valid mode: %s", ctx.String("mode"))
}
return nil
}
if err := app.Run(os.Args); err != nil {
bail(err)
}
}

View file

@ -0,0 +1,805 @@
#!/bin/bash
#
# bash completion file for runc command
#
# This script provides completion of:
# - commands and their options
# - filepaths
#
# To enable the completions either:
# - place this file in /usr/share/bash-completion/completions
# or
# - copy this file to e.g. ~/.runc-completion.sh and add the line
# below to your .bashrc after bash completion features are loaded
# . ~/.runc-completion.sh
#
# Configuration:
#
# Note for developers:
# Please arrange options sorted alphabetically by long name with the short
# options immediately following their corresponding long form.
# This order should be applied to lists, alternatives and code blocks.
__runc_previous_extglob_setting=$(shopt -p extglob)
shopt -s extglob
__runc_list_all() {
COMPREPLY=($(compgen -W "$(runc list -q)" -- $cur))
}
__runc_pos_first_nonflag() {
local argument_flags=$1
local counter=$((${subcommand_pos:-${command_pos}} + 1))
while [ $counter -le $cword ]; do
if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
((counter++))
else
case "${words[$counter]}" in
-*) ;;
*)
break
;;
esac
fi
((counter++))
done
echo $counter
}
# Transforms a multiline list of strings into a single line string
# with the words separated by "|".
# This is used to prepare arguments to __runc_pos_first_nonflag().
__runc_to_alternatives() {
local parts=($1)
local IFS='|'
echo "${parts[*]}"
}
# Transforms a multiline list of options into an extglob pattern
# suitable for use in case statements.
__runc_to_extglob() {
local extglob=$(__runc_to_alternatives "$1")
echo "@($extglob)"
}
# Subcommand processing.
# Locates the first occurrence of any of the subcommands contained in the
# first argument. In case of a match, calls the corresponding completion
# function and returns 0.
# If no match is found, 1 is returned. The calling function can then
# continue processing its completion.
#
# TODO if the preceding command has options that accept arguments and an
# argument is equal to one of the subcommands, this is falsely detected as
# a match.
__runc_subcommands() {
local subcommands="$1"
local counter=$(($command_pos + 1))
while [ $counter -lt $cword ]; do
case "${words[$counter]}" in
$(__runc_to_extglob "$subcommands"))
subcommand_pos=$counter
local subcommand=${words[$counter]}
local completions_func=_runc_${command}_${subcommand}
declare -F $completions_func >/dev/null && $completions_func
return 0
;;
esac
((counter++))
done
return 1
}
# List all Signals
__runc_list_signals() {
COMPREPLY=($(compgen -W "$(for i in $(kill -l | xargs); do echo $i; done | grep SIG)"))
}
# suppress trailing whitespace
__runc_nospace() {
# compopt is not available in ancient bash versions
type compopt &>/dev/null && compopt -o nospace
}
# The list of capabilities is defined in types.go, ALL was added manually.
__runc_complete_capabilities() {
COMPREPLY=($(compgen -W "
ALL
AUDIT_CONTROL
AUDIT_WRITE
AUDIT_READ
BLOCK_SUSPEND
CHOWN
DAC_OVERRIDE
DAC_READ_SEARCH
FOWNER
FSETID
IPC_LOCK
IPC_OWNER
KILL
LEASE
LINUX_IMMUTABLE
MAC_ADMIN
MAC_OVERRIDE
MKNOD
NET_ADMIN
NET_BIND_SERVICE
NET_BROADCAST
NET_RAW
SETFCAP
SETGID
SETPCAP
SETUID
SYS_ADMIN
SYS_BOOT
SYS_CHROOT
SYSLOG
SYS_MODULE
SYS_NICE
SYS_PACCT
SYS_PTRACE
SYS_RAWIO
SYS_RESOURCE
SYS_TIME
SYS_TTY_CONFIG
WAKE_ALARM
" -- "$cur"))
}
_runc_exec() {
local boolean_options="
--help
--no-new-privs
--tty, -t
--detach, -d
"
local options_with_args="
--console
--cwd
--env, -e
--user, -u
--process, -p
--pid-file
--process-label
--apparmor
--cap, -c
"
local all_options="$options_with_args $boolean_options"
case "$prev" in
--cap | -c)
__runc_complete_capabilities
return
;;
--console | --cwd | --process | --apparmor)
case "$cur" in
*:*) ;; # TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
/*)
_filedir
__runc_nospace
;;
esac
return
;;
--env | -e)
COMPREPLY=($(compgen -e -- "$cur"))
__runc_nospace
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$all_options" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
# global options that may appear after the runc command
_runc_runc() {
local boolean_options="
$global_boolean_options
--help
--version -v
--debug
"
local options_with_args="
--log
--log-format
--root
--criu
"
case "$prev" in
--log | --root | --criu)
case "$cur" in
*:*) ;; # TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
*)
_filedir
__runc_nospace
;;
esac
return
;;
--log-format)
COMPREPLY=($(compgen -W 'text json' -- "$cur"))
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
local counter=$(__runc_pos_first_nonflag $(__runc_to_extglob "$options_with_args"))
if [ $cword -eq $counter ]; then
COMPREPLY=($(compgen -W "${commands[*]} help" -- "$cur"))
fi
;;
esac
}
_runc_pause() {
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_ps() {
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_delete() {
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_kill() {
local boolean_options="
--help
-h
--all
-a
"
case "$prev" in
"kill")
__runc_list_all
return
;;
*)
__runc_list_signals
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_events() {
local boolean_options="
--help
--stats
"
local options_with_args="
--interval
"
case "$prev" in
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_list() {
local boolean_options="
--help
--quiet
-q
"
local options_with_args="
--format
-f
"
case "$prev" in
--format | -f)
COMPREPLY=($(compgen -W 'text json' -- "$cur"))
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
local counter=$(__runc_pos_first_nonflag $(__runc_to_extglob "$options_with_args"))
;;
esac
}
_runc_spec() {
local boolean_options="
--help
"
local options_with_args="
--bundle
-b
"
case "$prev" in
--bundle | -b)
case "$cur" in
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
/*)
_filedir
__runc_nospace
;;
esac
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
local counter=$(__runc_pos_first_nonflag $(__runc_to_extglob "$options_with_args"))
;;
esac
}
_runc_run() {
local boolean_options="
--help
--detatch
-d
--no-subreaper
--no-pivot
--no-new-keyring
"
local options_with_args="
--bundle
-b
--console
--pid-file
"
case "$prev" in
--bundle | -b | --console | --pid-file)
case "$cur" in
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
/*)
_filedir
__runc_nospace
;;
esac
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_checkpoint() {
local boolean_options="
--help
-h
--leave-running
--tcp-established
--ext-unix-sk
--shell-job
--file-locks
"
local options_with_args="
--image-path
--work-path
--page-server
--manage-cgroups-mode
"
case "$prev" in
--page-server) ;;
--manage-cgroups-mode)
COMPREPLY=($(compgen -W "soft full strict" -- "$cur"))
return
;;
--image-path | --work-path)
case "$cur" in
*:*) ;; # TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
*)
_filedir
__runc_nospace
;;
esac
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_create() {
local boolean_options="
--help
--no-pivot
--no-new-keyring
"
local options_with_args="
--bundle
-b
--console
--pid-file
"
case "$prev" in
--bundle | -b | --console | --pid-file)
case "$cur" in
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
/*)
_filedir
__runc_nospace
;;
esac
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_help() {
local counter=$(__runc_pos_first_nonflag)
if [ $cword -eq $counter ]; then
COMPREPLY=($(compgen -W "${commands[*]}" -- "$cur"))
fi
}
_runc_restore() {
local boolean_options="
--help
--tcp-established
--ext-unix-sk
--shell-job
--file-locks
--detach
-d
--no-subreaper
--no-pivot
"
local options_with_args="
-b
--bundle
--image-path
--work-path
--manage-cgroups-mode
--pid-file
"
local all_options="$options_with_args $boolean_options"
case "$prev" in
--manage-cgroups-mode)
COMPREPLY=($(compgen -W "soft full strict" -- "$cur"))
return
;;
--pid-file | --image-path | --work-path | --bundle | -b)
case "$cur" in
*:*) ;; # TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
'')
COMPREPLY=($(compgen -W '/' -- "$cur"))
__runc_nospace
;;
/*)
_filedir
__runc_nospace
;;
esac
return
;;
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$all_options" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_resume() {
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_state() {
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_start() {
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc_update() {
local boolean_options="
--help
"
local options_with_args="
--blkio-weight
--cpu-period
--cpu-quota
--cpu-rt-period
--cpu-rt-runtime
--cpu-share
--cpuset-cpus
--cpuset-mems
--kernel-memory
--kernel-memory-tcp
--memory
--memory-reservation
--memory-swap
"
case "$prev" in
$(__runc_to_extglob "$options_with_args"))
return
;;
esac
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__runc_list_all
;;
esac
}
_runc() {
local previous_extglob_setting=$(shopt -p extglob)
shopt -s extglob
local commands=(
checkpoint
create
delete
events
exec
init
kill
list
pause
ps
restore
resume
run
spec
start
state
update
help
h
)
# These options are valid as global options for all client commands
# and valid as command options for `runc daemon`
local global_boolean_options="
--help -h
--version -v
"
COMPREPLY=()
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword
local command='runc' command_pos=0 subcommand_pos
local counter=1
while [ $counter -lt $cword ]; do
case "${words[$counter]}" in
-*) ;;
=)
((counter++))
;;
*)
command="${words[$counter]}"
command_pos=$counter
break
;;
esac
((counter++))
done
local completions_func=_runc_${command}
declare -F $completions_func >/dev/null && $completions_func
eval "$previous_extglob_setting"
return 0
}
eval "$__runc_previous_extglob_setting"
unset __runc_previous_extglob_setting
complete -F _runc runc

314
vendor/github.com/opencontainers/runc/docs/terminals.md generated vendored Normal file
View file

@ -0,0 +1,314 @@
# Terminals and Standard IO #
*Note that the default configuration of `runc` (foreground, new terminal) is
generally the best option for most users. This document exists to help explain
what the purpose of the different modes is, and to try to steer users away from
common mistakes and misunderstandings.*
In general, most processes on Unix (and Unix-like) operating systems have 3
standard file descriptors provided at the start, collectively referred to as
"standard IO" (`stdio`):
* `0`: standard-in (`stdin`), the input stream into the process
* `1`: standard-out (`stdout`), the output stream from the process
* `2`: standard-error (`stderr`), the error stream from the process
When creating and running a container via `runc`, it is important to take care
to structure the `stdio` the new container's process receives. In some ways
containers are just regular processes, while in other ways they're an isolated
sub-partition of your machine (in a similar sense to a VM). This means that the
structure of IO is not as simple as with ordinary programs (which generally
just use the file descriptors you give them).
## Other File Descriptors ##
Before we continue, it is important to note that processes can have more file
descriptors than just `stdio`. By default in `runc` no other file descriptors
will be passed to the spawned container process. If you wish to explicitly pass
file descriptors to the container you have to use the `--preserve-fds` option.
These ancillary file descriptors don't have any of the strange semantics
discussed further in this document (those only apply to `stdio`) -- they are
passed untouched by `runc`.
It should be noted that `--preserve-fds` does not take individual file
descriptors to preserve. Instead, it takes how many file descriptors (not
including `stdio` or `LISTEN_FDS`) should be passed to the container. In the
following example:
```
% runc run --preserve-fds 5 <container>
```
`runc` will pass the first `5` file descriptors (`3`, `4`, `5`, `6`, and `7` --
assuming that `LISTEN_FDS` has not been configured) to the container.
In addition to `--preserve-fds`, `LISTEN_FDS` file descriptors are passed
automatically to allow for `systemd`-style socket activation. To extend the
above example:
```
% LISTEN_PID=$pid_of_runc LISTEN_FDS=3 runc run --preserve-fds 5 <container>
```
`runc` will now pass the first `8` file descriptors (and it will also pass
`LISTEN_FDS=3` and `LISTEN_PID=1` to the container). The first `3` (`3`, `4`,
and `5`) were passed due to `LISTEN_FDS` and the other `5` (`6`, `7`, `8`, `9`,
and `10`) were passed due to `--preserve-fds`. You should keep this in mind if
you use `runc` directly in something like a `systemd` unit file. To disable
this `LISTEN_FDS`-style passing just unset `LISTEN_FDS`.
**Be very careful when passing file descriptors to a container process.** Due
to some Linux kernel (mis)features, a container with access to certain types of
file descriptors (such as `O_PATH` descriptors) outside of the container's root
file system can use these to break out of the container's pivoted mount
namespace. [This has resulted in CVEs in the past.][CVE-2016-9962]
[CVE-2016-9962]: https://nvd.nist.gov/vuln/detail/CVE-2016-9962
## <a name="terminal-modes" /> Terminal Modes ##
`runc` supports two distinct methods for passing `stdio` to the container's
primary process:
* [new terminal](#new-terminal) (`terminal: true`)
* [pass-through](#pass-through) (`terminal: false`)
When first using `runc` these two modes will look incredibly similar, but this
can be quite deceptive as these different modes have quite different
characteristics.
By default, `runc spec` will create a configuration that will create a new
terminal (`terminal: true`). However, if the `terminal: ...` line is not
present in `config.json` then pass-through is the default.
*In general we recommend using new terminal, because it means that tools like
`sudo` will work inside your container. But pass-through can be useful if you
know what you're doing, or if you're using `runc` as part of a non-interactive
pipeline.*
### <a name="new-terminal"> New Terminal ###
In new terminal mode, `runc` will create a brand-new "console" (or more
precisely, a new pseudo-terminal using the container's namespaced
`/dev/pts/ptmx`) for your contained process to use as its `stdio`.
When you start a process in new terminal mode, `runc` will do the following:
1. Create a new pseudo-terminal.
2. Pass the slave end to the container's primary process as its `stdio`.
3. Send the master end to a process to interact with the `stdio` for the
container's primary process ([details below](#runc-modes)).
It should be noted that since a new pseudo-terminal is being used for
communication with the container, some strange properties of pseudo-terminals
might surprise you. For instance, by default, all new pseudo-terminals
translate the byte `'\n'` to the sequence `'\r\n'` on both `stdout` and
`stderr`. In addition there are [a whole range of `ioctls(2)` that can only
interact with pseudo-terminal `stdio`][tty_ioctl(4)].
> **NOTE**: In new terminal mode, all three `stdio` file descriptors are the
> same underlying file. The reason for this is to match how a shell's `stdio`
> looks to a process (as well as remove race condition issues with having to
> deal with multiple master pseudo-terminal file descriptors). However this
> means that it is not really possible to uniquely distinguish between `stdout`
> and `stderr` from the caller's perspective.
[tty_ioctl(4)]: https://linux.die.net/man/4/tty_ioctl
### <a name="pass-through"> Pass-Through ###
If you have already set up some file handles that you wish your contained
process to use as its `stdio`, then you can ask `runc` to pass them through to
the contained process (this is not necessarily the same as `--preserve-fds`'s
passing of file descriptors -- [details below](#runc-modes)). As an example
(assuming that `terminal: false` is set in `config.json`):
```
% echo input | runc run some_container > /tmp/log.out 2>& /tmp/log.err
```
Here the container's various `stdio` file descriptors will be substituted with
the following:
* `stdin` will be sourced from the `echo input` pipeline.
* `stdout` will be output into `/tmp/log.out` on the host.
* `stderr` will be output into `/tmp/log.err` on the host.
It should be noted that the actual file handles seen inside the container may
be different [based on the mode `runc` is being used in](#runc-modes) (for
instance, the file referenced by `1` could be `/tmp/log.out` directly or a pipe
which `runc` is using to buffer output, based on the mode). However the net
result will be the same in either case. In principle you could use the [new
terminal mode](#new-terminal) in a pipeline, but the difference will become
more clear when you are introduced to [`runc`'s detached mode](#runc-modes).
## <a name="runc-modes" /> `runc` Modes ##
`runc` itself runs in two modes:
* [foreground](#foreground)
* [detached](#detached)
You can use either [terminal mode](#terminal-modes) with either `runc` mode.
However, there are considerations that may indicate preference for one mode
over another. It should be noted that while two types of modes (terminal and
`runc`) are conceptually independent from each other, you should be aware of
the intricacies of which combination you are using.
*In general we recommend using foreground because it's the most
straight-forward to use, with the only downside being that you will have a
long-running `runc` process. Detached mode is difficult to get right and
generally requires having your own `stdio` management.*
### Foreground ###
The default (and most straight-forward) mode of `runc`. In this mode, your
`runc` command remains in the foreground with the container process as a child.
All `stdio` is buffered through the foreground `runc` process (irrespective of
which terminal mode you are using). This is conceptually quite similar to
running a normal process interactively in a shell (and if you are using `runc`
in a shell interactively, this is what you should use).
Because the `stdio` will be buffered in this mode, some very important
peculiarities of this mode should be kept in mind:
* With [new terminal mode](#new-terminal), the container will see a
pseudo-terminal as its `stdio` (as you might expect). However, the `stdio` of
the foreground `runc` process will remain the `stdio` that the process was
started with -- and `runc` will copy all `stdio` between its `stdio` and the
container's `stdio`. This means that while a new pseudo-terminal has been
created, the foreground `runc` process manages it over the lifetime of the
container.
* With [pass-through mode](#pass-through), the foreground `runc`'s `stdio` is
**not** passed to the container. Instead, the container's `stdio` is a set of
pipes which are used to copy data between `runc`'s `stdio` and the
container's `stdio`. This means that the container never has direct access to
host file descriptors (aside from the pipes created by the container runtime,
but that shouldn't be an issue).
The main drawback of the foreground mode of operation is that it requires a
long-running foreground `runc` process. If you kill the foreground `runc`
process then you will no longer have access to the `stdio` of the container
(and in most cases this will result in the container dying abnormally due to
`SIGPIPE` or some other error). By extension this means that any bug in the
long-running foreground `runc` process (such as a memory leak) or a stray
OOM-kill sweep could result in your container being killed **through no fault
of the user**. In addition, there is no way in foreground mode of passing a
file descriptor directly to the container process as its `stdio` (like
`--preserve-fds` does).
These shortcomings are obviously sub-optimal and are the reason that `runc` has
an additional mode called "detached mode".
### Detached ###
In contrast to foreground mode, in detached mode there is no long-running
foreground `runc` process once the container has started. In fact, there is no
long-running `runc` process at all. However, this means that it is up to the
caller to handle the `stdio` after `runc` has set it up for you. In a shell
this means that the `runc` command will exit and control will return to the
shell, after the container has been set up.
You can run `runc` in detached mode in one of the following ways:
* `runc run -d ...` which operates similar to `runc run` but is detached.
* `runc create` followed by `runc start` which is the standard container
lifecycle defined by the OCI runtime specification (`runc create` sets up the
container completely, waiting for `runc start` to begin execution of user
code).
The main use-case of detached mode is for higher-level tools that want to be
wrappers around `runc`. By running `runc` in detached mode, those tools have
far more control over the container's `stdio` without `runc` getting in the
way (most wrappers around `runc` like `cri-o` or `containerd` use detached mode
for this reason).
Unfortunately using detached mode is a bit more complicated and requires more
care than the foreground mode -- mainly because it is now up to the caller to
handle the `stdio` of the container.
#### Detached Pass-Through ####
In detached mode, pass-through actually does what it says on the tin -- the
`stdio` file descriptors of the `runc` process are passed through (untouched)
to the container's `stdio`. The purpose of this option is to allow a user to
set up `stdio` for a container themselves and then force `runc` to just use
their pre-prepared `stdio` (without any pseudo-terminal funny business). *If
you don't see why this would be useful, don't use this option.*
**You must be incredibly careful when using detached pass-through (especially
in a shell).** The reason for this is that by using detached pass-through you
are passing host file descriptors to the container. In the case of a shell,
usually your `stdio` is going to be a pseudo-terminal (on your host). A
malicious container could take advantage of TTY-specific `ioctls` like
`TIOCSTI` to fake input into the **host** shell (remember that in detached
mode, control is returned to your shell and so the terminal you've given the
container is being read by a shell prompt).
There are also several other issues with running non-malicious containers in a
shell with detached pass-through (where you pass your shell's `stdio` to the
container):
* Output from the container will be interleaved with output from your shell (in
a non-deterministic way), without any real way of distinguishing from where a
particular piece of output came from.
* Any input to `stdin` will be non-deterministically split and given to either
the container or the shell (because both are blocked on a `read(2)` of the
same FIFO-style file descriptor).
They are all related to the fact that there is going to be a race when either
your host or the container tries to read from (or write to) `stdio`. This
problem is especially obvious when in a shell, where usually the terminal has
been put into raw mode (where each individual key-press should cause `read(2)`
to return).
> **NOTE**: There is also currently a [known problem][issue-1721] where using
> detached pass-through will result in the container hanging if the `stdout` or
> `stderr` is a pipe (though this should be a temporary issue).
[issue-1721]: https://github.com/opencontainers/runc/issues/1721
#### Detached New Terminal ####
When creating a new pseudo-terminal in detached mode, and fairly obvious
problem appears -- how do we use the new terminal that `runc` created? Unlike
in pass-through, `runc` has created a new set of file descriptors that need to
be used by *something* in order for container communication to work.
The way this problem is resolved is through the use of Unix domain sockets.
There is a feature of Unix sockets called `SCM_RIGHTS` which allows a file
descriptor to be sent through a Unix socket to a completely separate process
(which can then use that file descriptor as though they opened it). When using
`runc` in detached new terminal mode, this is how a user gets access to the
pseudo-terminal's master file descriptor.
To this end, there is a new option (which is required if you want to use `runc`
in detached new terminal mode): `--console-socket`. This option takes the path
to a Unix domain socket which `runc` will connect to and send the
pseudo-terminal master file descriptor down. The general process for getting
the pseudo-terminal master is as follows:
1. Create a Unix domain socket at some path, `$socket_path`.
2. Call `runc run` or `runc create` with the argument `--console-socket
$socket_path`.
3. Using `recvmsg(2)` retrieve the file descriptor sent using `SCM_RIGHTS` by
`runc`.
4. Now the manager can interact with the `stdio` of the container, using the
retrieved pseudo-terminal master.
After `runc` exits, the only process with a copy of the pseudo-terminal master
file descriptor is whoever read the file descriptor from the socket.
> **NOTE**: Currently `runc` doesn't support abstract socket addresses (due to
> it not being possible to pass an `argv` with a null-byte as the first
> character). In the future this may change, but currently you must use a valid
> path name.
In order to help users make use of detached new terminal mode, we have provided
a [Go implementation in the `go-runc` bindings][containerd/go-runc.Socket], as
well as [a simple client][recvtty].
[containerd/go-runc.Socket]: https://godoc.org/github.com/containerd/go-runc#Socket
[recvtty]: /contrib/cmd/recvtty

View file

@ -140,6 +140,7 @@ func execProcess(context *cli.Context) (int, error) {
detach: detach,
pidFile: context.String("pid-file"),
action: CT_ACT_RUN,
init: false,
}
return r.run(p)
}

View file

@ -3,7 +3,6 @@
package fs
import (
"errors"
"fmt"
"io"
"io/ioutil"
@ -14,6 +13,8 @@ import (
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
var (
@ -35,7 +36,7 @@ var (
HugePageSizes, _ = cgroups.GetHugePageSize()
)
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
var errSubsystemDoesNotExist = fmt.Errorf("cgroup: subsystem does not exist")
type subsystemSet []subsystem
@ -62,9 +63,10 @@ type subsystem interface {
}
type Manager struct {
mu sync.Mutex
Cgroups *configs.Cgroup
Paths map[string]string
mu sync.Mutex
Cgroups *configs.Cgroup
Rootless bool
Paths map[string]string
}
// The absolute path to the root of the cgroup hierarchies.
@ -100,6 +102,33 @@ type cgroupData struct {
pid int
}
// isIgnorableError returns whether err is a permission error (in the loose
// sense of the word). This includes EROFS (which for an unprivileged user is
// basically a permission error) and EACCES (for similar reasons) as well as
// the normal EPERM.
func isIgnorableError(rootless bool, err error) bool {
// We do not ignore errors if we are root.
if !rootless {
return false
}
// Is it an ordinary EPERM?
if os.IsPermission(errors.Cause(err)) {
return true
}
// Try to handle other errnos.
var errno error
switch err := errors.Cause(err).(type) {
case *os.PathError:
errno = err.Err
case *os.LinkError:
errno = err.Err
case *os.SyscallError:
errno = err.Err
}
return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
}
func (m *Manager) Apply(pid int) (err error) {
if m.Cgroups == nil {
return nil
@ -145,11 +174,11 @@ func (m *Manager) Apply(pid int) (err error) {
m.Paths[sys.Name()] = p
if err := sys.Apply(d); err != nil {
if os.IsPermission(err) && m.Cgroups.Path == "" {
// If we didn't set a cgroup path, then let's defer the error here
// until we know whether we have set limits or not.
// If we hadn't set limits, then it's ok that we couldn't join this cgroup, because
// it will have the same limits as its parent.
// In the case of rootless, where an explicit cgroup path hasn't
// been set, we don't bail on error in case of permission problems.
// Cases where limits have been set (and we couldn't create our own
// cgroup) are handled by Set.
if isIgnorableError(m.Rootless, err) && m.Cgroups.Path == "" {
delete(m.Paths, sys.Name())
continue
}
@ -208,8 +237,9 @@ func (m *Manager) Set(container *configs.Config) error {
path := paths[sys.Name()]
if err := sys.Set(path, container.Cgroups); err != nil {
if path == "" {
// cgroup never applied
return fmt.Errorf("cannot set limits on the %s cgroup, as the container has not joined it", sys.Name())
// We never created a path for this cgroup, so we cannot set
// limits for it (though we have already tried at this point).
return fmt.Errorf("cannot set %s limit: container could not join or create cgroup", sys.Name())
}
return err
}

View file

@ -77,7 +77,7 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro
// The logic is, if user specified cpuset configs, use these
// specified configs, otherwise, inherit from parent. This makes
// cpuset configs work correctly with 'cpuset.cpu_exclusive', and
// keep backward compatbility.
// keep backward compatibility.
if err := s.ensureCpusAndMems(dir, cgroup); err != nil {
return err
}

View file

@ -86,7 +86,7 @@ func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats)
expectMemoryDataEquals(t, expected.KernelUsage, actual.KernelUsage)
if expected.UseHierarchy != actual.UseHierarchy {
logrus.Printf("Expected memory use hiearchy %v, but found %v\n", expected.UseHierarchy, actual.UseHierarchy)
logrus.Printf("Expected memory use hierarchy %v, but found %v\n", expected.UseHierarchy, actual.UseHierarchy)
t.Fail()
}

View file

@ -5,6 +5,7 @@ package systemd
import (
"errors"
"fmt"
"math"
"os"
"path/filepath"
"strings"
@ -75,7 +76,8 @@ var (
hasStartTransientUnit bool
hasStartTransientSliceUnit bool
hasTransientDefaultDependencies bool
hasDelegate bool
hasDelegateScope bool
hasDelegateSlice bool
)
func newProp(name string, units interface{}) systemdDbus.Property {
@ -150,12 +152,12 @@ func UseSystemd() bool {
theConn.StopUnit(scope, "replace", nil)
// Assume StartTransientUnit on a scope allows Delegate
hasDelegate = true
dl := newProp("Delegate", true)
if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{dl}, nil); err != nil {
hasDelegateScope = true
dlScope := newProp("Delegate", true)
if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{dlScope}, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
hasDelegate = false
hasDelegateScope = false
}
}
}
@ -187,6 +189,22 @@ func UseSystemd() bool {
time.Sleep(time.Millisecond)
}
// Not critical because of the stop unit logic above.
theConn.StopUnit(slice, "replace", nil)
// Assume StartTransientUnit on a slice allows Delegate
hasDelegateSlice = true
dlSlice := newProp("Delegate", true)
if _, err := theConn.StartTransientUnit(slice, "replace", []systemdDbus.Property{dlSlice}, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
// Starting with systemd v237, Delegate is not even a property of slices anymore,
// so the D-Bus call fails with "InvalidArgs" error.
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") || strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.InvalidArgs") {
hasDelegateSlice = false
}
}
}
// Not critical because of the stop unit logic above.
theConn.StopUnit(scope, "replace", nil)
theConn.StopUnit(slice, "replace", nil)
@ -242,9 +260,16 @@ func (m *Manager) Apply(pid int) error {
properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
}
if hasDelegate {
// This is only supported on systemd versions 218 and above.
properties = append(properties, newProp("Delegate", true))
// Check if we can delegate. This is only supported on systemd versions 218 and above.
if strings.HasSuffix(unitName, ".slice") {
if hasDelegateSlice {
// systemd 237 and above no longer allows delegation on a slice
properties = append(properties, newProp("Delegate", true))
}
} else {
if hasDelegateScope {
properties = append(properties, newProp("Delegate", true))
}
}
// Always enable accounting, this gets us the same behaviour as the fs implementation,
@ -271,13 +296,19 @@ func (m *Manager) Apply(pid int) error {
// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
cpuQuotaPerSecUSec := uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
// (integer percentage of CPU) internally. This means that if a fractional percent of
// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
if cpuQuotaPerSecUSec%10000 != 0 {
cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
// corresponds to USEC_INFINITY in systemd
// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
// always setting a property value ensures we can apply a quota and remove it later
cpuQuotaPerSecUSec := uint64(math.MaxUint64)
if c.Resources.CpuQuota > 0 {
// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
// (integer percentage of CPU) internally. This means that if a fractional percent of
// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
if cpuQuotaPerSecUSec%10000 != 0 {
cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
}
}
properties = append(properties,
newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
@ -296,17 +327,17 @@ func (m *Manager) Apply(pid int) error {
}
}
statusChan := make(chan string)
if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err != nil && !isUnitExists(err) {
statusChan := make(chan string, 1)
if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
select {
case <-statusChan:
case <-time.After(time.Second):
logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", unitName)
}
} else if !isUnitExists(err) {
return err
}
select {
case <-statusChan:
case <-time.After(time.Second):
logrus.Warnf("Timed out while waiting for StartTransientUnit completion signal from dbus. Continuing...")
}
if err := joinCgroups(c, pid); err != nil {
return err
}

View file

@ -13,7 +13,7 @@ import (
"strings"
"time"
"github.com/docker/go-units"
units "github.com/docker/go-units"
)
const (
@ -103,7 +103,7 @@ func FindCgroupMountpointDir() (string, error) {
}
if postSeparatorFields[0] == "cgroup" {
// Check that the mount is properly formated.
// Check that the mount is properly formatted.
if numPostFields < 3 {
return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}
@ -151,19 +151,20 @@ func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount,
Root: fields[3],
}
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if !ss[opt] {
seen, known := ss[opt]
if !known || (!all && seen) {
continue
}
ss[opt] = true
if strings.HasPrefix(opt, cgroupNamePrefix) {
m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):])
} else {
m.Subsystems = append(m.Subsystems, opt)
}
if !all {
numFound++
opt = opt[len(cgroupNamePrefix):]
}
m.Subsystems = append(m.Subsystems, opt)
numFound++
}
if len(m.Subsystems) > 0 || all {
res = append(res, m)
}
res = append(res, m)
}
if err := scanner.Err(); err != nil {
return nil, err
@ -187,7 +188,7 @@ func GetCgroupMounts(all bool) ([]Mount, error) {
allMap := make(map[string]bool)
for s := range allSubsystems {
allMap[s] = true
allMap[s] = false
}
return getCgroupMountsHelper(allMap, f, all)
}
@ -262,7 +263,7 @@ func getCgroupPathHelper(subsystem, cgroup string) (string, error) {
}
// This is needed for nested containers, because in /proc/self/cgroup we
// see pathes from host, which don't exist in container.
// see paths from host, which don't exist in container.
relCgroup, err := filepath.Rel(root, cgroup)
if err != nil {
return "", err

View file

@ -93,6 +93,62 @@ const systemdMountinfo = `115 83 0:32 / / rw,relatime - aufs none rw,si=c0bd3d3,
136 117 0:12 /1 /dev/console rw,nosuid,noexec,relatime - devpts none rw,gid=5,mode=620,ptmxmode=000
84 115 0:40 / /tmp rw,relatime - tmpfs none rw`
const bedrockMountinfo = `120 17 0:28 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:16 - tmpfs tmpfs ro,mode=755
124 28 0:28 / /bedrock/strata/arch/sys/fs/cgroup rw,nosuid,nodev,noexec shared:16 - tmpfs tmpfs ro,mode=755
123 53 0:28 / /bedrock/strata/fallback/sys/fs/cgroup rw,nosuid,nodev,noexec shared:16 - tmpfs tmpfs ro,mode=755
122 71 0:28 / /bedrock/strata/gentoo/sys/fs/cgroup rw,nosuid,nodev,noexec shared:16 - tmpfs tmpfs ro,mode=755
121 89 0:28 / /bedrock/strata/kde/sys/fs/cgroup rw,nosuid,nodev,noexec shared:16 - tmpfs tmpfs ro,mode=755
125 120 0:29 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
129 124 0:29 / /bedrock/strata/arch/sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
128 123 0:29 / /bedrock/strata/fallback/sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
127 122 0:29 / /bedrock/strata/gentoo/sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
126 121 0:29 / /bedrock/strata/kde/sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
140 120 0:32 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,net_cls,net_prio
144 124 0:32 / /bedrock/strata/arch/sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,net_cls,net_prio
143 123 0:32 / /bedrock/strata/fallback/sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,net_cls,net_prio
142 122 0:32 / /bedrock/strata/gentoo/sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,net_cls,net_prio
141 121 0:32 / /bedrock/strata/kde/sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,net_cls,net_prio
145 120 0:33 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,blkio
149 124 0:33 / /bedrock/strata/arch/sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,blkio
148 123 0:33 / /bedrock/strata/fallback/sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,blkio
147 122 0:33 / /bedrock/strata/gentoo/sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,blkio
146 121 0:33 / /bedrock/strata/kde/sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,blkio
150 120 0:34 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpu,cpuacct
154 124 0:34 / /bedrock/strata/arch/sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpu,cpuacct
153 123 0:34 / /bedrock/strata/fallback/sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpu,cpuacct
152 122 0:34 / /bedrock/strata/gentoo/sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpu,cpuacct
151 121 0:34 / /bedrock/strata/kde/sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpu,cpuacct
155 120 0:35 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,cpuset
159 124 0:35 / /bedrock/strata/arch/sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,cpuset
158 123 0:35 / /bedrock/strata/fallback/sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,cpuset
157 122 0:35 / /bedrock/strata/gentoo/sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,cpuset
156 121 0:35 / /bedrock/strata/kde/sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,cpuset
160 120 0:36 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,devices
164 124 0:36 / /bedrock/strata/arch/sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,devices
163 123 0:36 / /bedrock/strata/fallback/sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,devices
162 122 0:36 / /bedrock/strata/gentoo/sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,devices
161 121 0:36 / /bedrock/strata/kde/sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,devices
165 120 0:37 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,memory
169 124 0:37 / /bedrock/strata/arch/sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,memory
168 123 0:37 / /bedrock/strata/fallback/sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,memory
167 122 0:37 / /bedrock/strata/gentoo/sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,memory
166 121 0:37 / /bedrock/strata/kde/sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,memory
170 120 0:38 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,freezer
174 124 0:38 / /bedrock/strata/arch/sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,freezer
173 123 0:38 / /bedrock/strata/fallback/sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,freezer
172 122 0:38 / /bedrock/strata/gentoo/sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,freezer
171 121 0:38 / /bedrock/strata/kde/sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,freezer
175 120 0:39 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,pids
179 124 0:39 / /bedrock/strata/arch/sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,pids
178 123 0:39 / /bedrock/strata/fallback/sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,pids
177 122 0:39 / /bedrock/strata/gentoo/sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,pids
176 121 0:39 / /bedrock/strata/kde/sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,pids
180 120 0:40 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event
184 124 0:40 / /bedrock/strata/arch/sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event
183 123 0:40 / /bedrock/strata/fallback/sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event
182 122 0:40 / /bedrock/strata/gentoo/sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event
181 121 0:40 / /bedrock/strata/kde/sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event`
const cgroup2Mountinfo = `18 64 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw,seclabel
19 64 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
20 64 0:6 / /dev rw,nosuid shared:2 - devtmpfs devtmpfs rw,seclabel,size=8171204k,nr_inodes=2042801,mode=755
@ -132,31 +188,46 @@ func TestGetCgroupMounts(t *testing.T) {
mountInfo: fedoraMountinfo,
root: "/",
subsystems: map[string]bool{
"cpuset": true,
"cpu": true,
"cpuacct": true,
"memory": true,
"devices": true,
"freezer": true,
"net_cls": true,
"blkio": true,
"perf_event": true,
"hugetlb": true,
"cpuset": false,
"cpu": false,
"cpuacct": false,
"memory": false,
"devices": false,
"freezer": false,
"net_cls": false,
"blkio": false,
"perf_event": false,
"hugetlb": false,
},
},
{
mountInfo: systemdMountinfo,
root: "/system.slice/docker-dc4eaa1a34ec4d593bc0125d31eea823a1d76ae483aeb1409cca80304e34da2e.scope",
subsystems: map[string]bool{
"cpuset": true,
"cpu": true,
"cpuacct": true,
"memory": true,
"devices": true,
"freezer": true,
"net_cls": true,
"blkio": true,
"perf_event": true,
"cpuset": false,
"cpu": false,
"cpuacct": false,
"memory": false,
"devices": false,
"freezer": false,
"net_cls": false,
"blkio": false,
"perf_event": false,
},
},
{
mountInfo: bedrockMountinfo,
root: "/",
subsystems: map[string]bool{
"cpuset": false,
"cpu": false,
"cpuacct": false,
"memory": false,
"devices": false,
"freezer": false,
"net_cls": false,
"blkio": false,
"perf_event": false,
},
},
}
@ -199,16 +270,16 @@ func TestGetCgroupMounts(t *testing.T) {
func BenchmarkGetCgroupMounts(b *testing.B) {
subsystems := map[string]bool{
"cpuset": true,
"cpu": true,
"cpuacct": true,
"memory": true,
"devices": true,
"freezer": true,
"net_cls": true,
"blkio": true,
"perf_event": true,
"hugetlb": true,
"cpuset": false,
"cpu": false,
"cpuacct": false,
"memory": false,
"devices": false,
"freezer": false,
"net_cls": false,
"blkio": false,
"perf_event": false,
"hugetlb": false,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
@ -276,17 +347,17 @@ func TestParseCgroupString(t *testing.T) {
func TestIgnoreCgroup2Mount(t *testing.T) {
subsystems := map[string]bool{
"cpuset": true,
"cpu": true,
"cpuacct": true,
"memory": true,
"devices": true,
"freezer": true,
"net_cls": true,
"blkio": true,
"perf_event": true,
"pids": true,
"name=systemd": true,
"cpuset": false,
"cpu": false,
"cpuacct": false,
"memory": false,
"devices": false,
"freezer": false,
"net_cls": false,
"blkio": false,
"perf_event": false,
"pids": false,
"name=systemd": false,
}
mi := bytes.NewBufferString(cgroup2Mountinfo)

View file

@ -141,9 +141,10 @@ type Config struct {
// OomScoreAdj specifies the adjustment to be made by the kernel when calculating oom scores
// for a process. Valid values are between the range [-1000, '1000'], where processes with
// higher scores are preferred for being killed.
// higher scores are preferred for being killed. If it is unset then we don't touch the current
// value.
// More information about kernel oom score calculation here: https://lwn.net/Articles/317814/
OomScoreAdj int `json:"oom_score_adj"`
OomScoreAdj *int `json:"oom_score_adj,omitempty"`
// UidMappings is an array of User ID mappings for User Namespaces
UidMappings []IDMap `json:"uid_mappings"`

View file

@ -43,13 +43,12 @@ func rootlessMappings(config *configs.Config) error {
if !config.Namespaces.Contains(configs.NEWUSER) {
return fmt.Errorf("rootless containers require user namespaces")
}
}
if len(config.UidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one UID mapping")
}
if len(config.GidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one UID mapping")
if len(config.UidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one UID mapping")
}
if len(config.GidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one GID mapping")
}
}
return nil

View file

@ -151,6 +151,16 @@ func (v *ConfigValidator) sysctl(config *configs.Config) error {
return fmt.Errorf("sysctl %q is not allowed in the hosts network namespace", s)
}
}
if config.Namespaces.Contains(configs.NEWUTS) {
switch s {
case "kernel.domainname":
// This is namespaced and there's no explicit OCI field for it.
continue
case "kernel.hostname":
// This is namespaced but there's a conflicting (dedicated) OCI field for it.
return fmt.Errorf("sysctl %q is not allowed as it conflicts with the OCI %q field", s, "hostname")
}
}
return fmt.Errorf("sysctl %q is not in a separate kernel namespace", s)
}

View file

@ -28,7 +28,6 @@ import (
"github.com/golang/protobuf/proto"
"github.com/sirupsen/logrus"
"github.com/syndtr/gocapability/capability"
"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)
@ -225,17 +224,13 @@ func (c *linuxContainer) Set(config configs.Config) error {
func (c *linuxContainer) Start(process *Process) error {
c.m.Lock()
defer c.m.Unlock()
status, err := c.currentStatus()
if err != nil {
return err
}
if status == Stopped {
if process.Init {
if err := c.createExecFifo(); err != nil {
return err
}
}
if err := c.start(process, status == Stopped); err != nil {
if status == Stopped {
if err := c.start(process); err != nil {
if process.Init {
c.deleteExecFifo()
}
return err
@ -244,17 +239,10 @@ func (c *linuxContainer) Start(process *Process) error {
}
func (c *linuxContainer) Run(process *Process) error {
c.m.Lock()
status, err := c.currentStatus()
if err != nil {
c.m.Unlock()
return err
}
c.m.Unlock()
if err := c.Start(process); err != nil {
return err
}
if status == Stopped {
if process.Init {
return c.exec()
}
return nil
@ -335,8 +323,8 @@ type openResult struct {
err error
}
func (c *linuxContainer) start(process *Process, isInit bool) error {
parent, err := c.newParentProcess(process, isInit)
func (c *linuxContainer) start(process *Process) error {
parent, err := c.newParentProcess(process)
if err != nil {
return newSystemErrorWithCause(err, "creating new parent process")
}
@ -349,7 +337,7 @@ func (c *linuxContainer) start(process *Process, isInit bool) error {
}
// generate a timestamp indicating when the container was started
c.created = time.Now().UTC()
if isInit {
if process.Init {
c.state = &createdState{
c: c,
}
@ -377,10 +365,6 @@ func (c *linuxContainer) start(process *Process, isInit bool) error {
}
}
}
} else {
c.state = &runningState{
c: c,
}
}
return nil
}
@ -443,7 +427,7 @@ func (c *linuxContainer) includeExecFifo(cmd *exec.Cmd) error {
return nil
}
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
parentPipe, childPipe, err := utils.NewSockPair("init")
if err != nil {
return nil, newSystemErrorWithCause(err, "creating new init pipe")
@ -452,7 +436,7 @@ func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProces
if err != nil {
return nil, newSystemErrorWithCause(err, "creating new command template")
}
if !doInit {
if !p.Init {
return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
}
@ -477,6 +461,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.Env = append(cmd.Env, fmt.Sprintf("GOMAXPROCS=%s", os.Getenv("GOMAXPROCS")))
cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...)
if p.ConsoleSocket != nil {
cmd.ExtraFiles = append(cmd.ExtraFiles, p.ConsoleSocket)
@ -672,7 +657,7 @@ func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.
Features: criuFeat,
}
err := c.criuSwrk(nil, req, criuOpts, false)
err := c.criuSwrk(nil, req, criuOpts, false, nil)
if err != nil {
logrus.Debugf("%s", err)
return fmt.Errorf("CRIU feature check failed")
@ -785,7 +770,7 @@ func (c *linuxContainer) checkCriuVersion(minVersion int) error {
Type: &t,
}
err := c.criuSwrk(nil, req, nil, false)
err := c.criuSwrk(nil, req, nil, false, nil)
if err != nil {
return fmt.Errorf("CRIU version check failed: %s", err)
}
@ -943,6 +928,33 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
LazyPages: proto.Bool(criuOpts.LazyPages),
}
// If the container is running in a network namespace and has
// a path to the network namespace configured, we will dump
// that network namespace as an external namespace and we
// will expect that the namespace exists during restore.
// This basically means that CRIU will ignore the namespace
// and expect to be setup correctly.
nsPath := c.config.Namespaces.PathOf(configs.NEWNET)
if nsPath != "" {
// For this to work we need at least criu 3.11.0 => 31100.
// As there was already a successful version check we will
// not error out if it fails. runc will just behave as it used
// to do and ignore external network namespaces.
err := c.checkCriuVersion(31100)
if err == nil {
// CRIU expects the information about an external namespace
// like this: --external net[<inode>]:<key>
// This <key> is always 'extRootNetNS'.
var netns syscall.Stat_t
err = syscall.Stat(nsPath, &netns)
if err != nil {
return err
}
criuExternal := fmt.Sprintf("net[%d]:extRootNetNS", netns.Ino)
rpcOpts.External = append(rpcOpts.External, criuExternal)
}
}
fcg := c.cgroupManager.GetPaths()["freezer"]
if fcg != "" {
rpcOpts.FreezeCgroup = proto.String(fcg)
@ -1047,7 +1059,7 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
}
}
err = c.criuSwrk(nil, req, criuOpts, false)
err = c.criuSwrk(nil, req, criuOpts, false, nil)
if err != nil {
return err
}
@ -1091,6 +1103,8 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
c.m.Lock()
defer c.m.Unlock()
var extraFiles []*os.File
// TODO(avagin): Figure out how to make this work nicely. CRIU doesn't have
// support for unprivileged restore at the moment.
if c.config.Rootless {
@ -1165,6 +1179,38 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
},
}
// Same as during checkpointing. If the container has a specific network namespace
// assigned to it, this now expects that the checkpoint will be restored in a
// already created network namespace.
nsPath := c.config.Namespaces.PathOf(configs.NEWNET)
if nsPath != "" {
// For this to work we need at least criu 3.11.0 => 31100.
// As there was already a successful version check we will
// not error out if it fails. runc will just behave as it used
// to do and ignore external network namespaces.
err := c.checkCriuVersion(31100)
if err == nil {
// CRIU wants the information about an existing network namespace
// like this: --inherit-fd fd[<fd>]:<key>
// The <key> needs to be the same as during checkpointing.
// We are always using 'extRootNetNS' as the key in this.
netns, err := os.Open(nsPath)
defer netns.Close()
if err != nil {
logrus.Error("If a specific network namespace is defined it must exist: %s", err)
return fmt.Errorf("Requested network namespace %v does not exist", nsPath)
}
inheritFd := new(criurpc.InheritFd)
inheritFd.Key = proto.String("extRootNetNS")
// The offset of four is necessary because 0, 1, 2 and 3 is already
// used by stdin, stdout, stderr, 'criu swrk' socket.
inheritFd.Fd = proto.Int32(int32(4 + len(extraFiles)))
req.Opts.InheritFd = append(req.Opts.InheritFd, inheritFd)
// All open FDs need to be transferred to CRIU via extraFiles
extraFiles = append(extraFiles, netns)
}
}
for _, m := range c.config.Mounts {
switch m.Device {
case "bind":
@ -1223,7 +1269,7 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
req.Opts.InheritFd = append(req.Opts.InheritFd, inheritFd)
}
}
return c.criuSwrk(process, req, criuOpts, true)
return c.criuSwrk(process, req, criuOpts, true, extraFiles)
}
func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
@ -1253,7 +1299,7 @@ func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
return nil
}
func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *CriuOpts, applyCgroups bool) error {
func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *CriuOpts, applyCgroups bool, extraFiles []*os.File) error {
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0)
if err != nil {
return err
@ -1294,6 +1340,9 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
cmd.Stderr = process.Stderr
}
cmd.ExtraFiles = append(cmd.ExtraFiles, criuServer)
if extraFiles != nil {
cmd.ExtraFiles = append(cmd.ExtraFiles, extraFiles...)
}
if err := cmd.Start(); err != nil {
return err
@ -1801,28 +1850,22 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
Value: []byte(c.newgidmapPath),
})
}
// The following only applies if we are root.
if !c.config.Rootless {
// check if we have CAP_SETGID to setgroup properly
pid, err := capability.NewPid(0)
if err != nil {
return nil, err
}
if !pid.Get(capability.EFFECTIVE, capability.CAP_SETGID) {
r.AddData(&Boolmsg{
Type: SetgroupAttr,
Value: true,
})
}
if requiresRootOrMappingTool(c.config) {
r.AddData(&Boolmsg{
Type: SetgroupAttr,
Value: true,
})
}
}
}
// write oom_score_adj
r.AddData(&Bytemsg{
Type: OomScoreAdjAttr,
Value: []byte(fmt.Sprintf("%d", c.config.OomScoreAdj)),
})
if c.config.OomScoreAdj != nil {
// write oom_score_adj
r.AddData(&Bytemsg{
Type: OomScoreAdjAttr,
Value: []byte(fmt.Sprintf("%d", *c.config.OomScoreAdj)),
})
}
// write rootless
r.AddData(&Boolmsg{
@ -1847,3 +1890,10 @@ func ignoreTerminateErrors(err error) error {
}
return err
}
func requiresRootOrMappingTool(c *configs.Config) bool {
gidMap := []configs.IDMap{
{ContainerID: 0, HostID: os.Getegid(), Size: 1},
}
return !reflect.DeepEqual(c.GidMappings, gidMap)
}

View file

@ -159,7 +159,7 @@ func TestGetContainerStats(t *testing.T) {
t.Fatal("cgroup stats are nil")
}
if stats.CgroupStats.MemoryStats.Usage.Usage != 1024 {
t.Fatalf("expected memory usage 1024 but recevied %d", stats.CgroupStats.MemoryStats.Usage.Usage)
t.Fatalf("expected memory usage 1024 but received %d", stats.CgroupStats.MemoryStats.Usage.Usage)
}
if intelrdt.IsEnabled() {
if stats.IntelRdtStats == nil {

View file

@ -0,0 +1,105 @@
package devices
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/sys/unix"
)
var (
ErrNotADevice = errors.New("not a device node")
)
// Testing dependencies
var (
unixLstat = unix.Lstat
ioutilReadDir = ioutil.ReadDir
)
// Given the path to a device and its cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct.
func DeviceFromPath(path, permissions string) (*configs.Device, error) {
var stat unix.Stat_t
err := unixLstat(path, &stat)
if err != nil {
return nil, err
}
var (
devNumber = uint64(stat.Rdev)
major = unix.Major(devNumber)
minor = unix.Minor(devNumber)
)
if major == 0 {
return nil, ErrNotADevice
}
var (
devType rune
mode = stat.Mode
)
switch {
case mode&unix.S_IFBLK == unix.S_IFBLK:
devType = 'b'
case mode&unix.S_IFCHR == unix.S_IFCHR:
devType = 'c'
}
return &configs.Device{
Type: devType,
Path: path,
Major: int64(major),
Minor: int64(minor),
Permissions: permissions,
FileMode: os.FileMode(mode),
Uid: stat.Uid,
Gid: stat.Gid,
}, nil
}
func HostDevices() ([]*configs.Device, error) {
return getDevices("/dev")
}
func getDevices(path string) ([]*configs.Device, error) {
files, err := ioutilReadDir(path)
if err != nil {
return nil, err
}
out := []*configs.Device{}
for _, f := range files {
switch {
case f.IsDir():
switch f.Name() {
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":
continue
default:
sub, err := getDevices(filepath.Join(path, f.Name()))
if err != nil {
return nil, err
}
out = append(out, sub...)
continue
}
case f.Name() == "console":
continue
}
device, err := DeviceFromPath(filepath.Join(path, f.Name()), "rwm")
if err != nil {
if err == ErrNotADevice {
continue
}
if os.IsNotExist(err) {
continue
}
return nil, err
}
out = append(out, device)
}
return out, nil
}

View file

@ -0,0 +1,63 @@
package devices
import (
"errors"
"os"
"testing"
"golang.org/x/sys/unix"
)
func TestDeviceFromPathLstatFailure(t *testing.T) {
testError := errors.New("test error")
// Override unix.Lstat to inject error.
unixLstat = func(path string, stat *unix.Stat_t) error {
return testError
}
_, err := DeviceFromPath("", "")
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}
func TestHostDevicesIoutilReadDirFailure(t *testing.T) {
testError := errors.New("test error")
// Override ioutil.ReadDir to inject error.
ioutilReadDir = func(dirname string) ([]os.FileInfo, error) {
return nil, testError
}
_, err := HostDevices()
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}
func TestHostDevicesIoutilReadDirDeepFailure(t *testing.T) {
testError := errors.New("test error")
called := false
// Override ioutil.ReadDir to inject error after the first call.
ioutilReadDir = func(dirname string) ([]os.FileInfo, error) {
if called {
return nil, testError
}
called = true
// Provoke a second call.
fi, err := os.Lstat("/tmp")
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
return []os.FileInfo{fi}, nil
}
_, err := HostDevices()
if err != testError {
t.Fatalf("Unexpected error %v, expected %v", err, testError)
}
}

View file

@ -11,6 +11,7 @@ import (
"runtime/debug"
"strconv"
"github.com/cyphar/filepath-securejoin"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
@ -59,9 +60,9 @@ func SystemdCgroups(l *LinuxFactory) error {
return nil
}
// Cgroupfs is an options func to configure a LinuxFactory to return
// containers that use the native cgroups filesystem implementation to
// create and manage cgroups.
// Cgroupfs is an options func to configure a LinuxFactory to return containers
// that use the native cgroups filesystem implementation to create and manage
// cgroups.
func Cgroupfs(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
return &fs.Manager{
@ -72,6 +73,23 @@ func Cgroupfs(l *LinuxFactory) error {
return nil
}
// RootlessCgroupfs is an options func to configure a LinuxFactory to return
// containers that use the native cgroups filesystem implementation to create
// and manage cgroups. The difference between RootlessCgroupfs and Cgroupfs is
// that RootlessCgroupfs can transparently handle permission errors that occur
// during rootless container setup (while still allowing cgroup usage if
// they've been set up properly).
func RootlessCgroupfs(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
return &fs.Manager{
Cgroups: config,
Rootless: true,
Paths: paths,
}
}
return nil
}
// IntelRdtfs is an options func to configure a LinuxFactory to return
// containers that use the Intel RDT "resource control" filesystem to
// create and manage Intel Xeon platform shared resources (e.g., L3 cache).
@ -178,7 +196,10 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
if err := l.Validator.Validate(config); err != nil {
return nil, newGenericError(err, ConfigInvalid)
}
containerRoot := filepath.Join(l.Root, id)
containerRoot, err := securejoin.SecureJoin(l.Root, id)
if err != nil {
return nil, err
}
if _, err := os.Stat(containerRoot); err == nil {
return nil, newGenericError(fmt.Errorf("container with id exists: %v", id), IdInUse)
} else if !os.IsNotExist(err) {
@ -212,7 +233,14 @@ func (l *LinuxFactory) Load(id string) (Container, error) {
if l.Root == "" {
return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid)
}
containerRoot := filepath.Join(l.Root, id)
//when load, we need to check id is valid or not.
if err := l.validateID(id); err != nil {
return nil, err
}
containerRoot, err := securejoin.SecureJoin(l.Root, id)
if err != nil {
return nil, err
}
state, err := l.loadState(containerRoot, id)
if err != nil {
return nil, err
@ -322,7 +350,11 @@ func (l *LinuxFactory) StartInitialization() (err error) {
}
func (l *LinuxFactory) loadState(root, id string) (*State, error) {
f, err := os.Open(filepath.Join(root, stateFilename))
stateFilePath, err := securejoin.SecureJoin(root, stateFilename)
if err != nil {
return nil, err
}
f, err := os.Open(stateFilePath)
if err != nil {
if os.IsNotExist(err) {
return nil, newGenericError(fmt.Errorf("container %q does not exist", id), ContainerNotExists)
@ -338,7 +370,7 @@ func (l *LinuxFactory) loadState(root, id string) (*State, error) {
}
func (l *LinuxFactory) validateID(id string) error {
if !idRegex.MatchString(id) {
if !idRegex.MatchString(id) || string(os.PathSeparator)+id != utils.CleanPath(string(os.PathSeparator)+id) {
return newGenericError(fmt.Errorf("invalid id format: %v", id), InvalidIdFormat)
}

View file

@ -20,6 +20,7 @@ import (
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
)
@ -121,7 +122,7 @@ func finalizeNamespace(config *initConfig) error {
// inherited are marked close-on-exec so they stay out of the
// container
if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil {
return err
return errors.Wrap(err, "close exec fds")
}
capabilities := &configs.Capabilities{}
@ -136,20 +137,20 @@ func finalizeNamespace(config *initConfig) error {
}
// drop capabilities in bounding set before changing user
if err := w.ApplyBoundingSet(); err != nil {
return err
return errors.Wrap(err, "apply bounding set")
}
// preserve existing capabilities while we change users
if err := system.SetKeepCaps(); err != nil {
return err
return errors.Wrap(err, "set keep caps")
}
if err := setupUser(config); err != nil {
return err
return errors.Wrap(err, "setup user")
}
if err := system.ClearKeepCaps(); err != nil {
return err
return errors.Wrap(err, "clear keep caps")
}
if err := w.ApplyCaps(); err != nil {
return err
return errors.Wrap(err, "apply caps")
}
if config.Cwd != "" {
if err := unix.Chdir(config.Cwd); err != nil {

View file

@ -0,0 +1,257 @@
package integration
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/sys/unix"
)
func showFile(t *testing.T, fname string) error {
t.Logf("=== %s ===\n", fname)
f, err := os.Open(fname)
if err != nil {
t.Log(err)
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
t.Log(scanner.Text())
}
if err := scanner.Err(); err != nil {
return err
}
t.Logf("=== END ===\n")
return nil
}
func TestUsernsCheckpoint(t *testing.T) {
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
t.Skip("userns is unsupported")
}
cmd := exec.Command("criu", "check", "--feature", "userns")
if err := cmd.Run(); err != nil {
t.Skip("Unable to c/r a container with userns")
}
testCheckpoint(t, true)
}
func TestCheckpoint(t *testing.T) {
testCheckpoint(t, false)
}
func testCheckpoint(t *testing.T, userns bool) {
if testing.Short() {
return
}
root, err := newTestRoot()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Mounts = append(config.Mounts, &configs.Mount{
Destination: "/sys/fs/cgroup",
Device: "cgroup",
Flags: defaultMountFlags | unix.MS_RDONLY,
})
if userns {
config.UidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
config.GidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER})
}
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}
container, err := factory.Create("test", config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
stdinR, stdinW, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
var stdout bytes.Buffer
pconfig := libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Stdout: &stdout,
Init: true,
}
err = container.Run(&pconfig)
stdinR.Close()
defer stdinW.Close()
if err != nil {
t.Fatal(err)
}
pid, err := pconfig.Pid()
if err != nil {
t.Fatal(err)
}
process, err := os.FindProcess(pid)
if err != nil {
t.Fatal(err)
}
parentDir, err := ioutil.TempDir("", "criu-parent")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(parentDir)
preDumpOpts := &libcontainer.CriuOpts{
ImagesDirectory: parentDir,
WorkDirectory: parentDir,
PreDump: true,
}
preDumpLog := filepath.Join(preDumpOpts.WorkDirectory, "dump.log")
if err := container.Checkpoint(preDumpOpts); err != nil {
showFile(t, preDumpLog)
t.Fatal(err)
}
state, err := container.Status()
if err != nil {
t.Fatal(err)
}
if state != libcontainer.Running {
t.Fatal("Unexpected preDump state: ", state)
}
imagesDir, err := ioutil.TempDir("", "criu")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(imagesDir)
checkpointOpts := &libcontainer.CriuOpts{
ImagesDirectory: imagesDir,
WorkDirectory: imagesDir,
ParentImage: "../criu-parent",
}
dumpLog := filepath.Join(checkpointOpts.WorkDirectory, "dump.log")
restoreLog := filepath.Join(checkpointOpts.WorkDirectory, "restore.log")
if err := container.Checkpoint(checkpointOpts); err != nil {
showFile(t, dumpLog)
t.Fatal(err)
}
state, err = container.Status()
if err != nil {
t.Fatal(err)
}
if state != libcontainer.Stopped {
t.Fatal("Unexpected state checkpoint: ", state)
}
stdinW.Close()
_, err = process.Wait()
if err != nil {
t.Fatal(err)
}
// reload the container
container, err = factory.Load("test")
if err != nil {
t.Fatal(err)
}
restoreStdinR, restoreStdinW, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
restoreProcessConfig := &libcontainer.Process{
Cwd: "/",
Stdin: restoreStdinR,
Stdout: &stdout,
Init: true,
}
err = container.Restore(restoreProcessConfig, checkpointOpts)
restoreStdinR.Close()
defer restoreStdinW.Close()
if err != nil {
showFile(t, restoreLog)
t.Fatal(err)
}
state, err = container.Status()
if err != nil {
t.Fatal(err)
}
if state != libcontainer.Running {
t.Fatal("Unexpected restore state: ", state)
}
pid, err = restoreProcessConfig.Pid()
if err != nil {
t.Fatal(err)
}
process, err = os.FindProcess(pid)
if err != nil {
t.Fatal(err)
}
_, err = restoreStdinW.WriteString("Hello!")
if err != nil {
t.Fatal(err)
}
restoreStdinW.Close()
s, err := process.Wait()
if err != nil {
t.Fatal(err)
}
if !s.Success() {
t.Fatal(s.String(), pid)
}
output := string(stdout.Bytes())
if !strings.Contains(output, "Hello!") {
t.Fatal("Did not restore the pipe correctly:", output)
}
}

View file

@ -0,0 +1,2 @@
// integration is used for integration testing of libcontainer
package integration

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,608 @@
package integration
import (
"bytes"
"fmt"
"io"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/containerd/console"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/utils"
"golang.org/x/sys/unix"
)
func TestExecIn(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
buffers := newStdBuffers()
ps := &libcontainer.Process{
Cwd: "/",
Args: []string{"ps"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}
err = container.Run(ps)
ok(t, err)
waitProcess(ps, t)
stdinW.Close()
waitProcess(process, t)
out := buffers.Stdout.String()
if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
t.Fatalf("unexpected running process, output %q", out)
}
if strings.Contains(out, "\r") {
t.Fatalf("unexpected carriage-return in output")
}
}
func TestExecInUsernsRlimit(t *testing.T) {
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
t.Skip("userns is unsupported")
}
testExecInRlimit(t, true)
}
func TestExecInRlimit(t *testing.T) {
testExecInRlimit(t, false)
}
func testExecInRlimit(t *testing.T, userns bool) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
if userns {
config.UidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
config.GidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER})
}
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
buffers := newStdBuffers()
ps := &libcontainer.Process{
Cwd: "/",
Args: []string{"/bin/sh", "-c", "ulimit -n"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
Rlimits: []configs.Rlimit{
// increase process rlimit higher than container rlimit to test per-process limit
{Type: unix.RLIMIT_NOFILE, Hard: 1026, Soft: 1026},
},
Init: true,
}
err = container.Run(ps)
ok(t, err)
waitProcess(ps, t)
stdinW.Close()
waitProcess(process, t)
out := buffers.Stdout.String()
if limit := strings.TrimSpace(out); limit != "1026" {
t.Fatalf("expected rlimit to be 1026, got %s", limit)
}
}
func TestExecInAdditionalGroups(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
var stdout bytes.Buffer
pconfig := libcontainer.Process{
Cwd: "/",
Args: []string{"sh", "-c", "id", "-Gn"},
Env: standardEnvironment,
Stdin: nil,
Stdout: &stdout,
AdditionalGroups: []string{"plugdev", "audio"},
}
err = container.Run(&pconfig)
ok(t, err)
// Wait for process
waitProcess(&pconfig, t)
stdinW.Close()
waitProcess(process, t)
outputGroups := string(stdout.Bytes())
// Check that the groups output has the groups that we specified
if !strings.Contains(outputGroups, "audio") {
t.Fatalf("Listed groups do not contain the audio group as expected: %v", outputGroups)
}
if !strings.Contains(outputGroups, "plugdev") {
t.Fatalf("Listed groups do not contain the plugdev group as expected: %v", outputGroups)
}
}
func TestExecInError(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer func() {
stdinW.Close()
if _, err := process.Wait(); err != nil {
t.Log(err)
}
}()
ok(t, err)
for i := 0; i < 42; i++ {
var out bytes.Buffer
unexistent := &libcontainer.Process{
Cwd: "/",
Args: []string{"unexistent"},
Env: standardEnvironment,
Stderr: &out,
}
err = container.Run(unexistent)
if err == nil {
t.Fatal("Should be an error")
}
if !strings.Contains(err.Error(), "executable file not found") {
t.Fatalf("Should be error about not found executable, got %s", err)
}
if !bytes.Contains(out.Bytes(), []byte("executable file not found")) {
t.Fatalf("executable file not found error not delivered to stdio:\n%s", out.String())
}
}
}
func TestExecInTTY(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
var stdout bytes.Buffer
ps := &libcontainer.Process{
Cwd: "/",
Args: []string{"ps"},
Env: standardEnvironment,
}
parent, child, err := utils.NewSockPair("console")
if err != nil {
ok(t, err)
}
defer parent.Close()
defer child.Close()
ps.ConsoleSocket = child
type cdata struct {
c console.Console
err error
}
dc := make(chan *cdata, 1)
go func() {
f, err := utils.RecvFd(parent)
if err != nil {
dc <- &cdata{
err: err,
}
return
}
c, err := console.ConsoleFromFile(f)
if err != nil {
dc <- &cdata{
err: err,
}
return
}
console.ClearONLCR(c.Fd())
dc <- &cdata{
c: c,
}
}()
err = container.Run(ps)
ok(t, err)
data := <-dc
if data.err != nil {
ok(t, data.err)
}
console := data.c
copy := make(chan struct{})
go func() {
io.Copy(&stdout, console)
close(copy)
}()
ok(t, err)
select {
case <-time.After(5 * time.Second):
t.Fatal("Waiting for copy timed out")
case <-copy:
}
waitProcess(ps, t)
stdinW.Close()
waitProcess(process, t)
out := stdout.String()
if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
t.Fatalf("unexpected running process, output %q", out)
}
if strings.Contains(out, "\r") {
t.Fatalf("unexpected carriage-return in output")
}
}
func TestExecInEnvironment(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
buffers := newStdBuffers()
process2 := &libcontainer.Process{
Cwd: "/",
Args: []string{"env"},
Env: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"DEBUG=true",
"DEBUG=false",
"ENV=test",
},
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
Init: true,
}
err = container.Run(process2)
ok(t, err)
waitProcess(process2, t)
stdinW.Close()
waitProcess(process, t)
out := buffers.Stdout.String()
// check execin's process environment
if !strings.Contains(out, "DEBUG=false") ||
!strings.Contains(out, "ENV=test") ||
!strings.Contains(out, "HOME=/root") ||
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
strings.Contains(out, "DEBUG=true") {
t.Fatalf("unexpected running process, output %q", out)
}
}
func TestExecinPassExtraFiles(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
if err != nil {
t.Fatal(err)
}
var stdout bytes.Buffer
pipeout1, pipein1, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
pipeout2, pipein2, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
inprocess := &libcontainer.Process{
Cwd: "/",
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
ExtraFiles: []*os.File{pipein1, pipein2},
Stdin: nil,
Stdout: &stdout,
}
err = container.Run(inprocess)
if err != nil {
t.Fatal(err)
}
waitProcess(inprocess, t)
stdinW.Close()
waitProcess(process, t)
out := string(stdout.Bytes())
// fd 5 is the directory handle for /proc/$$/fd
if out != "0 1 2 3 4 5" {
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
}
var buf = []byte{0}
_, err = pipeout1.Read(buf)
if err != nil {
t.Fatal(err)
}
out1 := string(buf)
if out1 != "1" {
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
}
_, err = pipeout2.Read(buf)
if err != nil {
t.Fatal(err)
}
out2 := string(buf)
if out2 != "2" {
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
}
}
func TestExecInOomScoreAdj(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.OomScoreAdj = ptrInt(200)
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
buffers := newStdBuffers()
ps := &libcontainer.Process{
Cwd: "/",
Args: []string{"/bin/sh", "-c", "cat /proc/self/oom_score_adj"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}
err = container.Run(ps)
ok(t, err)
waitProcess(ps, t)
stdinW.Close()
waitProcess(process, t)
out := buffers.Stdout.String()
if oomScoreAdj := strings.TrimSpace(out); oomScoreAdj != strconv.Itoa(*config.OomScoreAdj) {
t.Fatalf("expected oomScoreAdj to be %d, got %s", *config.OomScoreAdj, oomScoreAdj)
}
}
func TestExecInUserns(t *testing.T) {
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
t.Skip("userns is unsupported")
}
if testing.Short() {
return
}
rootfs, err := newRootfs()
ok(t, err)
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.UidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
config.GidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER})
container, err := newContainer(config)
ok(t, err)
defer container.Destroy()
// Execute a first process in the container
stdinR, stdinW, err := os.Pipe()
ok(t, err)
process := &libcontainer.Process{
Cwd: "/",
Args: []string{"cat"},
Env: standardEnvironment,
Stdin: stdinR,
Init: true,
}
err = container.Run(process)
stdinR.Close()
defer stdinW.Close()
ok(t, err)
initPID, err := process.Pid()
ok(t, err)
initUserns, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/user", initPID))
ok(t, err)
buffers := newStdBuffers()
process2 := &libcontainer.Process{
Cwd: "/",
Args: []string{"readlink", "/proc/self/ns/user"},
Env: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
},
Stdout: buffers.Stdout,
Stderr: os.Stderr,
}
err = container.Run(process2)
ok(t, err)
waitProcess(process2, t)
stdinW.Close()
waitProcess(process, t)
if out := strings.TrimSpace(buffers.Stdout.String()); out != initUserns {
t.Errorf("execin userns(%s), wanted %s", out, initUserns)
}
}

View file

@ -0,0 +1,61 @@
package integration
import (
"os"
"runtime"
"testing"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
_ "github.com/opencontainers/runc/libcontainer/nsenter"
"github.com/sirupsen/logrus"
)
// init runs the libcontainer initialization code because of the busybox style needs
// to work around the go runtime and the issues with forking
func init() {
if len(os.Args) < 2 || os.Args[1] != "init" {
return
}
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, err := libcontainer.New("")
if err != nil {
logrus.Fatalf("unable to initialize for container: %s", err)
}
if err := factory.StartInitialization(); err != nil {
logrus.Fatal(err)
}
}
var (
factory libcontainer.Factory
systemdFactory libcontainer.Factory
)
func TestMain(m *testing.M) {
var (
err error
ret int
)
logrus.SetOutput(os.Stderr)
logrus.SetLevel(logrus.InfoLevel)
factory, err = libcontainer.New("/run/libctTests", libcontainer.Cgroupfs)
if err != nil {
logrus.Error(err)
os.Exit(1)
}
if systemd.UseSystemd() {
systemdFactory, err = libcontainer.New("/run/libctTests", libcontainer.SystemdCgroups)
if err != nil {
logrus.Error(err)
os.Exit(1)
}
}
ret = m.Run()
os.Exit(ret)
}

View file

@ -0,0 +1,422 @@
// +build linux,cgo,seccomp
package integration
import (
"strings"
"syscall"
"testing"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
libseccomp "github.com/seccomp/libseccomp-golang"
)
func TestSeccompDenyGetcwd(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "getcwd",
Action: configs.Errno,
},
},
}
container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
buffers := newStdBuffers()
pwd := &libcontainer.Process{
Cwd: "/",
Args: []string{"pwd"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
Init: true,
}
err = container.Run(pwd)
if err != nil {
t.Fatal(err)
}
ps, err := pwd.Wait()
if err == nil {
t.Fatal("Expecting error (negative return code); instead exited cleanly!")
}
var exitCode int
status := ps.Sys().(syscall.WaitStatus)
if status.Exited() {
exitCode = status.ExitStatus()
} else if status.Signaled() {
exitCode = -int(status.Signal())
} else {
t.Fatalf("Unrecognized exit reason!")
}
if exitCode == 0 {
t.Fatalf("Getcwd should fail with negative exit code, instead got %d!", exitCode)
}
expected := "pwd: getcwd: Operation not permitted"
actual := strings.Trim(buffers.Stderr.String(), "\n")
if actual != expected {
t.Fatalf("Expected output %s but got %s\n", expected, actual)
}
}
func TestSeccompPermitWriteConditional(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "write",
Action: configs.Errno,
Args: []*configs.Arg{
{
Index: 0,
Value: 2,
Op: configs.EqualTo,
},
},
},
},
}
container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
buffers := newStdBuffers()
dmesg := &libcontainer.Process{
Cwd: "/",
Args: []string{"busybox", "ls", "/"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
Init: true,
}
err = container.Run(dmesg)
if err != nil {
t.Fatal(err)
}
if _, err := dmesg.Wait(); err != nil {
t.Fatalf("%s: %s", err, buffers.Stderr)
}
}
func TestSeccompDenyWriteConditional(t *testing.T) {
if testing.Short() {
return
}
// Only test if library version is v2.2.1 or higher
// Conditional filtering will always error in v2.2.0 and lower
major, minor, micro := libseccomp.GetLibraryVersion()
if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "write",
Action: configs.Errno,
Args: []*configs.Arg{
{
Index: 0,
Value: 2,
Op: configs.EqualTo,
},
},
},
},
}
container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
buffers := newStdBuffers()
dmesg := &libcontainer.Process{
Cwd: "/",
Args: []string{"busybox", "ls", "does_not_exist"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
Init: true,
}
err = container.Run(dmesg)
if err != nil {
t.Fatal(err)
}
ps, err := dmesg.Wait()
if err == nil {
t.Fatal("Expecting negative return, instead got 0!")
}
var exitCode int
status := ps.Sys().(syscall.WaitStatus)
if status.Exited() {
exitCode = status.ExitStatus()
} else if status.Signaled() {
exitCode = -int(status.Signal())
} else {
t.Fatalf("Unrecognized exit reason!")
}
if exitCode == 0 {
t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
}
// We're denying write to stderr, so we expect an empty buffer
expected := ""
actual := strings.Trim(buffers.Stderr.String(), "\n")
if actual != expected {
t.Fatalf("Expected output %s but got %s\n", expected, actual)
}
}
func TestSeccompPermitWriteMultipleConditions(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "write",
Action: configs.Errno,
Args: []*configs.Arg{
{
Index: 0,
Value: 2,
Op: configs.EqualTo,
},
{
Index: 2,
Value: 0,
Op: configs.NotEqualTo,
},
},
},
},
}
buffers, exitCode, err := runContainer(config, "", "ls", "/")
if err != nil {
t.Fatalf("%s: %s", buffers, err)
}
if exitCode != 0 {
t.Fatalf("exit code not 0. code %d buffers %s", exitCode, buffers)
}
// We don't need to verify the actual thing printed
// Just that something was written to stdout
if len(buffers.Stdout.String()) == 0 {
t.Fatalf("Nothing was written to stdout, write call failed!\n")
}
}
func TestSeccompDenyWriteMultipleConditions(t *testing.T) {
if testing.Short() {
return
}
// Only test if library version is v2.2.1 or higher
// Conditional filtering will always error in v2.2.0 and lower
major, minor, micro := libseccomp.GetLibraryVersion()
if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "write",
Action: configs.Errno,
Args: []*configs.Arg{
{
Index: 0,
Value: 2,
Op: configs.EqualTo,
},
{
Index: 2,
Value: 0,
Op: configs.NotEqualTo,
},
},
},
},
}
buffers, exitCode, err := runContainer(config, "", "ls", "/does_not_exist")
if err == nil {
t.Fatalf("Expecting error return, instead got 0")
}
if exitCode == 0 {
t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
}
expected := ""
actual := strings.Trim(buffers.Stderr.String(), "\n")
if actual != expected {
t.Fatalf("Expected output %s but got %s\n", expected, actual)
}
}
func TestSeccompMultipleConditionSameArgDeniesStdout(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
// Prevent writing to both stdout and stderr
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "write",
Action: configs.Errno,
Args: []*configs.Arg{
{
Index: 0,
Value: 1,
Op: configs.EqualTo,
},
{
Index: 0,
Value: 2,
Op: configs.EqualTo,
},
},
},
},
}
buffers, exitCode, err := runContainer(config, "", "ls", "/")
if err != nil {
t.Fatalf("%s: %s", buffers, err)
}
if exitCode != 0 {
t.Fatalf("exit code not 0. code %d buffers %s", exitCode, buffers)
}
// Verify that nothing was printed
if len(buffers.Stdout.String()) != 0 {
t.Fatalf("Something was written to stdout, write call succeeded!\n")
}
}
func TestSeccompMultipleConditionSameArgDeniesStderr(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
// Prevent writing to both stdout and stderr
config := newTemplateConfig(rootfs)
config.Seccomp = &configs.Seccomp{
DefaultAction: configs.Allow,
Syscalls: []*configs.Syscall{
{
Name: "write",
Action: configs.Errno,
Args: []*configs.Arg{
{
Index: 0,
Value: 1,
Op: configs.EqualTo,
},
{
Index: 0,
Value: 2,
Op: configs.EqualTo,
},
},
},
},
}
buffers, exitCode, err := runContainer(config, "", "ls", "/does_not_exist")
if err == nil {
t.Fatalf("Expecting error return, instead got 0")
}
if exitCode == 0 {
t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
}
// Verify nothing was printed
if len(buffers.Stderr.String()) != 0 {
t.Fatalf("Something was written to stderr, write call succeeded!\n")
}
}

View file

@ -0,0 +1,191 @@
package integration
import (
"github.com/opencontainers/runc/libcontainer/configs"
"golang.org/x/sys/unix"
)
var standardEnvironment = []string{
"HOME=/root",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=integration",
"TERM=xterm",
}
const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
// newTemplateConfig returns a base template for running a container
//
// it uses a network strategy of just setting a loopback interface
// and the default setup for devices
func newTemplateConfig(rootfs string) *configs.Config {
allowAllDevices := false
return &configs.Config{
Rootfs: rootfs,
Capabilities: &configs.Capabilities{
Bounding: []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Permitted: []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Inheritable: []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Ambient: []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Effective: []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
},
Namespaces: configs.Namespaces([]configs.Namespace{
{Type: configs.NEWNS},
{Type: configs.NEWUTS},
{Type: configs.NEWIPC},
{Type: configs.NEWPID},
{Type: configs.NEWNET},
}),
Cgroups: &configs.Cgroup{
Path: "integration/test",
Resources: &configs.Resources{
MemorySwappiness: nil,
AllowAllDevices: &allowAllDevices,
AllowedDevices: configs.DefaultAllowedDevices,
},
},
MaskPaths: []string{
"/proc/kcore",
"/sys/firmware",
},
ReadonlyPaths: []string{
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
},
Devices: configs.DefaultAutoCreatedDevices,
Hostname: "integration",
Mounts: []*configs.Mount{
{
Source: "proc",
Destination: "/proc",
Device: "proc",
Flags: defaultMountFlags,
},
{
Source: "tmpfs",
Destination: "/dev",
Device: "tmpfs",
Flags: unix.MS_NOSUID | unix.MS_STRICTATIME,
Data: "mode=755",
},
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
Flags: unix.MS_NOSUID | unix.MS_NOEXEC,
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
},
{
Device: "tmpfs",
Source: "shm",
Destination: "/dev/shm",
Data: "mode=1777,size=65536k",
Flags: defaultMountFlags,
},
/*
CI is broken on the debian based kernels with this
{
Source: "mqueue",
Destination: "/dev/mqueue",
Device: "mqueue",
Flags: defaultMountFlags,
},
*/
{
Source: "sysfs",
Destination: "/sys",
Device: "sysfs",
Flags: defaultMountFlags | unix.MS_RDONLY,
},
},
Networks: []*configs.Network{
{
Type: "loopback",
Address: "127.0.0.1/0",
Gateway: "localhost",
},
},
Rlimits: []configs.Rlimit{
{
Type: unix.RLIMIT_NOFILE,
Hard: uint64(1025),
Soft: uint64(1025),
},
},
}
}

View file

@ -0,0 +1,175 @@
package integration
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"testing"
"time"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
)
func ptrInt(v int) *int {
return &v
}
func newStdBuffers() *stdBuffers {
return &stdBuffers{
Stdin: bytes.NewBuffer(nil),
Stdout: bytes.NewBuffer(nil),
Stderr: bytes.NewBuffer(nil),
}
}
type stdBuffers struct {
Stdin *bytes.Buffer
Stdout *bytes.Buffer
Stderr *bytes.Buffer
}
func (b *stdBuffers) String() string {
s := []string{}
if b.Stderr != nil {
s = append(s, b.Stderr.String())
}
if b.Stdout != nil {
s = append(s, b.Stdout.String())
}
return strings.Join(s, "|")
}
// ok fails the test if an err is not nil.
func ok(t testing.TB, err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error())
}
}
func waitProcess(p *libcontainer.Process, t *testing.T) {
_, file, line, _ := runtime.Caller(1)
status, err := p.Wait()
if err != nil {
t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error())
}
if !status.Success() {
t.Fatalf("%s:%d: unexpected status: %s\n\n", filepath.Base(file), line, status.String())
}
}
func newTestRoot() (string, error) {
dir, err := ioutil.TempDir("", "libcontainer")
if err != nil {
return "", err
}
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
return dir, nil
}
func newTestBundle() (string, error) {
dir, err := ioutil.TempDir("", "bundle")
if err != nil {
return "", err
}
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
return dir, nil
}
// newRootfs creates a new tmp directory and copies the busybox root filesystem
func newRootfs() (string, error) {
dir, err := ioutil.TempDir("", "")
if err != nil {
return "", err
}
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
if err := copyBusybox(dir); err != nil {
return "", err
}
return dir, nil
}
func remove(dir string) {
os.RemoveAll(dir)
}
// copyBusybox copies the rootfs for a busybox container created for the test image
// into the new directory for the specific test
func copyBusybox(dest string) error {
out, err := exec.Command("sh", "-c", fmt.Sprintf("cp -a /busybox/* %s/", dest)).CombinedOutput()
if err != nil {
return fmt.Errorf("copy error %q: %q", err, out)
}
return nil
}
func newContainer(config *configs.Config) (libcontainer.Container, error) {
h := md5.New()
h.Write([]byte(time.Now().String()))
return newContainerWithName(hex.EncodeToString(h.Sum(nil)), config)
}
func newContainerWithName(name string, config *configs.Config) (libcontainer.Container, error) {
f := factory
if config.Cgroups != nil && config.Cgroups.Parent == "system.slice" {
f = systemdFactory
}
return f.Create(name, config)
}
// runContainer runs the container with the specific config and arguments
//
// buffers are returned containing the STDOUT and STDERR output for the run
// along with the exit code and any go error
func runContainer(config *configs.Config, console string, args ...string) (buffers *stdBuffers, exitCode int, err error) {
container, err := newContainer(config)
if err != nil {
return nil, -1, err
}
defer container.Destroy()
buffers = newStdBuffers()
process := &libcontainer.Process{
Cwd: "/",
Args: args,
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
Init: true,
}
err = container.Run(process)
if err != nil {
return buffers, -1, err
}
ps, err := process.Wait()
if err != nil {
return buffers, -1, err
}
status := ps.Sys().(syscall.WaitStatus)
if status.Exited() {
exitCode = status.ExitStatus()
} else if status.Signaled() {
exitCode = -int(status.Signal())
} else {
return buffers, -1, err
}
return
}

View file

@ -177,7 +177,7 @@ func findIntelRdtMountpointDir() (string, error) {
}
if postSeparatorFields[0] == "resctrl" {
// Check that the mount is properly formated.
// Check that the mount is properly formatted.
if numPostFields < 3 {
return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}

View file

@ -77,13 +77,13 @@ func (msg *Boolmsg) Serialize() []byte {
native.PutUint16(buf[0:2], uint16(msg.Len()))
native.PutUint16(buf[2:4], msg.Type)
if msg.Value {
buf[4] = 1
native.PutUint32(buf[4:8], uint32(1))
} else {
buf[4] = 0
native.PutUint32(buf[4:8], uint32(0))
}
return buf
}
func (msg *Boolmsg) Len() int {
return unix.NLA_HDRLEN + 1
return unix.NLA_HDRLEN + 4 // alignment
}

View file

@ -5,18 +5,15 @@ package libcontainer
import (
"fmt"
"io/ioutil"
"net"
"path/filepath"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/vishvananda/netlink"
)
var strategies = map[string]networkStrategy{
"veth": &veth{},
"loopback": &loopback{},
}
@ -103,157 +100,3 @@ func (l *loopback) attach(n *configs.Network) (err error) {
func (l *loopback) detach(n *configs.Network) (err error) {
return nil
}
// veth is a network strategy that uses a bridge and creates
// a veth pair, one that is attached to the bridge on the host and the other
// is placed inside the container's namespace
type veth struct {
}
func (v *veth) detach(n *configs.Network) (err error) {
return netlink.LinkSetMaster(&netlink.Device{LinkAttrs: netlink.LinkAttrs{Name: n.HostInterfaceName}}, nil)
}
// attach a container network interface to an external network
func (v *veth) attach(n *configs.Network) (err error) {
brl, err := netlink.LinkByName(n.Bridge)
if err != nil {
return err
}
br, ok := brl.(*netlink.Bridge)
if !ok {
return fmt.Errorf("Wrong device type %T", brl)
}
host, err := netlink.LinkByName(n.HostInterfaceName)
if err != nil {
return err
}
if err := netlink.LinkSetMaster(host, br); err != nil {
return err
}
if err := netlink.LinkSetMTU(host, n.Mtu); err != nil {
return err
}
if n.HairpinMode {
if err := netlink.LinkSetHairpin(host, true); err != nil {
return err
}
}
if err := netlink.LinkSetUp(host); err != nil {
return err
}
return nil
}
func (v *veth) create(n *network, nspid int) (err error) {
tmpName, err := v.generateTempPeerName()
if err != nil {
return err
}
n.TempVethPeerName = tmpName
if n.Bridge == "" {
return fmt.Errorf("bridge is not specified")
}
veth := &netlink.Veth{
LinkAttrs: netlink.LinkAttrs{
Name: n.HostInterfaceName,
TxQLen: n.TxQueueLen,
},
PeerName: n.TempVethPeerName,
}
if err := netlink.LinkAdd(veth); err != nil {
return err
}
defer func() {
if err != nil {
netlink.LinkDel(veth)
}
}()
if err := v.attach(&n.Network); err != nil {
return err
}
child, err := netlink.LinkByName(n.TempVethPeerName)
if err != nil {
return err
}
return netlink.LinkSetNsPid(child, nspid)
}
func (v *veth) generateTempPeerName() (string, error) {
return utils.GenerateRandomName("veth", 7)
}
func (v *veth) initialize(config *network) error {
peer := config.TempVethPeerName
if peer == "" {
return fmt.Errorf("peer is not specified")
}
child, err := netlink.LinkByName(peer)
if err != nil {
return err
}
if err := netlink.LinkSetDown(child); err != nil {
return err
}
if err := netlink.LinkSetName(child, config.Name); err != nil {
return err
}
// get the interface again after we changed the name as the index also changes.
if child, err = netlink.LinkByName(config.Name); err != nil {
return err
}
if config.MacAddress != "" {
mac, err := net.ParseMAC(config.MacAddress)
if err != nil {
return err
}
if err := netlink.LinkSetHardwareAddr(child, mac); err != nil {
return err
}
}
ip, err := netlink.ParseAddr(config.Address)
if err != nil {
return err
}
if err := netlink.AddrAdd(child, ip); err != nil {
return err
}
if config.IPv6Address != "" {
ip6, err := netlink.ParseAddr(config.IPv6Address)
if err != nil {
return err
}
if err := netlink.AddrAdd(child, ip6); err != nil {
return err
}
}
if err := netlink.LinkSetMTU(child, config.Mtu); err != nil {
return err
}
if err := netlink.LinkSetUp(child); err != nil {
return err
}
if config.Gateway != "" {
gw := net.ParseIP(config.Gateway)
if err := netlink.RouteAdd(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: child.Attrs().Index,
Gw: gw,
}); err != nil {
return err
}
}
if config.IPv6Gateway != "" {
gw := net.ParseIP(config.IPv6Gateway)
if err := netlink.RouteAdd(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: child.Attrs().Index,
Gw: gw,
}); err != nil {
return err
}
}
return nil
}

View file

@ -10,8 +10,8 @@ The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd
package. In cgo, if the import of "C" is immediately preceded by a comment, that comment,
called the preamble, is used as a header when compiling the C parts of the package.
So every time we import package `nsenter`, the C code function `nsexec()` would be
called. And package `nsenter` is now only imported in `main_unix.go`, so every time
before we call `cmd.Start` on linux, that C code would run.
called. And package `nsenter` is only imported in `init.go`, so every time the runc
`init` command is invoked, that C code is run.
Because `nsexec()` must be run before the Go runtime in order to use the
Linux kernel namespace, you must `import` this library into a package if
@ -37,7 +37,7 @@ the parent `nsexec()` will exit and the child `nsexec()` process will
return to allow the Go runtime take over.
NOTE: We do both `setns(2)` and `clone(2)` even if we don't have any
CLONE_NEW* clone flags because we must fork a new process in order to
`CLONE_NEW*` clone flags because we must fork a new process in order to
enter the PID namespace.

View file

@ -211,7 +211,7 @@ static int try_mapping_tool(const char *app, int pid, char *map, size_t map_len)
/*
* If @app is NULL, execve will segfault. Just check it here and bail (if
* we're in this path, the caller is already getting desparate and there
* we're in this path, the caller is already getting desperate and there
* isn't a backup to this failing). This usually would be a configuration
* or programming issue.
*/
@ -505,7 +505,8 @@ void join_namespaces(char *nslist)
ns->fd = fd;
ns->ns = nsflag(namespace);
strncpy(ns->path, path, PATH_MAX);
strncpy(ns->path, path, PATH_MAX - 1);
ns->path[PATH_MAX - 1] = '\0';
} while ((namespace = strtok_r(NULL, ",", &saveptr)) != NULL);
/*
@ -678,17 +679,15 @@ void nsexec(void)
/*
* Enable setgroups(2) if we've been asked to. But we also
* have to explicitly disable setgroups(2) if we're
* creating a rootless container (this is required since
* Linux 3.19).
* creating a rootless container for single-entry mapping.
* i.e. config.is_setgroup == false.
* (this is required since Linux 3.19).
*
* For rootless multi-entry mapping, config.is_setgroup shall be true and
* newuidmap/newgidmap shall be used.
*/
if (config.is_rootless && config.is_setgroup) {
kill(child, SIGKILL);
bail("cannot allow setgroup in an unprivileged user namespace setup");
}
if (config.is_setgroup)
update_setgroups(child, SETGROUPS_ALLOW);
if (config.is_rootless)
if (config.is_rootless && !config.is_setgroup)
update_setgroups(child, SETGROUPS_DENY);
/* Set up mappings. */
@ -809,25 +808,30 @@ void nsexec(void)
if (config.namespaces)
join_namespaces(config.namespaces);
/*
* Unshare all of the namespaces. Now, it should be noted that this
* ordering might break in the future (especially with rootless
* containers). But for now, it's not possible to split this into
* CLONE_NEWUSER + [the rest] because of some RHEL SELinux issues.
*
* Note that we don't merge this with clone() because there were
* some old kernel versions where clone(CLONE_PARENT | CLONE_NEWPID)
* was broken, so we'll just do it the long way anyway.
*/
if (unshare(config.cloneflags) < 0)
bail("failed to unshare namespaces");
/*
* Deal with user namespaces first. They are quite special, as they
* affect our ability to unshare other namespaces and are used as
* context for privilege checks.
*
* We don't unshare all namespaces in one go. The reason for this
* is that, while the kernel documentation may claim otherwise,
* there are certain cases where unsharing all namespaces at once
* will result in namespace objects being owned incorrectly.
* Ideally we should just fix these kernel bugs, but it's better to
* be safe than sorry, and fix them separately.
*
* A specific case of this is that the SELinux label of the
* internal kern-mount that mqueue uses will be incorrect if the
* UTS namespace is cloned before the USER namespace is mapped.
* I've also heard of similar problems with the network namespace
* in some scenarios. This also mirrors how LXC deals with this
* problem.
*/
if (config.cloneflags & CLONE_NEWUSER) {
if (unshare(CLONE_NEWUSER) < 0)
bail("failed to unshare user namespace");
config.cloneflags &= ~CLONE_NEWUSER;
/*
* We don't have the privileges to do any mapping here (see the
* clone_parent rant). So signal our parent to hook us up.
@ -853,8 +857,21 @@ void nsexec(void)
if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0)
bail("failed to set process as dumpable");
}
/* Become root in the namespace proper. */
if (setresuid(0, 0, 0) < 0)
bail("failed to become root in user namespace");
}
/*
* Unshare all of the namespaces. Note that we don't merge this
* with clone() because there were some old kernel versions where
* clone(CLONE_PARENT | CLONE_NEWPID) was broken, so we'll just do
* it the long way.
*/
if (unshare(config.cloneflags) < 0)
bail("failed to unshare namespaces");
/*
* TODO: What about non-namespace clone flags that we're dropping here?
*

View file

@ -72,6 +72,9 @@ type Process struct {
// ConsoleSocket provides the masterfd console.
ConsoleSocket *os.File
// Init specifies whether the process is the first process in the container.
Init bool
ops processOperations
}

View file

@ -537,7 +537,7 @@ func (p *Process) InitializeIO(rootuid, rootgid int) (i *IO, err error) {
}
fds = append(fds, r.Fd(), w.Fd())
p.Stderr, i.Stderr = w, r
// change ownership of the pipes incase we are in a user namespace
// change ownership of the pipes in case we are in a user namespace
for _, fd := range fds {
if err := unix.Fchown(int(fd), rootuid, rootgid); err != nil {
return nil, err

View file

@ -152,6 +152,26 @@ func finalizeRootfs(config *configs.Config) (err error) {
return nil
}
// /tmp has to be mounted as private to allow MS_MOVE to work in all situations
func prepareTmp(topTmpDir string) (string, error) {
tmpdir, err := ioutil.TempDir(topTmpDir, "runctop")
if err != nil {
return "", err
}
if err := unix.Mount(tmpdir, tmpdir, "bind", unix.MS_BIND, ""); err != nil {
return "", err
}
if err := unix.Mount("", tmpdir, "", uintptr(unix.MS_PRIVATE), ""); err != nil {
return "", err
}
return tmpdir, nil
}
func cleanupTmp(tmpdir string) error {
unix.Unmount(tmpdir, 0)
return os.RemoveAll(tmpdir)
}
func mountCmd(cmd configs.Command) error {
command := exec.Command(cmd.Path, cmd.Args[:]...)
command.Env = cmd.Env
@ -199,7 +219,12 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error {
}
}
if copyUp {
tmpDir, err = ioutil.TempDir("/tmp", "runctmpdir")
tmpdir, err := prepareTmp("/tmp")
if err != nil {
return newSystemErrorWithCause(err, "tmpcopyup: failed to setup tmpdir")
}
defer cleanupTmp(tmpdir)
tmpDir, err = ioutil.TempDir(tmpdir, "runctmpdir")
if err != nil {
return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir")
}
@ -396,6 +421,7 @@ func checkMountDestination(rootfs, dest string) error {
"/proc/stat",
"/proc/swaps",
"/proc/uptime",
"/proc/loadavg",
"/proc/net/dev",
}
for _, valid := range validDestinations {
@ -412,7 +438,7 @@ func checkMountDestination(rootfs, dest string) error {
if err != nil {
return err
}
if path == "." || !strings.HasPrefix(path, "..") {
if path != "." && !strings.HasPrefix(path, "..") {
return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
}
}

View file

@ -9,13 +9,21 @@ import (
)
func TestCheckMountDestOnProc(t *testing.T) {
dest := "/rootfs/proc/"
dest := "/rootfs/proc/sys"
err := checkMountDestination("/rootfs", dest)
if err == nil {
t.Fatal("destination inside proc should return an error")
}
}
func TestCheckMountDestOnProcChroot(t *testing.T) {
dest := "/rootfs/proc/"
err := checkMountDestination("/rootfs", dest)
if err != nil {
t.Fatal("destination inside proc when using chroot should not return an error")
}
}
func TestCheckMountDestInSys(t *testing.T) {
dest := "/rootfs//sys/fs/cgroup"
err := checkMountDestination("/rootfs", dest)

View file

@ -0,0 +1,47 @@
Name: cat
State: R (running)
Tgid: 19383
Ngid: 0
Pid: 19383
PPid: 19275
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 256
Groups: 24 25 27 29 30 44 46 102 104 108 111 1000 1001
NStgid: 19383
NSpid: 19383
NSpgid: 19383
NSsid: 19275
VmPeak: 5944 kB
VmSize: 5944 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 744 kB
VmRSS: 744 kB
VmData: 324 kB
VmStk: 136 kB
VmExe: 48 kB
VmLib: 1776 kB
VmPTE: 32 kB
VmPMD: 12 kB
VmSwap: 0 kB
Threads: 1
SigQ: 0/30067
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000080
SigCgt: 0000000000000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Seccomp: 0
Cpus_allowed: f
Cpus_allowed_list: 0-3
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 0
nonvoluntary_ctxt_switches: 1

View file

@ -216,7 +216,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
}
config.Namespaces.Add(t, ns.Path)
}
if config.Namespaces.Contains(configs.NEWNET) {
if config.Namespaces.Contains(configs.NEWNET) && config.Namespaces.PathOf(configs.NEWNET) == "" {
config.Networks = []*configs.Network{
{
Type: "loopback",
@ -233,7 +233,7 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
config.MountLabel = spec.Linux.MountLabel
config.Sysctl = spec.Linux.Sysctl
if spec.Linux.Seccomp != nil {
seccomp, err := setupSeccomp(spec.Linux.Seccomp)
seccomp, err := SetupSeccomp(spec.Linux.Seccomp)
if err != nil {
return nil, err
}
@ -243,8 +243,8 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
if spec.Process.SelinuxLabel != "" {
config.ProcessLabel = spec.Process.SelinuxLabel
}
if spec.Process != nil && spec.Process.OOMScoreAdj != nil {
config.OomScoreAdj = *spec.Process.OOMScoreAdj
if spec.Process != nil {
config.OomScoreAdj = spec.Process.OOMScoreAdj
}
if spec.Process.Capabilities != nil {
config.Capabilities = &configs.Capabilities{
@ -269,13 +269,17 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
func createLibcontainerMount(cwd string, m specs.Mount) *configs.Mount {
flags, pgflags, data, ext := parseMountOptions(m.Options)
source := m.Source
if m.Type == "bind" {
device := m.Type
if flags&unix.MS_BIND != 0 {
if device == "" {
device = "bind"
}
if !filepath.IsAbs(source) {
source = filepath.Join(cwd, m.Source)
}
}
return &configs.Mount{
Device: m.Type,
Device: device,
Source: source,
Destination: m.Destination,
Data: data,
@ -732,7 +736,7 @@ func parseMountOptions(options []string) (int, []int, string, int) {
return flag, pgflag, strings.Join(data, ","), extFlags
}
func setupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
if config == nil {
return nil, nil
}

View file

@ -123,7 +123,7 @@ func TestSetupSeccomp(t *testing.T) {
},
},
}
seccomp, err := setupSeccomp(conf)
seccomp, err := SetupSeccomp(conf)
if err != nil {
t.Errorf("Couldn't create Seccomp config: %v", err)

View file

@ -19,7 +19,7 @@ func TestCaptureTestFunc(t *testing.T) {
// the first frame is the caller
frame := stack.Frames[0]
if expected := "captureFunc"; frame.Function != expected {
t.Fatalf("expected function %q but recevied %q", expected, frame.Function)
t.Fatalf("expected function %q but received %q", expected, frame.Function)
}
expected := "/runc/libcontainer/stacktrace"
if !strings.HasSuffix(frame.Package, expected) {

View file

@ -14,6 +14,7 @@ import (
"github.com/opencontainers/runc/libcontainer/seccomp"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@ -49,11 +50,11 @@ func (l *linuxStandardInit) Init() error {
// Do not inherit the parent's session keyring.
sessKeyId, err := keys.JoinSessionKeyring(ringname)
if err != nil {
return err
return errors.Wrap(err, "join session keyring")
}
// Make session keyring searcheable.
if err := keys.ModKeyringPerm(sessKeyId, keepperms, newperms); err != nil {
return err
return errors.Wrap(err, "mod keyring permissions")
}
}
@ -76,7 +77,7 @@ func (l *linuxStandardInit) Init() error {
return err
}
if err := system.Setctty(); err != nil {
return err
return errors.Wrap(err, "setctty")
}
}
@ -89,45 +90,45 @@ func (l *linuxStandardInit) Init() error {
if hostname := l.config.Config.Hostname; hostname != "" {
if err := unix.Sethostname([]byte(hostname)); err != nil {
return err
return errors.Wrap(err, "sethostname")
}
}
if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil {
return err
return errors.Wrap(err, "apply apparmor profile")
}
if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil {
return err
return errors.Wrap(err, "set process label")
}
for key, value := range l.config.Config.Sysctl {
if err := writeSystemProperty(key, value); err != nil {
return err
return errors.Wrapf(err, "write sysctl key %s", key)
}
}
for _, path := range l.config.Config.ReadonlyPaths {
if err := readonlyPath(path); err != nil {
return err
return errors.Wrapf(err, "readonly path %s", path)
}
}
for _, path := range l.config.Config.MaskPaths {
if err := maskPath(path, l.config.Config.MountLabel); err != nil {
return err
return errors.Wrapf(err, "mask path %s", path)
}
}
pdeath, err := system.GetParentDeathSignal()
if err != nil {
return err
return errors.Wrap(err, "get pdeath signal")
}
if l.config.NoNewPrivileges {
if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
return err
return errors.Wrap(err, "set nonewprivileges")
}
}
// Tell our parent that we're ready to Execv. This must be done before the
// Seccomp rules have been applied, because we need to be able to read and
// write to a socket.
if err := syncParentReady(l.pipe); err != nil {
return err
return errors.Wrap(err, "sync ready")
}
// Without NoNewPrivileges seccomp is a privileged operation, so we need to
// do this before dropping capabilities; otherwise do it as late as possible
@ -143,7 +144,7 @@ func (l *linuxStandardInit) Init() error {
// finalizeNamespace can change user/group which clears the parent death
// signal, so we restore it here.
if err := pdeath.Restore(); err != nil {
return err
return errors.Wrap(err, "restore pdeath signal")
}
// Compare the parent from the initial start of the init process and make
// sure that it did not change. if the parent changes that means it died

View file

@ -3,13 +3,12 @@
package system
import (
"bufio"
"fmt"
"os"
"os/exec"
"syscall" // only for exec
"unsafe"
"github.com/opencontainers/runc/libcontainer/user"
"golang.org/x/sys/unix"
)
@ -102,34 +101,43 @@ func Setctty() error {
}
// RunningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
// Originally copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
file, err := os.Open("/proc/self/uid_map")
uidmap, err := user.CurrentProcessUIDMap()
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return false
}
defer file.Close()
return UIDMapInUserNS(uidmap)
}
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return false
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
func UIDMapInUserNS(uidmap []user.IDMap) bool {
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
if len(uidmap) == 1 && uidmap[0].ID == 0 && uidmap[0].ParentID == 0 && uidmap[0].Count == 4294967295 {
return false
}
return true
}
// GetParentNSeuid returns the euid within the parent user namespace
func GetParentNSeuid() int64 {
euid := int64(os.Geteuid())
uidmap, err := user.CurrentProcessUIDMap()
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return euid
}
for _, um := range uidmap {
if um.ID <= euid && euid <= um.ID+um.Count-1 {
return um.ParentID + euid - um.ID
}
}
return euid
}
// SetSubreaper sets the value i as the subreaper setting for the calling process
func SetSubreaper(i int) error {
return unix.Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)

View file

@ -0,0 +1,45 @@
// +build linux
package system
import (
"strings"
"testing"
"github.com/opencontainers/runc/libcontainer/user"
)
func TestUIDMapInUserNS(t *testing.T) {
cases := []struct {
s string
expected bool
}{
{
s: " 0 0 4294967295\n",
expected: false,
},
{
s: " 0 0 1\n",
expected: true,
},
{
s: " 0 1001 1\n 1 231072 65536\n",
expected: true,
},
{
// file exist but empty (the initial state when userns is created. see man 7 user_namespaces)
s: "",
expected: true,
},
}
for _, c := range cases {
uidmap, err := user.ParseIDMap(strings.NewReader(c.s))
if err != nil {
t.Fatal(err)
}
actual := UIDMapInUserNS(uidmap)
if c.expected != actual {
t.Fatalf("expected %v, got %v for %q", c.expected, actual, c.s)
}
}
}

View file

@ -2,8 +2,26 @@
package system
import (
"os"
"github.com/opencontainers/runc/libcontainer/user"
)
// RunningInUserNS is a stub for non-Linux systems
// Always returns false
func RunningInUserNS() bool {
return false
}
// UIDMapInUserNS is a stub for non-Linux systems
// Always returns false
func UIDMapInUserNS(uidmap []user.IDMap) bool {
return false
}
// GetParentNSeuid returns the euid within the parent user namespace
// Always returns os.Geteuid on non-linux
func GetParentNSeuid() int {
return os.Geteuid()
}

View file

@ -114,3 +114,29 @@ func CurrentUser() (User, error) {
func CurrentGroup() (Group, error) {
return LookupGid(unix.Getgid())
}
func CurrentUserSubUIDs() ([]SubID, error) {
u, err := CurrentUser()
if err != nil {
return nil, err
}
return ParseSubIDFileFilter("/etc/subuid",
func(entry SubID) bool { return entry.Name == u.Name })
}
func CurrentGroupSubGIDs() ([]SubID, error) {
g, err := CurrentGroup()
if err != nil {
return nil, err
}
return ParseSubIDFileFilter("/etc/subgid",
func(entry SubID) bool { return entry.Name == g.Name })
}
func CurrentProcessUIDMap() ([]IDMap, error) {
return ParseIDMapFile("/proc/self/uid_map")
}
func CurrentProcessGIDMap() ([]IDMap, error) {
return ParseIDMapFile("/proc/self/gid_map")
}

View file

@ -75,12 +75,29 @@ func groupFromOS(g *user.Group) (Group, error) {
return newGroup, nil
}
// SubID represents an entry in /etc/sub{u,g}id
type SubID struct {
Name string
SubID int64
Count int64
}
// IDMap represents an entry in /proc/PID/{u,g}id_map
type IDMap struct {
ID int64
ParentID int64
Count int64
}
func parseLine(line string, v ...interface{}) {
if line == "" {
parseParts(strings.Split(line, ":"), v...)
}
func parseParts(parts []string, v ...interface{}) {
if len(parts) == 0 {
return
}
parts := strings.Split(line, ":")
for i, p := range parts {
// Ignore cases where we don't have enough fields to populate the arguments.
// Some configuration files like to misbehave.
@ -96,6 +113,8 @@ func parseLine(line string, v ...interface{}) {
case *int:
// "numbers", with conversion errors ignored because of some misbehaving configuration files.
*e, _ = strconv.Atoi(p)
case *int64:
*e, _ = strconv.ParseInt(p, 10, 64)
case *[]string:
// Comma-separated lists.
if p != "" {
@ -105,7 +124,7 @@ func parseLine(line string, v ...interface{}) {
}
default:
// Someone goof'd when writing code using this function. Scream so they can hear us.
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *int64, *[]string} as arguments! %#v is not a pointer!", e))
}
}
}
@ -479,3 +498,111 @@ func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int
}
return GetAdditionalGroups(additionalGroups, group)
}
func ParseSubIDFile(path string) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubID(subid)
}
func ParseSubID(subid io.Reader) ([]SubID, error) {
return ParseSubIDFilter(subid, nil)
}
func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubIDFilter(subid, filter)
}
func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
if r == nil {
return nil, fmt.Errorf("nil source for subid-formatted data")
}
var (
s = bufio.NewScanner(r)
out = []SubID{}
)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
// see: man 5 subuid
p := SubID{}
parseLine(line, &p.Name, &p.SubID, &p.Count)
if filter == nil || filter(p) {
out = append(out, p)
}
}
return out, nil
}
func ParseIDMapFile(path string) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMap(r)
}
func ParseIDMap(r io.Reader) ([]IDMap, error) {
return ParseIDMapFilter(r, nil)
}
func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMapFilter(r, filter)
}
func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
if r == nil {
return nil, fmt.Errorf("nil source for idmap-formatted data")
}
var (
s = bufio.NewScanner(r)
out = []IDMap{}
)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
// see: man 7 user_namespaces
p := IDMap{}
parseParts(strings.Fields(line), &p.ID, &p.ParentID, &p.Count)
if filter == nil || filter(p) {
out = append(out, p)
}
}
return out, nil
}

View file

@ -1,8 +1,6 @@
package utils
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"io"
"os"
@ -17,19 +15,6 @@ const (
exitSignalOffset = 128
)
// GenerateRandomName returns a new name joined with a prefix. This size
// specified is used to truncate the randomly generated value
func GenerateRandomName(prefix string, size int) (string, error) {
id := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, id); err != nil {
return "", err
}
if size > 64 {
size = 64
}
return prefix + hex.EncodeToString(id)[:size], nil
}
// ResolveRootfs ensures that the current working directory is
// not a symlink and returns the absolute path to the rootfs
func ResolveRootfs(uncleanRootfs string) (string, error) {

View file

@ -10,28 +10,6 @@ import (
"golang.org/x/sys/unix"
)
func TestGenerateName(t *testing.T) {
name, err := GenerateRandomName("veth", 5)
if err != nil {
t.Fatal(err)
}
expected := 5 + len("veth")
if len(name) != expected {
t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
}
name, err = GenerateRandomName("veth", 65)
if err != nil {
t.Fatal(err)
}
expected = 64 + len("veth")
if len(name) != expected {
t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
}
}
var labelTest = []struct {
labels []string
query string
@ -151,4 +129,14 @@ func TestCleanPath(t *testing.T) {
if path != "/var" {
t.Errorf("expected to receive '/var' and received %s", path)
}
path = CleanPath("/foo/bar/")
if path != "/foo/bar" {
t.Errorf("expected to receive '/foo/bar' and received %s", path)
}
path = CleanPath("/foo/bar/../")
if path != "/foo" {
t.Errorf("expected to receive '/foo' and received %s", path)
}
}

View file

@ -63,7 +63,11 @@ func main() {
app.Version = strings.Join(v, "\n")
root := "/run/runc"
if os.Geteuid() != 0 {
rootless, err := isRootless(nil)
if err != nil {
fatal(err)
}
if rootless {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir != "" {
root = runtimeDir + "/runc"
@ -108,6 +112,11 @@ func main() {
Name: "systemd-cgroup",
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
},
cli.StringFlag{
Name: "rootless",
Value: "auto",
Usage: "enable rootless mode ('true', 'false', or 'auto')",
},
}
app.Commands = []cli.Command{
checkpointCommand,

11
vendor/github.com/opencontainers/runc/man/README.md generated vendored Normal file
View file

@ -0,0 +1,11 @@
runc man pages
====================
This directory contains man pages for runc in markdown format.
To generate man pages from it, use this command
./md2man-all.sh
You will see man pages generated under the man8 directory.

27
vendor/github.com/opencontainers/runc/man/md2man-all.sh generated vendored Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
set -e
# get into this script's directory
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
[ "$1" = '-q' ] || {
set -x
pwd
}
if ! ( which go-md2man &>/dev/null ); then
echo "To install man pages, please install 'go-md2man'."
exit 0
fi
for FILE in *.md; do
base="$(basename "$FILE")"
name="${base%.md}"
num="${name##*.}"
if [ -z "$num" -o "$name" = "$num" ]; then
# skip files that aren't of the format xxxx.N.md (like README.md)
continue
fi
mkdir -p "./man${num}"
go-md2man -in "$FILE" -out "./man${num}/${name}"
done

View file

@ -0,0 +1,25 @@
# NAME
runc checkpoint - checkpoint a running container
# SYNOPSIS
runc checkpoint [command options] <container-id>
Where "<container-id>" is the name for the instance of the container to be
checkpointed.
# DESCRIPTION
The checkpoint command saves the state of the container instance.
# OPTIONS
--image-path value path for saving criu image files
--work-path value path for saving work files and logs
--parent-path value path for previous criu image files in pre-dump
--leave-running leave the process running after checkpointing
--tcp-established allow open tcp connections
--ext-unix-sk allow external unix sockets
--shell-job allow shell jobs
--page-server value ADDRESS:PORT of the page server
--file-locks handle file locks, for safety
--pre-dump dump container's memory information only, leave the container running after this
--manage-cgroups-mode value cgroups mode: 'soft' (default), 'full' and 'strict'
--empty-ns value create a namespace, but don't restore its properties

View file

@ -0,0 +1,27 @@
# NAME
runc create - create a container
# SYNOPSIS
runc create [command options] <container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.
# DESCRIPTION
The create command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "config.json" and a root
filesystem.
The specification file includes an args parameter. The args parameter is used
to specify command(s) that get run when the container is started. To change the
command(s) that get executed on start, edit the args parameter of the spec. See
"runc spec --help" for more explanation.
# OPTIONS
--bundle value, -b value path to the root of the bundle directory, defaults to the current directory
--console-socket value path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal
--pid-file value specify the file to write the process id to
--no-pivot do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk
--no-new-keyring do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key
--preserve-fds value Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) (default: 0)

View file

@ -0,0 +1,17 @@
# NAME
runc delete - delete any resources held by the container often used with detached container
# SYNOPSIS
runc delete [command options] <container-id>
Where "<container-id>" is the name for the instance of the container.
# OPTIONS
--force, -f Forcibly deletes the container if it is still running (uses SIGKILL)
# EXAMPLE
For example, if the container id is "ubuntu01" and runc list currently shows the
status of "ubuntu01" as "stopped" the following will delete resources held for
"ubuntu01" removing "ubuntu01" from the runc list of containers:
# runc delete ubuntu01

View file

@ -0,0 +1,15 @@
# NAME
runc events - display container events such as OOM notifications, cpu, memory, and IO usage statistics
# SYNOPSIS
runc events [command options] <container-id>
Where "<container-id>" is the name for the instance of the container.
# DESCRIPTION
The events command displays information about the container. By default the
information is displayed once every 5 seconds.
# OPTIONS
--interval value set the stats collection interval (default: 5s)
--stats display the container's stats then exit

View file

@ -0,0 +1,30 @@
# NAME
runc exec - execute new process inside the container
# SYNOPSIS
runc exec [command options] <container-id> -- <container command> [args...]
Where "<container-id>" is the name for the instance of the container and
"<container command>" is the command to be executed in the container.
# EXAMPLE
For example, if the container is configured to run the linux ps command the
following will output a list of processes running in the container:
# runc exec <container-id> ps
# OPTIONS
--console value specify the pty slave path for use with the container
--cwd value current working directory in the container
--env value, -e value set environment variables
--tty, -t allocate a pseudo-TTY
--user value, -u value UID (format: <uid>[:<gid>])
--additional-gids value, -g value additional gids
--process value, -p value path to the process.json
--detach, -d detach from the container's process
--pid-file value specify the file to write the process id to
--process-label value set the asm process label for the process commonly used with selinux
--apparmor value set the apparmor profile for the process
--no-new-privs set the no new privileges value for the process
--cap value, -c value add a capability to the bounding set for the process
--no-subreaper disable the use of the subreaper used to reap reparented processes

View file

@ -0,0 +1,18 @@
# NAME
runc kill - kill sends the specified signal (default: SIGTERM) to the container's init process
# SYNOPSIS
runc kill [command options] <container-id> <signal>
Where "<container-id>" is the name for the instance of the container and
"<signal>" is the signal to be sent to the init process.
# OPTIONS
--all, -a send the specified signal to all processes inside the container
# EXAMPLE
For example, if the container id is "ubuntu01" the following will send a "KILL"
signal to the init process of the "ubuntu01" container:
# runc kill ubuntu01 KILL

View file

@ -0,0 +1,19 @@
# NAME
runc list - lists containers started by runc with the given root
# SYNOPSIS
runc list [command options]
# EXAMPLE
Where the given root is specified via the global option "--root"
(default: "/run/runc").
To list containers created via the default "--root":
# runc list
To list containers created using a non-default value for "--root":
# runc --root value list
# OPTIONS
--format value, -f value select one of: table or json (default: "table")
--quiet, -q display only container IDs

View file

@ -0,0 +1,12 @@
# NAME
runc pause - pause suspends all processes inside the container
# SYNOPSIS
runc pause <container-id>
Where "<container-id>" is the name for the instance of the container to be
paused.
# DESCRIPTION
The pause command suspends all processes in the instance of the container.
Use runc list to identiy instances of containers and their current status.

13
vendor/github.com/opencontainers/runc/man/runc-ps.8.md generated vendored Normal file
View file

@ -0,0 +1,13 @@
# NAME
runc ps - ps displays the processes running inside a container
# SYNOPSIS
runc ps [command options] <container-id> [ps options]
# OPTIONS
--format value, -f value select one of: table(default) or json
The default format is table. The following will output the processes of a container
in json format:
# runc ps -f json <container-id>

View file

@ -0,0 +1,26 @@
# NAME
runc restore - restore a container from a previous checkpoint
# SYNOPSIS
runc restore [command options] <container-id>
Where "<container-id>" is the name for the instance of the container to be
restored.
# DESCRIPTION
Restores the saved state of the container instance that was previously saved
using the runc checkpoint command.
# OPTIONS
--image-path value path to criu image files for restoring
--work-path value path for saving work files and logs
--tcp-established allow open tcp connections
--ext-unix-sk allow external unix sockets
--shell-job allow shell jobs
--file-locks handle file locks, for safety
--manage-cgroups-mode value cgroups mode: 'soft' (default), 'full' and 'strict'
--bundle value, -b value path to the root of the bundle directory
--detach, -d detach from the container's process
--pid-file value specify the file to write the process id to
--no-subreaper disable the use of the subreaper used to reap reparented processes
--no-pivot do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk

View file

@ -0,0 +1,12 @@
# NAME
runc resume - resumes all processes that have been previously paused
# SYNOPSIS
runc resume <container-id>
Where "<container-id>" is the name for the instance of the container to be
resumed.
# DESCRIPTION
The resume command resumes all processes in the instance of the container.
Use runc list to identiy instances of containers and their current status.

View file

@ -0,0 +1,29 @@
# NAME
runc run - create and run a container
# SYNOPSIS
runc run [command options] <container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.
# DESCRIPTION
The run command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "config.json" and a root
filesystem.
The specification file includes an args parameter. The args parameter is used
to specify command(s) that get run when the container is started. To change the
command(s) that get executed on start, edit the args parameter of the spec. See
"runc spec --help" for more explanation.
# OPTIONS
--bundle value, -b value path to the root of the bundle directory, defaults to the current directory
--console-socket value path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal
--detach, -d detach from the container's process
--pid-file value specify the file to write the process id to
--no-subreaper disable the use of the subreaper used to reap reparented processes
--no-pivot do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk
--no-new-keyring do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key
--preserve-fds value Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) (default: 0)

View file

@ -0,0 +1,52 @@
# NAME
runc spec - create a new specification file
# SYNOPSIS
runc spec [command options] [arguments...]
# DESCRIPTION
The spec command creates the new specification file named "config.json" for
the bundle.
The spec generated is just a starter file. Editing of the spec is required to
achieve desired results. For example, the newly generated spec includes an args
parameter that is initially set to call the "sh" command when the container is
started. Calling "sh" may work for an ubuntu container or busybox, but will not
work for containers that do not include the "sh" program.
# EXAMPLE
To run docker's hello-world container one needs to set the args parameter
in the spec to call hello. This can be done using the sed command or a text
editor. The following commands create a bundle for hello-world, change the
default args parameter in the spec from "sh" to "/hello", then run the hello
command in a new hello-world container named container1:
mkdir hello
cd hello
docker pull hello-world
docker export $(docker create hello-world) > hello-world.tar
mkdir rootfs
tar -C rootfs -xf hello-world.tar
runc spec
sed -i 's;"sh";"/hello";' config.json
runc start container1
In the start command above, "container1" is the name for the instance of the
container that you are starting. The name you provide for the container instance
must be unique on your host.
An alternative for generating a customized spec config is to use "oci-runtime-tool", the
sub-command "oci-runtime-tool generate" has lots of options that can be used to do any
customizations as you want, see [runtime-tools](https://github.com/opencontainers/runtime-tools)
to get more information.
When starting a container through runc, runc needs root privilege. If not
already running as root, you can use sudo to give runc root privilege. For
example: "sudo runc start container1" will give runc root privilege to start the
container on your host.
Alternatively, you can start a rootless container, which has the ability to run without root privileges. For this to work, the specification file needs to be adjusted accordingly. You can pass the parameter --rootless to this command to generate a proper rootless spec file.
# OPTIONS
--bundle value, -b value path to the root of the bundle directory
--rootless generate a configuration for a rootless container

View file

@ -0,0 +1,12 @@
# NAME
runc start - start executes the user defined process in a created container
# SYNOPSIS
runc start <container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.
# DESCRIPTION
The start command executes the user defined process in a created container.

View file

@ -0,0 +1,11 @@
# NAME
runc state - output the state of a container
# SYNOPSIS
runc state <container-id>
Where "<container-id>" is your name for the instance of the container.
# DESCRIPTION
The state command outputs current state information for the
instance of a container.

View file

@ -0,0 +1,51 @@
# NAME
runc update - update container resource constraints
# SYNOPSIS
runc update [command options] <container-id>
# DESCRIPTION
The data can be read from a file or the standard input, the
accepted format is as follow (unchanged values can be omitted):
{
"memory": {
"limit": 0,
"reservation": 0,
"swap": 0,
"kernel": 0,
"kernelTCP": 0
},
"cpu": {
"shares": 0,
"quota": 0,
"period": 0,
"realtimeRuntime": 0,
"realtimePeriod": 0,
"cpus": "",
"mems": ""
},
"blockIO": {
"blkioWeight": 0
}
}
Note: if data is to be read from a file or the standard input, all
other options are ignored.
# OPTIONS
--resources value, -r value path to the file containing the resources to update or '-' to read from the standard input
--blkio-weight value Specifies per cgroup weight, range is from 10 to 1000 (default: 0)
--cpu-period value CPU CFS period to be used for hardcapping (in usecs). 0 to use system default
--cpu-quota value CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period
--cpu-rt-period value CPU realtime period to be used for hardcapping (in usecs). 0 to use system default
--cpu-rt-runtime value CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period
--cpu-share value CPU shares (relative weight vs. other containers)
--cpuset-cpus value CPU(s) to use
--cpuset-mems value Memory node(s) to use
--kernel-memory value Kernel memory limit (in bytes)
--kernel-memory-tcp value Kernel memory limit (in bytes) for tcp buffer
--memory value Memory limit (in bytes)
--memory-reservation value Memory reservation or soft_limit (in bytes)
--memory-swap value Total memory usage (memory + swap); set '-1' to enable unlimited swap
--pids-limit value Maximum number of pids allowed in the container (default: 0)

59
vendor/github.com/opencontainers/runc/man/runc.8.md generated vendored Normal file
View file

@ -0,0 +1,59 @@
# NAME
runc - Open Container Initiative runtime
# SYNOPSIS
runc [global options] command [command options] [arguments...]
# DESCRIPTION
runc is a command line client for running applications packaged according to
the Open Container Initiative (OCI) format and is a compliant implementation of the
Open Container Initiative specification.
runc integrates well with existing process supervisors to provide a production
container runtime environment for applications. It can be used with your
existing process monitoring tools and the container will be spawned as a
direct child of the process supervisor.
Containers are configured using bundles. A bundle for a container is a directory
that includes a specification file named "config.json" and a root filesystem.
The root filesystem contains the contents of the container.
To start a new instance of a container:
# runc start [ -b bundle ] <container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host. Providing the bundle directory using "-b" is optional. The default
value for "bundle" is the current directory.
# COMMANDS
checkpoint checkpoint a running container
create create a container
delete delete any resources held by the container often used with detached containers
events display container events such as OOM notifications, cpu, memory, IO and network stats
exec execute new process inside the container
init initialize the namespaces and launch the process (do not call it outside of runc)
kill kill sends the specified signal (default: SIGTERM) to the container's init process
list lists containers started by runc with the given root
pause pause suspends all processes inside the container
ps displays the processes running inside a container
restore restore a container from a previous checkpoint
resume resumes all processes that have been previously paused
run create and run a container
spec create a new specification file
start executes the user defined process in a created container
state output the state of a container
update update container resource constraints
help, h Shows a list of commands or help for one command
# GLOBAL OPTIONS
--debug enable debug output for logging
--log value set the log file path where internal debug information is written (default: "/dev/null")
--log-format value set the format used by logs ('text' (default), or 'json') (default: "text")
--root value root directory for storage of container state (this should be located in tmpfs) (default: "/run/runc" or $XDG_RUNTIME_DIR/runc for rootless containers)
--criu value path to the criu binary used for checkpoint and restore (default: "criu")
--systemd-cgroup enable systemd cgroup support, expects cgroupsPath to be of form "slice:prefix:name" for e.g. "system.slice:runc:434234"
--rootless value enable rootless mode ('true', 'false', or 'auto') (default: "auto")
--help, -h show help
--version, -v print the version

View file

@ -44,7 +44,7 @@ func (ns *notifySocket) Close() error {
// If systemd is supporting sd_notify protocol, this function will add support
// for sd_notify protocol from within the container.
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
mount := specs.Mount{Destination: s.host, Type: "bind", Source: s.socketPath, Options: []string{"bind"}}
mount := specs.Mount{Destination: s.host, Source: s.socketPath, Options: []string{"bind"}}
spec.Mounts = append(spec.Mounts, mount)
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
}

View file

@ -29,7 +29,11 @@ var psCommand = cli.Command{
return err
}
// XXX: Currently not supported with rootless containers.
if isRootless() {
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc ps requires root")
}

View file

@ -96,7 +96,11 @@ using the runc checkpoint command.`,
return err
}
// XXX: Currently this is untested with rootless containers.
if isRootless() {
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc restore requires root")
}
@ -105,6 +109,9 @@ using the runc checkpoint command.`,
return err
}
options := criuOptions(context)
if err := setEmptyNsMask(context, options); err != nil {
return err
}
status, err := startContainer(context, spec, CT_ACT_RESTORE, options)
if err != nil {
return err

33
vendor/github.com/opencontainers/runc/script/.validate generated vendored Normal file
View file

@ -0,0 +1,33 @@
#!/bin/bash
if [ -z "$VALIDATE_UPSTREAM" ]; then
# this is kind of an expensive check, so let's not do this twice if we
# are running more than one validate bundlescript
VALIDATE_REPO='https://github.com/opencontainers/runc.git'
VALIDATE_BRANCH='master'
if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then
VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git"
VALIDATE_BRANCH="${TRAVIS_BRANCH}"
fi
VALIDATE_HEAD="$(git rev-parse --verify HEAD)"
git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH"
VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)"
VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD"
VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD"
validate_diff() {
if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then
git diff "$VALIDATE_COMMIT_DIFF" "$@"
fi
}
validate_log() {
if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then
git log "$VALIDATE_COMMIT_LOG" "$@"
fi
}
fi

247
vendor/github.com/opencontainers/runc/script/check-config.sh generated vendored Executable file
View file

@ -0,0 +1,247 @@
#!/usr/bin/env bash
set -e
# bits of this were adapted from check_config.sh in docker
# see also https://github.com/docker/docker/blob/master/contrib/check-config.sh
possibleConfigs=(
'/proc/config.gz'
"/boot/config-$(uname -r)"
"/usr/src/linux-$(uname -r)/.config"
'/usr/src/linux/.config'
)
possibleConfigFiles=(
'config.gz'
"config-$(uname -r)"
'.config'
)
if ! command -v zgrep &>/dev/null; then
zgrep() {
zcat "$2" | grep "$1"
}
fi
kernelVersion="$(uname -r)"
kernelMajor="${kernelVersion%%.*}"
kernelMinor="${kernelVersion#$kernelMajor.}"
kernelMinor="${kernelMinor%%.*}"
is_set() {
zgrep "CONFIG_$1=[y|m]" "$CONFIG" >/dev/null
}
is_set_in_kernel() {
zgrep "CONFIG_$1=y" "$CONFIG" >/dev/null
}
is_set_as_module() {
zgrep "CONFIG_$1=m" "$CONFIG" >/dev/null
}
color() {
local codes=()
if [ "$1" = 'bold' ]; then
codes=("${codes[@]}" '1')
shift
fi
if [ "$#" -gt 0 ]; then
local code
case "$1" in
# see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
black) code=30 ;;
red) code=31 ;;
green) code=32 ;;
yellow) code=33 ;;
blue) code=34 ;;
magenta) code=35 ;;
cyan) code=36 ;;
white) code=37 ;;
esac
if [ "$code" ]; then
codes=("${codes[@]}" "$code")
fi
fi
local IFS=';'
echo -en '\033['"${codes[*]}"'m'
}
wrap_color() {
text="$1"
shift
color "$@"
echo -n "$text"
color reset
echo
}
wrap_good() {
echo "$(wrap_color "$1" white): $(wrap_color "$2" green)"
}
wrap_bad() {
echo "$(wrap_color "$1" bold): $(wrap_color "$2" bold red)"
}
wrap_warning() {
wrap_color >&2 "$*" red
}
check_flag() {
if is_set_in_kernel "$1"; then
wrap_good "CONFIG_$1" 'enabled'
elif is_set_as_module "$1"; then
wrap_good "CONFIG_$1" 'enabled (as module)'
else
wrap_bad "CONFIG_$1" 'missing'
fi
}
check_flags() {
for flag in "$@"; do
echo "- $(check_flag "$flag")"
done
}
check_distro_userns() {
source /etc/os-release 2>/dev/null || /bin/true
if [[ "${ID}" =~ ^(centos|rhel)$ && "${VERSION_ID}" =~ ^7 ]]; then
# this is a CentOS7 or RHEL7 system
grep -q "user_namespace.enable=1" /proc/cmdline || {
# no user namespace support enabled
wrap_bad " (RHEL7/CentOS7" "User namespaces disabled; add 'user_namespace.enable=1' to boot command line)"
}
fi
}
is_config() {
local config="$1"
# Todo: more check
[[ -f "$config" ]] && return 0
return 1
}
search_config() {
local target_dir="$1"
[[ "$target_dir" ]] || target_dir=("${possibleConfigs[@]}")
local tryConfig
for tryConfig in "${target_dir[@]}"; do
is_config "$tryConfig" && {
CONFIG="$tryConfig"
return
}
[[ -d "$tryConfig" ]] && {
for tryFile in "${possibleConfigFiles[@]}"; do
is_config "$tryConfig/$tryFile" && {
CONFIG="$tryConfig/$tryFile"
return
}
done
}
done
wrap_warning "error: cannot find kernel config"
wrap_warning " try running this script again, specifying the kernel config:"
wrap_warning " CONFIG=/path/to/kernel/.config $0 or $0 /path/to/kernel/.config"
exit 1
}
CONFIG="$1"
is_config "$CONFIG" || {
if [[ ! "$CONFIG" ]]; then
wrap_color "info: no config specified, searching for kernel config ..." white
search_config
elif [[ -d "$CONFIG" ]]; then
wrap_color "info: input is a directory, searching for kernel config in this directory..." white
search_config "$CONFIG"
else
wrap_warning "warning: $CONFIG seems not a kernel config, searching other paths for kernel config ..."
search_config
fi
}
wrap_color "info: reading kernel config from $CONFIG ..." white
echo
echo 'Generally Necessary:'
echo -n '- '
cgroupSubsystemDir="$(awk '/[, ](cpu|cpuacct|cpuset|devices|freezer|memory)[, ]/ && $3 == "cgroup" { print $2 }' /proc/mounts | head -n1)"
cgroupDir="$(dirname "$cgroupSubsystemDir")"
if [ -d "$cgroupDir/cpu" -o -d "$cgroupDir/cpuacct" -o -d "$cgroupDir/cpuset" -o -d "$cgroupDir/devices" -o -d "$cgroupDir/freezer" -o -d "$cgroupDir/memory" ]; then
echo "$(wrap_good 'cgroup hierarchy' 'properly mounted') [$cgroupDir]"
else
if [ "$cgroupSubsystemDir" ]; then
echo "$(wrap_bad 'cgroup hierarchy' 'single mountpoint!') [$cgroupSubsystemDir]"
else
echo "$(wrap_bad 'cgroup hierarchy' 'nonexistent??')"
fi
echo " $(wrap_color '(see https://github.com/tianon/cgroupfs-mount)' yellow)"
fi
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then
echo -n '- '
if command -v apparmor_parser &>/dev/null; then
echo "$(wrap_good 'apparmor' 'enabled and tools installed')"
else
echo "$(wrap_bad 'apparmor' 'enabled, but apparmor_parser missing')"
echo -n ' '
if command -v apt-get &>/dev/null; then
echo "$(wrap_color '(use "apt-get install apparmor" to fix this)')"
elif command -v yum &>/dev/null; then
echo "$(wrap_color '(your best bet is "yum install apparmor-parser")')"
else
echo "$(wrap_color '(look for an "apparmor" package for your distribution)')"
fi
fi
fi
flags=(
NAMESPACES {NET,PID,IPC,UTS}_NS
CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED CPUSETS MEMCG
KEYS
MACVLAN VETH BRIDGE BRIDGE_NETFILTER
NF_NAT_IPV4 IP_NF_FILTER IP_NF_TARGET_MASQUERADE
NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
NF_NAT NF_NAT_NEEDED
# required for bind-mounting /dev/mqueue into containers
POSIX_MQUEUE
)
check_flags "${flags[@]}"
echo
echo 'Optional Features:'
{
check_flags USER_NS
check_distro_userns
check_flags SECCOMP
check_flags CGROUP_PIDS
check_flags MEMCG_SWAP MEMCG_SWAP_ENABLED
if is_set MEMCG_SWAP && ! is_set MEMCG_SWAP_ENABLED; then
echo " $(wrap_color '(note that cgroup swap accounting is not enabled in your kernel config, you can enable it by setting boot option "swapaccount=1")' bold black)"
fi
}
if [ "$kernelMajor" -lt 4 ] || [ "$kernelMajor" -eq 4 -a "$kernelMinor" -le 5 ]; then
check_flags MEMCG_KMEM
fi
if [ "$kernelMajor" -lt 3 ] || [ "$kernelMajor" -eq 3 -a "$kernelMinor" -le 18 ]; then
check_flags RESOURCE_COUNTERS
fi
if [ "$kernelMajor" -lt 3 ] || [ "$kernelMajor" -eq 3 -a "$kernelMinor" -le 13 ]; then
netprio=NETPRIO_CGROUP
else
netprio=CGROUP_NET_PRIO
fi
flags=(
BLK_CGROUP BLK_DEV_THROTTLING IOSCHED_CFQ CFQ_GROUP_IOSCHED
CGROUP_PERF
CGROUP_HUGETLB
NET_CLS_CGROUP $netprio
CFS_BANDWIDTH FAIR_GROUP_SCHED RT_GROUP_SCHED
)
check_flags "${flags[@]}"

130
vendor/github.com/opencontainers/runc/script/release.sh generated vendored Executable file
View file

@ -0,0 +1,130 @@
#!/bin/bash
# Copyright (C) 2017 SUSE LLC.
#
# 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
#
# http://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.
set -e
## --->
# Project-specific options and functions. In *theory* you shouldn't need to
# touch anything else in this script in order to use this elsewhere.
project="runc"
root="$(readlink -f "$(dirname "${BASH_SOURCE}")/..")"
# This function takes an output path as an argument, where the built
# (preferably static) binary should be placed.
function build_project() {
builddir="$(dirname "$1")"
# Build with all tags enabled.
make -C "$root" COMMIT_NO= BUILDTAGS="seccomp selinux apparmor" static
mv "$root/$project" "$1"
}
# End of the easy-to-configure portion.
## <---
# Print usage information.
function usage() {
echo "usage: release.sh [-S <gpg-key-id>] [-c <commit-ish>] [-r <release-dir>] [-v <version>]" >&2
exit 1
}
# Log something to stderr.
function log() {
echo "[*] $*" >&2
}
# Log something to stderr and then exit with 0.
function bail() {
log "$@"
exit 0
}
# Conduct a sanity-check to make sure that GPG provided with the given
# arguments can sign something. Inability to sign things is not a fatal error.
function gpg_cansign() {
gpg "$@" --clear-sign </dev/null >/dev/null
}
# When creating releases we need to build static binaries, an archive of the
# current commit, and generate detached signatures for both.
keyid=""
commit="HEAD"
version=""
releasedir=""
hashcmd=""
while getopts "S:c:r:v:h:" opt; do
case "$opt" in
S)
keyid="$OPTARG"
;;
c)
commit="$OPTARG"
;;
r)
releasedir="$OPTARG"
;;
v)
version="$OPTARG"
;;
h)
hashcmd="$OPTARG"
;;
\:)
echo "Missing argument: -$OPTARG" >&2
usage
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
;;
esac
done
version="${version:-$(<"$root/VERSION")}"
releasedir="${releasedir:-release/$version}"
hashcmd="${hashcmd:-sha256sum}"
goarch="$(go env GOARCH || echo "amd64")"
log "creating $project release in '$releasedir'"
log " version: $version"
log " commit: $commit"
log " key: ${keyid:-DEFAULT}"
log " hash: $hashcmd"
# Make explicit what we're doing.
set -x
# Make the release directory.
rm -rf "$releasedir" && mkdir -p "$releasedir"
# Build project.
build_project "$releasedir/$project.$goarch"
# Generate new archive.
git archive --format=tar --prefix="$project-$version/" "$commit" | xz > "$releasedir/$project.tar.xz"
# Generate sha256 checksums for both.
( cd "$releasedir" ; "$hashcmd" "$project".{"$goarch",tar.xz} > "$project.$hashcmd" ; )
# Set up the gpgflags.
[[ "$keyid" ]] && export gpgflags="--default-key $keyid"
gpg_cansign $gpgflags || bail "Could not find suitable GPG key, skipping signing step."
# Sign everything.
gpg $gpgflags --detach-sign --armor "$releasedir/$project.$goarch"
gpg $gpgflags --detach-sign --armor "$releasedir/$project.tar.xz"
gpg $gpgflags --clear-sign --armor \
--output "$releasedir/$project.$hashcmd"{.tmp,} && \
mv "$releasedir/$project.$hashcmd"{.tmp,}

4
vendor/github.com/opencontainers/runc/script/tmpmount generated vendored Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
mount -t tmpfs none /tmp
exec "$@"

42
vendor/github.com/opencontainers/runc/script/validate-c generated vendored Executable file
View file

@ -0,0 +1,42 @@
#!/bin/bash
source "$(dirname "$BASH_SOURCE")/.validate"
IFS=$'\n'
files=($(validate_diff --diff-filter=ACMR --name-only -- '*.c' | grep -v '^vendor/' || true))
unset IFS
# indent(1): "You must use the -T option to tell indent the name of all the typenames in your program that are defined by typedef."
INDENT="indent -linux -l120 -T size_t -T jmp_buf"
if [ -z "$(indent --version 2>&1 | grep GNU)" ]; then
echo "Skipping C indentation checks, as GNU indent is not installed."
exit 0
fi
badFiles=()
for f in "${files[@]}"; do
orig=$(mktemp)
formatted=$(mktemp)
# we use "git show" here to validate that what's committed is formatted
git show "$VALIDATE_HEAD:$f" > ${orig}
${INDENT} ${orig} -o ${formatted}
if [ "$(diff -u ${orig} ${formatted})" ]; then
badFiles+=("$f")
fi
rm -f ${orig} ${formatted}
done
if [ ${#badFiles[@]} -eq 0 ]; then
echo 'Congratulations! All C source files are properly formatted.'
else
{
echo "These files are not properly formatted:"
for f in "${badFiles[@]}"; do
echo " - $f"
done
echo
echo "Please reformat the above files using \"${INDENT}\" and commit the result."
echo
} >&2
false
fi

30
vendor/github.com/opencontainers/runc/script/validate-gofmt generated vendored Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
source "$(dirname "$BASH_SOURCE")/.validate"
IFS=$'\n'
files=($(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true))
unset IFS
badFiles=()
for f in "${files[@]}"; do
# we use "git show" here to validate that what's committed is formatted
if [ "$(git show "$VALIDATE_HEAD:$f" | gofmt -s -l)" ]; then
badFiles+=("$f")
fi
done
if [ ${#badFiles[@]} -eq 0 ]; then
echo 'Congratulations! All Go source files are properly formatted.'
else
{
echo "These files are not properly gofmt'd:"
for f in "${badFiles[@]}"; do
echo " - $f"
done
echo
echo 'Please reformat the above files using "gofmt -s -w" and commit the result.'
echo
} >&2
false
fi

View file

@ -37,7 +37,6 @@ var signalMap = map[string]syscall.Signal{
"TSTP": unix.SIGTSTP,
"TTIN": unix.SIGTTIN,
"TTOU": unix.SIGTTOU,
"UNUSED": unix.SIGUNUSED,
"URG": unix.SIGURG,
"USR1": unix.SIGUSR1,
"USR2": unix.SIGUSR2,

View file

@ -29,7 +29,7 @@ func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) *signalH
}
}
// ensure that we have a large buffer size so that we do not miss any signals
// incase we are not processing them fast enough.
// in case we are not processing them fast enough.
s := make(chan os.Signal, signalBufferSize)
// handle all signals for the process.
signal.Notify(s)

View file

@ -0,0 +1,83 @@
# runc Integration Tests
Integration tests provide end-to-end testing of runc.
Note that integration tests do **not** replace unit tests.
As a rule of thumb, code should be tested thoroughly with unit tests.
Integration tests on the other hand are meant to test a specific feature end
to end.
Integration tests are written in *bash* using the
[bats](https://github.com/sstephenson/bats) framework.
## Running integration tests
The easiest way to run integration tests is with Docker:
```
$ make integration
```
Alternatively, you can run integration tests directly on your host through make:
```
$ sudo make localintegration
```
Or you can just run them directly using bats
```
$ sudo bats tests/integration
```
To run a single test bucket:
```
$ make integration TESTPATH="/checkpoint.bats"
```
To run them on your host, you will need to setup a development environment plus
[bats](https://github.com/sstephenson/bats#installing-bats-from-source)
For example:
```
$ cd ~/go/src/github.com
$ git clone https://github.com/sstephenson/bats.git
$ cd bats
$ ./install.sh /usr/local
```
> **Note**: There are known issues running the integration tests using
> **devicemapper** as a storage driver, make sure that your docker daemon
> is using **aufs** if you want to successfully run the integration tests.
## Writing integration tests
[helper functions]
(https://github.com/opencontainers/runc/blob/master/test/integration/helpers.bash)
are provided in order to facilitate writing tests.
```sh
#!/usr/bin/env bats
# This will load the helpers.
load helpers
# setup is called at the beginning of every test.
function setup() {
# see functions teardown_hello and setup_hello in helpers.bash, used to
# create a pristine environment for running your tests
teardown_hello
setup_hello
}
# teardown is called at the end of every test.
function teardown() {
teardown_hello
}
@test "this is a simple test" {
runc run containerid
# "The runc macro" automatically populates $status, $output and $lines.
# Please refer to bats documentation to find out more.
[ "$status" -eq 0 ]
# check expected output
[[ "${output}" == *"Hello"* ]]
}
```

View file

@ -0,0 +1,127 @@
#!/usr/bin/env bats
load helpers
function teardown() {
rm -f $BATS_TMPDIR/runc-cgroups-integration-test.json
teardown_running_container test_cgroups_kmem
teardown_running_container test_cgroups_permissions
teardown_busybox
}
function setup() {
teardown
setup_busybox
}
function check_cgroup_value() {
cgroup=$1
source=$2
expected=$3
current=$(cat $cgroup/$source)
echo $cgroup/$source
echo "current" $current "!?" "$expected"
[ "$current" -eq "$expected" ]
}
@test "runc update --kernel-memory (initialized)" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
requires cgroups_kmem
set_cgroups_path "$BUSYBOX_BUNDLE"
# Set some initial known values
DATA=$(cat <<-EOF
"memory": {
"kernel": 16777216
},
EOF
)
DATA=$(echo ${DATA} | sed 's/\n/\\n/g')
sed -i "s/\(\"resources\": {\)/\1\n${DATA}/" ${BUSYBOX_BUNDLE}/config.json
# run a detached busybox to work with
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_kmem
[ "$status" -eq 0 ]
# update kernel memory limit
runc update test_cgroups_kmem --kernel-memory 50331648
[ "$status" -eq 0 ]
# check the value
check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 50331648
}
@test "runc update --kernel-memory (uninitialized)" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
requires cgroups_kmem
set_cgroups_path "$BUSYBOX_BUNDLE"
# run a detached busybox to work with
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_kmem
[ "$status" -eq 0 ]
# update kernel memory limit
runc update test_cgroups_kmem --kernel-memory 50331648
# Since kernel 4.6, we can update kernel memory without initialization
# because it's accounted by default.
if [ "$KERNEL_MAJOR" -lt 4 ] || [ "$KERNEL_MAJOR" -eq 4 -a "$KERNEL_MINOR" -le 5 ]; then
[ ! "$status" -eq 0 ]
else
[ "$status" -eq 0 ]
check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 50331648
fi
}
@test "runc create (no limits + no cgrouppath + no permission) succeeds" {
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
[ "$status" -eq 0 ]
}
@test "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error" {
requires rootless
requires rootless_no_cgroup
set_cgroups_path "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
[ "$status" -eq 1 ]
[[ ${lines[1]} == *"permission denied"* ]]
}
@test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error" {
requires rootless
requires rootless_no_cgroup
set_resources_limit "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
[ "$status" -eq 1 ]
[[ ${lines[1]} == *"cannot set pids limit: container could not join or create cgroup"* ]]
}
@test "runc create (limits + cgrouppath + permission on the cgroup dir) succeeds" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
set_cgroups_path "$BUSYBOX_BUNDLE"
set_resources_limit "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
[ "$status" -eq 0 ]
}
@test "runc exec (limits + cgrouppath + permission on the cgroup dir) succeeds" {
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
set_cgroups_path "$BUSYBOX_BUNDLE"
set_resources_limit "$BUSYBOX_BUNDLE"
runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_permissions
[ "$status" -eq 0 ]
runc exec test_cgroups_permissions echo "cgroups_exec"
[ "$status" -eq 0 ]
[[ ${lines[0]} == *"cgroups_exec"* ]]
}

View file

@ -0,0 +1,297 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "checkpoint and restore" {
# XXX: currently criu require root containers.
requires criu root
# criu does not work with external terminals so..
# setting terminal and root:readonly: to false
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
for i in `seq 2`; do
# checkpoint the running container
runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
ret=$?
# if you are having problems getting criu to work uncomment the following dump:
#cat /run/opencontainer/containers/test_busybox/criu.work/dump.log
cat ./work-dir/dump.log | grep -B 5 Error || true
[ "$ret" -eq 0 ]
# after checkpoint busybox is no longer running
runc state test_busybox
[ "$status" -ne 0 ]
# restore from checkpoint
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
ret=$?
cat ./work-dir/restore.log | grep -B 5 Error || true
[ "$ret" -eq 0 ]
# busybox should be back up and running
testcontainer test_busybox running
done
}
@test "checkpoint --pre-dump and restore" {
# XXX: currently criu require root containers.
requires criu root
sed -i 's;"terminal": true;"terminal": false;' config.json
sed -i 's;"readonly": true;"readonly": false;' config.json
sed -i 's/"sh"/"sh","-c","for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"/' config.json
# The following code creates pipes for stdin and stdout.
# CRIU can't handle fifo-s, so we need all these tricks.
fifo=`mktemp -u /tmp/runc-fifo-XXXXXX`
mkfifo $fifo
# stdout
cat $fifo | cat $fifo &
pid=$!
exec 50</proc/$pid/fd/0
exec 51>/proc/$pid/fd/0
# stdin
cat $fifo | cat $fifo &
pid=$!
exec 60</proc/$pid/fd/0
exec 61>/proc/$pid/fd/0
echo -n > $fifo
unlink $fifo
# run busybox (not detached)
__runc run -d test_busybox <&60 >&51 2>&51
[ $? -eq 0 ]
testcontainer test_busybox running
#test checkpoint pre-dump
mkdir parent-dir
runc --criu "$CRIU" checkpoint --pre-dump --image-path ./parent-dir test_busybox
[ "$status" -eq 0 ]
# busybox should still be running
runc state test_busybox
[ "$status" -eq 0 ]
[[ "${output}" == *"running"* ]]
# checkpoint the running container
mkdir image-dir
mkdir work-dir
runc --criu "$CRIU" checkpoint --parent-path ./parent-dir --work-path ./work-dir --image-path ./image-dir test_busybox
cat ./work-dir/dump.log | grep -B 5 Error || true
[ "$status" -eq 0 ]
# after checkpoint busybox is no longer running
runc state test_busybox
[ "$status" -ne 0 ]
# restore from checkpoint
__runc --criu "$CRIU" restore -d --work-path ./work-dir --image-path ./image-dir test_busybox <&60 >&51 2>&51
ret=$?
cat ./work-dir/restore.log | grep -B 5 Error || true
[ $ret -eq 0 ]
# busybox should be back up and running
testcontainer test_busybox running
runc exec --cwd /bin test_busybox echo ok
[ "$status" -eq 0 ]
[[ ${output} == "ok" ]]
echo Ping >&61
exec 61>&-
exec 51>&-
run cat <&50
[ "$status" -eq 0 ]
[[ "${output}" == *"ponG Ping"* ]]
}
@test "checkpoint --lazy-pages and restore" {
# XXX: currently criu require root containers.
requires criu root
# check if lazy-pages is supported
run ${CRIU} check --feature uffd-noncoop
if [ "$status" -eq 1 ]; then
# this criu does not support lazy migration; skip the test
skip "this criu does not support lazy migration"
fi
sed -i 's;"terminal": true;"terminal": false;' config.json
sed -i 's;"readonly": true;"readonly": false;' config.json
sed -i 's/"sh"/"sh","-c","for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"/' config.json
# The following code creates pipes for stdin and stdout.
# CRIU can't handle fifo-s, so we need all these tricks.
fifo=`mktemp -u /tmp/runc-fifo-XXXXXX`
mkfifo $fifo
# For lazy migration we need to know when CRIU is ready to serve
# the memory pages via TCP.
lazy_pipe=`mktemp -u /tmp/lazy-pipe-XXXXXX`
mkfifo $lazy_pipe
# TCP port for lazy migration
port=27277
# stdout
cat $fifo | cat $fifo &
pid=$!
exec 50</proc/$pid/fd/0
exec 51>/proc/$pid/fd/0
# stdin
cat $fifo | cat $fifo &
pid=$!
exec 60</proc/$pid/fd/0
exec 61>/proc/$pid/fd/0
echo -n > $fifo
unlink $fifo
# run busybox
__runc run -d test_busybox <&60 >&51 2>&51
[ $? -eq 0 ]
testcontainer test_busybox running
# checkpoint the running container
mkdir image-dir
mkdir work-dir
# Double fork taken from helpers.bats
# We need to start 'runc checkpoint --lazy-pages' in the background,
# so we double fork in the shell.
(runc --criu "$CRIU" checkpoint --lazy-pages --page-server 0.0.0.0:${port} --status-fd ${lazy_pipe} --work-path ./work-dir --image-path ./image-dir test_busybox & ) &
# Sleeping here. This is ugly, but not sure how else to handle it.
# The return code of the in the background running runc is needed, if
# there is some basic error. If the lazy migration is ready can
# be handled by $lazy_pipe. Which probably will always be ready
# after sleeping two seconds.
sleep 2
# Check if inventory.img was written
[ -e image-dir/inventory.img ]
# If the inventory.img exists criu checkpointed some things, let's see
# if there were other errors in the log file.
run grep -B 5 Error ./work-dir/dump.log -q
[ "$status" -eq 1 ]
# This will block until CRIU is ready to serve memory pages
cat $lazy_pipe
[ "$status" -eq 1 ]
unlink $lazy_pipe
# Double fork taken from helpers.bats
# We need to start 'criu lazy-pages' in the background,
# so we double fork in the shell.
# Start CRIU in lazy-daemon mode
$(${CRIU} lazy-pages --page-server --address 127.0.0.1 --port ${port} -D image-dir &) &
# Restore lazily from checkpoint.
# The restored container needs a different name as the checkpointed
# container is not yet destroyed. It is only destroyed at that point
# in time when the last page is lazily transferred to the destination.
# Killing the CRIU on the checkpoint side will let the container
# continue to run if the migration failed at some point.
__runc --criu "$CRIU" restore -d --work-path ./image-dir --image-path ./image-dir --lazy-pages test_busybox_restore <&60 >&51 2>&51
ret=$?
[ $ret -eq 0 ]
run grep -B 5 Error ./work-dir/dump.log -q
[ "$status" -eq 1 ]
# busybox should be back up and running
testcontainer test_busybox_restore running
runc exec --cwd /bin test_busybox_restore echo ok
[ "$status" -eq 0 ]
[[ ${output} == "ok" ]]
echo Ping >&61
exec 61>&-
exec 51>&-
run cat <&50
[ "$status" -eq 0 ]
[[ "${output}" == *"ponG Ping"* ]]
}
@test "checkpoint and restore in external network namespace" {
# XXX: currently criu require root containers.
requires criu root
# check if external_net_ns is supported; only with criu 3.10++
run ${CRIU} check --feature external_net_ns
if [ "$status" -eq 1 ]; then
# this criu does not support external_net_ns; skip the test
skip "this criu does not support external network namespaces"
fi
# create a temporary name for the test network namespace
tmp=`mktemp`
rm -f $tmp
ns_name=`basename $tmp`
# create network namespace
ip netns add $ns_name
ns_path=`ip netns add $ns_name 2>&1 | sed -e 's/.*"\(.*\)".*/\1/'`
ns_inode=`ls -iL $ns_path | awk '{ print $1 }'`
# not necessary with criu 3.10 any more
sed -i 's;"readonly": true;"readonly": false;' config.json
# tell runc which network namespace to use
sed -i "s;\"type\": \"network\";\"type\": \"network\",\"path\": \"$ns_path\";" config.json
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
for i in `seq 2`; do
# checkpoint the running container; this automatically tells CRIU to
# handle the network namespace defined in config.json as an external
runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
ret=$?
# if you are having problems getting criu to work uncomment the following dump:
#cat /run/opencontainer/containers/test_busybox/criu.work/dump.log
cat ./work-dir/dump.log | grep -B 5 Error || true
[ "$ret" -eq 0 ]
# after checkpoint busybox is no longer running
runc state test_busybox
[ "$status" -ne 0 ]
# restore from checkpoint; this should restore the container into the existing network namespace
runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
ret=$?
cat ./work-dir/restore.log | grep -B 5 Error || true
[ "$ret" -eq 0 ]
# busybox should be back up and running
testcontainer test_busybox running
# container should be running in same network namespace as before
pid=`__runc state test_busybox | jq '.pid'`
ns_inode_new=`readlink /proc/$pid/ns/net | sed -e 's/.*\[\(.*\)\]/\1/'`
echo "old network namespace inode $ns_inode"
echo "new network namespace inode $ns_inode_new"
[ "$ns_inode" -eq "$ns_inode_new" ]
done
ip netns del $ns_name
}

View file

@ -0,0 +1,89 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "runc create" {
runc create --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
# start the command
runc start test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
}
@test "runc create exec" {
runc create --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
runc exec test_busybox true
[ "$status" -eq 0 ]
testcontainer test_busybox created
# start the command
runc start test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
}
@test "runc create --pid-file" {
runc create --pid-file pid.txt --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
# check pid.txt was generated
[ -e pid.txt ]
run cat pid.txt
[ "$status" -eq 0 ]
[[ ${lines[0]} == $(__runc state test_busybox | jq '.pid') ]]
# start the command
runc start test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
}
@test "runc create --pid-file with new CWD" {
# create pid_file directory as the CWD
run mkdir pid_file
[ "$status" -eq 0 ]
run cd pid_file
[ "$status" -eq 0 ]
runc create --pid-file pid.txt -b $BUSYBOX_BUNDLE --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox created
# check pid.txt was generated
[ -e pid.txt ]
run cat pid.txt
[ "$status" -eq 0 ]
[[ ${lines[0]} == $(__runc state test_busybox | jq '.pid') ]]
# start the command
runc start test_busybox
[ "$status" -eq 0 ]
testcontainer test_busybox running
}

View file

@ -0,0 +1,70 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_hello
setup_hello
}
function teardown() {
teardown_hello
}
@test "global --debug" {
# run hello-world
runc --debug run test_hello
echo "${output}"
[ "$status" -eq 0 ]
}
@test "global --debug to --log" {
# run hello-world
runc --log log.out --debug run test_hello
[ "$status" -eq 0 ]
# check output does not include debug info
[[ "${output}" != *"level=debug"* ]]
# check log.out was generated
[ -e log.out ]
# check expected debug output was sent to log.out
run cat log.out
[ "$status" -eq 0 ]
[[ "${output}" == *"level=debug"* ]]
}
@test "global --debug to --log --log-format 'text'" {
# run hello-world
runc --log log.out --log-format "text" --debug run test_hello
[ "$status" -eq 0 ]
# check output does not include debug info
[[ "${output}" != *"level=debug"* ]]
# check log.out was generated
[ -e log.out ]
# check expected debug output was sent to log.out
run cat log.out
[ "$status" -eq 0 ]
[[ "${output}" == *"level=debug"* ]]
}
@test "global --debug to --log --log-format 'json'" {
# run hello-world
runc --log log.out --log-format "json" --debug run test_hello
[ "$status" -eq 0 ]
# check output does not include debug info
[[ "${output}" != *"level=debug"* ]]
# check log.out was generated
[ -e log.out ]
# check expected debug output was sent to log.out
run cat log.out
[ "$status" -eq 0 ]
[[ "${output}" == *'"level":"debug"'* ]]
}

View file

@ -0,0 +1,53 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "runc delete" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
# check state
testcontainer test_busybox running
runc kill test_busybox KILL
[ "$status" -eq 0 ]
# wait for busybox to be in the destroyed state
retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'"
# delete test_busybox
runc delete test_busybox
[ "$status" -eq 0 ]
runc state test_busybox
[ "$status" -ne 0 ]
}
@test "runc delete --force" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
# check state
testcontainer test_busybox running
# force delete test_busybox
runc delete --force test_busybox
runc state test_busybox
[ "$status" -ne 0 ]
}
@test "runc delete --force ignore not exist" {
runc delete --force notexists
[ "$status" -eq 0 ]
}

View file

@ -0,0 +1,109 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "events --stats" {
# XXX: currently cgroups require root containers.
requires root
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
# generate stats
runc events --stats test_busybox
[ "$status" -eq 0 ]
[[ "${lines[0]}" == [\{]"\"type\""[:]"\"stats\""[,]"\"id\""[:]"\"test_busybox\""[,]* ]]
[[ "${lines[0]}" == *"data"* ]]
}
@test "events --interval default " {
# XXX: currently cgroups require root containers.
requires root
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
# spawn two sub processes (shells)
# the first sub process is an event logger that sends stats events to events.log
# the second sub process waits for an event that includes test_busybox then
# kills the test_busybox container which causes the event logger to exit
(__runc events test_busybox > events.log) &
(
retry 10 1 eval "grep -q 'test_busybox' events.log"
teardown_running_container test_busybox
) &
wait # wait for the above sub shells to finish
[ -e events.log ]
run cat events.log
[ "$status" -eq 0 ]
[[ "${lines[0]}" == [\{]"\"type\""[:]"\"stats\""[,]"\"id\""[:]"\"test_busybox\""[,]* ]]
[[ "${lines[0]}" == *"data"* ]]
}
@test "events --interval 1s " {
# XXX: currently cgroups require root containers.
requires root
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
# spawn two sub processes (shells)
# the first sub process is an event logger that sends stats events to events.log once a second
# the second sub process tries 3 times for an event that incudes test_busybox
# pausing 1s between each attempt then kills the test_busybox container which
# causes the event logger to exit
(__runc events --interval 1s test_busybox > events.log) &
(
retry 3 1 eval "grep -q 'test_busybox' events.log"
teardown_running_container test_busybox
) &
wait # wait for the above sub shells to finish
[ -e events.log ]
run eval "grep -q 'test_busybox' events.log"
[ "$status" -eq 0 ]
}
@test "events --interval 100ms " {
# XXX: currently cgroups require root containers.
requires root
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
#prove there is no carry over of events.log from a prior test
[ ! -e events.log ]
# spawn two sub processes (shells)
# the first sub process is an event logger that sends stats events to events.log once every 100ms
# the second sub process tries 3 times for an event that incudes test_busybox
# pausing 100s between each attempt then kills the test_busybox container which
# causes the event logger to exit
(__runc events --interval 100ms test_busybox > events.log) &
(
retry 3 0.100 eval "grep -q 'test_busybox' events.log"
teardown_running_container test_busybox
) &
wait # wait for the above sub shells to finish
[ -e events.log ]
run eval "grep -q 'test_busybox' events.log"
[ "$status" -eq 0 ]
}

View file

@ -0,0 +1,129 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "runc exec" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox echo Hello from exec
[ "$status" -eq 0 ]
echo text echoed = "'""${output}""'"
[[ "${output}" == *"Hello from exec"* ]]
}
@test "runc exec --pid-file" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec --pid-file pid.txt test_busybox echo Hello from exec
[ "$status" -eq 0 ]
echo text echoed = "'""${output}""'"
[[ "${output}" == *"Hello from exec"* ]]
# check pid.txt was generated
[ -e pid.txt ]
run cat pid.txt
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ [0-9]+ ]]
[[ ${lines[0]} != $(__runc state test_busybox | jq '.pid') ]]
}
@test "runc exec --pid-file with new CWD" {
# create pid_file directory as the CWD
run mkdir pid_file
[ "$status" -eq 0 ]
run cd pid_file
[ "$status" -eq 0 ]
# run busybox detached
runc run -d -b $BUSYBOX_BUNDLE --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec --pid-file pid.txt test_busybox echo Hello from exec
[ "$status" -eq 0 ]
echo text echoed = "'""${output}""'"
[[ "${output}" == *"Hello from exec"* ]]
# check pid.txt was generated
[ -e pid.txt ]
run cat pid.txt
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ [0-9]+ ]]
[[ ${lines[0]} != $(__runc state test_busybox | jq '.pid') ]]
}
@test "runc exec ls -la" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox ls -la
[ "$status" -eq 0 ]
[[ ${lines[0]} == *"total"* ]]
[[ ${lines[1]} == *"."* ]]
[[ ${lines[2]} == *".."* ]]
}
@test "runc exec ls -la with --cwd" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec --cwd /bin test_busybox pwd
[ "$status" -eq 0 ]
[[ ${output} == "/bin"* ]]
}
@test "runc exec --env" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec --env RUNC_EXEC_TEST=true test_busybox env
[ "$status" -eq 0 ]
[[ ${output} == *"RUNC_EXEC_TEST=true"* ]]
}
@test "runc exec --user" {
# --user can't work in rootless containers that don't have idmap.
[[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec --user 1000:1000 test_busybox id
[ "$status" -eq 0 ]
[[ "${output}" == "uid=1000 gid=1000"* ]]
}
@test "runc exec --additional-gids" {
requires root
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
wait_for_container 15 1 test_busybox
runc exec --user 1000:1000 --additional-gids 100 --additional-gids 99 test_busybox id
[ "$status" -eq 0 ]
[[ ${output} == "uid=1000 gid=1000 groups=99(nogroup),100(users)" ]]
}

View file

@ -0,0 +1,87 @@
#!/usr/bin/env bats
load helpers
@test "runc -h" {
runc -h
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ NAME:+ ]]
[[ ${lines[1]} =~ runc\ '-'\ Open\ Container\ Initiative\ runtime+ ]]
runc --help
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ NAME:+ ]]
[[ ${lines[1]} =~ runc\ '-'\ Open\ Container\ Initiative\ runtime+ ]]
}
@test "runc command -h" {
runc checkpoint -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ checkpoint+ ]]
runc delete -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ delete+ ]]
runc events -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ events+ ]]
runc exec -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ exec+ ]]
runc kill -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ kill+ ]]
runc list -h
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ NAME:+ ]]
[[ ${lines[1]} =~ runc\ list+ ]]
runc list --help
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ NAME:+ ]]
[[ ${lines[1]} =~ runc\ list+ ]]
runc pause -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ pause+ ]]
runc restore -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ restore+ ]]
runc resume -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ resume+ ]]
# We don't use runc_spec here, because we're just testing the help page.
runc spec -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ spec+ ]]
runc start -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ start+ ]]
runc run -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ run+ ]]
runc state -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ state+ ]]
runc update -h
[ "$status" -eq 0 ]
[[ ${lines[1]} =~ runc\ update+ ]]
}
@test "runc foo -h" {
runc foo -h
[ "$status" -ne 0 ]
[[ "${output}" == *"No help topic for 'foo'"* ]]
}

View file

@ -0,0 +1,330 @@
#!/bin/bash
# Root directory of integration tests.
INTEGRATION_ROOT=$(dirname "$(readlink -f "$BASH_SOURCE")")
. ${INTEGRATION_ROOT}/multi-arch.bash
RUNC="${INTEGRATION_ROOT}/../../runc"
RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty"
GOPATH="$(mktemp -d --tmpdir runc-integration-gopath.XXXXXX)"
# Test data path.
TESTDATA="${INTEGRATION_ROOT}/testdata"
# Busybox image
BUSYBOX_IMAGE="$BATS_TMPDIR/busybox.tar"
BUSYBOX_BUNDLE="$BATS_TMPDIR/busyboxtest"
# hello-world in tar format
HELLO_FILE=`get_hello`
HELLO_IMAGE="$TESTDATA/$HELLO_FILE"
HELLO_BUNDLE="$BATS_TMPDIR/hello-world"
# CRIU PATH
CRIU="$(which criu || true)"
# Kernel version
KERNEL_VERSION="$(uname -r)"
KERNEL_MAJOR="${KERNEL_VERSION%%.*}"
KERNEL_MINOR="${KERNEL_VERSION#$KERNEL_MAJOR.}"
KERNEL_MINOR="${KERNEL_MINOR%%.*}"
# Root state path.
ROOT=$(mktemp -d "$BATS_TMPDIR/runc.XXXXXX")
# Path to console socket.
CONSOLE_SOCKET="$BATS_TMPDIR/console.sock"
# Cgroup paths
CGROUP_MEMORY_BASE_PATH=$(grep "cgroup" /proc/self/mountinfo | gawk 'toupper($NF) ~ /\<MEMORY\>/ { print $5; exit }')
CGROUP_CPU_BASE_PATH=$(grep "cgroup" /proc/self/mountinfo | gawk 'toupper($NF) ~ /\<CPU\>/ { print $5; exit }')
CGROUPS_PATH="/runc-cgroups-integration-test/test-cgroup"
CGROUP_MEMORY="${CGROUP_MEMORY_BASE_PATH}${CGROUPS_PATH}"
# CONFIG_MEMCG_KMEM support
KMEM="${CGROUP_MEMORY_BASE_PATH}/memory.kmem.limit_in_bytes"
RT_PERIOD="${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us"
# Check if we're in rootless mode.
ROOTLESS=$(id -u)
# Wrapper for runc.
function runc() {
run __runc "$@"
# Some debug information to make life easier. bats will only print it if the
# test failed, in which case the output is useful.
echo "runc $@ (status=$status):" >&2
echo "$output" >&2
}
# Raw wrapper for runc.
function __runc() {
"$RUNC" --log /proc/self/fd/2 --root "$ROOT" "$@"
}
# Wrapper for runc spec, which takes only one argument (the bundle path).
function runc_spec() {
! [[ "$#" > 1 ]]
local args=()
local bundle=""
if [ "$ROOTLESS" -ne 0 ]; then
args+=("--rootless")
fi
if [ "$#" -ne 0 ]; then
bundle="$1"
args+=("--bundle" "$bundle")
fi
runc spec "${args[@]}"
# Always add additional mappings if we have idmaps.
if [[ "$ROOTLESS" -ne 0 ]] && [[ "$ROOTLESS_FEATURES" == *"idmap"* ]]; then
runc_rootless_idmap "$bundle"
fi
# Ensure config.json contains linux.resources
if [[ "$ROOTLESS" -ne 0 ]] && [[ "$ROOTLESS_FEATURES" == *"cgroup"* ]]; then
runc_rootless_cgroup "$bundle"
fi
}
# Shortcut to add additional uids and gids, based on the values set as part of
# a rootless configuration.
function runc_rootless_idmap() {
bundle="${1:-.}"
cat "$bundle/config.json" \
| jq '.mounts |= map((select(.type == "devpts") | .options += ["gid=5"]) // .)' \
| jq '.linux.uidMappings |= .+ [{"hostID": '"$ROOTLESS_UIDMAP_START"', "containerID": 1000, "size": '"$ROOTLESS_UIDMAP_LENGTH"'}]' \
| jq '.linux.gidMappings |= .+ [{"hostID": '"$ROOTLESS_GIDMAP_START"', "containerID": 100, "size": 1}]' \
| jq '.linux.gidMappings |= .+ [{"hostID": '"$(($ROOTLESS_GIDMAP_START+10))"', "containerID": 1, "size": 20}]' \
| jq '.linux.gidMappings |= .+ [{"hostID": '"$(($ROOTLESS_GIDMAP_START+100))"', "containerID": 1000, "size": '"$(($ROOTLESS_GIDMAP_LENGTH-1000))"'}]' \
>"$bundle/config.json.tmp"
mv "$bundle/config.json"{.tmp,}
}
# Shortcut to add empty resources as part of a rootless configuration.
function runc_rootless_cgroup() {
bundle="${1:-.}"
cat "$bundle/config.json" \
| jq '.linux.resources |= .+ {"memory":{},"cpu":{},"blockio":{},"pids":{}}' \
>"$bundle/config.json.tmp"
mv "$bundle/config.json"{.tmp,}
}
# Helper function to set cgroupsPath to the value of $CGROUPS_PATH
function set_cgroups_path() {
bundle="${1:-.}"
sed -i 's/\("linux": {\)/\1\n "cgroupsPath": "\/runc-cgroups-integration-test\/test-cgroup",/' "$bundle/config.json"
}
# Helper function to set a resources limit
function set_resources_limit() {
bundle="${1:-.}"
sed -i 's/\("linux": {\)/\1\n "resources": { "pids": { "limit": 100 } },/' "$bundle/config.json"
}
# Fails the current test, providing the error given.
function fail() {
echo "$@" >&2
exit 1
}
# Allows a test to specify what things it requires. If the environment can't
# support it, the test is skipped with a message.
function requires() {
for var in "$@"; do
case $var in
criu)
if [ ! -e "$CRIU" ]; then
skip "test requires ${var}"
fi
;;
root)
if [ "$ROOTLESS" -ne 0 ]; then
skip "test requires ${var}"
fi
;;
rootless)
if [ "$ROOTLESS" -eq 0 ]; then
skip "test requires ${var}"
fi
;;
rootless_idmap)
if [[ "$ROOTLESS_FEATURES" != *"idmap"* ]]; then
skip "test requires ${var}"
fi
;;
rootless_cgroup)
if [[ "$ROOTLESS_FEATURES" != *"cgroup"* ]]; then
skip "test requires ${var}"
fi
;;
rootless_no_cgroup)
if [[ "$ROOTLESS_FEATURES" == *"cgroup"* ]]; then
skip "test requires ${var}"
fi
;;
cgroups_kmem)
if [ ! -e "$KMEM" ]; then
skip "Test requires ${var}"
fi
;;
cgroups_rt)
if [ ! -e "$RT_PERIOD" ]; then
skip "Test requires ${var}"
fi
;;
*)
fail "BUG: Invalid requires ${var}."
;;
esac
done
}
# Retry a command $1 times until it succeeds. Wait $2 seconds between retries.
function retry() {
local attempts=$1
shift
local delay=$1
shift
local i
for ((i = 0; i < attempts; i++)); do
run "$@"
if [[ "$status" -eq 0 ]]; then
return 0
fi
sleep $delay
done
echo "Command \"$@\" failed $attempts times. Output: $output"
false
}
# retry until the given container has state
function wait_for_container() {
local attempts=$1
local delay=$2
local cid=$3
local i
for ((i = 0; i < attempts; i++)); do
runc state $cid
if [[ "$status" -eq 0 ]]; then
return 0
fi
sleep $delay
done
echo "runc state failed to return state $statecheck $attempts times. Output: $output"
false
}
# retry until the given container has state
function wait_for_container_inroot() {
local attempts=$1
local delay=$2
local cid=$3
local i
for ((i = 0; i < attempts; i++)); do
ROOT=$4 runc state $cid
if [[ "$status" -eq 0 ]]; then
return 0
fi
sleep $delay
done
echo "runc state failed to return state $statecheck $attempts times. Output: $output"
false
}
function testcontainer() {
# test state of container
runc state $1
[ "$status" -eq 0 ]
[[ "${output}" == *"$2"* ]]
}
function setup_recvtty() {
# We need to start recvtty in the background, so we double fork in the shell.
("$RECVTTY" --pid-file "$BATS_TMPDIR/recvtty.pid" --mode null "$CONSOLE_SOCKET" &) &
}
function teardown_recvtty() {
# When we kill recvtty, the container will also be killed.
if [ -f "$BATS_TMPDIR/recvtty.pid" ]; then
kill -9 $(cat "$BATS_TMPDIR/recvtty.pid")
fi
# Clean up the files that might be left over.
rm -f "$BATS_TMPDIR/recvtty.pid"
rm -f "$CONSOLE_SOCKET"
}
function setup_busybox() {
setup_recvtty
run mkdir "$BUSYBOX_BUNDLE"
run mkdir "$BUSYBOX_BUNDLE"/rootfs
if [ -e "/testdata/busybox.tar" ]; then
BUSYBOX_IMAGE="/testdata/busybox.tar"
fi
if [ ! -e $BUSYBOX_IMAGE ]; then
curl -o $BUSYBOX_IMAGE -sSL `get_busybox`
fi
tar --exclude './dev/*' -C "$BUSYBOX_BUNDLE"/rootfs -xf "$BUSYBOX_IMAGE"
cd "$BUSYBOX_BUNDLE"
runc_spec
}
function setup_hello() {
setup_recvtty
run mkdir "$HELLO_BUNDLE"
run mkdir "$HELLO_BUNDLE"/rootfs
tar --exclude './dev/*' -C "$HELLO_BUNDLE"/rootfs -xf "$HELLO_IMAGE"
cd "$HELLO_BUNDLE"
runc_spec
sed -i 's;"sh";"/hello";' config.json
}
function teardown_running_container() {
runc list
# $1 should be a container name such as "test_busybox"
# here we detect "test_busybox "(with one extra blank) to avoid conflict prefix
# e.g. "test_busybox" and "test_busybox_update"
if [[ "${output}" == *"$1 "* ]]; then
runc kill $1 KILL
retry 10 1 eval "__runc state '$1' | grep -q 'stopped'"
runc delete $1
fi
}
function teardown_running_container_inroot() {
ROOT=$2 runc list
# $1 should be a container name such as "test_busybox"
# here we detect "test_busybox "(with one extra blank) to avoid conflict prefix
# e.g. "test_busybox" and "test_busybox_update"
if [[ "${output}" == *"$1 "* ]]; then
ROOT=$2 runc kill $1 KILL
retry 10 1 eval "ROOT='$2' __runc state '$1' | grep -q 'stopped'"
ROOT=$2 runc delete $1
fi
}
function teardown_busybox() {
cd "$INTEGRATION_ROOT"
teardown_recvtty
teardown_running_container test_busybox
run rm -f -r "$BUSYBOX_BUNDLE"
}
function teardown_hello() {
cd "$INTEGRATION_ROOT"
teardown_recvtty
teardown_running_container test_hello
run rm -f -r "$HELLO_BUNDLE"
}

View file

@ -0,0 +1,30 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "kill detached busybox" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
# check state
testcontainer test_busybox running
runc kill test_busybox KILL
[ "$status" -eq 0 ]
retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'"
runc delete test_busybox
[ "$status" -eq 0 ]
}

View file

@ -0,0 +1,56 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_running_container_inroot test_box1 $HELLO_BUNDLE
teardown_running_container_inroot test_box2 $HELLO_BUNDLE
teardown_running_container_inroot test_box3 $HELLO_BUNDLE
teardown_busybox
setup_busybox
}
function teardown() {
teardown_running_container_inroot test_box1 $HELLO_BUNDLE
teardown_running_container_inroot test_box2 $HELLO_BUNDLE
teardown_running_container_inroot test_box3 $HELLO_BUNDLE
teardown_busybox
}
@test "list" {
# run a few busyboxes detached
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_box1
[ "$status" -eq 0 ]
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_box2
[ "$status" -eq 0 ]
ROOT=$HELLO_BUNDLE runc run -d --console-socket $CONSOLE_SOCKET test_box3
[ "$status" -eq 0 ]
ROOT=$HELLO_BUNDLE runc list
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ ID\ +PID\ +STATUS\ +BUNDLE\ +CREATED+ ]]
[[ "${lines[1]}" == *"test_box1"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]]
[[ "${lines[2]}" == *"test_box2"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]]
[[ "${lines[3]}" == *"test_box3"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]]
ROOT=$HELLO_BUNDLE runc list -q
[ "$status" -eq 0 ]
[[ "${lines[0]}" == "test_box1" ]]
[[ "${lines[1]}" == "test_box2" ]]
[[ "${lines[2]}" == "test_box3" ]]
ROOT=$HELLO_BUNDLE runc list --format table
[ "$status" -eq 0 ]
[[ ${lines[0]} =~ ID\ +PID\ +STATUS\ +BUNDLE\ +CREATED+ ]]
[[ "${lines[1]}" == *"test_box1"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]]
[[ "${lines[2]}" == *"test_box2"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]]
[[ "${lines[3]}" == *"test_box3"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]]
ROOT=$HELLO_BUNDLE runc list --format json
[ "$status" -eq 0 ]
[[ "${lines[0]}" == [\[][\{]"\"ociVersion\""[:]"\""*[0-9][\.]*[0-9][\.]*[0-9]*"\""[,]"\"id\""[:]"\"test_box1\""[,]"\"pid\""[:]*[0-9][,]"\"status\""[:]*"\"running\""[,]"\"bundle\""[:]*$BUSYBOX_BUNDLE*[,]"\"rootfs\""[:]"\""*"\""[,]"\"created\""[:]*[0-9]*[\}]* ]]
[[ "${lines[0]}" == *[,][\{]"\"ociVersion\""[:]"\""*[0-9][\.]*[0-9][\.]*[0-9]*"\""[,]"\"id\""[:]"\"test_box2\""[,]"\"pid\""[:]*[0-9][,]"\"status\""[:]*"\"running\""[,]"\"bundle\""[:]*$BUSYBOX_BUNDLE*[,]"\"rootfs\""[:]"\""*"\""[,]"\"created\""[:]*[0-9]*[\}]* ]]
[[ "${lines[0]}" == *[,][\{]"\"ociVersion\""[:]"\""*[0-9][\.]*[0-9][\.]*[0-9]*"\""[,]"\"id\""[:]"\"test_box3\""[,]"\"pid\""[:]*[0-9][,]"\"status\""[:]*"\"running\""[,]"\"bundle\""[:]*$BUSYBOX_BUNDLE*[,]"\"rootfs\""[:]"\""*"\""[,]"\"created\""[:]*[0-9]*[\}][\]] ]]
}

View file

@ -0,0 +1,59 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
# Create fake rootfs.
mkdir rootfs/testdir
echo "Forbidden information!" > rootfs/testfile
# add extra masked paths
sed -i 's;"maskedPaths": \[;"maskedPaths": \["/testdir","/testfile",;g' config.json
}
function teardown() {
teardown_busybox
}
@test "mask paths [file]" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox cat /testfile
[ "$status" -eq 0 ]
[[ "${output}" == "" ]]
runc exec test_busybox rm -f /testfile
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]
runc exec test_busybox umount /testfile
[ "$status" -eq 1 ]
[[ "${output}" == *"Operation not permitted"* ]]
}
@test "mask paths [directory]" {
# run busybox detached
runc run -d --console-socket $CONSOLE_SOCKET test_busybox
[ "$status" -eq 0 ]
runc exec test_busybox ls /testdir
[ "$status" -eq 0 ]
[[ "${output}" == "" ]]
runc exec test_busybox touch /testdir/foo
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]
runc exec test_busybox rm -rf /testdir
[ "$status" -eq 1 ]
[[ "${output}" == *"Read-only file system"* ]]
runc exec test_busybox umount /testdir
[ "$status" -eq 1 ]
[[ "${output}" == *"Operation not permitted"* ]]
}

View file

@ -0,0 +1,21 @@
#!/usr/bin/env bats
load helpers
function setup() {
teardown_busybox
setup_busybox
}
function teardown() {
teardown_busybox
}
@test "runc run [bind mount]" {
CONFIG=$(jq '.mounts |= . + [{"source": ".", "destination": "/tmp/bind", "options": ["bind"]}] | .process.args = ["ls", "/tmp/bind/config.json"]' config.json)
echo "${CONFIG}" >config.json
runc run test_bind_mount
[ "$status" -eq 0 ]
[[ "${lines[0]}" =~ '/tmp/bind/config.json' ]]
}

View file

@ -0,0 +1,22 @@
#!/bin/bash
get_busybox(){
case $(go env GOARCH) in
arm64)
echo 'https://github.com/docker-library/busybox/raw/23fbd9c43e0f4bec7605091bfba23db278c367ac/glibc/busybox.tar.xz'
;;
*)
echo 'https://github.com/docker-library/busybox/raw/a0558a9006ce0dd6f6ec5d56cfd3f32ebeeb815f/glibc/busybox.tar.xz'
;;
esac
}
get_hello(){
case $(go env GOARCH) in
arm64)
echo 'hello-world-aarch64.tar'
;;
*)
echo 'hello-world.tar'
;;
esac
}

Some files were not shown because too many files have changed in this diff Show more