├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── Makefile ├── NOTICE ├── PRINCIPLES.md ├── README.md ├── SECURITY.md ├── VERSION ├── Vagrantfile.centos7 ├── Vagrantfile.fedora33 ├── checkpoint.go ├── contrib ├── cmd │ └── recvtty │ │ └── recvtty.go └── completions │ └── bash │ └── runc ├── create.go ├── delete.go ├── docs ├── Security-Audit.pdf ├── cgroup-v2.md ├── checkpoint-restore.md ├── systemd-properties.md └── terminals.md ├── events.go ├── exec.go ├── go.mod ├── go.sum ├── init.go ├── kill.go ├── libcontainer ├── README.md ├── SPEC.md ├── apparmor │ ├── apparmor_linux.go │ └── apparmor_unsupported.go ├── capabilities_linux.go ├── cgroups │ ├── cgroups.go │ ├── cgroups_test.go │ ├── cgroups_unsupported.go │ ├── devices │ │ ├── devices_emulator.go │ │ └── devices_emulator_test.go │ ├── ebpf │ │ ├── devicefilter │ │ │ ├── devicefilter.go │ │ │ └── devicefilter_test.go │ │ └── ebpf.go │ ├── file.go │ ├── file_test.go │ ├── fs │ │ ├── blkio.go │ │ ├── blkio_test.go │ │ ├── cpu.go │ │ ├── cpu_test.go │ │ ├── cpuacct.go │ │ ├── cpuacct_test.go │ │ ├── cpuset.go │ │ ├── cpuset_test.go │ │ ├── devices.go │ │ ├── devices_test.go │ │ ├── freezer.go │ │ ├── freezer_test.go │ │ ├── fs.go │ │ ├── fs_test.go │ │ ├── hugetlb.go │ │ ├── hugetlb_test.go │ │ ├── memory.go │ │ ├── memory_test.go │ │ ├── name.go │ │ ├── net_cls.go │ │ ├── net_cls_test.go │ │ ├── net_prio.go │ │ ├── net_prio_test.go │ │ ├── perf_event.go │ │ ├── pids.go │ │ ├── pids_test.go │ │ ├── rdma.go │ │ ├── stats_util_test.go │ │ ├── unsupported.go │ │ └── util_test.go │ ├── fs2 │ │ ├── cpu.go │ │ ├── cpuset.go │ │ ├── create.go │ │ ├── defaultpath.go │ │ ├── defaultpath_test.go │ │ ├── devices.go │ │ ├── freezer.go │ │ ├── fs2.go │ │ ├── hugetlb.go │ │ ├── io.go │ │ ├── memory.go │ │ └── pids.go │ ├── fscommon │ │ ├── rdma.go │ │ ├── rdma_test.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── stats.go │ ├── systemd │ │ ├── common.go │ │ ├── cpuset.go │ │ ├── cpuset_test.go │ │ ├── systemd_test.go │ │ ├── unsupported.go │ │ ├── user.go │ │ ├── v1.go │ │ └── v2.go │ ├── utils.go │ ├── utils_test.go │ └── v1_utils.go ├── configs │ ├── blkio_device.go │ ├── cgroup_linux.go │ ├── cgroup_unsupported.go │ ├── config.go │ ├── config_linux.go │ ├── config_linux_test.go │ ├── config_test.go │ ├── config_windows_test.go │ ├── devices.go │ ├── fsentry.go │ ├── hugepage_limit.go │ ├── intelrdt.go │ ├── interface_priority_map.go │ ├── mount.go │ ├── namespaces.go │ ├── namespaces_linux.go │ ├── namespaces_syscall.go │ ├── namespaces_syscall_unsupported.go │ ├── namespaces_unsupported.go │ ├── network.go │ ├── rdma.go │ └── validate │ │ ├── rootless.go │ │ ├── rootless_test.go │ │ ├── validator.go │ │ └── validator_test.go ├── console_linux.go ├── container.go ├── container_linux.go ├── container_linux_test.go ├── criu_opts_linux.go ├── devices │ ├── device.go │ ├── device_unix.go │ ├── device_windows.go │ ├── devices.go │ └── devices_test.go ├── error.go ├── error_test.go ├── factory.go ├── factory_linux.go ├── factory_linux_test.go ├── generic_error.go ├── generic_error_test.go ├── init_linux.go ├── integration │ ├── checkpoint_test.go │ ├── doc.go │ ├── exec_test.go │ ├── execin_test.go │ ├── init_test.go │ ├── seccomp_test.go │ ├── template_test.go │ └── utils_test.go ├── intelrdt │ ├── cmt.go │ ├── cmt_test.go │ ├── intelrdt.go │ ├── intelrdt_test.go │ ├── mbm.go │ ├── mbm_test.go │ ├── monitoring.go │ ├── monitoring_test.go │ ├── stats.go │ └── util_test.go ├── keys │ └── keyctl.go ├── logs │ ├── logs.go │ └── logs_linux_test.go ├── message_linux.go ├── network_linux.go ├── notify_linux.go ├── notify_linux_test.go ├── notify_linux_v2.go ├── nsenter │ ├── README.md │ ├── cloned_binary.c │ ├── namespace.h │ ├── nsenter.go │ ├── nsenter_gccgo.go │ ├── nsenter_test.go │ ├── nsenter_unsupported.go │ └── nsexec.c ├── process.go ├── process_linux.go ├── restored_process.go ├── rootfs_init_linux.go ├── rootfs_linux.go ├── rootfs_linux_test.go ├── seccomp │ ├── config.go │ ├── patchbpf │ │ ├── enosys_linux.go │ │ ├── enosys_linux_test.go │ │ └── enosys_unsupported.go │ ├── seccomp_linux.go │ └── seccomp_unsupported.go ├── setns_init_linux.go ├── specconv │ ├── example.go │ ├── spec_linux.go │ └── spec_linux_test.go ├── stacktrace │ ├── capture.go │ ├── capture_test.go │ ├── frame.go │ ├── frame_test.go │ └── stacktrace.go ├── standard_init_linux.go ├── state_linux.go ├── state_linux_test.go ├── stats_linux.go ├── sync.go ├── system │ ├── linux.go │ ├── linux_test.go │ ├── proc.go │ ├── proc_test.go │ ├── syscall_linux_32.go │ ├── syscall_linux_64.go │ ├── unsupported.go │ └── xattrs_linux.go ├── user │ ├── MAINTAINERS │ ├── lookup.go │ ├── lookup_unix.go │ ├── lookup_windows.go │ ├── user.go │ └── user_test.go └── utils │ ├── cmsg.go │ ├── utils.go │ ├── utils_test.go │ └── utils_unix.go ├── libsysbox ├── sysbox │ ├── fs.go │ ├── mgr.go │ ├── sysbox.go │ └── utils.go └── syscont │ ├── example.go │ ├── spec.go │ ├── spec_test.go │ ├── syscalls.go │ ├── utils.go │ └── utils_test.go ├── list.go ├── main.go ├── man ├── README.md ├── md2man-all.sh ├── runc-checkpoint.8.md ├── runc-create.8.md ├── runc-delete.8.md ├── runc-events.8.md ├── runc-exec.8.md ├── runc-kill.8.md ├── runc-list.8.md ├── runc-pause.8.md ├── runc-ps.8.md ├── runc-restore.8.md ├── runc-resume.8.md ├── runc-run.8.md ├── runc-spec.8.md ├── runc-start.8.md ├── runc-state.8.md ├── runc-update.8.md └── runc.8.md ├── notify_socket.go ├── pause.go ├── profile.go ├── ps.go ├── restore.go ├── rlimit_linux.go ├── rootless_linux.go ├── run.go ├── script ├── .validate ├── check-config.sh ├── release.sh ├── validate-c └── validate-gofmt ├── signals.go ├── spec.go ├── start.go ├── state.go ├── tests ├── integration │ ├── README.md │ ├── cgroups.bats │ ├── checkpoint.bats │ ├── create.bats │ ├── cwd.bats │ ├── debug.bats │ ├── delete.bats │ ├── dev.bats │ ├── events.bats │ ├── exec.bats │ ├── help.bats │ ├── helpers.bash │ ├── hooks.bats │ ├── kill.bats │ ├── list.bats │ ├── mask.bats │ ├── mounts.bats │ ├── multi-arch.bash │ ├── no_pivot.bats │ ├── pause.bats │ ├── ps.bats │ ├── root.bats │ ├── run.bats │ ├── spec.bats │ ├── start.bats │ ├── start_detached.bats │ ├── start_hello.bats │ ├── state.bats │ ├── syscont-caps.bats │ ├── syscont-cgroup.bats │ ├── syscont-ns.bats │ ├── syscont-oom.bats │ ├── syscont-syscalls.bats │ ├── syscont-uid.bats │ ├── testdata │ │ ├── hello-world-aarch64.tar │ │ └── hello-world.tar │ ├── tty.bats │ ├── umask.bats │ ├── update.bats │ └── version.bats └── rootless.sh ├── tty.go ├── types └── events.go ├── update.go ├── utils.go └── utils_linux.go /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor/pkg 3 | contrib/cmd/recvtty/recvtty 4 | man/man8 5 | release 6 | Vagrantfile 7 | .vagrant 8 | GPATH 9 | GRTAGS 10 | GTAGS 11 | .ccls-cache 12 | .vscode 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to Sysbox-runc 2 | 3 | Sysbox-runc is a component of the Sysbox container runtime. If you want to 4 | contribute, please refer to the Sysbox contribution 5 | [guidelines](https://github.com/nestybox/sysbox/blob/master/CONTRIBUTING.md). 6 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Rodny Molina (@rodnymolina) 2 | Cesar Talledo (@ctalledo) 3 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | runc 2 | 3 | Copyright 2012-2015 Docker, Inc. 4 | 5 | This product includes software developed at Docker, Inc. (http://www.docker.com). 6 | 7 | The following is courtesy of our legal counsel: 8 | 9 | 10 | Use and transfer of Docker may be subject to certain restrictions by the 11 | United States and other governments. 12 | It is your responsibility to ensure that your use and/or transfer does not 13 | violate applicable laws. 14 | 15 | For more information, please see http://www.bis.doc.gov 16 | 17 | See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. 18 | -------------------------------------------------------------------------------- /PRINCIPLES.md: -------------------------------------------------------------------------------- 1 | # runc principles 2 | 3 | In the design and development of runc and libcontainer we try to follow these principles: 4 | 5 | (Work in progress) 6 | 7 | * Don't try to replace every tool. Instead, be an ingredient to improve them. 8 | * Less code is better. 9 | * Fewer components are better. Do you really need to add one more class? 10 | * 50 lines of straightforward, readable code is better than 10 lines of magic that nobody can understand. 11 | * Don't do later what you can do now. "//TODO: refactor" is not acceptable in new code. 12 | * When hesitating between two options, choose the one that is easier to reverse. 13 | * "No" is temporary; "Yes" is forever. If you're not sure about a new feature, say no. You can change your mind later. 14 | * Containers must be portable to the greatest possible number of machines. Be suspicious of any change which makes machines less interchangeable. 15 | * The fewer moving parts in a container, the better. 16 | * Don't merge it unless you document it. 17 | * Don't document it unless you can keep it up-to-date. 18 | * Don't merge it unless you test it! 19 | * Everyone's problem is slightly different. Focus on the part that is the same for everyone, and solve that. 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | The reporting process and disclosure communications are outlined [here](https://github.com/opencontainers/org/blob/master/SECURITY.md). 4 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /Vagrantfile.centos7: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "centos/7" 6 | config.vm.provider :virtualbox do |v| 7 | v.memory = 2048 8 | v.cpus = 2 9 | end 10 | config.vm.provider :libvirt do |v| 11 | v.memory = 2048 12 | v.cpus = 2 13 | end 14 | config.vm.provision "shell", inline: <<-SHELL 15 | set -e -u -o pipefail 16 | 17 | # configuration 18 | GO_VERSION="1.15" 19 | BATS_VERSION="v1.2.1" 20 | UMOCI_VERSION="v0.4.6" 21 | 22 | # install yum packages 23 | yum install -y -q epel-release 24 | (cd /etc/yum.repos.d && curl -O https://copr.fedorainfracloud.org/coprs/adrian/criu-el7/repo/epel-7/adrian-criu-el7-epel-7.repo) 25 | yum install -y -q gcc git iptables jq libseccomp-devel make skopeo criu 26 | yum clean all 27 | 28 | # install Go 29 | curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" | tar Cxz /usr/local 30 | 31 | # Install umoci 32 | curl -o /usr/local/bin/umoci -fsSL https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/umoci.amd64 33 | chmod +x /usr/local/bin/umoci 34 | 35 | # install bats 36 | git clone https://github.com/bats-core/bats-core 37 | cd bats-core 38 | git checkout $BATS_VERSION 39 | ./install.sh /usr/local 40 | cd .. 41 | rm -rf bats-core 42 | 43 | # set PATH (NOTE: sudo without -i ignores this PATH) 44 | cat >> /etc/profile.d/sh.local < /etc/sysctl.d/userns.conf 52 | sysctl --system 53 | 54 | # Add a user for rootless tests 55 | useradd -u2000 -m -d/home/rootless -s/bin/bash rootless 56 | 57 | # Add busybox for libcontainer/integration tests 58 | . /vagrant/tests/integration/multi-arch.bash \ 59 | && mkdir /busybox \ 60 | && curl -fsSL $(get_busybox) | tar xfJC - /busybox 61 | SHELL 62 | end 63 | -------------------------------------------------------------------------------- /Vagrantfile.fedora33: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | # Fedora box is used for testing cgroup v2 support 6 | config.vm.box = "fedora/33-cloud-base" 7 | config.vm.provider :virtualbox do |v| 8 | v.memory = 2048 9 | v.cpus = 2 10 | end 11 | config.vm.provider :libvirt do |v| 12 | v.memory = 2048 13 | v.cpus = 2 14 | end 15 | config.vm.provision "shell", inline: <<-SHELL 16 | set -e -u -o pipefail 17 | # Work around dnf mirror failures by retrying a few times 18 | for i in $(seq 0 2); do 19 | sleep $i 20 | cat << EOF | dnf -y shell && break 21 | config exclude kernel,kernel-core 22 | config install_weak_deps false 23 | update 24 | install iptables gcc make golang-go libseccomp-devel bats jq git-core criu skopeo 25 | ts run 26 | EOF 27 | done 28 | dnf clean all 29 | 30 | # Add a user for rootless tests 31 | useradd -u2000 -m -d/home/rootless -s/bin/bash rootless 32 | 33 | # Allow root to execute `ssh rootless@localhost` in tests/rootless.sh 34 | ssh-keygen -t ecdsa -N "" -f /root/rootless.key 35 | mkdir -m 0700 -p /home/rootless/.ssh 36 | cat /root/rootless.key.pub >> /home/rootless/.ssh/authorized_keys 37 | chown -R rootless.rootless /home/rootless 38 | 39 | # Install umoci 40 | UMOCI_VERSION=v0.4.6 41 | curl -o /usr/local/bin/umoci -fsSL https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/umoci.amd64 42 | chmod +x /usr/local/bin/umoci 43 | 44 | # Add busybox for libcontainer/integration tests 45 | . /vagrant/tests/integration/multi-arch.bash \ 46 | && mkdir /busybox /debian \ 47 | && curl -fsSL $(get_busybox) | tar xfJC - /busybox \ 48 | && get_and_extract_debian /debian 49 | 50 | # Delegate cgroup v2 controllers to rootless user via --systemd-cgroup 51 | mkdir -p /etc/systemd/system/user@.service.d 52 | cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF 53 | [Service] 54 | # default: Delegate=pids memory 55 | # NOTE: delegation of cpuset requires systemd >= 244 (Fedora >= 32, Ubuntu >= 20.04). cpuset is ignored on Fedora 31. 56 | Delegate=cpu cpuset io memory pids 57 | EOF 58 | systemctl daemon-reload 59 | SHELL 60 | end 61 | -------------------------------------------------------------------------------- /delete.go: -------------------------------------------------------------------------------- 1 | // +build !solaris 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "time" 11 | 12 | "github.com/opencontainers/runc/libcontainer" 13 | "github.com/urfave/cli" 14 | 15 | "golang.org/x/sys/unix" 16 | ) 17 | 18 | func killContainer(container libcontainer.Container) error { 19 | _ = container.Signal(unix.SIGKILL, false) 20 | for i := 0; i < 100; i++ { 21 | time.Sleep(100 * time.Millisecond) 22 | if err := container.Signal(unix.Signal(0), false); err != nil { 23 | destroy(container) 24 | return nil 25 | } 26 | } 27 | return errors.New("container init still running") 28 | } 29 | 30 | var deleteCommand = cli.Command{ 31 | Name: "delete", 32 | Usage: "delete any resources held by the system container; often used with detached container", 33 | ArgsUsage: ` 34 | 35 | Where "" is the name for the instance of the container. 36 | 37 | EXAMPLE: 38 | For example, if the container id is "ubuntu01" and sysbox-runc list currently shows the 39 | status of "ubuntu01" as "stopped" the following will delete resources held for 40 | "ubuntu01" removing "ubuntu01" from the sysbox-runc list of containers: 41 | 42 | # sysbox-runc delete ubuntu01`, 43 | Flags: []cli.Flag{ 44 | cli.BoolFlag{ 45 | Name: "force, f", 46 | Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)", 47 | }, 48 | }, 49 | Action: func(context *cli.Context) error { 50 | if err := checkArgs(context, 1, exactArgs); err != nil { 51 | return err 52 | } 53 | 54 | id := context.Args().First() 55 | force := context.Bool("force") 56 | container, err := getContainer(context) 57 | if err != nil { 58 | if lerr, ok := err.(libcontainer.Error); ok && lerr.Code() == libcontainer.ContainerNotExists { 59 | // if there was an aborted start or something of the sort then the container's directory could exist but 60 | // libcontainer does not see it because the state.json file inside that directory was never created. 61 | path := filepath.Join(context.GlobalString("root"), id) 62 | if e := os.RemoveAll(path); e != nil { 63 | fmt.Fprintf(os.Stderr, "remove %s: %v\n", path, e) 64 | } 65 | if force { 66 | return nil 67 | } 68 | } 69 | return err 70 | } 71 | s, err := container.Status() 72 | if err != nil { 73 | return err 74 | } 75 | switch s { 76 | case libcontainer.Stopped: 77 | destroy(container) 78 | case libcontainer.Created: 79 | return killContainer(container) 80 | default: 81 | if force { 82 | return killContainer(container) 83 | } 84 | return fmt.Errorf("cannot delete container %s that is not stopped: %s\n", id, s) 85 | } 86 | 87 | return nil 88 | }, 89 | } 90 | -------------------------------------------------------------------------------- /docs/Security-Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestybox/sysbox-runc/c58eba1be027c762c495bc4eeba7c0984beda1ab/docs/Security-Audit.pdf -------------------------------------------------------------------------------- /docs/cgroup-v2.md: -------------------------------------------------------------------------------- 1 | # cgroup v2 2 | 3 | runc fully supports cgroup v2 (unified mode) since v1.0.0-rc93. 4 | 5 | To use cgroup v2, you might need to change the configuration of the host init system. 6 | Fedora (>= 31) uses cgroup v2 by default and no extra configuration is required. 7 | On other systemd-based distros, cgroup v2 can be enabled by adding `systemd.unified_cgroup_hierarchy=1` to the kernel cmdline. 8 | 9 | ## Am I using cgroup v2? 10 | 11 | Yes if `/sys/fs/cgroup/cgroup.controllers` is present. 12 | 13 | ## Host Requirements 14 | ### Kernel 15 | * Recommended version: 5.2 or later 16 | * Minimum version: 4.15 17 | 18 | Kernel older than 5.2 is not recommended due to lack of freezer. 19 | 20 | Notably, kernel older than 4.15 MUST NOT be used (unless you are running containers with user namespaces), as it lacks support for controlling permissions of devices. 21 | 22 | ### Systemd 23 | On cgroup v2 hosts, it is highly recommended to run runc with the systemd cgroup driver (`runc --systemd-cgroup`), though not mandatory. 24 | 25 | The recommended systemd version is 244 or later. Older systemd does not support delegation of `cpuset` controller. 26 | 27 | Make sure you also have the `dbus-user-session` (Debian/Ubuntu) or `dbus-daemon` (CentOS/Fedora) package installed, and that `dbus` is running. On Debian-flavored distros, this can be accomplished like so: 28 | 29 | ```console 30 | $ sudo apt install -y dbus-user-session 31 | $ systemctl --user start dbus 32 | ``` 33 | 34 | ## Rootless 35 | On cgroup v2 hosts, rootless runc can talk to systemd to get cgroup permissions to be delegated. 36 | 37 | ```console 38 | $ runc spec --rootless 39 | $ jq '.linux.cgroupsPath="user.slice:runc:foo"' config.json | sponge config.json 40 | $ runc --systemd-cgroup run foo 41 | ``` 42 | 43 | The container processes are executed in a cgroup like `/user.slice/user-$(id -u).slice/user@$(id -u).service/user.slice/runc-foo.scope`. 44 | 45 | ### Configuring delegation 46 | Typically, only `memory` and `pids` controllers are delegated to non-root users by default. 47 | 48 | ```console 49 | $ cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers 50 | memory pids 51 | ``` 52 | 53 | To allow delegation of other controllers, you need to change the systemd configuration as follows: 54 | 55 | ```console 56 | # mkdir -p /etc/systemd/system/user@.service.d 57 | # cat > /etc/systemd/system/user@.service.d/delegate.conf << EOF 58 | [Service] 59 | Delegate=cpu cpuset io memory pids 60 | EOF 61 | # systemctl daemon-reload 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/checkpoint-restore.md: -------------------------------------------------------------------------------- 1 | # Checkpoint and Restore # 2 | 3 | For a basic description about checkpointing and restoring containers with 4 | `runc` please see [runc-checkpoint(8)](../man/runc-checkpoint.8.md) and 5 | [runc-restore(8)](../man/runc-restore.8.md). 6 | 7 | ## Checkpoint/Restore Annotations ## 8 | 9 | In addition to specifying options on the command-line like it is described 10 | in the man-pages (see above), it is also possible to influence CRIU's 11 | behaviour using CRIU configuration files. For details about CRIU's 12 | configuration file support please see [CRIU's wiki](https://criu.org/Configuration_files). 13 | 14 | In addition to CRIU's default configuration files `runc` tells CRIU to 15 | also evaluate the file `/etc/criu/runc.conf`. Using the annotation 16 | `org.criu.config` it is, however, possible to change this additional 17 | CRIU configuration file. 18 | 19 | If the annotation `org.criu.config` is set to an empty string `runc` 20 | will not pass any additional configuration file to CRIU. With an empty 21 | string it is therefore possible to disable the additional CRIU configuration 22 | file. This can be used to make sure that no additional configuration file 23 | changes CRIU's behaviour accidentally. 24 | 25 | If the annotation `org.criu.config` is set to a non-empty string `runc` will 26 | pass that string to CRIU to be evaluated as an additional configuration file. 27 | If CRIU cannot open this additional configuration file, it will ignore this 28 | file and continue. 29 | 30 | ### Annotation Example to disable additional CRIU configuration file ### 31 | 32 | ``` 33 | { 34 | "ociVersion": "1.0.0", 35 | "annotations": { 36 | "org.criu.config": "" 37 | }, 38 | "process": { 39 | ``` 40 | 41 | ### Annotation Example to set a specific CRIU configuration file ### 42 | 43 | ``` 44 | { 45 | "ociVersion": "1.0.0", 46 | "annotations": { 47 | "org.criu.config": "/etc/special-runc-criu-options" 48 | }, 49 | "process": { 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/systemd-properties.md: -------------------------------------------------------------------------------- 1 | ## Changing systemd unit properties 2 | 3 | In case runc uses systemd to set cgroup parameters for a container (i.e. 4 | `--systemd-cgroup` CLI flag is set), systemd creates a scope (a.k.a. 5 | transient unit) for the container, usually named like `runc-$ID.scope`. 6 | 7 | The systemd properties of this unit (shown by `systemctl show runc-$ID.scope` 8 | after the container is started) can be modified by adding annotations 9 | to container's runtime spec (`config.json`). For example: 10 | 11 | ```json 12 | "annotations": { 13 | "org.systemd.property.TimeoutStopUSec": "uint64 123456789", 14 | "org.systemd.property.CollectMode":"'inactive-or-failed'" 15 | }, 16 | ``` 17 | 18 | The above will set the following properties: 19 | 20 | * `TimeoutStopSec` to 2 minutes and 3 seconds; 21 | * `CollectMode` to "inactive-or-failed". 22 | 23 | The values must be in the gvariant format (for details, see 24 | [gvariant documentation](https://developer.gnome.org/glib/stable/gvariant-text.html)). 25 | 26 | To find out which type systemd expects for a particular parameter, please 27 | consult systemd sources. 28 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/opencontainers/runc/libcontainer" 9 | "github.com/opencontainers/runc/libcontainer/logs" 10 | _ "github.com/opencontainers/runc/libcontainer/nsenter" 11 | "github.com/sirupsen/logrus" 12 | "github.com/urfave/cli" 13 | ) 14 | 15 | func init() { 16 | if len(os.Args) > 1 && os.Args[1] == "init" { 17 | runtime.GOMAXPROCS(1) 18 | runtime.LockOSThread() 19 | 20 | level := os.Getenv("_LIBCONTAINER_LOGLEVEL") 21 | logLevel, err := logrus.ParseLevel(level) 22 | if err != nil { 23 | panic(fmt.Sprintf("libcontainer: failed to parse log level: %q: %v", level, err)) 24 | } 25 | 26 | err = logs.ConfigureLogging(logs.Config{ 27 | LogPipeFd: os.Getenv("_LIBCONTAINER_LOGPIPE"), 28 | LogFormat: "json", 29 | LogLevel: logLevel, 30 | }) 31 | if err != nil { 32 | panic(fmt.Sprintf("libcontainer: failed to configure logging: %v", err)) 33 | } 34 | logrus.Debug("child process in init()") 35 | } 36 | } 37 | 38 | var initCommand = cli.Command{ 39 | Name: "init", 40 | Usage: `initialize the namespaces and launch the process (do not call it outside of sysbox-runc)`, 41 | Action: func(context *cli.Context) error { 42 | factory, _ := libcontainer.New("") 43 | if err := factory.StartInitialization(); err != nil { 44 | // as the error is sent back to the parent there is no need to log 45 | // or write it to stderr because the parent process will handle this 46 | os.Exit(1) 47 | } 48 | panic("libcontainer: container init failed to exec") 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /kill.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/urfave/cli" 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | var killCommand = cli.Command{ 15 | Name: "kill", 16 | Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process", 17 | ArgsUsage: ` [signal] 18 | 19 | Where "" is the name for the instance of the container and 20 | "[signal]" is the signal to be sent to the init process. 21 | 22 | EXAMPLE: 23 | For example, if the container id is "ubuntu01" the following will send a "KILL" 24 | signal to the init process of the "ubuntu01" container: 25 | 26 | # sysbox-runc kill ubuntu01 KILL`, 27 | Flags: []cli.Flag{ 28 | cli.BoolFlag{ 29 | Name: "all, a", 30 | Usage: "send the specified signal to all processes inside the container", 31 | }, 32 | }, 33 | Action: func(context *cli.Context) error { 34 | if err := checkArgs(context, 1, minArgs); err != nil { 35 | return err 36 | } 37 | if err := checkArgs(context, 2, maxArgs); err != nil { 38 | return err 39 | } 40 | container, err := getContainer(context) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | sigstr := context.Args().Get(1) 46 | if sigstr == "" { 47 | sigstr = "SIGTERM" 48 | } 49 | 50 | signal, err := parseSignal(sigstr) 51 | if err != nil { 52 | return err 53 | } 54 | return container.Signal(signal, context.Bool("all")) 55 | }, 56 | } 57 | 58 | func parseSignal(rawSignal string) (unix.Signal, error) { 59 | s, err := strconv.Atoi(rawSignal) 60 | if err == nil { 61 | return unix.Signal(s), nil 62 | } 63 | sig := strings.ToUpper(rawSignal) 64 | if !strings.HasPrefix(sig, "SIG") { 65 | sig = "SIG" + sig 66 | } 67 | signal := unix.SignalNum(sig) 68 | if signal == 0 { 69 | return -1, fmt.Errorf("unknown signal %q", rawSignal) 70 | } 71 | return signal, nil 72 | } 73 | -------------------------------------------------------------------------------- /libcontainer/apparmor/apparmor_linux.go: -------------------------------------------------------------------------------- 1 | package apparmor 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/opencontainers/runc/libcontainer/utils" 10 | ) 11 | 12 | // IsEnabled returns true if apparmor is enabled for the host. 13 | func IsEnabled() bool { 14 | if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil { 15 | buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") 16 | return err == nil && bytes.HasPrefix(buf, []byte("Y")) 17 | } 18 | return false 19 | } 20 | 21 | func setProcAttr(attr, value string) error { 22 | // Under AppArmor you can only change your own attr, so use /proc/self/ 23 | // instead of /proc// like libapparmor does 24 | f, err := os.OpenFile("/proc/self/attr/"+attr, os.O_WRONLY, 0) 25 | if err != nil { 26 | return err 27 | } 28 | defer f.Close() 29 | 30 | if err := utils.EnsureProcHandle(f); err != nil { 31 | return err 32 | } 33 | 34 | _, err = f.WriteString(value) 35 | return err 36 | } 37 | 38 | // changeOnExec reimplements aa_change_onexec from libapparmor in Go 39 | func changeOnExec(name string) error { 40 | if err := setProcAttr("exec", "exec "+name); err != nil { 41 | return fmt.Errorf("apparmor failed to apply profile: %s", err) 42 | } 43 | return nil 44 | } 45 | 46 | // ApplyProfile will apply the profile with the specified name to the process after 47 | // the next exec. 48 | func ApplyProfile(name string) error { 49 | if name == "" { 50 | return nil 51 | } 52 | 53 | return changeOnExec(name) 54 | } 55 | -------------------------------------------------------------------------------- /libcontainer/apparmor/apparmor_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package apparmor 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | var ErrApparmorNotEnabled = errors.New("apparmor: config provided but apparmor not supported") 10 | 11 | func IsEnabled() bool { 12 | return false 13 | } 14 | 15 | func ApplyProfile(name string) error { 16 | if name != "" { 17 | return ErrApparmorNotEnabled 18 | } 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /libcontainer/cgroups/cgroups_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package cgroups 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestParseCgroups(t *testing.T) { 10 | cgroups, err := ParseCgroupFile("/proc/self/cgroup") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | if IsCgroup2UnifiedMode() { 15 | return 16 | } 17 | if _, ok := cgroups["cpu"]; !ok { 18 | t.Fail() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libcontainer/cgroups/cgroups_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package cgroups 4 | -------------------------------------------------------------------------------- /libcontainer/cgroups/ebpf/ebpf.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "github.com/cilium/ebpf" 5 | "github.com/cilium/ebpf/asm" 6 | "github.com/pkg/errors" 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // LoadAttachCgroupDeviceFilter installs eBPF device filter program to /sys/fs/cgroup/ directory. 11 | // 12 | // Requires the system to be running in cgroup2 unified-mode with kernel >= 4.15 . 13 | // 14 | // https://github.com/torvalds/linux/commit/ebc614f687369f9df99828572b1d85a7c2de3d92 15 | func LoadAttachCgroupDeviceFilter(insts asm.Instructions, license string, dirFD int) (func() error, error) { 16 | nilCloser := func() error { 17 | return nil 18 | } 19 | // Increase `ulimit -l` limit to avoid BPF_PROG_LOAD error (#2167). 20 | // This limit is not inherited into the container. 21 | memlockLimit := &unix.Rlimit{ 22 | Cur: unix.RLIM_INFINITY, 23 | Max: unix.RLIM_INFINITY, 24 | } 25 | _ = unix.Setrlimit(unix.RLIMIT_MEMLOCK, memlockLimit) 26 | spec := &ebpf.ProgramSpec{ 27 | Type: ebpf.CGroupDevice, 28 | Instructions: insts, 29 | License: license, 30 | } 31 | prog, err := ebpf.NewProgram(spec) 32 | if err != nil { 33 | return nilCloser, err 34 | } 35 | if err := prog.Attach(dirFD, ebpf.AttachCGroupDevice, unix.BPF_F_ALLOW_MULTI); err != nil { 36 | return nilCloser, errors.Wrap(err, "failed to call BPF_PROG_ATTACH (BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI)") 37 | } 38 | closer := func() error { 39 | if err := prog.Detach(dirFD, ebpf.AttachCGroupDevice, unix.BPF_F_ALLOW_MULTI); err != nil { 40 | return errors.Wrap(err, "failed to call BPF_PROG_DETACH (BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI)") 41 | } 42 | return nil 43 | } 44 | return closer, nil 45 | } 46 | -------------------------------------------------------------------------------- /libcontainer/cgroups/file_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package cgroups 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestWriteCgroupFileHandlesInterrupt(t *testing.T) { 15 | const ( 16 | memoryCgroupMount = "/sys/fs/cgroup/memory" 17 | memoryLimit = "memory.limit_in_bytes" 18 | ) 19 | if _, err := os.Stat(memoryCgroupMount); err != nil { 20 | // most probably cgroupv2 21 | t.Skip(err) 22 | } 23 | 24 | cgroupName := fmt.Sprintf("test-eint-%d", time.Now().Nanosecond()) 25 | cgroupPath := filepath.Join(memoryCgroupMount, cgroupName) 26 | if err := os.MkdirAll(cgroupPath, 0755); err != nil { 27 | t.Fatal(err) 28 | } 29 | defer os.RemoveAll(cgroupPath) 30 | 31 | if _, err := os.Stat(filepath.Join(cgroupPath, memoryLimit)); err != nil { 32 | // either cgroupv2, or memory controller is not available 33 | t.Skip(err) 34 | } 35 | 36 | for i := 0; i < 100000; i++ { 37 | limit := 1024*1024 + i 38 | if err := WriteFile(cgroupPath, memoryLimit, strconv.Itoa(limit)); err != nil { 39 | t.Fatalf("Failed to write %d on attempt %d: %+v", limit, i, err) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/devices_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 9 | "github.com/opencontainers/runc/libcontainer/devices" 10 | ) 11 | 12 | func TestDevicesSetAllow(t *testing.T) { 13 | helper := NewCgroupTestUtil("devices", t) 14 | defer helper.cleanup() 15 | 16 | helper.writeFileContents(map[string]string{ 17 | "devices.allow": "", 18 | "devices.deny": "", 19 | "devices.list": "a *:* rwm", 20 | }) 21 | 22 | helper.CgroupData.config.Resources.Devices = []*devices.Rule{ 23 | { 24 | Type: devices.CharDevice, 25 | Major: 1, 26 | Minor: 5, 27 | Permissions: devices.Permissions("rwm"), 28 | Allow: true, 29 | }, 30 | } 31 | 32 | d := &DevicesGroup{testingSkipFinalCheck: true} 33 | if err := d.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | // The default deny rule must be written. 38 | value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "devices.deny") 39 | if err != nil { 40 | t.Fatalf("Failed to parse devices.deny: %s", err) 41 | } 42 | if value[0] != 'a' { 43 | t.Errorf("Got the wrong value (%q), set devices.deny failed.", value) 44 | } 45 | 46 | // Permitted rule must be written. 47 | if value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "devices.allow"); err != nil { 48 | t.Fatalf("Failed to parse devices.allow: %s", err) 49 | } else if value != "c 1:5 rwm" { 50 | t.Errorf("Got the wrong value (%q), set devices.allow failed.", value) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/freezer_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 9 | "github.com/opencontainers/runc/libcontainer/configs" 10 | ) 11 | 12 | func TestFreezerSetState(t *testing.T) { 13 | helper := NewCgroupTestUtil("freezer", t) 14 | defer helper.cleanup() 15 | 16 | helper.writeFileContents(map[string]string{ 17 | "freezer.state": string(configs.Frozen), 18 | }) 19 | 20 | helper.CgroupData.config.Resources.Freezer = configs.Thawed 21 | freezer := &FreezerGroup{} 22 | if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "freezer.state") 27 | if err != nil { 28 | t.Fatalf("Failed to parse freezer.state - %s", err) 29 | } 30 | if value != string(configs.Thawed) { 31 | t.Fatal("Got the wrong value, set freezer.state failed.") 32 | } 33 | } 34 | 35 | func TestFreezerSetInvalidState(t *testing.T) { 36 | helper := NewCgroupTestUtil("freezer", t) 37 | defer helper.cleanup() 38 | 39 | const ( 40 | invalidArg configs.FreezerState = "Invalid" 41 | ) 42 | 43 | helper.CgroupData.config.Resources.Freezer = invalidArg 44 | freezer := &FreezerGroup{} 45 | if err := freezer.Set(helper.CgroupPath, helper.CgroupData.config); err == nil { 46 | t.Fatal("Failed to return invalid argument error") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/hugetlb.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/opencontainers/runc/libcontainer/cgroups" 11 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 12 | "github.com/opencontainers/runc/libcontainer/configs" 13 | ) 14 | 15 | type HugetlbGroup struct { 16 | } 17 | 18 | func (s *HugetlbGroup) Name() string { 19 | return "hugetlb" 20 | } 21 | 22 | func (s *HugetlbGroup) Apply(path string, d *cgroupData) error { 23 | return join(path, d.pid) 24 | } 25 | 26 | func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error { 27 | for _, hugetlb := range cgroup.Resources.HugetlbLimit { 28 | if err := fscommon.WriteFile(path, "hugetlb."+hugetlb.Pagesize+".limit_in_bytes", strconv.FormatUint(hugetlb.Limit, 10)); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error { 37 | hugetlbStats := cgroups.HugetlbStats{} 38 | if !cgroups.PathExists(path) { 39 | return nil 40 | } 41 | for _, pageSize := range HugePageSizes { 42 | usage := "hugetlb." + pageSize + ".usage_in_bytes" 43 | value, err := fscommon.GetCgroupParamUint(path, usage) 44 | if err != nil { 45 | return fmt.Errorf("failed to parse %s - %v", usage, err) 46 | } 47 | hugetlbStats.Usage = value 48 | 49 | maxUsage := "hugetlb." + pageSize + ".max_usage_in_bytes" 50 | value, err = fscommon.GetCgroupParamUint(path, maxUsage) 51 | if err != nil { 52 | return fmt.Errorf("failed to parse %s - %v", maxUsage, err) 53 | } 54 | hugetlbStats.MaxUsage = value 55 | 56 | failcnt := "hugetlb." + pageSize + ".failcnt" 57 | value, err = fscommon.GetCgroupParamUint(path, failcnt) 58 | if err != nil { 59 | return fmt.Errorf("failed to parse %s - %v", failcnt, err) 60 | } 61 | hugetlbStats.Failcnt = value 62 | 63 | stats.HugetlbStats[pageSize] = hugetlbStats 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func (s *HugetlbGroup) Clone(source, dest string) error { 70 | 71 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 72 | return err 73 | } 74 | 75 | if err := os.MkdirAll(dest, 0755); err != nil { 76 | return fmt.Errorf("Failed to create cgroup %s", dest) 77 | } 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/name.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups" 10 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 11 | "github.com/opencontainers/runc/libcontainer/configs" 12 | ) 13 | 14 | type NameGroup struct { 15 | GroupName string 16 | Join bool 17 | } 18 | 19 | func (s *NameGroup) Name() string { 20 | return s.GroupName 21 | } 22 | 23 | func (s *NameGroup) Apply(path string, d *cgroupData) error { 24 | if s.Join { 25 | // ignore errors if the named cgroup does not exist 26 | join(path, d.pid) 27 | } 28 | return nil 29 | } 30 | 31 | func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error { 32 | return nil 33 | } 34 | 35 | func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error { 36 | return nil 37 | } 38 | 39 | func (s *NameGroup) Clone(source, dest string) error { 40 | 41 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 42 | return err 43 | } 44 | 45 | if err := os.MkdirAll(dest, 0755); err != nil { 46 | return fmt.Errorf("Failed to create cgroup %s", dest) 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/net_cls.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/opencontainers/runc/libcontainer/cgroups" 11 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 12 | "github.com/opencontainers/runc/libcontainer/configs" 13 | ) 14 | 15 | type NetClsGroup struct { 16 | } 17 | 18 | func (s *NetClsGroup) Name() string { 19 | return "net_cls" 20 | } 21 | 22 | func (s *NetClsGroup) Apply(path string, d *cgroupData) error { 23 | return join(path, d.pid) 24 | } 25 | 26 | func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error { 27 | if cgroup.Resources.NetClsClassid != 0 { 28 | if err := fscommon.WriteFile(path, "net_cls.classid", strconv.FormatUint(uint64(cgroup.Resources.NetClsClassid), 10)); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error { 37 | return nil 38 | } 39 | 40 | func (s *NetClsGroup) Clone(source, dest string) error { 41 | 42 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 43 | return err 44 | } 45 | 46 | if err := os.MkdirAll(dest, 0755); err != nil { 47 | return fmt.Errorf("Failed to create cgroup %s", dest) 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/net_cls_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 10 | ) 11 | 12 | const ( 13 | classidBefore = 0x100002 14 | classidAfter = 0x100001 15 | ) 16 | 17 | func TestNetClsSetClassid(t *testing.T) { 18 | helper := NewCgroupTestUtil("net_cls", t) 19 | defer helper.cleanup() 20 | 21 | helper.writeFileContents(map[string]string{ 22 | "net_cls.classid": strconv.FormatUint(classidBefore, 10), 23 | }) 24 | 25 | helper.CgroupData.config.Resources.NetClsClassid = classidAfter 26 | netcls := &NetClsGroup{} 27 | if err := netcls.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | // As we are in mock environment, we can't get correct value of classid from 32 | // net_cls.classid. 33 | // So. we just judge if we successfully write classid into file 34 | value, err := fscommon.GetCgroupParamUint(helper.CgroupPath, "net_cls.classid") 35 | if err != nil { 36 | t.Fatalf("Failed to parse net_cls.classid - %s", err) 37 | } 38 | if value != classidAfter { 39 | t.Fatal("Got the wrong value, set net_cls.classid failed.") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/net_prio.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups" 10 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 11 | "github.com/opencontainers/runc/libcontainer/configs" 12 | ) 13 | 14 | type NetPrioGroup struct { 15 | } 16 | 17 | func (s *NetPrioGroup) Name() string { 18 | return "net_prio" 19 | } 20 | 21 | func (s *NetPrioGroup) Apply(path string, d *cgroupData) error { 22 | return join(path, d.pid) 23 | } 24 | 25 | func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error { 26 | for _, prioMap := range cgroup.Resources.NetPrioIfpriomap { 27 | if err := fscommon.WriteFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil { 28 | return err 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error { 36 | return nil 37 | } 38 | 39 | func (s *NetPrioGroup) Clone(source, dest string) error { 40 | 41 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 42 | return err 43 | } 44 | 45 | if err := os.MkdirAll(dest, 0755); err != nil { 46 | return fmt.Errorf("Failed to create cgroup %s", dest) 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/net_prio_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 10 | "github.com/opencontainers/runc/libcontainer/configs" 11 | ) 12 | 13 | var ( 14 | prioMap = []*configs.IfPrioMap{ 15 | { 16 | Interface: "test", 17 | Priority: 5, 18 | }, 19 | } 20 | ) 21 | 22 | func TestNetPrioSetIfPrio(t *testing.T) { 23 | helper := NewCgroupTestUtil("net_prio", t) 24 | defer helper.cleanup() 25 | 26 | helper.CgroupData.config.Resources.NetPrioIfpriomap = prioMap 27 | netPrio := &NetPrioGroup{} 28 | if err := netPrio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "net_prio.ifpriomap") 33 | if err != nil { 34 | t.Fatalf("Failed to parse net_prio.ifpriomap - %s", err) 35 | } 36 | if !strings.Contains(value, "test 5") { 37 | t.Fatal("Got the wrong value, set net_prio.ifpriomap failed.") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/perf_event.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups" 10 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 11 | "github.com/opencontainers/runc/libcontainer/configs" 12 | ) 13 | 14 | type PerfEventGroup struct { 15 | } 16 | 17 | func (s *PerfEventGroup) Name() string { 18 | return "perf_event" 19 | } 20 | 21 | func (s *PerfEventGroup) Apply(path string, d *cgroupData) error { 22 | return join(path, d.pid) 23 | } 24 | 25 | func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error { 26 | return nil 27 | } 28 | 29 | func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error { 30 | return nil 31 | } 32 | 33 | func (s *PerfEventGroup) Clone(source, dest string) error { 34 | 35 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 36 | return err 37 | } 38 | 39 | if err := os.MkdirAll(dest, 0755); err != nil { 40 | return fmt.Errorf("Failed to create cgroup %s", dest) 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/pids.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | 11 | "github.com/opencontainers/runc/libcontainer/cgroups" 12 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 13 | "github.com/opencontainers/runc/libcontainer/configs" 14 | ) 15 | 16 | type PidsGroup struct { 17 | } 18 | 19 | func (s *PidsGroup) Name() string { 20 | return "pids" 21 | } 22 | 23 | func (s *PidsGroup) Apply(path string, d *cgroupData) error { 24 | return join(path, d.pid) 25 | } 26 | 27 | func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error { 28 | if cgroup.Resources.PidsLimit != 0 { 29 | // "max" is the fallback value. 30 | limit := "max" 31 | 32 | if cgroup.Resources.PidsLimit > 0 { 33 | limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10) 34 | } 35 | 36 | if err := fscommon.WriteFile(path, "pids.max", limit); err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error { 45 | if !cgroups.PathExists(path) { 46 | return nil 47 | } 48 | current, err := fscommon.GetCgroupParamUint(path, "pids.current") 49 | if err != nil { 50 | return fmt.Errorf("failed to parse pids.current - %s", err) 51 | } 52 | 53 | maxString, err := fscommon.GetCgroupParamString(path, "pids.max") 54 | if err != nil { 55 | return fmt.Errorf("failed to parse pids.max - %s", err) 56 | } 57 | 58 | // Default if pids.max == "max" is 0 -- which represents "no limit". 59 | var max uint64 60 | if maxString != "max" { 61 | max, err = fscommon.ParseUint(maxString, 10, 64) 62 | if err != nil { 63 | return fmt.Errorf("failed to parse pids.max - unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "pids.max")) 64 | } 65 | } 66 | 67 | stats.PidsStats.Current = current 68 | stats.PidsStats.Limit = max 69 | return nil 70 | } 71 | 72 | func (s *PidsGroup) Clone(source, dest string) error { 73 | 74 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 75 | return err 76 | } 77 | 78 | if err := os.MkdirAll(dest, 0755); err != nil { 79 | return fmt.Errorf("Failed to create cgroup %s", dest) 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/rdma.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/opencontainers/runc/libcontainer/cgroups" 8 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 9 | "github.com/opencontainers/runc/libcontainer/configs" 10 | ) 11 | 12 | type RdmaGroup struct{} 13 | 14 | func (s *RdmaGroup) Name() string { 15 | return "rdma" 16 | } 17 | 18 | func (s *RdmaGroup) Apply(path string, d *cgroupData) error { 19 | return join(path, d.pid) 20 | } 21 | 22 | func (s *RdmaGroup) Set(path string, cgroup *configs.Cgroup) error { 23 | return fscommon.RdmaSet(path, cgroup) 24 | } 25 | 26 | func (s *RdmaGroup) GetStats(path string, stats *cgroups.Stats) error { 27 | return fscommon.RdmaGetStats(path, stats) 28 | } 29 | 30 | func (s *RdmaGroup) Clone(source, dest string) error { 31 | 32 | if err := fscommon.WriteFile(source, "cgroup.clone_children", "1"); err != nil { 33 | return err 34 | } 35 | 36 | if err := os.MkdirAll(dest, 0755); err != nil { 37 | return fmt.Errorf("Failed to create cgroup %s", dest) 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package fs 4 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs/util_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | /* 4 | Utility for testing cgroup operations. 5 | 6 | Creates a mock of the cgroup filesystem for the duration of the test. 7 | */ 8 | package fs 9 | 10 | import ( 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "testing" 15 | 16 | "github.com/opencontainers/runc/libcontainer/cgroups" 17 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 18 | "github.com/opencontainers/runc/libcontainer/configs" 19 | ) 20 | 21 | func init() { 22 | cgroups.TestMode = true 23 | } 24 | 25 | type cgroupTestUtil struct { 26 | // cgroup data to use in tests. 27 | CgroupData *cgroupData 28 | 29 | // Path to the mock cgroup directory. 30 | CgroupPath string 31 | 32 | // Temporary directory to store mock cgroup filesystem. 33 | tempDir string 34 | t *testing.T 35 | } 36 | 37 | // Creates a new test util for the specified subsystem 38 | func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { 39 | d := &cgroupData{ 40 | config: &configs.Cgroup{}, 41 | } 42 | d.config.Resources = &configs.Resources{} 43 | tempDir, err := ioutil.TempDir("", "cgroup_test") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | d.root = tempDir 48 | testCgroupPath := filepath.Join(d.root, subsystem) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | // Ensure the full mock cgroup path exists. 54 | err = os.MkdirAll(testCgroupPath, 0755) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} 59 | } 60 | 61 | func (c *cgroupTestUtil) cleanup() { 62 | os.RemoveAll(c.tempDir) 63 | } 64 | 65 | // Write the specified contents on the mock of the specified cgroup files. 66 | func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { 67 | for file, contents := range fileContents { 68 | err := fscommon.WriteFile(c.CgroupPath, file, contents) 69 | if err != nil { 70 | c.t.Fatal(err) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/cpu.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs2 4 | 5 | import ( 6 | "bufio" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/opencontainers/runc/libcontainer/cgroups" 11 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 12 | "github.com/opencontainers/runc/libcontainer/configs" 13 | ) 14 | 15 | func isCpuSet(cgroup *configs.Cgroup) bool { 16 | return cgroup.Resources.CpuWeight != 0 || cgroup.Resources.CpuQuota != 0 || cgroup.Resources.CpuPeriod != 0 17 | } 18 | 19 | func setCpu(dirPath string, cgroup *configs.Cgroup) error { 20 | if !isCpuSet(cgroup) { 21 | return nil 22 | } 23 | r := cgroup.Resources 24 | 25 | // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility. 26 | if r.CpuWeight != 0 { 27 | if err := fscommon.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(r.CpuWeight, 10)); err != nil { 28 | return err 29 | } 30 | } 31 | 32 | if r.CpuQuota != 0 || r.CpuPeriod != 0 { 33 | str := "max" 34 | if r.CpuQuota > 0 { 35 | str = strconv.FormatInt(r.CpuQuota, 10) 36 | } 37 | period := r.CpuPeriod 38 | if period == 0 { 39 | // This default value is documented in 40 | // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html 41 | period = 100000 42 | } 43 | str += " " + strconv.FormatUint(period, 10) 44 | if err := fscommon.WriteFile(dirPath, "cpu.max", str); err != nil { 45 | return err 46 | } 47 | } 48 | 49 | return nil 50 | } 51 | func statCpu(dirPath string, stats *cgroups.Stats) error { 52 | f, err := fscommon.OpenFile(dirPath, "cpu.stat", os.O_RDONLY) 53 | if err != nil { 54 | return err 55 | } 56 | defer f.Close() 57 | 58 | sc := bufio.NewScanner(f) 59 | for sc.Scan() { 60 | t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text()) 61 | if err != nil { 62 | return err 63 | } 64 | switch t { 65 | case "usage_usec": 66 | stats.CpuStats.CpuUsage.TotalUsage = v * 1000 67 | 68 | case "user_usec": 69 | stats.CpuStats.CpuUsage.UsageInUsermode = v * 1000 70 | 71 | case "system_usec": 72 | stats.CpuStats.CpuUsage.UsageInKernelmode = v * 1000 73 | } 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/cpuset.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs2 4 | 5 | import ( 6 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 7 | "github.com/opencontainers/runc/libcontainer/configs" 8 | ) 9 | 10 | func isCpusetSet(cgroup *configs.Cgroup) bool { 11 | return cgroup.Resources.CpusetCpus != "" || cgroup.Resources.CpusetMems != "" 12 | } 13 | 14 | func setCpuset(dirPath string, cgroup *configs.Cgroup) error { 15 | if !isCpusetSet(cgroup) { 16 | return nil 17 | } 18 | 19 | if cgroup.Resources.CpusetCpus != "" { 20 | if err := fscommon.WriteFile(dirPath, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil { 21 | return err 22 | } 23 | } 24 | if cgroup.Resources.CpusetMems != "" { 25 | if err := fscommon.WriteFile(dirPath, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil { 26 | return err 27 | } 28 | } 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/defaultpath_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright The containerd Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fs2 18 | 19 | import ( 20 | "path/filepath" 21 | "strings" 22 | "testing" 23 | 24 | "github.com/opencontainers/runc/libcontainer/cgroups" 25 | ) 26 | 27 | func TestParseCgroupFromReader(t *testing.T) { 28 | cases := map[string]string{ 29 | "0::/user.slice/user-1001.slice/session-1.scope\n": "/user.slice/user-1001.slice/session-1.scope", 30 | "2:cpuset:/foo\n1:name=systemd:/\n": "", 31 | "2:cpuset:/foo\n1:name=systemd:/\n0::/user.slice/user-1001.slice/session-1.scope\n": "/user.slice/user-1001.slice/session-1.scope", 32 | } 33 | for s, expected := range cases { 34 | g, err := parseCgroupFromReader(strings.NewReader(s)) 35 | if expected != "" { 36 | if g != expected { 37 | t.Errorf("expected %q, got %q", expected, g) 38 | } 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | } else { 43 | if err == nil { 44 | t.Error("error is expected") 45 | } 46 | } 47 | } 48 | } 49 | 50 | func TestDefaultDirPath(t *testing.T) { 51 | if !cgroups.IsCgroup2UnifiedMode() { 52 | t.Skip("need cgroupv2") 53 | } 54 | // same code as in defaultDirPath() 55 | ownCgroup, err := parseCgroupFile("/proc/self/cgroup") 56 | if err != nil { 57 | // Not a test failure, but rather some weird 58 | // environment so we can't run this test. 59 | t.Skipf("can't get own cgroup: %v", err) 60 | } 61 | ownCgroup = filepath.Dir(ownCgroup) 62 | 63 | cases := []struct { 64 | cgPath string 65 | cgParent string 66 | cgName string 67 | expected string 68 | }{ 69 | { 70 | cgPath: "/foo/bar", 71 | expected: "/sys/fs/cgroup/foo/bar", 72 | }, 73 | { 74 | cgPath: "foo/bar", 75 | expected: filepath.Join(UnifiedMountpoint, ownCgroup, "foo/bar"), 76 | }, 77 | } 78 | for _, c := range cases { 79 | got, err := _defaultDirPath(UnifiedMountpoint, c.cgPath, c.cgParent, c.cgName) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | if got != c.expected { 84 | t.Fatalf("expected %q, got %q", c.expected, got) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/devices.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs2 4 | 5 | import ( 6 | "github.com/opencontainers/runc/libcontainer/cgroups/ebpf" 7 | "github.com/opencontainers/runc/libcontainer/cgroups/ebpf/devicefilter" 8 | "github.com/opencontainers/runc/libcontainer/configs" 9 | "github.com/opencontainers/runc/libcontainer/devices" 10 | "github.com/pkg/errors" 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | func isRWM(perms devices.Permissions) bool { 15 | var r, w, m bool 16 | for _, perm := range perms { 17 | switch perm { 18 | case 'r': 19 | r = true 20 | case 'w': 21 | w = true 22 | case 'm': 23 | m = true 24 | } 25 | } 26 | return r && w && m 27 | } 28 | 29 | // the logic is from crun 30 | // https://github.com/containers/crun/blob/0.10.2/src/libcrun/cgroup.c#L1644-L1652 31 | func canSkipEBPFError(cgroup *configs.Cgroup) bool { 32 | for _, dev := range cgroup.Resources.Devices { 33 | if dev.Allow || !isRWM(dev.Permissions) { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | 40 | func setDevices(dirPath string, cgroup *configs.Cgroup) error { 41 | if cgroup.SkipDevices { 42 | return nil 43 | } 44 | // XXX: This is currently a white-list (but all callers pass a blacklist of 45 | // devices). This is bad for a whole variety of reasons, but will need 46 | // to be fixed with co-ordinated effort with downstreams. 47 | devices := cgroup.Devices 48 | insts, license, err := devicefilter.DeviceFilter(devices) 49 | if err != nil { 50 | return err 51 | } 52 | dirFD, err := unix.Open(dirPath, unix.O_DIRECTORY|unix.O_RDONLY, 0600) 53 | if err != nil { 54 | return errors.Errorf("cannot get dir FD for %s", dirPath) 55 | } 56 | defer unix.Close(dirFD) 57 | // XXX: This code is currently incorrect when it comes to updating an 58 | // existing cgroup with new rules (new rulesets are just appended to 59 | // the program list because this uses BPF_F_ALLOW_MULTI). If we didn't 60 | // use BPF_F_ALLOW_MULTI we could actually atomically swap the 61 | // programs. 62 | // 63 | // The real issue is that BPF_F_ALLOW_MULTI makes it hard to have a 64 | // race-free blacklist because it acts as a whitelist by default, and 65 | // having a deny-everything program cannot be overridden by other 66 | // programs. You could temporarily insert a deny-everything program 67 | // but that would result in spurrious failures during updates. 68 | if _, err := ebpf.LoadAttachCgroupDeviceFilter(insts, license, dirFD); err != nil { 69 | if !canSkipEBPFError(cgroup) { 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/freezer.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs2 4 | 5 | import ( 6 | stdErrors "errors" 7 | "os" 8 | "strings" 9 | 10 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 11 | "github.com/opencontainers/runc/libcontainer/configs" 12 | "github.com/pkg/errors" 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | func setFreezer(dirPath string, state configs.FreezerState) error { 17 | if err := supportsFreezer(dirPath); err != nil { 18 | // We can ignore this request as long as the user didn't ask us to 19 | // freeze the container (since without the freezer cgroup, that's a 20 | // no-op). 21 | if state == configs.Undefined || state == configs.Thawed { 22 | return nil 23 | } 24 | return errors.Wrap(err, "freezer not supported") 25 | } 26 | 27 | var stateStr string 28 | switch state { 29 | case configs.Undefined: 30 | return nil 31 | case configs.Frozen: 32 | stateStr = "1" 33 | case configs.Thawed: 34 | stateStr = "0" 35 | default: 36 | return errors.Errorf("invalid freezer state %q requested", state) 37 | } 38 | 39 | if err := fscommon.WriteFile(dirPath, "cgroup.freeze", stateStr); err != nil { 40 | return err 41 | } 42 | // Confirm that the cgroup did actually change states. 43 | if actualState, err := getFreezer(dirPath); err != nil { 44 | return err 45 | } else if actualState != state { 46 | return errors.Errorf(`expected "cgroup.freeze" to be in state %q but was in %q`, state, actualState) 47 | } 48 | return nil 49 | } 50 | 51 | func supportsFreezer(dirPath string) error { 52 | _, err := fscommon.ReadFile(dirPath, "cgroup.freeze") 53 | return err 54 | } 55 | 56 | func getFreezer(dirPath string) (configs.FreezerState, error) { 57 | state, err := fscommon.ReadFile(dirPath, "cgroup.freeze") 58 | if err != nil { 59 | // If the kernel is too old, then we just treat the freezer as being in 60 | // an "undefined" state. 61 | if os.IsNotExist(err) || stdErrors.Is(err, unix.ENODEV) { 62 | err = nil 63 | } 64 | return configs.Undefined, err 65 | } 66 | switch strings.TrimSpace(state) { 67 | case "0": 68 | return configs.Thawed, nil 69 | case "1": 70 | return configs.Frozen, nil 71 | default: 72 | return configs.Undefined, errors.Errorf(`unknown "cgroup.freeze" state: %q`, state) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/hugetlb.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs2 4 | 5 | import ( 6 | "strconv" 7 | 8 | "github.com/pkg/errors" 9 | 10 | "github.com/opencontainers/runc/libcontainer/cgroups" 11 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 12 | "github.com/opencontainers/runc/libcontainer/configs" 13 | ) 14 | 15 | func isHugeTlbSet(cgroup *configs.Cgroup) bool { 16 | return len(cgroup.Resources.HugetlbLimit) > 0 17 | } 18 | 19 | func setHugeTlb(dirPath string, cgroup *configs.Cgroup) error { 20 | if !isHugeTlbSet(cgroup) { 21 | return nil 22 | } 23 | for _, hugetlb := range cgroup.Resources.HugetlbLimit { 24 | if err := fscommon.WriteFile(dirPath, "hugetlb."+hugetlb.Pagesize+".max", strconv.FormatUint(hugetlb.Limit, 10)); err != nil { 25 | return err 26 | } 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func statHugeTlb(dirPath string, stats *cgroups.Stats) error { 33 | hugePageSizes, err := cgroups.GetHugePageSize() 34 | if err != nil { 35 | return errors.Wrap(err, "failed to fetch hugetlb info") 36 | } 37 | hugetlbStats := cgroups.HugetlbStats{} 38 | 39 | for _, pagesize := range hugePageSizes { 40 | value, err := fscommon.GetCgroupParamUint(dirPath, "hugetlb."+pagesize+".current") 41 | if err != nil { 42 | return err 43 | } 44 | hugetlbStats.Usage = value 45 | 46 | fileName := "hugetlb." + pagesize + ".events" 47 | contents, err := fscommon.ReadFile(dirPath, fileName) 48 | if err != nil { 49 | return errors.Wrap(err, "failed to read stats") 50 | } 51 | _, value, err = fscommon.GetCgroupParamKeyValue(contents) 52 | if err != nil { 53 | return errors.Wrap(err, "failed to parse "+fileName) 54 | } 55 | hugetlbStats.Failcnt = value 56 | 57 | stats.HugetlbStats[pagesize] = hugetlbStats 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fs2/pids.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fs2 4 | 5 | import ( 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups" 10 | "github.com/opencontainers/runc/libcontainer/cgroups/fscommon" 11 | "github.com/opencontainers/runc/libcontainer/configs" 12 | "github.com/pkg/errors" 13 | "golang.org/x/sys/unix" 14 | ) 15 | 16 | func isPidsSet(cgroup *configs.Cgroup) bool { 17 | return cgroup.Resources.PidsLimit != 0 18 | } 19 | 20 | func setPids(dirPath string, cgroup *configs.Cgroup) error { 21 | if !isPidsSet(cgroup) { 22 | return nil 23 | } 24 | if val := numToStr(cgroup.Resources.PidsLimit); val != "" { 25 | if err := fscommon.WriteFile(dirPath, "pids.max", val); err != nil { 26 | return err 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func statPidsWithoutController(dirPath string, stats *cgroups.Stats) error { 34 | // if the controller is not enabled, let's read PIDS from cgroups.procs 35 | // (or threads if cgroup.threads is enabled) 36 | contents, err := fscommon.ReadFile(dirPath, "cgroup.procs") 37 | if errors.Is(err, unix.ENOTSUP) { 38 | contents, err = fscommon.ReadFile(dirPath, "cgroup.threads") 39 | } 40 | if err != nil { 41 | return err 42 | } 43 | pids := make(map[string]string) 44 | for _, i := range strings.Split(contents, "\n") { 45 | if i != "" { 46 | pids[i] = i 47 | } 48 | } 49 | stats.PidsStats.Current = uint64(len(pids)) 50 | stats.PidsStats.Limit = 0 51 | return nil 52 | } 53 | 54 | func statPids(dirPath string, stats *cgroups.Stats) error { 55 | current, err := fscommon.GetCgroupParamUint(dirPath, "pids.current") 56 | if err != nil { 57 | return errors.Wrap(err, "failed to parse pids.current") 58 | } 59 | 60 | maxString, err := fscommon.GetCgroupParamString(dirPath, "pids.max") 61 | if err != nil { 62 | return errors.Wrap(err, "failed to parse pids.max") 63 | } 64 | 65 | // Default if pids.max == "max" is 0 -- which represents "no limit". 66 | var max uint64 67 | if maxString != "max" { 68 | max, err = fscommon.ParseUint(maxString, 10, 64) 69 | if err != nil { 70 | return errors.Wrapf(err, "failed to parse pids.max - unable to parse %q as a uint from Cgroup file %q", 71 | maxString, filepath.Join(dirPath, "pids.max")) 72 | } 73 | } 74 | 75 | stats.PidsStats.Current = current 76 | stats.PidsStats.Limit = max 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fscommon/rdma_test.go: -------------------------------------------------------------------------------- 1 | package fscommon 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/opencontainers/runc/libcontainer/configs" 9 | ) 10 | 11 | /* Roadmap for future */ 12 | // (Low-priority) TODO: Check if it is possible to virtually mimic an actual RDMA device. 13 | // TODO: Think of more edge-cases to add. 14 | 15 | // TestRdmaSet performs an E2E test of RdmaSet(), parseRdmaKV() using dummy device and a dummy cgroup file-system. 16 | // Note: Following test does not guarantees that your host supports RDMA since this mocks underlying infrastructure. 17 | func TestRdmaSet(t *testing.T) { 18 | 19 | // XXX: sysbox-runc: 20 | // Test fails with: rdma_test.go:44: open /tmp/TestRdmaSet933593826/001/rdma/rdma.max: no such file or directory 21 | // Skip for now. 22 | return 23 | 24 | testCgroupPath := filepath.Join(t.TempDir(), "rdma") 25 | 26 | // Ensure the full mock cgroup path exists. 27 | err := os.Mkdir(testCgroupPath, 0o755) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | rdmaDevice := "mlx5_1" 33 | maxHandles := uint32(100) 34 | maxObjects := uint32(300) 35 | 36 | rdmaStubResource := &configs.Resources{ 37 | Rdma: map[string]configs.LinuxRdma{ 38 | rdmaDevice: { 39 | HcaHandles: &maxHandles, 40 | HcaObjects: &maxObjects, 41 | }, 42 | }, 43 | } 44 | 45 | cgroup := &configs.Cgroup{ 46 | Resources: rdmaStubResource, 47 | } 48 | 49 | if err := RdmaSet(testCgroupPath, cgroup); err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | // The default rdma.max must be written. 54 | rdmaEntries, err := readRdmaEntries(testCgroupPath, "rdma.max") 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if len(rdmaEntries) != 1 { 59 | t.Fatal("rdma_test: Got the wrong values while parsing entries from rdma.max") 60 | } 61 | if rdmaEntries[0].HcaHandles != maxHandles { 62 | t.Fatalf("rdma_test: Got the wrong value for hca_handles") 63 | } 64 | if rdmaEntries[0].HcaObjects != maxObjects { 65 | t.Fatalf("rdma_test: Got the wrong value for hca_Objects") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /libcontainer/cgroups/fscommon/utils_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package fscommon 4 | 5 | import ( 6 | "io/ioutil" 7 | "math" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "testing" 12 | ) 13 | 14 | const ( 15 | cgroupFile = "cgroup.file" 16 | floatValue = 2048.0 17 | floatString = "2048" 18 | ) 19 | 20 | func TestGetCgroupParamsInt(t *testing.T) { 21 | // Setup tempdir. 22 | tempDir, err := ioutil.TempDir("", "cgroup_utils_test") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | defer os.RemoveAll(tempDir) 27 | tempFile := filepath.Join(tempDir, cgroupFile) 28 | 29 | // Success. 30 | err = ioutil.WriteFile(tempFile, []byte(floatString), 0755) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | value, err := GetCgroupParamUint(tempDir, cgroupFile) 35 | if err != nil { 36 | t.Fatal(err) 37 | } else if value != floatValue { 38 | t.Fatalf("Expected %d to equal %f", value, floatValue) 39 | } 40 | 41 | // Success with new line. 42 | err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | value, err = GetCgroupParamUint(tempDir, cgroupFile) 47 | if err != nil { 48 | t.Fatal(err) 49 | } else if value != floatValue { 50 | t.Fatalf("Expected %d to equal %f", value, floatValue) 51 | } 52 | 53 | // Success with negative values 54 | err = ioutil.WriteFile(tempFile, []byte("-12345"), 0755) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | value, err = GetCgroupParamUint(tempDir, cgroupFile) 59 | if err != nil { 60 | t.Fatal(err) 61 | } else if value != 0 { 62 | t.Fatalf("Expected %d to equal %d", value, 0) 63 | } 64 | 65 | // Success with negative values lesser than min int64 66 | s := strconv.FormatFloat(math.MinInt64, 'f', -1, 64) 67 | err = ioutil.WriteFile(tempFile, []byte(s), 0755) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | value, err = GetCgroupParamUint(tempDir, cgroupFile) 72 | if err != nil { 73 | t.Fatal(err) 74 | } else if value != 0 { 75 | t.Fatalf("Expected %d to equal %d", value, 0) 76 | } 77 | 78 | // Not a float. 79 | err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | _, err = GetCgroupParamUint(tempDir, cgroupFile) 84 | if err == nil { 85 | t.Fatal("Expecting error, got none") 86 | } 87 | 88 | // Unknown file. 89 | err = os.Remove(tempFile) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | _, err = GetCgroupParamUint(tempDir, cgroupFile) 94 | if err == nil { 95 | t.Fatal("Expecting error, got none") 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /libcontainer/cgroups/systemd/cpuset.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "encoding/binary" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/willf/bitset" 10 | ) 11 | 12 | // rangeToBits converts a text representation of a CPU mask (as written to 13 | // or read from cgroups' cpuset.* files, e.g. "1,3-5") to a slice of bytes 14 | // with the corresponding bits set (as consumed by systemd over dbus as 15 | // AllowedCPUs/AllowedMemoryNodes unit property value). 16 | func rangeToBits(str string) ([]byte, error) { 17 | bits := &bitset.BitSet{} 18 | 19 | for _, r := range strings.Split(str, ",") { 20 | // allow extra spaces around 21 | r = strings.TrimSpace(r) 22 | // allow empty elements (extra commas) 23 | if r == "" { 24 | continue 25 | } 26 | ranges := strings.SplitN(r, "-", 2) 27 | if len(ranges) > 1 { 28 | start, err := strconv.ParseUint(ranges[0], 10, 32) 29 | if err != nil { 30 | return nil, err 31 | } 32 | end, err := strconv.ParseUint(ranges[1], 10, 32) 33 | if err != nil { 34 | return nil, err 35 | } 36 | if start > end { 37 | return nil, errors.New("invalid range: " + r) 38 | } 39 | for i := uint(start); i <= uint(end); i++ { 40 | bits.Set(i) 41 | } 42 | } else { 43 | val, err := strconv.ParseUint(ranges[0], 10, 32) 44 | if err != nil { 45 | return nil, err 46 | } 47 | bits.Set(uint(val)) 48 | } 49 | } 50 | 51 | val := bits.Bytes() 52 | if len(val) == 0 { 53 | // do not allow empty values 54 | return nil, errors.New("empty value") 55 | } 56 | ret := make([]byte, len(val)*8) 57 | for i := range val { 58 | // bitset uses BigEndian internally 59 | binary.BigEndian.PutUint64(ret[i*8:], val[len(val)-1-i]) 60 | } 61 | // remove upper all-zero bytes 62 | for ret[0] == 0 { 63 | ret = ret[1:] 64 | } 65 | 66 | return ret, nil 67 | } 68 | -------------------------------------------------------------------------------- /libcontainer/cgroups/systemd/cpuset_test.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestRangeToBits(t *testing.T) { 9 | testCases := []struct { 10 | in string 11 | out []byte 12 | isErr bool 13 | }{ 14 | {in: "", isErr: true}, 15 | {in: "0", out: []byte{1}}, 16 | {in: "1", out: []byte{2}}, 17 | {in: "0-1", out: []byte{3}}, 18 | {in: "0,1", out: []byte{3}}, 19 | {in: ",0,1,", out: []byte{3}}, 20 | {in: "0-3", out: []byte{0x0f}}, 21 | {in: "0,1,2-3", out: []byte{0x0f}}, 22 | {in: "4-7", out: []byte{0xf0}}, 23 | {in: "0-7", out: []byte{0xff}}, 24 | {in: "0-15", out: []byte{0xff, 0xff}}, 25 | {in: "16", out: []byte{1, 0, 0}}, 26 | {in: "0-3,32-33", out: []byte{3, 0, 0, 0, 0x0f}}, 27 | // extra spaces and tabs are ok 28 | {in: "1, 2, 1-2", out: []byte{6}}, 29 | {in: " , 1 , 3 , 5-7, ", out: []byte{0xea}}, 30 | // somewhat large values 31 | {in: "128-130,1", out: []byte{7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}}, 32 | 33 | {in: "-", isErr: true}, 34 | {in: "1-", isErr: true}, 35 | {in: "-3", isErr: true}, 36 | // bad range (start > end) 37 | {in: "54-53", isErr: true}, 38 | // kernel does not allow extra spaces inside a range 39 | {in: "1 - 2", isErr: true}, 40 | } 41 | 42 | for _, tc := range testCases { 43 | out, err := rangeToBits(tc.in) 44 | if err != nil { 45 | if !tc.isErr { 46 | t.Errorf("case %q: unexpected error: %v", tc.in, err) 47 | } 48 | 49 | continue 50 | } 51 | if !bytes.Equal(out, tc.out) { 52 | t.Errorf("case %q: expected %v, got %v", tc.in, tc.out, out) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /libcontainer/cgroups/systemd/systemd_test.go: -------------------------------------------------------------------------------- 1 | package systemd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSystemdVersion(t *testing.T) { 8 | var systemdVersionTests = []struct { 9 | verStr string 10 | expectedVer int 11 | expectErr bool 12 | }{ 13 | {`"219"`, 219, false}, 14 | {`"v245.4-1.fc32"`, 245, false}, 15 | {`"241-1"`, 241, false}, 16 | {`"v241-1"`, 241, false}, 17 | {"NaN", 0, true}, 18 | {"", 0, true}, 19 | } 20 | for _, sdTest := range systemdVersionTests { 21 | ver, err := systemdVersionAtoi(sdTest.verStr) 22 | if !sdTest.expectErr && err != nil { 23 | t.Errorf("systemdVersionAtoi(%s); want nil; got %v", sdTest.verStr, err) 24 | } 25 | if sdTest.expectErr && err == nil { 26 | t.Errorf("systemdVersionAtoi(%s); wanted failure; got nil", sdTest.verStr) 27 | } 28 | if ver != sdTest.expectedVer { 29 | t.Errorf("systemdVersionAtoi(%s); want %d; got %d", sdTest.verStr, sdTest.expectedVer, ver) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libcontainer/cgroups/systemd/unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package systemd 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/opencontainers/runc/libcontainer/cgroups" 10 | "github.com/opencontainers/runc/libcontainer/configs" 11 | ) 12 | 13 | type Manager struct { 14 | Cgroups *configs.Cgroup 15 | Paths map[string]string 16 | } 17 | 18 | func IsRunningSystemd() bool { 19 | return false 20 | } 21 | 22 | func NewSystemdCgroupsManager() (func(config *configs.Cgroup, paths map[string]string) cgroups.Manager, error) { 23 | return nil, errors.New("Systemd not supported") 24 | } 25 | 26 | func (m *Manager) Apply(pid int) error { 27 | return errors.New("Systemd not supported") 28 | } 29 | 30 | func (m *Manager) GetPids() ([]int, error) { 31 | return nil, errors.New("Systemd not supported") 32 | } 33 | 34 | func (m *Manager) GetAllPids() ([]int, error) { 35 | return nil, errors.New("Systemd not supported") 36 | } 37 | 38 | func (m *Manager) Destroy() error { 39 | return errors.New("Systemd not supported") 40 | } 41 | 42 | func (m *Manager) GetPaths() map[string]string { 43 | return nil 44 | } 45 | 46 | func (m *Manager) Path(_ string) string { 47 | return "" 48 | } 49 | 50 | func (m *Manager) GetStats() (*cgroups.Stats, error) { 51 | return nil, errors.New("Systemd not supported") 52 | } 53 | 54 | func (m *Manager) Set(container *configs.Config) error { 55 | return errors.New("Systemd not supported") 56 | } 57 | 58 | func (m *Manager) Freeze(state configs.FreezerState) error { 59 | return errors.New("Systemd not supported") 60 | } 61 | 62 | func Freeze(c *configs.Cgroup, state configs.FreezerState) error { 63 | return errors.New("Systemd not supported") 64 | } 65 | 66 | func (m *Manager) GetCgroups() (*configs.Cgroup, error) { 67 | return nil, errors.New("Systemd not supported") 68 | } 69 | 70 | func (m *Manager) Exists() bool { 71 | return false 72 | } 73 | 74 | func (m *Manager) CreateChildCgroup(container *configs.Config) error { 75 | return fmt.Errorf("Systemd not supported") 76 | } 77 | 78 | func (m *Manager) ApplyChildCgroup(pid int) error { 79 | return fmt.Errorf("Systemd not supported") 80 | } 81 | 82 | func (m *Manager) GetChildCgroupPaths() map[string]string { 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /libcontainer/configs/blkio_device.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import "fmt" 4 | 5 | // blockIODevice holds major:minor format supported in blkio cgroup 6 | type blockIODevice struct { 7 | // Major is the device's major number 8 | Major int64 `json:"major"` 9 | // Minor is the device's minor number 10 | Minor int64 `json:"minor"` 11 | } 12 | 13 | // WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair 14 | type WeightDevice struct { 15 | blockIODevice 16 | // Weight is the bandwidth rate for the device, range is from 10 to 1000 17 | Weight uint16 `json:"weight"` 18 | // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only 19 | LeafWeight uint16 `json:"leafWeight"` 20 | } 21 | 22 | // NewWeightDevice returns a configured WeightDevice pointer 23 | func NewWeightDevice(major, minor int64, weight, leafWeight uint16) *WeightDevice { 24 | wd := &WeightDevice{} 25 | wd.Major = major 26 | wd.Minor = minor 27 | wd.Weight = weight 28 | wd.LeafWeight = leafWeight 29 | return wd 30 | } 31 | 32 | // WeightString formats the struct to be writable to the cgroup specific file 33 | func (wd *WeightDevice) WeightString() string { 34 | return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight) 35 | } 36 | 37 | // LeafWeightString formats the struct to be writable to the cgroup specific file 38 | func (wd *WeightDevice) LeafWeightString() string { 39 | return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight) 40 | } 41 | 42 | // ThrottleDevice struct holds a `major:minor rate_per_second` pair 43 | type ThrottleDevice struct { 44 | blockIODevice 45 | // Rate is the IO rate limit per cgroup per device 46 | Rate uint64 `json:"rate"` 47 | } 48 | 49 | // NewThrottleDevice returns a configured ThrottleDevice pointer 50 | func NewThrottleDevice(major, minor int64, rate uint64) *ThrottleDevice { 51 | td := &ThrottleDevice{} 52 | td.Major = major 53 | td.Minor = minor 54 | td.Rate = rate 55 | return td 56 | } 57 | 58 | // String formats the struct to be writable to the cgroup specific file 59 | func (td *ThrottleDevice) String() string { 60 | return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate) 61 | } 62 | 63 | // StringName formats the struct to be writable to the cgroup specific file 64 | func (td *ThrottleDevice) StringName(name string) string { 65 | return fmt.Sprintf("%d:%d %s=%d", td.Major, td.Minor, name, td.Rate) 66 | } 67 | -------------------------------------------------------------------------------- /libcontainer/configs/cgroup_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package configs 4 | 5 | // TODO Windows: This can ultimately be entirely factored out on Windows as 6 | // cgroups are a Unix-specific construct. 7 | type Cgroup struct { 8 | } 9 | -------------------------------------------------------------------------------- /libcontainer/configs/config_linux.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import "fmt" 4 | 5 | // HostUID gets the translated uid for the process on host which could be 6 | // different when user namespaces are enabled. 7 | func (c Config) HostUID(containerId int) (int, error) { 8 | if c.Namespaces.Contains(NEWUSER) { 9 | if c.UidMappings == nil { 10 | return -1, fmt.Errorf("User namespaces enabled, but no uid mappings found.") 11 | } 12 | id, found := c.hostIDFromMapping(containerId, c.UidMappings) 13 | if !found { 14 | return -1, fmt.Errorf("User namespaces enabled, but no user mapping found.") 15 | } 16 | return id, nil 17 | } 18 | // Return unchanged id. 19 | return containerId, nil 20 | } 21 | 22 | // HostRootUID gets the root uid for the process on host which could be non-zero 23 | // when user namespaces are enabled. 24 | func (c Config) HostRootUID() (int, error) { 25 | return c.HostUID(0) 26 | } 27 | 28 | // HostGID gets the translated gid for the process on host which could be 29 | // different when user namespaces are enabled. 30 | func (c Config) HostGID(containerId int) (int, error) { 31 | if c.Namespaces.Contains(NEWUSER) { 32 | if c.GidMappings == nil { 33 | return -1, fmt.Errorf("User namespaces enabled, but no gid mappings found.") 34 | } 35 | id, found := c.hostIDFromMapping(containerId, c.GidMappings) 36 | if !found { 37 | return -1, fmt.Errorf("User namespaces enabled, but no group mapping found.") 38 | } 39 | return id, nil 40 | } 41 | // Return unchanged id. 42 | return containerId, nil 43 | } 44 | 45 | // HostRootGID gets the root gid for the process on host which could be non-zero 46 | // when user namespaces are enabled. 47 | func (c Config) HostRootGID() (int, error) { 48 | return c.HostGID(0) 49 | } 50 | 51 | // Utility function that gets a host ID for a container ID from user namespace map 52 | // if that ID is present in the map. 53 | func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) { 54 | for _, m := range uMap { 55 | if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) { 56 | hostID := m.HostID + (containerID - m.ContainerID) 57 | return hostID, true 58 | } 59 | } 60 | return -1, false 61 | } 62 | -------------------------------------------------------------------------------- /libcontainer/configs/config_linux_test.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRemoveNamespace(t *testing.T) { 8 | ns := Namespaces{ 9 | {Type: NEWNET}, 10 | } 11 | if !ns.Remove(NEWNET) { 12 | t.Fatal("NEWNET was not removed") 13 | } 14 | if len(ns) != 0 { 15 | t.Fatalf("namespaces should have 0 items but reports %d", len(ns)) 16 | } 17 | } 18 | 19 | func TestHostRootUIDNoUSERNS(t *testing.T) { 20 | config := &Config{ 21 | Namespaces: Namespaces{}, 22 | } 23 | uid, err := config.HostRootUID() 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if uid != 0 { 28 | t.Fatalf("expected uid 0 with no USERNS but received %d", uid) 29 | } 30 | } 31 | 32 | func TestHostRootUIDWithUSERNS(t *testing.T) { 33 | config := &Config{ 34 | Namespaces: Namespaces{{Type: NEWUSER}}, 35 | UidMappings: []IDMap{ 36 | { 37 | ContainerID: 0, 38 | HostID: 1000, 39 | Size: 1, 40 | }, 41 | }, 42 | } 43 | uid, err := config.HostRootUID() 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if uid != 1000 { 48 | t.Fatalf("expected uid 1000 with no USERNS but received %d", uid) 49 | } 50 | } 51 | 52 | func TestHostRootGIDNoUSERNS(t *testing.T) { 53 | config := &Config{ 54 | Namespaces: Namespaces{}, 55 | } 56 | uid, err := config.HostRootGID() 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if uid != 0 { 61 | t.Fatalf("expected gid 0 with no USERNS but received %d", uid) 62 | } 63 | } 64 | 65 | func TestHostRootGIDWithUSERNS(t *testing.T) { 66 | config := &Config{ 67 | Namespaces: Namespaces{{Type: NEWUSER}}, 68 | GidMappings: []IDMap{ 69 | { 70 | ContainerID: 0, 71 | HostID: 1000, 72 | Size: 1, 73 | }, 74 | }, 75 | } 76 | uid, err := config.HostRootGID() 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | if uid != 1000 { 81 | t.Fatalf("expected gid 1000 with no USERNS but received %d", uid) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /libcontainer/configs/config_windows_test.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | // All current tests are for Unix-specific functionality 4 | -------------------------------------------------------------------------------- /libcontainer/configs/devices.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import "github.com/opencontainers/runc/libcontainer/devices" 4 | 5 | type ( 6 | // Deprecated: use libcontainer/devices.Device 7 | Device = devices.Device 8 | 9 | // Deprecated: use libcontainer/devices.Rule 10 | DeviceRule = devices.Rule 11 | 12 | // Deprecated: use libcontainer/devices.Type 13 | DeviceType = devices.Type 14 | 15 | // Deprecated: use libcontainer/devices.Permissions 16 | DevicePermissions = devices.Permissions 17 | ) 18 | -------------------------------------------------------------------------------- /libcontainer/configs/hugepage_limit.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | type HugepageLimit struct { 4 | // which type of hugepage to limit. 5 | Pagesize string `json:"page_size"` 6 | 7 | // usage limit for hugepage. 8 | Limit uint64 `json:"limit"` 9 | } 10 | -------------------------------------------------------------------------------- /libcontainer/configs/intelrdt.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | type IntelRdt struct { 4 | // The schema for L3 cache id and capacity bitmask (CBM) 5 | // Format: "L3:=;=;..." 6 | L3CacheSchema string `json:"l3_cache_schema,omitempty"` 7 | 8 | // The schema of memory bandwidth per L3 cache id 9 | // Format: "MB:=bandwidth0;=bandwidth1;..." 10 | // The unit of memory bandwidth is specified in "percentages" by 11 | // default, and in "MBps" if MBA Software Controller is enabled. 12 | MemBwSchema string `json:"memBwSchema,omitempty"` 13 | } 14 | -------------------------------------------------------------------------------- /libcontainer/configs/interface_priority_map.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type IfPrioMap struct { 8 | Interface string `json:"interface"` 9 | Priority int64 `json:"priority"` 10 | } 11 | 12 | func (i *IfPrioMap) CgroupString() string { 13 | return fmt.Sprintf("%s %d", i.Interface, i.Priority) 14 | } 15 | -------------------------------------------------------------------------------- /libcontainer/configs/mount.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | const ( 4 | // EXT_COPYUP is a directive to copy up the contents of a directory when 5 | // a tmpfs is mounted over it. 6 | EXT_COPYUP = 1 << iota 7 | ) 8 | 9 | type BindSrcInfo struct { 10 | IsDir bool `json:"is_dir,omitempty"` 11 | Uid uint32 `json:"uid,omitempty"` 12 | Gid uint32 `json:"gid,omitempty"` 13 | } 14 | 15 | type Mount struct { 16 | // Source path for the mount. 17 | Source string `json:"source"` 18 | 19 | // Destination path for the mount inside the container. 20 | Destination string `json:"destination"` 21 | 22 | // Device the mount is for. 23 | Device string `json:"device"` 24 | 25 | // Mount flags. 26 | Flags int `json:"flags"` 27 | 28 | // Propagation Flags 29 | PropagationFlags []int `json:"propagation_flags"` 30 | 31 | // Mount data applied to the mount. 32 | Data string `json:"data"` 33 | 34 | // Relabel source if set, "z" indicates shared, "Z" indicates unshared. 35 | Relabel string `json:"relabel"` 36 | 37 | // Extensions are additional flags that are specific to runc. 38 | Extensions int `json:"extensions"` 39 | 40 | // Bind mount source info 41 | BindSrcInfo BindSrcInfo `json:"bind_src_info,omitempty"` 42 | 43 | // Indicates if mounts is to be ID-mapped (see mount_setattr(2) in Linux >= 5.12). 44 | IDMappedMount bool `json:"idmap_mount"` 45 | } 46 | -------------------------------------------------------------------------------- /libcontainer/configs/namespaces.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | type NamespaceType string 4 | 5 | type Namespaces []Namespace 6 | -------------------------------------------------------------------------------- /libcontainer/configs/namespaces_syscall.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package configs 4 | 5 | import "golang.org/x/sys/unix" 6 | 7 | func (n *Namespace) Syscall() int { 8 | return namespaceInfo[n.Type] 9 | } 10 | 11 | var namespaceInfo = map[NamespaceType]int{ 12 | NEWNET: unix.CLONE_NEWNET, 13 | NEWNS: unix.CLONE_NEWNS, 14 | NEWUSER: unix.CLONE_NEWUSER, 15 | NEWIPC: unix.CLONE_NEWIPC, 16 | NEWUTS: unix.CLONE_NEWUTS, 17 | NEWPID: unix.CLONE_NEWPID, 18 | NEWCGROUP: unix.CLONE_NEWCGROUP, 19 | } 20 | 21 | // CloneFlags parses the container's Namespaces options to set the correct 22 | // flags on clone, unshare. This function returns flags only for new namespaces. 23 | func (n *Namespaces) CloneFlags() uintptr { 24 | var flag int 25 | for _, v := range *n { 26 | if v.Path != "" { 27 | continue 28 | } 29 | flag |= namespaceInfo[v.Type] 30 | } 31 | return uintptr(flag) 32 | } 33 | -------------------------------------------------------------------------------- /libcontainer/configs/namespaces_syscall_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!windows 2 | 3 | package configs 4 | 5 | func (n *Namespace) Syscall() int { 6 | panic("No namespace syscall support") 7 | } 8 | 9 | // CloneFlags parses the container's Namespaces options to set the correct 10 | // flags on clone, unshare. This function returns flags only for new namespaces. 11 | func (n *Namespaces) CloneFlags() uintptr { 12 | panic("No namespace syscall support") 13 | } 14 | -------------------------------------------------------------------------------- /libcontainer/configs/namespaces_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package configs 4 | 5 | // Namespace defines configuration for each namespace. It specifies an 6 | // alternate path that is able to be joined via setns. 7 | type Namespace struct { 8 | } 9 | -------------------------------------------------------------------------------- /libcontainer/configs/rdma.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | // LinuxRdma for Linux cgroup 'rdma' resource management (Linux 4.11) 4 | type LinuxRdma struct { 5 | // Maximum number of HCA handles that can be opened. Default is "no limit". 6 | HcaHandles *uint32 `json:"hca_handles,omitempty"` 7 | // Maximum number of HCA objects that can be created. Default is "no limit". 8 | HcaObjects *uint32 `json:"hca_objects,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /libcontainer/console_linux.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "os" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | // mount initializes the console inside the rootfs mounting with the specified mount label 10 | // and applying the correct ownership of the console. 11 | func mountConsole(slavePath string) error { 12 | oldMask := unix.Umask(0000) 13 | defer unix.Umask(oldMask) 14 | f, err := os.Create("/dev/console") 15 | if err != nil && !os.IsExist(err) { 16 | return err 17 | } 18 | if f != nil { 19 | f.Close() 20 | } 21 | return unix.Mount(slavePath, "/dev/console", "bind", unix.MS_BIND, "") 22 | } 23 | 24 | // dupStdio opens the slavePath for the console and dups the fds to the current 25 | // processes stdio, fd 0,1,2. 26 | func dupStdio(slavePath string) error { 27 | fd, err := unix.Open(slavePath, unix.O_RDWR, 0) 28 | if err != nil { 29 | return &os.PathError{ 30 | Op: "open", 31 | Path: slavePath, 32 | Err: err, 33 | } 34 | } 35 | for _, i := range []int{0, 1, 2} { 36 | if err := unix.Dup3(fd, i, 0); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /libcontainer/criu_opts_linux.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import criu "github.com/checkpoint-restore/go-criu/v4/rpc" 4 | 5 | type CriuPageServerInfo struct { 6 | Address string // IP address of CRIU page server 7 | Port int32 // port number of CRIU page server 8 | } 9 | 10 | type VethPairName struct { 11 | ContainerInterfaceName string 12 | HostInterfaceName string 13 | } 14 | 15 | type CriuOpts struct { 16 | ImagesDirectory string // directory for storing image files 17 | WorkDirectory string // directory to cd and write logs/pidfiles/stats to 18 | ParentImage string // directory for storing parent image files in pre-dump and dump 19 | LeaveRunning bool // leave container in running state after checkpoint 20 | TcpEstablished bool // checkpoint/restore established TCP connections 21 | ExternalUnixConnections bool // allow external unix connections 22 | ShellJob bool // allow to dump and restore shell jobs 23 | FileLocks bool // handle file locks, for safety 24 | PreDump bool // call criu predump to perform iterative checkpoint 25 | PageServer CriuPageServerInfo // allow to dump to criu page server 26 | VethPairs []VethPairName // pass the veth to criu when restore 27 | ManageCgroupsMode criu.CriuCgMode // dump or restore cgroup mode 28 | EmptyNs uint32 // don't c/r properties for namespace from this mask 29 | AutoDedup bool // auto deduplication for incremental dumps 30 | LazyPages bool // restore memory pages lazily using userfaultfd 31 | StatusFd int // fd for feedback when lazy server is ready 32 | } 33 | -------------------------------------------------------------------------------- /libcontainer/devices/device_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package devices 4 | 5 | import ( 6 | "errors" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func (d *Rule) Mkdev() (uint64, error) { 12 | if d.Major == Wildcard || d.Minor == Wildcard { 13 | return 0, errors.New("cannot mkdev() device with wildcards") 14 | } 15 | return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil 16 | } 17 | -------------------------------------------------------------------------------- /libcontainer/devices/device_windows.go: -------------------------------------------------------------------------------- 1 | package devices 2 | 3 | func (d *Rule) Mkdev() (uint64, error) { 4 | return 0, nil 5 | } 6 | -------------------------------------------------------------------------------- /libcontainer/error.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import "io" 4 | 5 | // ErrorCode is the API error code type. 6 | type ErrorCode int 7 | 8 | // API error codes. 9 | const ( 10 | // Factory errors 11 | IdInUse ErrorCode = iota 12 | InvalidIdFormat 13 | 14 | // Container errors 15 | ContainerNotExists 16 | ContainerPaused 17 | ContainerNotStopped 18 | ContainerNotRunning 19 | ContainerNotPaused 20 | 21 | // Process errors 22 | NoProcessOps 23 | 24 | // Common errors 25 | ConfigInvalid 26 | ConsoleExists 27 | SystemError 28 | ) 29 | 30 | func (c ErrorCode) String() string { 31 | switch c { 32 | case IdInUse: 33 | return "Id already in use" 34 | case InvalidIdFormat: 35 | return "Invalid format" 36 | case ContainerPaused: 37 | return "Container paused" 38 | case ConfigInvalid: 39 | return "Invalid configuration" 40 | case SystemError: 41 | return "System error" 42 | case ContainerNotExists: 43 | return "Container does not exist" 44 | case ContainerNotStopped: 45 | return "Container is not stopped" 46 | case ContainerNotRunning: 47 | return "Container is not running" 48 | case ConsoleExists: 49 | return "Console exists for process" 50 | case ContainerNotPaused: 51 | return "Container is not paused" 52 | case NoProcessOps: 53 | return "No process operations" 54 | default: 55 | return "Unknown error" 56 | } 57 | } 58 | 59 | // Error is the API error type. 60 | type Error interface { 61 | error 62 | 63 | // Returns an error if it failed to write the detail of the Error to w. 64 | // The detail of the Error may include the error message and a 65 | // representation of the stack trace. 66 | Detail(w io.Writer) error 67 | 68 | // Returns the error code for this error. 69 | Code() ErrorCode 70 | } 71 | -------------------------------------------------------------------------------- /libcontainer/error_test.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import "testing" 4 | 5 | func TestErrorCode(t *testing.T) { 6 | codes := map[ErrorCode]string{ 7 | IdInUse: "Id already in use", 8 | InvalidIdFormat: "Invalid format", 9 | ContainerPaused: "Container paused", 10 | ConfigInvalid: "Invalid configuration", 11 | SystemError: "System error", 12 | ContainerNotExists: "Container does not exist", 13 | ContainerNotStopped: "Container is not stopped", 14 | ContainerNotRunning: "Container is not running", 15 | ConsoleExists: "Console exists for process", 16 | ContainerNotPaused: "Container is not paused", 17 | NoProcessOps: "No process operations", 18 | } 19 | 20 | for code, expected := range codes { 21 | if actual := code.String(); actual != expected { 22 | t.Fatalf("expected string %q but received %q", expected, actual) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libcontainer/factory.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "github.com/opencontainers/runc/libcontainer/configs" 5 | ) 6 | 7 | type Factory interface { 8 | // Creates a new container with the given id and starts the initial process inside it. 9 | // id must be a string containing only letters, digits and underscores and must contain 10 | // between 1 and 1024 characters, inclusive. 11 | // 12 | // The id must not already be in use by an existing container. Containers created using 13 | // a factory with the same path (and filesystem) must have distinct ids. 14 | // 15 | // Returns the new container with a running process. 16 | // 17 | // errors: 18 | // IdInUse - id is already in use by a container 19 | // InvalidIdFormat - id has incorrect format 20 | // ConfigInvalid - config is invalid 21 | // Systemerror - System error 22 | // 23 | // On error, any partially created container parts are cleaned up (the operation is atomic). 24 | Create(id string, config *configs.Config) (Container, error) 25 | 26 | // Load takes an ID for an existing container and returns the container information 27 | // from the state. This presents a read only view of the container. 28 | // 29 | // errors: 30 | // Path does not exist 31 | // System error 32 | Load(id string) (Container, error) 33 | 34 | // StartInitialization is an internal API to libcontainer used during the reexec of the 35 | // container. 36 | // 37 | // Errors: 38 | // Pipe connection error 39 | // System error 40 | StartInitialization() error 41 | 42 | // Type returns info string about factory type (e.g. lxc, libcontainer...) 43 | Type() string 44 | } 45 | -------------------------------------------------------------------------------- /libcontainer/generic_error.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "text/template" 7 | "time" 8 | 9 | "github.com/opencontainers/runc/libcontainer/stacktrace" 10 | ) 11 | 12 | var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} 13 | Code: {{.ECode}} 14 | {{if .Message }} 15 | Message: {{.Message}} 16 | {{end}} 17 | Frames:{{range $i, $frame := .Stack.Frames}} 18 | --- 19 | {{$i}}: {{$frame.Function}} 20 | Package: {{$frame.Package}} 21 | File: {{$frame.File}}@{{$frame.Line}}{{end}} 22 | `)) 23 | 24 | func newGenericError(err error, c ErrorCode) Error { 25 | if le, ok := err.(Error); ok { 26 | return le 27 | } 28 | gerr := &genericError{ 29 | Timestamp: time.Now(), 30 | Err: err, 31 | ECode: c, 32 | Stack: stacktrace.Capture(1), 33 | } 34 | if err != nil { 35 | gerr.Message = err.Error() 36 | } 37 | return gerr 38 | } 39 | 40 | func newSystemError(err error) Error { 41 | return createSystemError(err, "") 42 | } 43 | 44 | func newSystemErrorWithCausef(err error, cause string, v ...interface{}) Error { 45 | return createSystemError(err, fmt.Sprintf(cause, v...)) 46 | } 47 | 48 | func newSystemErrorWithCause(err error, cause string) Error { 49 | return createSystemError(err, cause) 50 | } 51 | 52 | // createSystemError creates the specified error with the correct number of 53 | // stack frames skipped. This is only to be called by the other functions for 54 | // formatting the error. 55 | func createSystemError(err error, cause string) Error { 56 | gerr := &genericError{ 57 | Timestamp: time.Now(), 58 | Err: err, 59 | ECode: SystemError, 60 | Cause: cause, 61 | Stack: stacktrace.Capture(2), 62 | } 63 | if err != nil { 64 | gerr.Message = err.Error() 65 | } 66 | return gerr 67 | } 68 | 69 | type genericError struct { 70 | Timestamp time.Time 71 | ECode ErrorCode 72 | Err error `json:"-"` 73 | Cause string 74 | Message string 75 | Stack stacktrace.Stacktrace 76 | } 77 | 78 | func (e *genericError) Error() string { 79 | if e.Cause == "" { 80 | return e.Message 81 | } 82 | frame := e.Stack.Frames[0] 83 | return fmt.Sprintf("%s:%d: %s caused: %s", frame.File, frame.Line, e.Cause, e.Message) 84 | } 85 | 86 | func (e *genericError) Code() ErrorCode { 87 | return e.ECode 88 | } 89 | 90 | func (e *genericError) Detail(w io.Writer) error { 91 | return errorTemplate.Execute(w, e) 92 | } 93 | -------------------------------------------------------------------------------- /libcontainer/generic_error_test.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestErrorDetail(t *testing.T) { 10 | err := newGenericError(fmt.Errorf("test error"), SystemError) 11 | if derr := err.Detail(ioutil.Discard); derr != nil { 12 | t.Fatal(derr) 13 | } 14 | } 15 | 16 | func TestErrorWithCode(t *testing.T) { 17 | err := newGenericError(fmt.Errorf("test error"), SystemError) 18 | if code := err.Code(); code != SystemError { 19 | t.Fatalf("expected err code %q but %q", SystemError, code) 20 | } 21 | } 22 | 23 | func TestErrorWithError(t *testing.T) { 24 | cc := []struct { 25 | errmsg string 26 | cause string 27 | }{ 28 | { 29 | errmsg: "test error", 30 | }, 31 | { 32 | errmsg: "test error", 33 | cause: "test", 34 | }, 35 | } 36 | 37 | for _, v := range cc { 38 | err := newSystemErrorWithCause(fmt.Errorf(v.errmsg), v.cause) 39 | 40 | msg := err.Error() 41 | if v.cause == "" && msg != v.errmsg { 42 | t.Fatalf("expected err(%q) equal errmsg(%q)", msg, v.errmsg) 43 | } 44 | if v.cause != "" && msg == v.errmsg { 45 | t.Fatalf("unexpected err(%q) equal errmsg(%q)", msg, v.errmsg) 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libcontainer/integration/doc.go: -------------------------------------------------------------------------------- 1 | // integration is used for integration testing of libcontainer 2 | package integration 3 | -------------------------------------------------------------------------------- /libcontainer/integration/init_test.go: -------------------------------------------------------------------------------- 1 | package integration 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/opencontainers/runc/libcontainer" 9 | _ "github.com/opencontainers/runc/libcontainer/nsenter" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // init runs the libcontainer initialization code because of the busybox style needs 15 | // to work around the go runtime and the issues with forking 16 | func init() { 17 | if len(os.Args) < 2 || os.Args[1] != "init" { 18 | return 19 | } 20 | runtime.GOMAXPROCS(1) 21 | runtime.LockOSThread() 22 | factory, err := libcontainer.New("") 23 | if err != nil { 24 | logrus.Fatalf("unable to initialize for container: %s", err) 25 | } 26 | if err := factory.StartInitialization(); err != nil { 27 | logrus.Fatal(err) 28 | } 29 | } 30 | 31 | var testRoots []string 32 | 33 | func TestMain(m *testing.M) { 34 | logrus.SetOutput(os.Stderr) 35 | logrus.SetLevel(logrus.InfoLevel) 36 | 37 | // Clean up roots after running everything. 38 | defer func() { 39 | for _, root := range testRoots { 40 | os.RemoveAll(root) 41 | } 42 | }() 43 | 44 | ret := m.Run() 45 | os.Exit(ret) 46 | } 47 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/cmt.go: -------------------------------------------------------------------------------- 1 | package intelrdt 2 | 3 | var ( 4 | cmtEnabled bool 5 | ) 6 | 7 | // Check if Intel RDT/CMT is enabled. 8 | func IsCMTEnabled() bool { 9 | featuresInit() 10 | return cmtEnabled 11 | } 12 | 13 | func getCMTNumaNodeStats(numaPath string) (*CMTNumaNodeStats, error) { 14 | stats := &CMTNumaNodeStats{} 15 | 16 | if enabledMonFeatures.llcOccupancy { 17 | llcOccupancy, err := getIntelRdtParamUint(numaPath, "llc_occupancy") 18 | if err != nil { 19 | return nil, err 20 | } 21 | stats.LLCOccupancy = llcOccupancy 22 | } 23 | 24 | return stats, nil 25 | } 26 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/cmt_test.go: -------------------------------------------------------------------------------- 1 | package intelrdt 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestGetCMTNumaNodeStats(t *testing.T) { 10 | mocksNUMANodesToCreate := []string{"mon_l3_00", "mon_l3_01"} 11 | 12 | mocksFilesToCreate := map[string]uint64{ 13 | "llc_occupancy": 9123911, 14 | } 15 | 16 | mockedL3_MON, err := mockResctrlL3_MON(mocksNUMANodesToCreate, mocksFilesToCreate) 17 | 18 | defer func() { 19 | err := os.RemoveAll(mockedL3_MON) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | }() 24 | 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | t.Run("Gather mbm", func(t *testing.T) { 30 | enabledMonFeatures.llcOccupancy = true 31 | 32 | stats := make([]CMTNumaNodeStats, 0, len(mocksNUMANodesToCreate)) 33 | for _, numa := range mocksNUMANodesToCreate { 34 | other, err := getCMTNumaNodeStats(filepath.Join(mockedL3_MON, "mon_data", numa)) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | stats = append(stats, *other) 39 | } 40 | 41 | expectedStats := CMTNumaNodeStats{ 42 | LLCOccupancy: mocksFilesToCreate["llc_occupancy"], 43 | } 44 | 45 | checkCMTStatCorrection(stats[0], expectedStats, t) 46 | checkCMTStatCorrection(stats[1], expectedStats, t) 47 | }) 48 | } 49 | 50 | func checkCMTStatCorrection(got CMTNumaNodeStats, expected CMTNumaNodeStats, t *testing.T) { 51 | if got.LLCOccupancy != expected.LLCOccupancy { 52 | t.Fatalf("Wrong value of `llc_occupancy`. Expected: %v but got: %v", 53 | expected.LLCOccupancy, 54 | got.LLCOccupancy) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/mbm.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package intelrdt 4 | 5 | var ( 6 | // The flag to indicate if Intel RDT/MBM is enabled 7 | mbmEnabled bool 8 | ) 9 | 10 | // Check if Intel RDT/MBM is enabled. 11 | func IsMBMEnabled() bool { 12 | featuresInit() 13 | return mbmEnabled 14 | } 15 | 16 | func getMBMNumaNodeStats(numaPath string) (*MBMNumaNodeStats, error) { 17 | stats := &MBMNumaNodeStats{} 18 | if enabledMonFeatures.mbmTotalBytes { 19 | mbmTotalBytes, err := getIntelRdtParamUint(numaPath, "mbm_total_bytes") 20 | if err != nil { 21 | return nil, err 22 | } 23 | stats.MBMTotalBytes = mbmTotalBytes 24 | } 25 | 26 | if enabledMonFeatures.mbmLocalBytes { 27 | mbmLocalBytes, err := getIntelRdtParamUint(numaPath, "mbm_local_bytes") 28 | if err != nil { 29 | return nil, err 30 | } 31 | stats.MBMLocalBytes = mbmLocalBytes 32 | } 33 | 34 | return stats, nil 35 | } 36 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/mbm_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package intelrdt 4 | 5 | import ( 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | ) 10 | 11 | func TestGetMBMNumaNodeStats(t *testing.T) { 12 | mocksNUMANodesToCreate := []string{"mon_l3_00", "mon_l3_01"} 13 | 14 | mocksFilesToCreate := map[string]uint64{ 15 | "mbm_total_bytes": 9123911, 16 | "mbm_local_bytes": 2361361, 17 | } 18 | 19 | mockedL3_MON, err := mockResctrlL3_MON(mocksNUMANodesToCreate, mocksFilesToCreate) 20 | 21 | defer func() { 22 | err := os.RemoveAll(mockedL3_MON) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | }() 27 | 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | t.Run("Gather mbm", func(t *testing.T) { 33 | enabledMonFeatures.mbmTotalBytes = true 34 | enabledMonFeatures.mbmLocalBytes = true 35 | 36 | stats := make([]MBMNumaNodeStats, 0, len(mocksNUMANodesToCreate)) 37 | for _, numa := range mocksNUMANodesToCreate { 38 | other, err := getMBMNumaNodeStats(filepath.Join(mockedL3_MON, "mon_data", numa)) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | stats = append(stats, *other) 43 | } 44 | 45 | expectedStats := MBMNumaNodeStats{ 46 | MBMTotalBytes: mocksFilesToCreate["mbm_total_bytes"], 47 | MBMLocalBytes: mocksFilesToCreate["mbm_local_bytes"], 48 | } 49 | 50 | checkMBMStatCorrection(stats[0], expectedStats, t) 51 | checkMBMStatCorrection(stats[1], expectedStats, t) 52 | }) 53 | } 54 | 55 | func checkMBMStatCorrection(got MBMNumaNodeStats, expected MBMNumaNodeStats, t *testing.T) { 56 | if got.MBMTotalBytes != expected.MBMTotalBytes { 57 | t.Fatalf("Wrong value of mbm_total_bytes. Expected: %v but got: %v", 58 | expected.MBMTotalBytes, 59 | got.MBMTotalBytes) 60 | } 61 | 62 | if got.MBMLocalBytes != expected.MBMLocalBytes { 63 | t.Fatalf("Wrong value of mbm_local_bytes. Expected: %v but got: %v", 64 | expected.MBMLocalBytes, 65 | got.MBMLocalBytes) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/monitoring.go: -------------------------------------------------------------------------------- 1 | package intelrdt 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | var ( 14 | enabledMonFeatures monFeatures 15 | ) 16 | 17 | type monFeatures struct { 18 | mbmTotalBytes bool 19 | mbmLocalBytes bool 20 | llcOccupancy bool 21 | } 22 | 23 | func getMonFeatures(intelRdtRoot string) (monFeatures, error) { 24 | file, err := os.Open(filepath.Join(intelRdtRoot, "info", "L3_MON", "mon_features")) 25 | if err != nil { 26 | return monFeatures{}, err 27 | } 28 | defer file.Close() 29 | return parseMonFeatures(file) 30 | } 31 | 32 | func parseMonFeatures(reader io.Reader) (monFeatures, error) { 33 | scanner := bufio.NewScanner(reader) 34 | 35 | monFeatures := monFeatures{} 36 | 37 | for scanner.Scan() { 38 | switch feature := scanner.Text(); feature { 39 | case "mbm_total_bytes": 40 | monFeatures.mbmTotalBytes = true 41 | case "mbm_local_bytes": 42 | monFeatures.mbmLocalBytes = true 43 | case "llc_occupancy": 44 | monFeatures.llcOccupancy = true 45 | default: 46 | logrus.Warnf("Unsupported Intel RDT monitoring feature: %s", feature) 47 | } 48 | } 49 | 50 | return monFeatures, scanner.Err() 51 | } 52 | 53 | func getMonitoringStats(containerPath string, stats *Stats) error { 54 | numaFiles, err := ioutil.ReadDir(filepath.Join(containerPath, "mon_data")) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | var mbmStats []MBMNumaNodeStats 60 | var cmtStats []CMTNumaNodeStats 61 | 62 | for _, file := range numaFiles { 63 | if file.IsDir() { 64 | numaPath := filepath.Join(containerPath, "mon_data", file.Name()) 65 | if IsMBMEnabled() { 66 | numaMBMStats, err := getMBMNumaNodeStats(numaPath) 67 | if err != nil { 68 | return err 69 | } 70 | mbmStats = append(mbmStats, *numaMBMStats) 71 | } 72 | if IsCMTEnabled() { 73 | numaCMTStats, err := getCMTNumaNodeStats(numaPath) 74 | if err != nil { 75 | return err 76 | } 77 | cmtStats = append(cmtStats, *numaCMTStats) 78 | } 79 | } 80 | } 81 | 82 | stats.MBMStats = &mbmStats 83 | stats.CMTStats = &cmtStats 84 | 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/stats.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package intelrdt 4 | 5 | type L3CacheInfo struct { 6 | CbmMask string `json:"cbm_mask,omitempty"` 7 | MinCbmBits uint64 `json:"min_cbm_bits,omitempty"` 8 | NumClosids uint64 `json:"num_closids,omitempty"` 9 | } 10 | 11 | type MemBwInfo struct { 12 | BandwidthGran uint64 `json:"bandwidth_gran,omitempty"` 13 | DelayLinear uint64 `json:"delay_linear,omitempty"` 14 | MinBandwidth uint64 `json:"min_bandwidth,omitempty"` 15 | NumClosids uint64 `json:"num_closids,omitempty"` 16 | } 17 | 18 | type MBMNumaNodeStats struct { 19 | // The 'mbm_total_bytes' in 'container_id' group. 20 | MBMTotalBytes uint64 `json:"mbm_total_bytes"` 21 | 22 | // The 'mbm_local_bytes' in 'container_id' group. 23 | MBMLocalBytes uint64 `json:"mbm_local_bytes"` 24 | } 25 | 26 | type CMTNumaNodeStats struct { 27 | // The 'llc_occupancy' in 'container_id' group. 28 | LLCOccupancy uint64 `json:"llc_occupancy"` 29 | } 30 | 31 | type Stats struct { 32 | // The read-only L3 cache information 33 | L3CacheInfo *L3CacheInfo `json:"l3_cache_info,omitempty"` 34 | 35 | // The read-only L3 cache schema in root 36 | L3CacheSchemaRoot string `json:"l3_cache_schema_root,omitempty"` 37 | 38 | // The L3 cache schema in 'container_id' group 39 | L3CacheSchema string `json:"l3_cache_schema,omitempty"` 40 | 41 | // The read-only memory bandwidth information 42 | MemBwInfo *MemBwInfo `json:"mem_bw_info,omitempty"` 43 | 44 | // The read-only memory bandwidth schema in root 45 | MemBwSchemaRoot string `json:"mem_bw_schema_root,omitempty"` 46 | 47 | // The memory bandwidth schema in 'container_id' group 48 | MemBwSchema string `json:"mem_bw_schema,omitempty"` 49 | 50 | // The memory bandwidth monitoring statistics from NUMA nodes in 'container_id' group 51 | MBMStats *[]MBMNumaNodeStats `json:"mbm_stats,omitempty"` 52 | 53 | // The cache monitoring technology statistics from NUMA nodes in 'container_id' group 54 | CMTStats *[]CMTNumaNodeStats `json:"cmt_stats,omitempty"` 55 | } 56 | 57 | func NewStats() *Stats { 58 | return &Stats{} 59 | } 60 | -------------------------------------------------------------------------------- /libcontainer/intelrdt/util_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | /* 4 | * Utility for testing Intel RDT operations. 5 | * Creates a mock of the Intel RDT "resource control" filesystem for the duration of the test. 6 | */ 7 | package intelrdt 8 | 9 | import ( 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "testing" 14 | 15 | "github.com/opencontainers/runc/libcontainer/configs" 16 | ) 17 | 18 | type intelRdtTestUtil struct { 19 | // intelRdt data to use in tests 20 | IntelRdtData *intelRdtData 21 | 22 | // Path to the mock Intel RDT "resource control" filesystem directory 23 | IntelRdtPath string 24 | 25 | // Temporary directory to store mock Intel RDT "resource control" filesystem 26 | tempDir string 27 | t *testing.T 28 | } 29 | 30 | // Creates a new test util 31 | func NewIntelRdtTestUtil(t *testing.T) *intelRdtTestUtil { 32 | d := &intelRdtData{ 33 | config: &configs.Config{ 34 | IntelRdt: &configs.IntelRdt{}, 35 | }, 36 | } 37 | tempDir, err := ioutil.TempDir("", "intelrdt_test") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | d.root = tempDir 42 | testIntelRdtPath := filepath.Join(d.root, "resctrl") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | // Ensure the full mock Intel RDT "resource control" filesystem path exists 48 | err = os.MkdirAll(testIntelRdtPath, 0755) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | return &intelRdtTestUtil{IntelRdtData: d, IntelRdtPath: testIntelRdtPath, tempDir: tempDir, t: t} 53 | } 54 | 55 | func (c *intelRdtTestUtil) cleanup() { 56 | os.RemoveAll(c.tempDir) 57 | } 58 | 59 | // Write the specified contents on the mock of the specified Intel RDT "resource control" files 60 | func (c *intelRdtTestUtil) writeFileContents(fileContents map[string]string) { 61 | for file, contents := range fileContents { 62 | err := writeFile(c.IntelRdtPath, file, contents) 63 | if err != nil { 64 | c.t.Fatal(err) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /libcontainer/keys/keyctl.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package keys 4 | 5 | import ( 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | type KeySerial uint32 15 | 16 | func JoinSessionKeyring(name string) (KeySerial, error) { 17 | sessKeyId, err := unix.KeyctlJoinSessionKeyring(name) 18 | if err != nil { 19 | return 0, errors.Wrap(err, "create session key") 20 | } 21 | return KeySerial(sessKeyId), nil 22 | } 23 | 24 | // ModKeyringPerm modifies permissions on a keyring by reading the current permissions, 25 | // anding the bits with the given mask (clearing permissions) and setting 26 | // additional permission bits 27 | func ModKeyringPerm(ringId KeySerial, mask, setbits uint32) error { 28 | dest, err := unix.KeyctlString(unix.KEYCTL_DESCRIBE, int(ringId)) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | res := strings.Split(dest, ";") 34 | if len(res) < 5 { 35 | return errors.New("Destination buffer for key description is too small") 36 | } 37 | 38 | // parse permissions 39 | perm64, err := strconv.ParseUint(res[3], 16, 32) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | perm := (uint32(perm64) & mask) | setbits 45 | 46 | return unix.KeyctlSetperm(int(ringId), perm) 47 | } 48 | -------------------------------------------------------------------------------- /libcontainer/logs/logs.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | "sync" 11 | 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | var ( 16 | configureMutex = sync.Mutex{} 17 | // loggingConfigured will be set once logging has been configured via invoking `ConfigureLogging`. 18 | // Subsequent invocations of `ConfigureLogging` would be no-op 19 | loggingConfigured = false 20 | ) 21 | 22 | type Config struct { 23 | LogLevel logrus.Level 24 | LogFormat string 25 | LogFilePath string 26 | LogPipeFd string 27 | } 28 | 29 | func ForwardLogs(logPipe io.Reader) { 30 | lineReader := bufio.NewReader(logPipe) 31 | for { 32 | line, err := lineReader.ReadBytes('\n') 33 | if len(line) > 0 { 34 | processEntry(line) 35 | } 36 | if err == io.EOF { 37 | logrus.Debugf("log pipe has been closed: %+v", err) 38 | return 39 | } 40 | if err != nil { 41 | logrus.Errorf("log pipe read error: %+v", err) 42 | } 43 | } 44 | } 45 | 46 | func processEntry(text []byte) { 47 | type jsonLog struct { 48 | Level string `json:"level"` 49 | Msg string `json:"msg"` 50 | } 51 | 52 | var jl jsonLog 53 | if err := json.Unmarshal(text, &jl); err != nil { 54 | logrus.Errorf("failed to decode %q to json: %+v", text, err) 55 | return 56 | } 57 | 58 | lvl, err := logrus.ParseLevel(jl.Level) 59 | if err != nil { 60 | logrus.Errorf("failed to parse log level %q: %v\n", jl.Level, err) 61 | return 62 | } 63 | logrus.StandardLogger().Logf(lvl, jl.Msg) 64 | } 65 | 66 | func ConfigureLogging(config Config) error { 67 | configureMutex.Lock() 68 | defer configureMutex.Unlock() 69 | 70 | if loggingConfigured { 71 | logrus.Debug("logging has already been configured") 72 | return nil 73 | } 74 | 75 | logrus.SetLevel(config.LogLevel) 76 | 77 | if config.LogPipeFd != "" { 78 | logPipeFdInt, err := strconv.Atoi(config.LogPipeFd) 79 | if err != nil { 80 | return fmt.Errorf("failed to convert _LIBCONTAINER_LOGPIPE environment variable value %q to int: %v", config.LogPipeFd, err) 81 | } 82 | logrus.SetOutput(os.NewFile(uintptr(logPipeFdInt), "logpipe")) 83 | } else if config.LogFilePath != "" { 84 | f, err := os.OpenFile(config.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0644) 85 | if err != nil { 86 | return err 87 | } 88 | logrus.SetOutput(f) 89 | } 90 | 91 | switch config.LogFormat { 92 | case "text": 93 | // retain logrus's default. 94 | case "json": 95 | logrus.SetFormatter(new(logrus.JSONFormatter)) 96 | default: 97 | return fmt.Errorf("unknown log-format %q", config.LogFormat) 98 | } 99 | 100 | loggingConfigured = true 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /libcontainer/message_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package libcontainer 5 | 6 | import ( 7 | "github.com/vishvananda/netlink/nl" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | // list of known message types we want to send to bootstrap program 12 | // The number is randomly chosen to not conflict with known netlink types 13 | const ( 14 | InitMsg uint16 = 62000 15 | CloneFlagsAttr uint16 = 27281 16 | NsPathsAttr uint16 = 27282 17 | UidmapAttr uint16 = 27283 18 | GidmapAttr uint16 = 27284 19 | SetgroupAttr uint16 = 27285 20 | OomScoreAdjAttr uint16 = 27286 21 | RootlessEUIDAttr uint16 = 27287 22 | UidmapPathAttr uint16 = 27288 23 | GidmapPathAttr uint16 = 27289 24 | 25 | // sysbox-runc 26 | PrepRootfsAttr uint16 = 27290 27 | MakeParentPrivAttr uint16 = 27291 28 | RootfsPropAttr uint16 = 27292 29 | RootfsAttr uint16 = 27293 30 | ParentMountAttr uint16 = 27294 31 | ShiftfsMountsAttr uint16 = 27295 32 | ) 33 | 34 | type Int32msg struct { 35 | Type uint16 36 | Value uint32 37 | } 38 | 39 | // Serialize serializes the message. 40 | // Int32msg has the following representation 41 | // | nlattr len | nlattr type | 42 | // | uint32 value | 43 | func (msg *Int32msg) Serialize() []byte { 44 | buf := make([]byte, msg.Len()) 45 | native := nl.NativeEndian() 46 | native.PutUint16(buf[0:2], uint16(msg.Len())) 47 | native.PutUint16(buf[2:4], msg.Type) 48 | native.PutUint32(buf[4:8], msg.Value) 49 | return buf 50 | } 51 | 52 | func (msg *Int32msg) Len() int { 53 | return unix.NLA_HDRLEN + 4 54 | } 55 | 56 | // Bytemsg has the following representation 57 | // | nlattr len | nlattr type | 58 | // | value | pad | 59 | type Bytemsg struct { 60 | Type uint16 61 | Value []byte 62 | } 63 | 64 | func (msg *Bytemsg) Serialize() []byte { 65 | l := msg.Len() 66 | buf := make([]byte, (l+unix.NLA_ALIGNTO-1) & ^(unix.NLA_ALIGNTO-1)) 67 | native := nl.NativeEndian() 68 | native.PutUint16(buf[0:2], uint16(l)) 69 | native.PutUint16(buf[2:4], msg.Type) 70 | copy(buf[4:], msg.Value) 71 | return buf 72 | } 73 | 74 | func (msg *Bytemsg) Len() int { 75 | return unix.NLA_HDRLEN + len(msg.Value) + 1 // null-terminated 76 | } 77 | 78 | type Boolmsg struct { 79 | Type uint16 80 | Value bool 81 | } 82 | 83 | func (msg *Boolmsg) Serialize() []byte { 84 | buf := make([]byte, msg.Len()) 85 | native := nl.NativeEndian() 86 | native.PutUint16(buf[0:2], uint16(msg.Len())) 87 | native.PutUint16(buf[2:4], msg.Type) 88 | if msg.Value { 89 | native.PutUint32(buf[4:8], uint32(1)) 90 | } else { 91 | native.PutUint32(buf[4:8], uint32(0)) 92 | } 93 | return buf 94 | } 95 | 96 | func (msg *Boolmsg) Len() int { 97 | return unix.NLA_HDRLEN + 4 // alignment 98 | } 99 | -------------------------------------------------------------------------------- /libcontainer/notify_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package libcontainer 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | type PressureLevel uint 16 | 17 | const ( 18 | LowPressure PressureLevel = iota 19 | MediumPressure 20 | CriticalPressure 21 | ) 22 | 23 | func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) { 24 | evFile, err := os.Open(filepath.Join(cgDir, evName)) 25 | if err != nil { 26 | return nil, err 27 | } 28 | fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC) 29 | if err != nil { 30 | evFile.Close() 31 | return nil, err 32 | } 33 | 34 | eventfd := os.NewFile(uintptr(fd), "eventfd") 35 | 36 | eventControlPath := filepath.Join(cgDir, "cgroup.event_control") 37 | data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg) 38 | if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil { 39 | eventfd.Close() 40 | evFile.Close() 41 | return nil, err 42 | } 43 | ch := make(chan struct{}) 44 | go func() { 45 | defer func() { 46 | eventfd.Close() 47 | evFile.Close() 48 | close(ch) 49 | }() 50 | buf := make([]byte, 8) 51 | for { 52 | if _, err := eventfd.Read(buf); err != nil { 53 | return 54 | } 55 | // When a cgroup is destroyed, an event is sent to eventfd. 56 | // So if the control path is gone, return instead of notifying. 57 | if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) { 58 | return 59 | } 60 | ch <- struct{}{} 61 | } 62 | }() 63 | return ch, nil 64 | } 65 | 66 | // notifyOnOOM returns channel on which you can expect event about OOM, 67 | // if process died without OOM this channel will be closed. 68 | func notifyOnOOM(dir string) (<-chan struct{}, error) { 69 | if dir == "" { 70 | return nil, errors.New("memory controller missing") 71 | } 72 | 73 | return registerMemoryEvent(dir, "memory.oom_control", "") 74 | } 75 | 76 | func notifyMemoryPressure(dir string, level PressureLevel) (<-chan struct{}, error) { 77 | if dir == "" { 78 | return nil, errors.New("memory controller missing") 79 | } 80 | 81 | if level > CriticalPressure { 82 | return nil, fmt.Errorf("invalid pressure level %d", level) 83 | } 84 | 85 | levelStr := []string{"low", "medium", "critical"}[level] 86 | return registerMemoryEvent(dir, "memory.pressure_level", levelStr) 87 | } 88 | -------------------------------------------------------------------------------- /libcontainer/nsenter/README.md: -------------------------------------------------------------------------------- 1 | ## nsenter 2 | 3 | The `nsenter` package registers a special init constructor that is called before 4 | the Go runtime has a chance to boot. This provides us the ability to `setns` on 5 | existing namespaces and avoid the issues that the Go runtime has with multiple 6 | threads. This constructor will be called if this package is registered, 7 | imported, in your go application. 8 | 9 | The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/) 10 | package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, 11 | called the preamble, is used as a header when compiling the C parts of the package. 12 | So every time we import package `nsenter`, the C code function `nsexec()` would be 13 | called. And package `nsenter` is only imported in `init.go`, so every time the runc 14 | `init` command is invoked, that C code is run. 15 | 16 | Because `nsexec()` must be run before the Go runtime in order to use the 17 | Linux kernel namespace, you must `import` this library into a package if 18 | you plan to use `libcontainer` directly. Otherwise Go will not execute 19 | the `nsexec()` constructor, which means that the re-exec will not cause 20 | the namespaces to be joined. You can import it like this: 21 | 22 | ```go 23 | import _ "github.com/opencontainers/runc/libcontainer/nsenter" 24 | ``` 25 | 26 | `nsexec()` will first get the file descriptor number for the init pipe 27 | from the environment variable `_LIBCONTAINER_INITPIPE` (which was opened 28 | by the parent and kept open across the fork-exec of the `nsexec()` init 29 | process). The init pipe is used to read bootstrap data (namespace paths, 30 | clone flags, uid and gid mappings, and the console path) from the parent 31 | process. `nsexec()` will then call `setns(2)` to join the namespaces 32 | provided in the bootstrap data (if available), `clone(2)` a child process 33 | with the provided clone flags, update the user and group ID mappings, do 34 | some further miscellaneous setup steps, and then send the PID of the 35 | child process to the parent of the `nsexec()` "caller". Finally, 36 | the parent `nsexec()` will exit and the child `nsexec()` process will 37 | return to allow the Go runtime take over. 38 | 39 | NOTE: We do both `setns(2)` and `clone(2)` even if we don't have any 40 | `CLONE_NEW*` clone flags because we must fork a new process in order to 41 | enter the PID namespace. 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /libcontainer/nsenter/namespace.h: -------------------------------------------------------------------------------- 1 | #ifndef NSENTER_NAMESPACE_H 2 | #define NSENTER_NAMESPACE_H 3 | 4 | #ifndef _GNU_SOURCE 5 | # define _GNU_SOURCE 6 | #endif 7 | #include 8 | 9 | /* All of these are taken from include/uapi/linux/sched.h */ 10 | #ifndef CLONE_NEWNS 11 | # define CLONE_NEWNS 0x00020000 /* New mount namespace group */ 12 | #endif 13 | #ifndef CLONE_NEWCGROUP 14 | # define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ 15 | #endif 16 | #ifndef CLONE_NEWUTS 17 | # define CLONE_NEWUTS 0x04000000 /* New utsname namespace */ 18 | #endif 19 | #ifndef CLONE_NEWIPC 20 | # define CLONE_NEWIPC 0x08000000 /* New ipc namespace */ 21 | #endif 22 | #ifndef CLONE_NEWUSER 23 | # define CLONE_NEWUSER 0x10000000 /* New user namespace */ 24 | #endif 25 | #ifndef CLONE_NEWPID 26 | # define CLONE_NEWPID 0x20000000 /* New pid namespace */ 27 | #endif 28 | #ifndef CLONE_NEWNET 29 | # define CLONE_NEWNET 0x40000000 /* New network namespace */ 30 | #endif 31 | 32 | #endif /* NSENTER_NAMESPACE_H */ 33 | -------------------------------------------------------------------------------- /libcontainer/nsenter/nsenter.go: -------------------------------------------------------------------------------- 1 | // +build linux,!gccgo 2 | 3 | package nsenter 4 | 5 | /* 6 | #cgo CFLAGS: -Wall 7 | extern void nsexec(); 8 | void __attribute__((constructor)) init(void) { 9 | nsexec(); 10 | } 11 | */ 12 | import "C" 13 | 14 | -------------------------------------------------------------------------------- /libcontainer/nsenter/nsenter_gccgo.go: -------------------------------------------------------------------------------- 1 | // +build linux,gccgo 2 | 3 | package nsenter 4 | 5 | /* 6 | #cgo CFLAGS: -Wall 7 | extern void nsexec(); 8 | void __attribute__((constructor)) init(void) { 9 | nsexec(); 10 | } 11 | */ 12 | import "C" 13 | 14 | // AlwaysFalse is here to stay false 15 | // (and be exported so the compiler doesn't optimize out its reference) 16 | var AlwaysFalse bool 17 | 18 | func init() { 19 | if AlwaysFalse { 20 | // by referencing this C init() in a noop test, it will ensure the compiler 21 | // links in the C function. 22 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65134 23 | C.init() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libcontainer/nsenter/nsenter_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux !cgo 2 | 3 | package nsenter 4 | -------------------------------------------------------------------------------- /libcontainer/rootfs_linux_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package libcontainer 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/opencontainers/runc/libcontainer/configs" 9 | ) 10 | 11 | func TestNeedsSetupDev(t *testing.T) { 12 | config := &configs.Config{ 13 | Mounts: []*configs.Mount{ 14 | { 15 | Device: "bind", 16 | Source: "/dev", 17 | Destination: "/dev", 18 | }, 19 | }, 20 | } 21 | if needsSetupDev(config) { 22 | t.Fatal("expected needsSetupDev to be false, got true") 23 | } 24 | } 25 | 26 | func TestNeedsSetupDevStrangeSource(t *testing.T) { 27 | config := &configs.Config{ 28 | Mounts: []*configs.Mount{ 29 | { 30 | Device: "bind", 31 | Source: "/devx", 32 | Destination: "/dev", 33 | }, 34 | }, 35 | } 36 | if needsSetupDev(config) { 37 | t.Fatal("expected needsSetupDev to be false, got true") 38 | } 39 | } 40 | 41 | func TestNeedsSetupDevStrangeDest(t *testing.T) { 42 | config := &configs.Config{ 43 | Mounts: []*configs.Mount{ 44 | { 45 | Device: "bind", 46 | Source: "/dev", 47 | Destination: "/devx", 48 | }, 49 | }, 50 | } 51 | if !needsSetupDev(config) { 52 | t.Fatal("expected needsSetupDev to be true, got false") 53 | } 54 | } 55 | 56 | func TestNeedsSetupDevStrangeSourceDest(t *testing.T) { 57 | config := &configs.Config{ 58 | Mounts: []*configs.Mount{ 59 | { 60 | Device: "bind", 61 | Source: "/devx", 62 | Destination: "/devx", 63 | }, 64 | }, 65 | } 66 | if !needsSetupDev(config) { 67 | t.Fatal("expected needsSetupDev to be true, got false") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /libcontainer/seccomp/patchbpf/enosys_unsupported.go: -------------------------------------------------------------------------------- 1 | //go:build !linux || !cgo || !seccomp 2 | // +build !linux !cgo !seccomp 3 | 4 | package patchbpf 5 | -------------------------------------------------------------------------------- /libcontainer/seccomp/seccomp_unsupported.go: -------------------------------------------------------------------------------- 1 | //go:build !linux || !cgo || !seccomp 2 | // +build !linux !cgo !seccomp 3 | 4 | package seccomp 5 | 6 | import ( 7 | "errors" 8 | "os" 9 | 10 | "github.com/opencontainers/runc/libcontainer/configs" 11 | "github.com/opencontainers/runtime-spec/specs-go" 12 | ) 13 | 14 | var ErrSeccompNotEnabled = errors.New("seccomp: config provided but seccomp not supported") 15 | 16 | // InitSeccomp does nothing because seccomp is not supported. 17 | func InitSeccomp(config *configs.Seccomp) (*os.File, error) { 18 | if config != nil { 19 | return nil, ErrSeccompNotEnabled 20 | } 21 | return nil, nil 22 | } 23 | 24 | // FlagSupported tells if a provided seccomp flag is supported. 25 | func FlagSupported(_ specs.LinuxSeccompFlag) error { 26 | return ErrSeccompNotEnabled 27 | } 28 | 29 | // Version returns major, minor, and micro. 30 | func Version() (uint, uint, uint) { 31 | return 0, 0, 0 32 | } 33 | 34 | // Enabled is true if seccomp support is compiled in. 35 | const Enabled = false 36 | -------------------------------------------------------------------------------- /libcontainer/stacktrace/capture.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import "runtime" 4 | 5 | // Capture captures a stacktrace for the current calling go program 6 | // 7 | // skip is the number of frames to skip 8 | func Capture(userSkip int) Stacktrace { 9 | var ( 10 | skip = userSkip + 1 // add one for our own function 11 | frames []Frame 12 | prevPc uintptr 13 | ) 14 | for i := skip; ; i++ { 15 | pc, file, line, ok := runtime.Caller(i) 16 | //detect if caller is repeated to avoid loop, gccgo 17 | //currently runs into a loop without this check 18 | if !ok || pc == prevPc { 19 | break 20 | } 21 | frames = append(frames, NewFrame(pc, file, line)) 22 | prevPc = pc 23 | } 24 | return Stacktrace{ 25 | Frames: frames, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libcontainer/stacktrace/capture_test.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func captureFunc() Stacktrace { 9 | return Capture(0) 10 | } 11 | 12 | func TestCaptureTestFunc(t *testing.T) { 13 | stack := captureFunc() 14 | 15 | if len(stack.Frames) == 0 { 16 | t.Fatal("expected stack frames to be returned") 17 | } 18 | 19 | // the first frame is the caller 20 | frame := stack.Frames[0] 21 | if expected := "captureFunc"; frame.Function != expected { 22 | t.Fatalf("expected function %q but received %q", expected, frame.Function) 23 | } 24 | 25 | expected := "github.com/nestybox/sysbox-runc/libcontainer/stacktrace" 26 | 27 | if !strings.HasSuffix(frame.Package, expected) { 28 | t.Fatalf("expected package %q but received %q", expected, frame.Package) 29 | } 30 | if expected := "capture_test.go"; frame.File != expected { 31 | t.Fatalf("expected file %q but received %q", expected, frame.File) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libcontainer/stacktrace/frame.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import ( 4 | "path/filepath" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | // NewFrame returns a new stack frame for the provided information 10 | func NewFrame(pc uintptr, file string, line int) Frame { 11 | fn := runtime.FuncForPC(pc) 12 | if fn == nil { 13 | return Frame{} 14 | } 15 | pack, name := parseFunctionName(fn.Name()) 16 | return Frame{ 17 | Line: line, 18 | File: filepath.Base(file), 19 | Package: pack, 20 | Function: name, 21 | } 22 | } 23 | 24 | func parseFunctionName(name string) (string, string) { 25 | i := strings.LastIndex(name, ".") 26 | if i == -1 { 27 | return "", name 28 | } 29 | return name[:i], name[i+1:] 30 | } 31 | 32 | // Frame contains all the information for a stack frame within a go program 33 | type Frame struct { 34 | File string 35 | Function string 36 | Package string 37 | Line int 38 | } 39 | -------------------------------------------------------------------------------- /libcontainer/stacktrace/frame_test.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import "testing" 4 | 5 | func TestParsePackageName(t *testing.T) { 6 | var ( 7 | name = "github.com/opencontainers/runc/libcontainer/stacktrace.captureFunc" 8 | expectedPackage = "github.com/opencontainers/runc/libcontainer/stacktrace" 9 | expectedFunction = "captureFunc" 10 | ) 11 | 12 | pack, funcName := parseFunctionName(name) 13 | if pack != expectedPackage { 14 | t.Fatalf("expected package %q but received %q", expectedPackage, pack) 15 | } 16 | 17 | if funcName != expectedFunction { 18 | t.Fatalf("expected function %q but received %q", expectedFunction, funcName) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libcontainer/stacktrace/stacktrace.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | type Stacktrace struct { 4 | Frames []Frame 5 | } 6 | -------------------------------------------------------------------------------- /libcontainer/stats_linux.go: -------------------------------------------------------------------------------- 1 | package libcontainer 2 | 3 | import ( 4 | "github.com/opencontainers/runc/libcontainer/cgroups" 5 | "github.com/opencontainers/runc/libcontainer/intelrdt" 6 | "github.com/opencontainers/runc/types" 7 | ) 8 | 9 | type Stats struct { 10 | Interfaces []*types.NetworkInterface 11 | CgroupStats *cgroups.Stats 12 | IntelRdtStats *intelrdt.Stats 13 | } 14 | -------------------------------------------------------------------------------- /libcontainer/system/linux_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package system 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | 9 | "github.com/opencontainers/runc/libcontainer/user" 10 | ) 11 | 12 | func TestUIDMapInUserNS(t *testing.T) { 13 | cases := []struct { 14 | s string 15 | expected bool 16 | }{ 17 | { 18 | s: " 0 0 4294967295\n", 19 | expected: false, 20 | }, 21 | { 22 | s: " 0 0 1\n", 23 | expected: true, 24 | }, 25 | { 26 | s: " 0 1001 1\n 1 231072 65536\n", 27 | expected: true, 28 | }, 29 | { 30 | // file exist but empty (the initial state when userns is created. see man 7 user_namespaces) 31 | s: "", 32 | expected: true, 33 | }, 34 | } 35 | for _, c := range cases { 36 | uidmap, err := user.ParseIDMap(strings.NewReader(c.s)) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | actual := UIDMapInUserNS(uidmap) 41 | if c.expected != actual { 42 | t.Fatalf("expected %v, got %v for %q", c.expected, actual, c.s) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /libcontainer/system/proc_test.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import "testing" 4 | 5 | func TestParseStartTime(t *testing.T) { 6 | data := map[string]Stat_t{ 7 | "4902 (gunicorn: maste) S 4885 4902 4902 0 -1 4194560 29683 29929 61 83 78 16 96 17 20 0 1 0 9126532 52965376 1903 18446744073709551615 4194304 7461796 140733928751520 140733928698072 139816984959091 0 0 16781312 137447943 1 0 0 17 3 0 0 9 0 0 9559488 10071156 33050624 140733928758775 140733928758945 140733928758945 140733928759264 0": { 8 | PID: 4902, 9 | Name: "gunicorn: maste", 10 | State: 'S', 11 | StartTime: 9126532, 12 | }, 13 | "9534 (cat) R 9323 9534 9323 34828 9534 4194304 95 0 0 0 0 0 0 0 20 0 1 0 9214966 7626752 168 18446744073709551615 4194304 4240332 140732237651568 140732237650920 140570710391216 0 0 0 0 0 0 0 17 1 0 0 0 0 0 6340112 6341364 21553152 140732237653865 140732237653885 140732237653885 140732237656047 0": { 14 | PID: 9534, 15 | Name: "cat", 16 | State: 'R', 17 | StartTime: 9214966, 18 | }, 19 | 20 | "24767 (irq/44-mei_me) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 8722075 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0": { 21 | PID: 24767, 22 | Name: "irq/44-mei_me", 23 | State: 'S', 24 | StartTime: 8722075, 25 | }, 26 | } 27 | for line, expected := range data { 28 | st, err := parseStat(line) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | if st.PID != expected.PID { 33 | t.Fatalf("expected PID %q but received %q", expected.PID, st.PID) 34 | } 35 | if st.State != expected.State { 36 | t.Fatalf("expected state %q but received %q", expected.State, st.State) 37 | } 38 | if st.Name != expected.Name { 39 | t.Fatalf("expected name %q but received %q", expected.Name, st.Name) 40 | } 41 | if st.StartTime != expected.StartTime { 42 | t.Fatalf("expected start time %q but received %q", expected.StartTime, st.StartTime) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /libcontainer/system/syscall_linux_32.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | // +build 386 arm 3 | 4 | package system 5 | 6 | import ( 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // Setuid sets the uid of the calling thread to the specified uid. 11 | func Setuid(uid int) (err error) { 12 | _, _, e1 := unix.RawSyscall(unix.SYS_SETUID32, uintptr(uid), 0, 0) 13 | if e1 != 0 { 14 | err = e1 15 | } 16 | return 17 | } 18 | 19 | // Setgid sets the gid of the calling thread to the specified gid. 20 | func Setgid(gid int) (err error) { 21 | _, _, e1 := unix.RawSyscall(unix.SYS_SETGID32, uintptr(gid), 0, 0) 22 | if e1 != 0 { 23 | err = e1 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /libcontainer/system/syscall_linux_64.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | // +build arm64 amd64 mips mipsle mips64 mips64le ppc ppc64 ppc64le riscv64 s390x 3 | 4 | package system 5 | 6 | import ( 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // Setuid sets the uid of the calling thread to the specified uid. 11 | func Setuid(uid int) (err error) { 12 | _, _, e1 := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0) 13 | if e1 != 0 { 14 | err = e1 15 | } 16 | return 17 | } 18 | 19 | // Setgid sets the gid of the calling thread to the specified gid. 20 | func Setgid(gid int) (err error) { 21 | _, _, e1 := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0) 22 | if e1 != 0 { 23 | err = e1 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /libcontainer/system/unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package system 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/opencontainers/runc/libcontainer/user" 9 | ) 10 | 11 | // RunningInUserNS is a stub for non-Linux systems 12 | // Always returns false 13 | func RunningInUserNS() bool { 14 | return false 15 | } 16 | 17 | // UIDMapInUserNS is a stub for non-Linux systems 18 | // Always returns false 19 | func UIDMapInUserNS(uidmap []user.IDMap) bool { 20 | return false 21 | } 22 | 23 | // GetParentNSeuid returns the euid within the parent user namespace 24 | // Always returns os.Geteuid on non-linux 25 | func GetParentNSeuid() int { 26 | return os.Geteuid() 27 | } 28 | -------------------------------------------------------------------------------- /libcontainer/system/xattrs_linux.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import "golang.org/x/sys/unix" 4 | 5 | // Returns a []byte slice if the xattr is set and nil otherwise 6 | // Requires path and its attribute as arguments 7 | func Lgetxattr(path string, attr string) ([]byte, error) { 8 | var sz int 9 | // Start with a 128 length byte array 10 | dest := make([]byte, 128) 11 | sz, errno := unix.Lgetxattr(path, attr, dest) 12 | 13 | switch { 14 | case errno == unix.ENODATA: 15 | return nil, errno 16 | case errno == unix.ENOTSUP: 17 | return nil, errno 18 | case errno == unix.ERANGE: 19 | // 128 byte array might just not be good enough, 20 | // A dummy buffer is used to get the real size 21 | // of the xattrs on disk 22 | sz, errno = unix.Lgetxattr(path, attr, []byte{}) 23 | if errno != nil { 24 | return nil, errno 25 | } 26 | dest = make([]byte, sz) 27 | sz, errno = unix.Lgetxattr(path, attr, dest) 28 | if errno != nil { 29 | return nil, errno 30 | } 31 | case errno != nil: 32 | return nil, errno 33 | } 34 | return dest[:sz], nil 35 | } 36 | -------------------------------------------------------------------------------- /libcontainer/user/MAINTAINERS: -------------------------------------------------------------------------------- 1 | Tianon Gravi (@tianon) 2 | Aleksa Sarai (@cyphar) 3 | -------------------------------------------------------------------------------- /libcontainer/user/lookup.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // The current operating system does not provide the required data for user lookups. 9 | ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data") 10 | // No matching entries found in file. 11 | ErrNoPasswdEntries = errors.New("no matching entries in passwd file") 12 | ErrNoGroupEntries = errors.New("no matching entries in group file") 13 | ) 14 | 15 | // LookupUser looks up a user by their username in /etc/passwd. If the user 16 | // cannot be found (or there is no /etc/passwd file on the filesystem), then 17 | // LookupUser returns an error. 18 | func LookupUser(username string) (User, error) { 19 | return lookupUser(username) 20 | } 21 | 22 | // LookupUid looks up a user by their user id in /etc/passwd. If the user cannot 23 | // be found (or there is no /etc/passwd file on the filesystem), then LookupId 24 | // returns an error. 25 | func LookupUid(uid int) (User, error) { 26 | return lookupUid(uid) 27 | } 28 | 29 | // LookupGroup looks up a group by its name in /etc/group. If the group cannot 30 | // be found (or there is no /etc/group file on the filesystem), then LookupGroup 31 | // returns an error. 32 | func LookupGroup(groupname string) (Group, error) { 33 | return lookupGroup(groupname) 34 | } 35 | 36 | // LookupGid looks up a group by its group id in /etc/group. If the group cannot 37 | // be found (or there is no /etc/group file on the filesystem), then LookupGid 38 | // returns an error. 39 | func LookupGid(gid int) (Group, error) { 40 | return lookupGid(gid) 41 | } 42 | -------------------------------------------------------------------------------- /libcontainer/user/lookup_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package user 4 | 5 | import ( 6 | "os/user" 7 | "strconv" 8 | ) 9 | 10 | func lookupUser(username string) (User, error) { 11 | u, err := user.Lookup(username) 12 | if err != nil { 13 | return User{}, err 14 | } 15 | return userFromOS(u) 16 | } 17 | 18 | func lookupUid(uid int) (User, error) { 19 | u, err := user.LookupId(strconv.Itoa(uid)) 20 | if err != nil { 21 | return User{}, err 22 | } 23 | return userFromOS(u) 24 | } 25 | 26 | func lookupGroup(groupname string) (Group, error) { 27 | g, err := user.LookupGroup(groupname) 28 | if err != nil { 29 | return Group{}, err 30 | } 31 | return groupFromOS(g) 32 | } 33 | 34 | func lookupGid(gid int) (Group, error) { 35 | g, err := user.LookupGroupId(strconv.Itoa(gid)) 36 | if err != nil { 37 | return Group{}, err 38 | } 39 | return groupFromOS(g) 40 | } 41 | -------------------------------------------------------------------------------- /libsysbox/sysbox/utils.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2019-2020 Nestybox, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package sysbox 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "unsafe" 23 | ) 24 | 25 | // The min supported kernel release is chosen based on whether it contains all kernel 26 | // fixes required to run Sysbox. Refer to the Sysbox distro compatibility doc. 27 | type kernelRelease struct{ major, minor int } 28 | 29 | var minKernel = kernelRelease{5, 5} // 5.5 30 | var minKernelUbuntu = kernelRelease{5, 0} // 5.0 31 | 32 | func readFileInt(path string) (int, error) { 33 | 34 | f, err := os.Open(path) 35 | if err != nil { 36 | return -1, err 37 | } 38 | defer f.Close() 39 | 40 | var b []byte = make([]byte, unsafe.Sizeof(int(0))) 41 | _, err = f.Read(b) 42 | if err != nil { 43 | return -1, err 44 | } 45 | 46 | var val int 47 | _, err = fmt.Sscanf(string(b), "%d", &val) 48 | if err != nil { 49 | return -1, err 50 | } 51 | 52 | return val, nil 53 | } 54 | 55 | // checks if the kernel is configured to allow unprivileged users to create 56 | // namespaces. This is necessary for running containers inside a system 57 | // container. 58 | func checkUnprivilegedUserns() error { 59 | 60 | // In Debian-based distros, unprivileged userns creation is enabled via 61 | // "/proc/sys/kernel/unprivileged_userns_clone". In Fedora (and related) 62 | // distros this sysctl does not exist. Rather, unprivileged userns creation 63 | // is enabled by setting a non-zero value in "/proc/sys/user/max_user_namespaces". 64 | // Here we check both. 65 | 66 | path := "/proc/sys/kernel/unprivileged_userns_clone" 67 | if _, err := os.Stat(path); err == nil { 68 | 69 | val, err := readFileInt(path) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | if val != 1 { 75 | return fmt.Errorf("kernel is not configured to allow unprivileged users to create namespaces: %s: want 1, have %d", 76 | path, val) 77 | } 78 | } 79 | 80 | path = "/proc/sys/user/max_user_namespaces" 81 | 82 | val, err := readFileInt(path) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | if val == 0 { 88 | return fmt.Errorf("kernel is not configured to allow unprivileged users to create namespaces: %s: want >= 1, have %d", 89 | path, val) 90 | } 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /man/README.md: -------------------------------------------------------------------------------- 1 | runc man pages 2 | ==================== 3 | 4 | This directory contains man pages for runc in markdown format. 5 | 6 | To generate man pages from it, use this command 7 | 8 | ./md2man-all.sh 9 | 10 | You will see man pages generated under the man8 directory. 11 | 12 | -------------------------------------------------------------------------------- /man/md2man-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # get into this script's directory 5 | cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" 6 | 7 | [ "$1" = '-q' ] || { 8 | set -x 9 | pwd 10 | } 11 | 12 | if ! type go-md2man; then 13 | echo "To install man pages, please install 'go-md2man'." 14 | exit 0 15 | fi 16 | 17 | for FILE in *.md; do 18 | base="$(basename "$FILE")" 19 | name="${base%.md}" 20 | num="${name##*.}" 21 | if [ -z "$num" -o "$name" = "$num" ]; then 22 | # skip files that aren't of the format xxxx.N.md (like README.md) 23 | continue 24 | fi 25 | mkdir -p "./man${num}" 26 | go-md2man -in "$FILE" -out "./man${num}/${name}" 27 | done 28 | -------------------------------------------------------------------------------- /man/runc-checkpoint.8.md: -------------------------------------------------------------------------------- 1 | % runc-checkpoint "8" 2 | 3 | # NAME 4 | runc checkpoint - checkpoint a running container 5 | 6 | # SYNOPSIS 7 | runc checkpoint [command options] `` 8 | 9 | Where "``" is the name for the instance of the container to be 10 | checkpointed. 11 | 12 | # DESCRIPTION 13 | The checkpoint command saves the state of the container instance. 14 | 15 | # OPTIONS 16 | --image-path value path for saving criu image files 17 | --work-path value path for saving work files and logs 18 | --parent-path value path for previous criu image files in pre-dump 19 | --leave-running leave the process running after checkpointing 20 | --tcp-established allow open tcp connections 21 | --ext-unix-sk allow external unix sockets 22 | --shell-job allow shell jobs 23 | --lazy-pages use userfaultfd to lazily restore memory pages 24 | --status-fd value criu writes \0 to this FD once lazy-pages is ready 25 | --page-server value ADDRESS:PORT of the page server 26 | --file-locks handle file locks, for safety 27 | --pre-dump dump container's memory information only, leave the container running after this 28 | --manage-cgroups-mode value cgroups mode: 'soft' (default), 'full' and 'strict' 29 | --empty-ns value create a namespace, but don't restore its properties 30 | --auto-dedup enable auto deduplication of memory images 31 | -------------------------------------------------------------------------------- /man/runc-create.8.md: -------------------------------------------------------------------------------- 1 | % runc-create "8" 2 | 3 | # NAME 4 | runc create - create a container 5 | 6 | # SYNOPSIS 7 | runc create [command options] `` 8 | 9 | Where "``" is your name for the instance of the container that you 10 | are starting. The name you provide for the container instance must be unique on 11 | your host. 12 | 13 | # DESCRIPTION 14 | The create command creates an instance of a container for a bundle. The bundle 15 | is a directory with a specification file named "config.json" and a root 16 | filesystem. 17 | 18 | The specification file includes an args parameter. The args parameter is used 19 | to specify command(s) that get run when the container is started. To change the 20 | command(s) that get executed on start, edit the args parameter of the spec. See 21 | "runc spec --help" for more explanation. 22 | 23 | # OPTIONS 24 | --bundle value, -b value path to the root of the bundle directory, defaults to the current directory 25 | --console-socket value path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal 26 | --pid-file value specify the file to write the process id to 27 | --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 28 | --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 29 | --preserve-fds value Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) (default: 0) 30 | -------------------------------------------------------------------------------- /man/runc-delete.8.md: -------------------------------------------------------------------------------- 1 | % runc-delete "8" 2 | 3 | # NAME 4 | runc delete - delete any resources held by the container often used with detached container 5 | 6 | # SYNOPSIS 7 | runc delete [command options] `` 8 | 9 | Where "``" is the name for the instance of the container. 10 | 11 | # OPTIONS 12 | --force, -f Forcibly deletes the container if it is still running (uses SIGKILL) 13 | 14 | # EXAMPLE 15 | For example, if the container id is "ubuntu01" and runc list currently shows the 16 | status of "ubuntu01" as "stopped" the following will delete resources held for 17 | "ubuntu01" removing "ubuntu01" from the runc list of containers: 18 | 19 | # runc delete ubuntu01 20 | -------------------------------------------------------------------------------- /man/runc-events.8.md: -------------------------------------------------------------------------------- 1 | % runc-events "8" 2 | 3 | # NAME 4 | runc events - display container events such as OOM notifications, cpu, memory, and IO usage statistics 5 | 6 | # SYNOPSIS 7 | runc events [command options] `` 8 | 9 | Where "``" is the name for the instance of the container. 10 | 11 | # DESCRIPTION 12 | The events command displays information about the container. By default the 13 | information is displayed once every 5 seconds. 14 | 15 | # OPTIONS 16 | --interval value set the stats collection interval (default: 5s) 17 | --stats display the container's stats then exit 18 | -------------------------------------------------------------------------------- /man/runc-exec.8.md: -------------------------------------------------------------------------------- 1 | % runc-exec "8" 2 | 3 | # NAME 4 | runc exec - execute new process inside the container 5 | 6 | # SYNOPSIS 7 | runc exec [command options] `` -- `` [args...] 8 | 9 | Where "``" is the name for the instance of the container and 10 | "``" is the command to be executed in the container. 11 | 12 | # EXAMPLE 13 | For example, if the container is configured to run the linux ps command the 14 | following will output a list of processes running in the container: 15 | 16 | # runc exec ps 17 | 18 | # OPTIONS 19 | --console value specify the pty slave path for use with the container 20 | --cwd value current working directory in the container 21 | --env value, -e value set environment variables 22 | --tty, -t allocate a pseudo-TTY 23 | --user value, -u value UID (format: [:]) 24 | --additional-gids value, -g value additional gids 25 | --process value, -p value path to the process.json 26 | --detach, -d detach from the container's process 27 | --pid-file value specify the file to write the process id to 28 | --process-label value set the asm process label for the process commonly used with selinux 29 | --apparmor value set the apparmor profile for the process 30 | --no-new-privs set the no new privileges value for the process 31 | --cap value, -c value add a capability to the bounding set for the process 32 | --no-subreaper disable the use of the subreaper used to reap reparented processes 33 | --preserve-fds value pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) (default: 0) 34 | -------------------------------------------------------------------------------- /man/runc-kill.8.md: -------------------------------------------------------------------------------- 1 | % runc-kill "8" 2 | 3 | # NAME 4 | runc kill - kill sends the specified signal (default: SIGTERM) to the container's init process 5 | 6 | # SYNOPSIS 7 | runc kill [command options] `` `` 8 | 9 | Where "``" is the name for the instance of the container and 10 | "``" is the signal to be sent to the init process. 11 | 12 | # OPTIONS 13 | --all, -a send the specified signal to all processes inside the container 14 | 15 | # EXAMPLE 16 | 17 | For example, if the container id is "ubuntu01" the following will send a "KILL" 18 | signal to the init process of the "ubuntu01" container: 19 | 20 | # runc kill ubuntu01 KILL 21 | -------------------------------------------------------------------------------- /man/runc-list.8.md: -------------------------------------------------------------------------------- 1 | % runc-list "8" 2 | 3 | # NAME 4 | runc list - lists containers started by runc with the given root 5 | 6 | # SYNOPSIS 7 | runc list [command options] 8 | 9 | # EXAMPLE 10 | Where the given root is specified via the global option "--root" 11 | (default: "/run/runc"). 12 | 13 | To list containers created via the default "--root": 14 | # runc list 15 | 16 | To list containers created using a non-default value for "--root": 17 | # runc --root value list 18 | 19 | # OPTIONS 20 | --format value, -f value select one of: table or json (default: "table") 21 | --quiet, -q display only container IDs 22 | -------------------------------------------------------------------------------- /man/runc-pause.8.md: -------------------------------------------------------------------------------- 1 | % runc-pause "8" 2 | 3 | # NAME 4 | runc pause - pause suspends all processes inside the container 5 | 6 | # SYNOPSIS 7 | runc pause `` 8 | 9 | Where "``" is the name for the instance of the container to be 10 | paused. 11 | 12 | # DESCRIPTION 13 | The pause command suspends all processes in the instance of the container. 14 | Use runc list to identify instances of containers and their current status. 15 | -------------------------------------------------------------------------------- /man/runc-ps.8.md: -------------------------------------------------------------------------------- 1 | % runc-ps "8" 2 | 3 | # NAME 4 | runc ps - ps displays the processes running inside a container 5 | 6 | # SYNOPSIS 7 | runc ps [command options] `` [ps options] 8 | 9 | # OPTIONS 10 | --format value, -f value select one of: table(default) or json 11 | 12 | The default format is table. The following will output the processes of a container 13 | in json format: 14 | 15 | # runc ps -f json 16 | -------------------------------------------------------------------------------- /man/runc-restore.8.md: -------------------------------------------------------------------------------- 1 | % runc-restore "8" 2 | 3 | # NAME 4 | runc restore - restore a container from a previous checkpoint 5 | 6 | # SYNOPSIS 7 | runc restore [command options] `` 8 | 9 | Where "``" is the name for the instance of the container to be 10 | restored. 11 | 12 | # DESCRIPTION 13 | Restores the saved state of the container instance that was previously saved 14 | using the runc checkpoint command. 15 | 16 | # OPTIONS 17 | --image-path value path to criu image files for restoring 18 | --work-path value path for saving work files and logs 19 | --tcp-established allow open tcp connections 20 | --ext-unix-sk allow external unix sockets 21 | --shell-job allow shell jobs 22 | --file-locks handle file locks, for safety 23 | --manage-cgroups-mode value cgroups mode: 'soft' (default), 'full' and 'strict' 24 | --bundle value, -b value path to the root of the bundle directory 25 | --detach, -d detach from the container's process 26 | --pid-file value specify the file to write the process id to 27 | --no-subreaper disable the use of the subreaper used to reap reparented processes 28 | --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 29 | -------------------------------------------------------------------------------- /man/runc-resume.8.md: -------------------------------------------------------------------------------- 1 | % runc-resume "8" 2 | 3 | # NAME 4 | runc resume - resumes all processes that have been previously paused 5 | 6 | # SYNOPSIS 7 | runc resume `` 8 | 9 | Where "``" is the name for the instance of the container to be 10 | resumed. 11 | 12 | # DESCRIPTION 13 | The resume command resumes all processes in the instance of the container. 14 | Use runc list to identify instances of containers and their current status. 15 | -------------------------------------------------------------------------------- /man/runc-run.8.md: -------------------------------------------------------------------------------- 1 | % runc-run "8" 2 | 3 | # NAME 4 | runc run - create and run a container 5 | 6 | # SYNOPSIS 7 | runc run [command options] `` 8 | 9 | Where "``" is your name for the instance of the container that you 10 | are starting. The name you provide for the container instance must be unique on 11 | your host. 12 | 13 | # DESCRIPTION 14 | The run command creates an instance of a container for a bundle. The bundle 15 | is a directory with a specification file named "config.json" and a root 16 | filesystem. 17 | 18 | The specification file includes an args parameter. The args parameter is used 19 | to specify command(s) that get run when the container is started. To change the 20 | command(s) that get executed on start, edit the args parameter of the spec. See 21 | "runc spec --help" for more explanation. 22 | 23 | # OPTIONS 24 | --bundle value, -b value path to the root of the bundle directory, defaults to the current directory 25 | --console-socket value path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal 26 | --detach, -d detach from the container's process 27 | --keep keep the container's state directory and cgroup. This can be helpful if a user wants to check the state (e.g., of cgroup controllers) after the container has exited. If this option is used, a manual **runc delete** is needed afterwards to clean the exited container's artifacts. 28 | --pid-file value specify the file to write the process id to 29 | --no-subreaper disable the use of the subreaper used to reap reparented processes 30 | --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 31 | --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 32 | --preserve-fds value Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) (default: 0) 33 | -------------------------------------------------------------------------------- /man/runc-spec.8.md: -------------------------------------------------------------------------------- 1 | % runc-spec "8" 2 | 3 | # NAME 4 | runc spec - create a new specification file 5 | 6 | # SYNOPSIS 7 | runc spec [command options] [arguments...] 8 | 9 | # DESCRIPTION 10 | The spec command creates the new specification file named "config.json" for 11 | the bundle. 12 | 13 | The spec generated is just a starter file. Editing of the spec is required to 14 | achieve desired results. For example, the newly generated spec includes an args 15 | parameter that is initially set to call the "sh" command when the container is 16 | started. Calling "sh" may work for an ubuntu container or busybox, but will not 17 | work for containers that do not include the "sh" program. 18 | 19 | # EXAMPLE 20 | To run docker's hello-world container one needs to set the args parameter 21 | in the spec to call hello. This can be done using the sed command or a text 22 | editor. The following commands create a bundle for hello-world, change the 23 | default args parameter in the spec from "sh" to "/hello", then run the hello 24 | command in a new hello-world container named container1: 25 | 26 | mkdir hello 27 | cd hello 28 | docker pull hello-world 29 | docker export $(docker create hello-world) > hello-world.tar 30 | mkdir rootfs 31 | tar -C rootfs -xf hello-world.tar 32 | runc spec 33 | sed -i 's;"sh";"/hello";' config.json 34 | runc start container1 35 | 36 | In the start command above, "container1" is the name for the instance of the 37 | container that you are starting. The name you provide for the container instance 38 | must be unique on your host. 39 | 40 | An alternative for generating a customized spec config is to use "oci-runtime-tool", the 41 | sub-command "oci-runtime-tool generate" has lots of options that can be used to do any 42 | customizations as you want, see [runtime-tools](https://github.com/opencontainers/runtime-tools) 43 | to get more information. 44 | 45 | When starting a container through runc, runc needs root privilege. If not 46 | already running as root, you can use sudo to give runc root privilege. For 47 | example: "sudo runc start container1" will give runc root privilege to start the 48 | container on your host. 49 | 50 | Alternatively, you can start a rootless container, which has the ability to run without root privileges. 51 | For this to work, the specification file needs to be adjusted accordingly. 52 | You can pass the parameter **--rootless** to this command to generate a proper rootless spec file. 53 | 54 | # OPTIONS 55 | --bundle value, -b value path to the root of the bundle directory 56 | --rootless generate a configuration for a rootless container 57 | -------------------------------------------------------------------------------- /man/runc-start.8.md: -------------------------------------------------------------------------------- 1 | % runc-start "8" 2 | 3 | # NAME 4 | runc start - start executes the user defined process in a created container 5 | 6 | # SYNOPSIS 7 | runc start `` 8 | 9 | Where "``" is your name for the instance of the container that you 10 | are starting. The name you provide for the container instance must be unique on 11 | your host. 12 | 13 | # DESCRIPTION 14 | The start command executes the user defined process in a created container. 15 | -------------------------------------------------------------------------------- /man/runc-state.8.md: -------------------------------------------------------------------------------- 1 | % runc-state "8" 2 | 3 | # NAME 4 | runc state - output the state of a container 5 | 6 | # SYNOPSIS 7 | runc state `` 8 | 9 | Where "``" is your name for the instance of the container. 10 | 11 | # DESCRIPTION 12 | The state command outputs current state information for the 13 | instance of a container. 14 | -------------------------------------------------------------------------------- /man/runc-update.8.md: -------------------------------------------------------------------------------- 1 | % runc-update "8" 2 | 3 | # NAME 4 | runc update - update container resource constraints 5 | 6 | # SYNOPSIS 7 | runc update [command options] `` 8 | 9 | # DESCRIPTION 10 | The data can be read from a file or the standard input, the 11 | accepted format is as follow (unchanged values can be omitted): 12 | 13 | { 14 | "memory": { 15 | "limit": 0, 16 | "reservation": 0, 17 | "swap": 0, 18 | "kernel": 0, 19 | "kernelTCP": 0 20 | }, 21 | "cpu": { 22 | "shares": 0, 23 | "quota": 0, 24 | "period": 0, 25 | "realtimeRuntime": 0, 26 | "realtimePeriod": 0, 27 | "cpus": "", 28 | "mems": "" 29 | }, 30 | "blockIO": { 31 | "blkioWeight": 0 32 | } 33 | } 34 | 35 | Note: if data is to be read from a file or the standard input, all 36 | other options are ignored. 37 | 38 | # OPTIONS 39 | --resources value, -r value path to the file containing the resources to update or '-' to read from the standard input 40 | --blkio-weight value Specifies per cgroup weight, range is from 10 to 1000 (default: 0) 41 | --cpu-period value CPU CFS period to be used for hardcapping (in usecs). 0 to use system default 42 | --cpu-quota value CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period 43 | --cpu-rt-period value CPU realtime period to be used for hardcapping (in usecs). 0 to use system default 44 | --cpu-rt-runtime value CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period 45 | --cpu-share value CPU shares (relative weight vs. other containers) 46 | --cpuset-cpus value CPU(s) to use 47 | --cpuset-mems value Memory node(s) to use 48 | --memory value Memory limit (in bytes) 49 | --memory-reservation value Memory reservation or soft_limit (in bytes) 50 | --memory-swap value Total memory usage (memory + swap); set '-1' to enable unlimited swap 51 | --pids-limit value Maximum number of pids allowed in the container (default: 0) 52 | --l3-cache-schema The string of Intel RDT/CAT L3 cache schema 53 | --mem-bw-schema The string of Intel RDT/MBA memory bandwidth schema 54 | -------------------------------------------------------------------------------- /pause.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/sirupsen/logrus" 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | var pauseCommand = cli.Command{ 11 | Name: "pause", 12 | Usage: "pause suspends all processes inside the container", 13 | ArgsUsage: ` 14 | 15 | Where "" is the name for the instance of the container to be 16 | paused. `, 17 | Description: `The pause command suspends all processes in the instance of the container. 18 | 19 | Use sysbox-runc list to identify instances of containers and their current status.`, 20 | Action: func(context *cli.Context) error { 21 | if err := checkArgs(context, 1, exactArgs); err != nil { 22 | return err 23 | } 24 | rootlessCg, err := shouldUseRootlessCgroupManager(context) 25 | if err != nil { 26 | return err 27 | } 28 | if rootlessCg { 29 | logrus.Warnf("runc pause may fail if you don't have the full access to cgroups") 30 | } 31 | container, err := getContainer(context) 32 | if err != nil { 33 | return err 34 | } 35 | return container.Pause() 36 | }, 37 | } 38 | 39 | var resumeCommand = cli.Command{ 40 | Name: "resume", 41 | Usage: "resumes all processes that have been previously paused", 42 | ArgsUsage: ` 43 | 44 | Where "" is the name for the instance of the container to be 45 | resumed.`, 46 | Description: `The resume command resumes all processes in the instance of the container. 47 | 48 | Use sysbox-runc list to identify instances of containers and their current status.`, 49 | Action: func(context *cli.Context) error { 50 | if err := checkArgs(context, 1, exactArgs); err != nil { 51 | return err 52 | } 53 | rootlessCg, err := shouldUseRootlessCgroupManager(context) 54 | if err != nil { 55 | return err 56 | } 57 | if rootlessCg { 58 | logrus.Warn("runc resume may fail if you don't have the full access to cgroups") 59 | } 60 | container, err := getContainer(context) 61 | if err != nil { 62 | return err 63 | } 64 | return container.Resume() 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /profile.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/pkg/profile" 8 | "github.com/sirupsen/logrus" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | // Run cpu / memory profiling collection. 13 | func runProfiler(ctx *cli.Context) (interface{ Stop() }, error) { 14 | 15 | var prof interface{ Stop() } 16 | 17 | cpuProfOn := ctx.GlobalBool("cpu-profiling") 18 | memProfOn := ctx.GlobalBool("memory-profiling") 19 | 20 | // Typical (i.e., non-profiling) case. 21 | if !cpuProfOn && !memProfOn { 22 | return nil, nil 23 | } 24 | 25 | // Cpu and Memory profiling options seem to be mutually exclused in pprof. 26 | if cpuProfOn && memProfOn { 27 | return nil, fmt.Errorf("Unsupported parameter combination: cpu and memory profiling") 28 | } 29 | 30 | if cpuProfOn { 31 | 32 | // set the profiler's sampling rate at twice the usual to get a 33 | // more accurate result (sysbox-runc executes quickly). 34 | // 35 | // Note: this may result in the following error message when 36 | // running sysbox-runc with profiling enabled: "runtime: cannot 37 | // set cpu profile rate until previous profile has finished." 38 | // We can ignore it; it occurs because profile.Start() invokes 39 | // pprof.go which calls SetCPUProfileRate() again. Since we have 40 | // already set the value, the one from pprof will be ignored. 41 | runtime.SetCPUProfileRate(200) 42 | 43 | prof = profile.Start( 44 | profile.Quiet, 45 | profile.CPUProfile, 46 | profile.ProfilePath("."), 47 | ) 48 | logrus.Info("Initiated cpu-profiling data collection.") 49 | } 50 | 51 | if memProfOn { 52 | prof = profile.Start( 53 | profile.Quiet, 54 | profile.MemProfile, 55 | profile.ProfilePath("."), 56 | ) 57 | logrus.Info("Initiated memory-profiling data collection.") 58 | } 59 | 60 | return prof, nil 61 | } 62 | -------------------------------------------------------------------------------- /rlimit_linux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "golang.org/x/sys/unix" 5 | 6 | var rlimitMap = map[string]int{ 7 | "RLIMIT_CPU": unix.RLIMIT_CPU, 8 | "RLIMIT_FSIZE": unix.RLIMIT_FSIZE, 9 | "RLIMIT_DATA": unix.RLIMIT_DATA, 10 | "RLIMIT_STACK": unix.RLIMIT_STACK, 11 | "RLIMIT_CORE": unix.RLIMIT_CORE, 12 | "RLIMIT_RSS": unix.RLIMIT_RSS, 13 | "RLIMIT_NPROC": unix.RLIMIT_NPROC, 14 | "RLIMIT_NOFILE": unix.RLIMIT_NOFILE, 15 | "RLIMIT_MEMLOCK": unix.RLIMIT_MEMLOCK, 16 | "RLIMIT_AS": unix.RLIMIT_AS, 17 | "RLIMIT_LOCKS": unix.RLIMIT_LOCKS, 18 | "RLIMIT_SIGPENDING": unix.RLIMIT_SIGPENDING, 19 | "RLIMIT_MSGQUEUE": unix.RLIMIT_MSGQUEUE, 20 | "RLIMIT_NICE": unix.RLIMIT_NICE, 21 | "RLIMIT_RTPRIO": unix.RLIMIT_RTPRIO, 22 | "RLIMIT_RTTIME": unix.RLIMIT_RTTIME, 23 | } 24 | 25 | func strToRlimit(key string) (int, error) { 26 | rl, ok := rlimitMap[key] 27 | if !ok { 28 | return 0, fmt.Errorf("wrong rlimit value: %s", key) 29 | } 30 | return rl, nil 31 | } 32 | -------------------------------------------------------------------------------- /rootless_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/opencontainers/runc/libcontainer/cgroups/systemd" 9 | "github.com/opencontainers/runc/libcontainer/system" 10 | "github.com/sirupsen/logrus" 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) { 15 | if context != nil { 16 | b, err := parseBoolOrAuto(context.GlobalString("rootless")) 17 | if err != nil { 18 | return false, err 19 | } 20 | // nil b stands for "auto detect" 21 | if b != nil { 22 | return *b, nil 23 | } 24 | } 25 | if os.Geteuid() != 0 { 26 | return true, nil 27 | } 28 | if !system.RunningInUserNS() { 29 | // euid == 0 , in the initial ns (i.e. the real root) 30 | return false, nil 31 | } 32 | // euid = 0, in a userns. 33 | // 34 | // [systemd driver] 35 | // We can call DetectUID() to parse the OwnerUID value from `busctl --user --no-pager status` result. 36 | // The value corresponds to sd_bus_creds_get_owner_uid(3). 37 | // If the value is 0, we have rootful systemd inside userns, so we do not need the rootless cgroup manager. 38 | // 39 | // On error, we assume we are root. An error may happen during shelling out to `busctl` CLI, 40 | // mostly when $DBUS_SESSION_BUS_ADDRESS is unset. 41 | if context.GlobalBool("systemd-cgroup") { 42 | ownerUID, err := systemd.DetectUID() 43 | if err != nil { 44 | logrus.WithError(err).Debug("failed to get the OwnerUID value, assuming the value to be 0") 45 | ownerUID = 0 46 | } 47 | return ownerUID != 0, nil 48 | } 49 | // [cgroupfs driver] 50 | // As we are unaware of cgroups path, we can't determine whether we have the full 51 | // access to the cgroups path. 52 | // Either way, we can safely decide to use the rootless cgroups manager. 53 | return true, nil 54 | } 55 | 56 | func shouldHonorXDGRuntimeDir() bool { 57 | if os.Getenv("XDG_RUNTIME_DIR") == "" { 58 | return false 59 | } 60 | if os.Geteuid() != 0 { 61 | return true 62 | } 63 | if !system.RunningInUserNS() { 64 | // euid == 0 , in the initial ns (i.e. the real root) 65 | // in this case, we should use /run/runc and ignore 66 | // $XDG_RUNTIME_DIR (e.g. /run/user/0) for backward 67 | // compatibility. 68 | return false 69 | } 70 | // euid = 0, in a userns. 71 | u, ok := os.LookupEnv("USER") 72 | return !ok || u != "root" 73 | } 74 | -------------------------------------------------------------------------------- /script/.validate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$VALIDATE_UPSTREAM" ]; then 4 | # this is kind of an expensive check, so let's not do this twice if we 5 | # are running more than one validate bundlescript 6 | 7 | VALIDATE_REPO='https://github.com/opencontainers/runc.git' 8 | VALIDATE_BRANCH='master' 9 | 10 | if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then 11 | VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git" 12 | VALIDATE_BRANCH="${TRAVIS_BRANCH}" 13 | fi 14 | 15 | VALIDATE_HEAD="$(git rev-parse --verify HEAD)" 16 | 17 | git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH" 18 | VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)" 19 | 20 | VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD" 21 | VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD" 22 | 23 | validate_diff() { 24 | if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then 25 | git diff "$VALIDATE_COMMIT_DIFF" "$@" 26 | fi 27 | } 28 | validate_log() { 29 | if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then 30 | git log "$VALIDATE_COMMIT_LOG" "$@" 31 | fi 32 | } 33 | fi 34 | -------------------------------------------------------------------------------- /script/validate-c: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "$(dirname "$BASH_SOURCE")/.validate" 4 | 5 | IFS=$'\n' 6 | files=($(validate_diff --diff-filter=ACMR --name-only -- '*.c' | grep -v '^vendor/' || true)) 7 | unset IFS 8 | 9 | # 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." 10 | INDENT="indent -linux -l120 -T size_t -T jmp_buf" 11 | if [ -z "$(indent --version 2>&1 | grep GNU)" ]; then 12 | echo "Skipping C indentation checks, as GNU indent is not installed." 13 | exit 0 14 | fi 15 | 16 | badFiles=() 17 | for f in "${files[@]}"; do 18 | orig=$(mktemp) 19 | formatted=$(mktemp) 20 | # we use "git show" here to validate that what's committed is formatted 21 | git show "$VALIDATE_HEAD:$f" >${orig} 22 | ${INDENT} ${orig} -o ${formatted} 23 | if [ "$(diff -u ${orig} ${formatted})" ]; then 24 | badFiles+=("$f") 25 | fi 26 | rm -f ${orig} ${formatted} 27 | done 28 | 29 | if [ ${#badFiles[@]} -eq 0 ]; then 30 | echo 'Congratulations! All C source files are properly formatted.' 31 | else 32 | { 33 | echo "These files are not properly formatted:" 34 | for f in "${badFiles[@]}"; do 35 | echo " - $f" 36 | done 37 | echo 38 | echo "Please reformat the above files using \"${INDENT}\" and commit the result." 39 | echo 40 | } >&2 41 | false 42 | fi 43 | -------------------------------------------------------------------------------- /script/validate-gofmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "$(dirname "$BASH_SOURCE")/.validate" 4 | 5 | IFS=$'\n' 6 | files=($(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true)) 7 | unset IFS 8 | 9 | badFiles=() 10 | for f in "${files[@]}"; do 11 | # we use "git show" here to validate that what's committed is formatted 12 | if [ "$(git show "$VALIDATE_HEAD:$f" | gofmt -s -l)" ]; then 13 | badFiles+=("$f") 14 | fi 15 | done 16 | 17 | if [ ${#badFiles[@]} -eq 0 ]; then 18 | echo 'Congratulations! All Go source files are properly formatted.' 19 | else 20 | { 21 | echo "These files are not properly gofmt'd:" 22 | for f in "${badFiles[@]}"; do 23 | echo " - $f" 24 | done 25 | echo 26 | echo 'Please reformat the above files using "gofmt -s -w" and commit the result.' 27 | echo 28 | } >&2 29 | false 30 | fi 31 | -------------------------------------------------------------------------------- /start.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/opencontainers/runc/libcontainer" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | var startCommand = cli.Command{ 13 | Name: "start", 14 | Usage: "executes the user defined process in a created system container", 15 | ArgsUsage: ` 16 | 17 | Where "" is your name for the instance of the system container that you 18 | are starting. The name you provide for the container instance must be unique on 19 | your host.`, 20 | Description: `The start command executes the user defined process in a created container.`, 21 | Action: func(context *cli.Context) error { 22 | if err := checkArgs(context, 1, exactArgs); err != nil { 23 | return err 24 | } 25 | container, err := getContainer(context) 26 | if err != nil { 27 | return err 28 | } 29 | status, err := container.Status() 30 | if err != nil { 31 | return err 32 | } 33 | switch status { 34 | case libcontainer.Created: 35 | notifySocket, err := notifySocketStart(context, os.Getenv("NOTIFY_SOCKET"), container.ID()) 36 | if err != nil { 37 | return err 38 | } 39 | if err := container.Exec(); err != nil { 40 | return err 41 | } 42 | if notifySocket != nil { 43 | return notifySocket.waitForContainer(container) 44 | } 45 | return nil 46 | case libcontainer.Stopped: 47 | return errors.New("cannot start a container that has stopped") 48 | case libcontainer.Running: 49 | return errors.New("cannot start an already running container") 50 | default: 51 | return fmt.Errorf("cannot start a container in the %s state\n", status) 52 | } 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "os" 8 | 9 | "github.com/opencontainers/runc/libcontainer" 10 | "github.com/opencontainers/runc/libcontainer/utils" 11 | "github.com/urfave/cli" 12 | ) 13 | 14 | var stateCommand = cli.Command{ 15 | Name: "state", 16 | Usage: "output the state of a container", 17 | ArgsUsage: ` 18 | 19 | Where "" is your name for the instance of the container.`, 20 | Description: `The state command outputs current state information for the 21 | instance of a container.`, 22 | Action: func(context *cli.Context) error { 23 | if err := checkArgs(context, 1, exactArgs); err != nil { 24 | return err 25 | } 26 | container, err := getContainer(context) 27 | if err != nil { 28 | return err 29 | } 30 | containerStatus, err := container.Status() 31 | if err != nil { 32 | return err 33 | } 34 | state, err := container.State() 35 | if err != nil { 36 | return err 37 | } 38 | pid := state.BaseState.InitProcessPid 39 | if containerStatus == libcontainer.Stopped { 40 | pid = 0 41 | } 42 | bundle, annotations := utils.Annotations(state.Config.Labels) 43 | cs := containerState{ 44 | Version: state.BaseState.Config.Version, 45 | ID: state.BaseState.ID, 46 | InitProcessPid: pid, 47 | Status: containerStatus.String(), 48 | Bundle: bundle, 49 | Rootfs: state.BaseState.Config.Rootfs, 50 | Created: state.BaseState.Created, 51 | Annotations: annotations, 52 | } 53 | data, err := json.MarshalIndent(cs, "", " ") 54 | if err != nil { 55 | return err 56 | } 57 | os.Stdout.Write(data) 58 | return nil 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /tests/integration/README.md: -------------------------------------------------------------------------------- 1 | # sysbox-runc Integration Tests 2 | 3 | Integration tests provide end-to-end testing of sysbox-runc. 4 | 5 | Note that integration tests do **not** replace unit tests. 6 | 7 | As a rule of thumb, code should be tested thoroughly with unit tests. 8 | Integration tests on the other hand are meant to test a specific feature end 9 | to end. 10 | 11 | Integration tests are written in *bash* using the 12 | [bats (Bash Automated Testing System)](https://github.com/bats-core/bats-core) 13 | framework. 14 | 15 | ## Running integration tests 16 | 17 | The easiest way to run integration tests is with Docker: 18 | ``` 19 | $ make integration 20 | ``` 21 | Alternatively, you can run integration tests directly on your host through make: 22 | ``` 23 | $ sudo make localintegration 24 | ``` 25 | Or you can just run them directly using bats 26 | ``` 27 | $ sudo bats tests/integration 28 | ``` 29 | To run a single test bucket: 30 | ``` 31 | $ make integration TESTPATH="/checkpoint.bats" 32 | ``` 33 | 34 | 35 | To run them on your host, you need to set up a development environment plus 36 | [bats (Bash Automated Testing System)](https://github.com/bats-core/bats-core#installing-bats-from-source). 37 | 38 | For example: 39 | ``` 40 | $ cd ~/go/src/github.com 41 | $ git clone https://github.com/bats-core/bats-core.git 42 | $ cd bats-core 43 | $ ./install.sh /usr/local 44 | ``` 45 | 46 | > **Note**: There are known issues running the integration tests using 47 | > **devicemapper** as a storage driver, make sure that your docker daemon 48 | > is using **aufs** if you want to successfully run the integration tests. 49 | 50 | ## Writing integration tests 51 | 52 | [helper functions] 53 | (https://github.com/opencontainers/runc/blob/master/tests/integration/helpers.bash) 54 | are provided in order to facilitate writing tests. 55 | 56 | ```sh 57 | #!/usr/bin/env bats 58 | 59 | # This will load the helpers. 60 | load helpers 61 | 62 | # setup is called at the beginning of every test. 63 | function setup() { 64 | # see functions teardown_hello and setup_hello in helpers.bash, used to 65 | # create a pristine environment for running your tests 66 | teardown_hello 67 | setup_hello 68 | } 69 | 70 | # teardown is called at the end of every test. 71 | function teardown() { 72 | teardown_hello 73 | } 74 | 75 | @test "this is a simple test" { 76 | runc run containerid 77 | # "The runc macro" automatically populates $status, $output and $lines. 78 | # Please refer to bats documentation to find out more. 79 | [ "$status" -eq 0 ] 80 | 81 | # check expected output 82 | [[ "${output}" == *"Hello"* ]] 83 | } 84 | 85 | ``` 86 | -------------------------------------------------------------------------------- /tests/integration/create.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "runc create" { 15 | runc create --console-socket "$CONSOLE_SOCKET" test_busybox 16 | [ "$status" -eq 0 ] 17 | 18 | testcontainer test_busybox created 19 | 20 | # start the command 21 | runc start test_busybox 22 | [ "$status" -eq 0 ] 23 | 24 | testcontainer test_busybox running 25 | } 26 | 27 | @test "runc create exec" { 28 | runc create --console-socket "$CONSOLE_SOCKET" test_busybox 29 | [ "$status" -eq 0 ] 30 | 31 | testcontainer test_busybox created 32 | 33 | runc exec test_busybox true 34 | [ "$status" -eq 0 ] 35 | 36 | testcontainer test_busybox created 37 | 38 | # start the command 39 | runc start test_busybox 40 | [ "$status" -eq 0 ] 41 | 42 | testcontainer test_busybox running 43 | } 44 | 45 | @test "runc create --pid-file" { 46 | runc create --pid-file pid.txt --console-socket "$CONSOLE_SOCKET" test_busybox 47 | [ "$status" -eq 0 ] 48 | 49 | testcontainer test_busybox created 50 | 51 | # check pid.txt was generated 52 | [ -e pid.txt ] 53 | 54 | [[ $(cat pid.txt) == $(__runc state test_busybox | jq '.pid') ]] 55 | 56 | # start the command 57 | runc start test_busybox 58 | [ "$status" -eq 0 ] 59 | 60 | testcontainer test_busybox running 61 | } 62 | 63 | @test "runc create --pid-file with new CWD" { 64 | # create pid_file directory as the CWD 65 | mkdir pid_file 66 | cd pid_file 67 | 68 | runc create --pid-file pid.txt -b "$BUSYBOX_BUNDLE" --console-socket "$CONSOLE_SOCKET" test_busybox 69 | [ "$status" -eq 0 ] 70 | 71 | testcontainer test_busybox created 72 | 73 | # check pid.txt was generated 74 | [ -e pid.txt ] 75 | 76 | [[ $(cat pid.txt) == $(__runc state test_busybox | jq '.pid') ]] 77 | 78 | # start the command 79 | runc start test_busybox 80 | [ "$status" -eq 0 ] 81 | 82 | testcontainer test_busybox running 83 | } 84 | -------------------------------------------------------------------------------- /tests/integration/cwd.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | # Test case for https://github.com/opencontainers/runc/pull/2086 15 | @test "runc exec --user with no access to cwd" { 16 | requires root 17 | 18 | # sysbox-runc: containers always user the user-ns. If uid-shifting is not 19 | # used, the rootfs ownership must be within the range of host uids assigned 20 | # to the container. 21 | local uid 22 | if [ -z "$SHIFT_ROOTFS_UIDS" ]; then 23 | uid=$((UID_MAP + 42)) 24 | else 25 | uid=42 26 | fi 27 | 28 | chown $uid rootfs/root 29 | chmod 700 rootfs/root 30 | 31 | update_config ' .process.cwd = "/root" 32 | | .process.user.uid = 42 33 | | .process.user.gid = 42 34 | | .process.args |= ["sleep", "1h"]' 35 | 36 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 37 | [ "$status" -eq 0 ] 38 | 39 | runc exec --user 0 test_busybox true 40 | [ "$status" -eq 0 ] 41 | } 42 | 43 | # Verify a cwd owned by the container user can be chdir'd to, 44 | # even if runc doesn't have the privilege to do so. 45 | @test "runc create sets up user before chdir to cwd" { 46 | requires rootless rootless_idmap 47 | 48 | # Some setup for this test (AUX_DIR and AUX_UID) is done 49 | # by rootless.sh. Check that setup is done... 50 | if [[ ! -d "$AUX_DIR" || -z "$AUX_UID" ]]; then 51 | skip "bad/unset AUX_DIR/AUX_UID" 52 | fi 53 | # ... and is correct, i.e. the current user 54 | # does not have permission to access AUX_DIR. 55 | if ls -l "$AUX_DIR" 2>/dev/null; then 56 | skip "bad AUX_DIR permissions" 57 | fi 58 | 59 | update_config ' .mounts += [{ 60 | source: "'"$AUX_DIR"'", 61 | destination: "'"$AUX_DIR"'", 62 | options: ["bind"] 63 | }] 64 | | .process.user.uid = '"$AUX_UID"' 65 | | .process.cwd = "'"$AUX_DIR"'" 66 | | .process.args |= ["ls", "'"$AUX_DIR"'"]' 67 | 68 | runc run test_busybox 69 | [ "$status" -eq 0 ] 70 | } 71 | -------------------------------------------------------------------------------- /tests/integration/debug.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_hello 7 | setup_hello 8 | } 9 | 10 | function teardown() { 11 | teardown_hello 12 | } 13 | 14 | @test "global --debug" { 15 | # run hello-world 16 | runc --debug run test_hello 17 | echo "${output}" 18 | [ "$status" -eq 0 ] 19 | 20 | # check expected debug output was sent to stderr 21 | [[ "${output}" == *"level=debug"* ]] 22 | [[ "${output}" == *"nsexec started"* ]] 23 | [[ "${output}" == *"child process in init()"* ]] 24 | } 25 | 26 | @test "global --debug to --log" { 27 | # run hello-world 28 | runc --log log.out --debug run test_hello 29 | [ "$status" -eq 0 ] 30 | 31 | # check output does not include debug info 32 | [[ "${output}" != *"level=debug"* ]] 33 | 34 | # check log.out was generated 35 | [ -e log.out ] 36 | 37 | # check expected debug output was sent to log.out 38 | output=$(cat log.out) 39 | [[ "${output}" == *"level=debug"* ]] 40 | [[ "${output}" == *"nsexec started"* ]] 41 | [[ "${output}" == *"child process in init()"* ]] 42 | } 43 | 44 | @test "global --debug to --log --log-format 'text'" { 45 | # run hello-world 46 | runc --log log.out --log-format "text" --debug run test_hello 47 | [ "$status" -eq 0 ] 48 | 49 | # check output does not include debug info 50 | [[ "${output}" != *"level=debug"* ]] 51 | 52 | # check log.out was generated 53 | [ -e log.out ] 54 | 55 | # check expected debug output was sent to log.out 56 | output=$(cat log.out) 57 | [[ "${output}" == *"level=debug"* ]] 58 | [[ "${output}" == *"nsexec started"* ]] 59 | [[ "${output}" == *"child process in init()"* ]] 60 | } 61 | 62 | @test "global --debug to --log --log-format 'json'" { 63 | # run hello-world 64 | runc --log log.out --log-format "json" --debug run test_hello 65 | [ "$status" -eq 0 ] 66 | 67 | # check output does not include debug info 68 | [[ "${output}" != *"level=debug"* ]] 69 | 70 | # check log.out was generated 71 | [ -e log.out ] 72 | 73 | # check expected debug output was sent to log.out 74 | output=$(cat log.out) 75 | [[ "${output}" == *'"level":"debug"'* ]] 76 | [[ "${output}" == *"nsexec started"* ]] 77 | [[ "${output}" == *"child process in init()"* ]] 78 | } 79 | -------------------------------------------------------------------------------- /tests/integration/help.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | @test "runc -h" { 6 | runc -h 7 | [ "$status" -eq 0 ] 8 | [[ ${lines[0]} =~ NAME:+ ]] 9 | [[ ${lines[1]} =~ sysbox-runc\ '-'\ sysbox-runc+ ]] 10 | 11 | runc --help 12 | [ "$status" -eq 0 ] 13 | [[ ${lines[0]} =~ NAME:+ ]] 14 | [[ ${lines[1]} =~ sysbox-runc\ '-'\ sysbox-runc+ ]] 15 | } 16 | 17 | @test "runc command -h" { 18 | 19 | # sysbox-runc does not yet support checkpoint/restore 20 | # runc checkpoint -h 21 | # [ "$status" -eq 0 ] 22 | # [[ ${lines[1]} =~ runc\ checkpoint+ ]] 23 | 24 | runc delete -h 25 | [ "$status" -eq 0 ] 26 | [[ ${lines[1]} =~ runc\ delete+ ]] 27 | 28 | runc events -h 29 | [ "$status" -eq 0 ] 30 | [[ ${lines[1]} =~ runc\ events+ ]] 31 | 32 | runc exec -h 33 | [ "$status" -eq 0 ] 34 | [[ ${lines[1]} =~ runc\ exec+ ]] 35 | 36 | runc kill -h 37 | [ "$status" -eq 0 ] 38 | [[ ${lines[1]} =~ runc\ kill+ ]] 39 | 40 | runc list -h 41 | [ "$status" -eq 0 ] 42 | [[ ${lines[0]} =~ NAME:+ ]] 43 | [[ ${lines[1]} =~ runc\ list+ ]] 44 | 45 | runc list --help 46 | [ "$status" -eq 0 ] 47 | [[ ${lines[0]} =~ NAME:+ ]] 48 | [[ ${lines[1]} =~ runc\ list+ ]] 49 | 50 | runc pause -h 51 | [ "$status" -eq 0 ] 52 | [[ ${lines[1]} =~ runc\ pause+ ]] 53 | 54 | # sysbox-runc does not yet support checkpoint/restore 55 | # runc restore -h 56 | # [ "$status" -eq 0 ] 57 | # [[ ${lines[1]} =~ runc\ restore+ ]] 58 | 59 | runc resume -h 60 | [ "$status" -eq 0 ] 61 | [[ ${lines[1]} =~ runc\ resume+ ]] 62 | 63 | # We don't use runc_spec here, because we're just testing the help page. 64 | runc spec -h 65 | [ "$status" -eq 0 ] 66 | [[ ${lines[1]} =~ runc\ spec+ ]] 67 | 68 | runc start -h 69 | [ "$status" -eq 0 ] 70 | [[ ${lines[1]} =~ runc\ start+ ]] 71 | 72 | runc run -h 73 | [ "$status" -eq 0 ] 74 | [[ ${lines[1]} =~ runc\ run+ ]] 75 | 76 | runc state -h 77 | [ "$status" -eq 0 ] 78 | [[ ${lines[1]} =~ runc\ state+ ]] 79 | 80 | runc update -h 81 | [ "$status" -eq 0 ] 82 | [[ ${lines[1]} =~ runc\ update+ ]] 83 | 84 | } 85 | 86 | @test "runc foo -h" { 87 | runc foo -h 88 | [ "$status" -ne 0 ] 89 | [[ "${output}" == *"No help topic for 'foo'"* ]] 90 | } 91 | -------------------------------------------------------------------------------- /tests/integration/hooks.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | # CR = CreateRuntime 6 | # CC = CreataContainer 7 | HOOKLIBCR=librunc-hooks-create-runtime.so 8 | HOOKLIBCC=librunc-hooks-create-container.so 9 | LIBPATH="$DEBIAN_BUNDLE/rootfs/lib/" 10 | 11 | function setup() { 12 | umount "$LIBPATH"/$HOOKLIBCR.1.0.0 &>/dev/null || true 13 | umount "$LIBPATH"/$HOOKLIBCC.1.0.0 &>/dev/null || true 14 | 15 | requires root no_systemd 16 | 17 | teardown_debian 18 | setup_debian 19 | } 20 | 21 | function teardown() { 22 | umount "$LIBPATH"/$HOOKLIBCR.1.0.0 &>/dev/null || true 23 | umount "$LIBPATH"/$HOOKLIBCC.1.0.0 &>/dev/null || true 24 | 25 | rm -f $HOOKLIBCR.1.0.0 $HOOKLIBCC.1.0.0 26 | teardown_debian 27 | } 28 | 29 | @test "runc run (hooks library tests)" { 30 | skip "unsupported" 31 | 32 | # setup some dummy libs 33 | gcc -shared -Wl,-soname,librunc-hooks-create-runtime.so.1 -o "$HOOKLIBCR.1.0.0" 34 | gcc -shared -Wl,-soname,librunc-hooks-create-container.so.1 -o "$HOOKLIBCC.1.0.0" 35 | 36 | current_pwd="$(pwd)" 37 | 38 | # To mount $HOOKLIBCR we need to do that in the container namespace 39 | create_runtime_hook=$( 40 | cat <<-EOF 41 | pid=\$(cat - | jq -r '.pid') 42 | touch "$LIBPATH/$HOOKLIBCR.1.0.0" 43 | nsenter -m \$ns -t \$pid mount --bind "$current_pwd/$HOOKLIBCR.1.0.0" "$LIBPATH/$HOOKLIBCR.1.0.0" 44 | EOF 45 | ) 46 | 47 | create_container_hook="touch ./lib/$HOOKLIBCC.1.0.0 && mount --bind $current_pwd/$HOOKLIBCC.1.0.0 ./lib/$HOOKLIBCC.1.0.0" 48 | 49 | CONFIG=$(jq --arg create_runtime_hook "$create_runtime_hook" --arg create_container_hook "$create_container_hook" ' 50 | .hooks |= . + {"createRuntime": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", $create_runtime_hook]}]} | 51 | .hooks |= . + {"createContainer": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", $create_container_hook]}]} | 52 | .hooks |= . + {"startContainer": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", "ldconfig"]}]} | 53 | .process.args = ["/bin/sh", "-c", "ldconfig -p | grep librunc"]' "$DEBIAN_BUNDLE"/config.json) 54 | echo "${CONFIG}" >config.json 55 | 56 | runc run test_debian 57 | [ "$status" -eq 0 ] 58 | 59 | echo "Checking create-runtime library" 60 | echo "$output" | grep $HOOKLIBCR 61 | 62 | echo "Checking create-container library" 63 | echo "$output" | grep $HOOKLIBCC 64 | } 65 | -------------------------------------------------------------------------------- /tests/integration/kill.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "kill detached busybox" { 15 | # run busybox detached 16 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 17 | [ "$status" -eq 0 ] 18 | 19 | # check state 20 | testcontainer test_busybox running 21 | 22 | runc kill test_busybox KILL 23 | [ "$status" -eq 0 ] 24 | 25 | retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" 26 | 27 | # we should ensure kill work after the container stopped 28 | runc kill -a test_busybox 0 29 | [ "$status" -eq 0 ] 30 | 31 | runc delete test_busybox 32 | [ "$status" -eq 0 ] 33 | } 34 | -------------------------------------------------------------------------------- /tests/integration/list.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_running_container_inroot test_box1 "$HELLO_BUNDLE" 7 | teardown_running_container_inroot test_box2 "$HELLO_BUNDLE" 8 | teardown_running_container_inroot test_box3 "$HELLO_BUNDLE" 9 | teardown_busybox 10 | setup_busybox 11 | } 12 | 13 | function teardown() { 14 | teardown_running_container_inroot test_box1 "$HELLO_BUNDLE" 15 | teardown_running_container_inroot test_box2 "$HELLO_BUNDLE" 16 | teardown_running_container_inroot test_box3 "$HELLO_BUNDLE" 17 | teardown_busybox 18 | } 19 | 20 | @test "list" { 21 | # run a few busyboxes detached 22 | ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_box1 23 | [ "$status" -eq 0 ] 24 | 25 | ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_box2 26 | [ "$status" -eq 0 ] 27 | 28 | ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_box3 29 | [ "$status" -eq 0 ] 30 | 31 | ROOT=$HELLO_BUNDLE runc list 32 | [ "$status" -eq 0 ] 33 | [[ ${lines[0]} =~ ID\ +PID\ +STATUS\ +BUNDLE\ +CREATED+ ]] 34 | [[ "${lines[1]}" == *"test_box1"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]] 35 | [[ "${lines[2]}" == *"test_box2"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]] 36 | [[ "${lines[3]}" == *"test_box3"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]] 37 | 38 | ROOT=$HELLO_BUNDLE runc list -q 39 | [ "$status" -eq 0 ] 40 | [[ "${lines[0]}" == "test_box1" ]] 41 | [[ "${lines[1]}" == "test_box2" ]] 42 | [[ "${lines[2]}" == "test_box3" ]] 43 | 44 | ROOT=$HELLO_BUNDLE runc list --format table 45 | [ "$status" -eq 0 ] 46 | [[ ${lines[0]} =~ ID\ +PID\ +STATUS\ +BUNDLE\ +CREATED+ ]] 47 | [[ "${lines[1]}" == *"test_box1"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]] 48 | [[ "${lines[2]}" == *"test_box2"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]] 49 | [[ "${lines[3]}" == *"test_box3"*[0-9]*"running"*$BUSYBOX_BUNDLE*[0-9]* ]] 50 | 51 | ROOT=$HELLO_BUNDLE runc list --format json 52 | [ "$status" -eq 0 ] 53 | [[ "${lines[0]}" == [\[][\{]"\"ociVersion\""[:]"\""*[0-9][\.]*[0-9][\.]*[0-9]*"\""[,]"\"id\""[:]"\"test_box1\""[,]"\"pid\""[:]*[0-9][,]"\"status\""[:]*"\"running\""[,]"\"bundle\""[:]*$BUSYBOX_BUNDLE*[,]"\"rootfs\""[:]"\""*"\""[,]"\"created\""[:]*[0-9]*[\}]* ]] 54 | [[ "${lines[0]}" == *[,][\{]"\"ociVersion\""[:]"\""*[0-9][\.]*[0-9][\.]*[0-9]*"\""[,]"\"id\""[:]"\"test_box2\""[,]"\"pid\""[:]*[0-9][,]"\"status\""[:]*"\"running\""[,]"\"bundle\""[:]*$BUSYBOX_BUNDLE*[,]"\"rootfs\""[:]"\""*"\""[,]"\"created\""[:]*[0-9]*[\}]* ]] 55 | [[ "${lines[0]}" == *[,][\{]"\"ociVersion\""[:]"\""*[0-9][\.]*[0-9][\.]*[0-9]*"\""[,]"\"id\""[:]"\"test_box3\""[,]"\"pid\""[:]*[0-9][,]"\"status\""[:]*"\"running\""[,]"\"bundle\""[:]*$BUSYBOX_BUNDLE*[,]"\"rootfs\""[:]"\""*"\""[,]"\"created\""[:]*[0-9]*[\}][\]] ]] 56 | } 57 | -------------------------------------------------------------------------------- /tests/integration/mask.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | 9 | # Create fake rootfs. 10 | mkdir rootfs/testdir 11 | echo "Forbidden information!" >rootfs/testfile 12 | 13 | # sysbox-runc 14 | if [ -z "$SHIFT_ROOTFS_UIDS" ]; then 15 | chown "$UID_MAP":"$GID_MAP" rootfs/testdir 16 | chown "$UID_MAP":"$GID_MAP" rootfs/testfile 17 | fi 18 | 19 | # add extra masked paths 20 | update_config '(.. | select(.maskedPaths? != null)) .maskedPaths += ["/testdir", "/testfile"]' 21 | } 22 | 23 | function teardown() { 24 | teardown_busybox 25 | } 26 | 27 | @test "mask paths [file]" { 28 | 29 | skip "NEEDS FIX" 30 | 31 | # run busybox detached 32 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 33 | [ "$status" -eq 0 ] 34 | 35 | runc exec test_busybox cat /testfile 36 | [ "$status" -eq 0 ] 37 | [[ "${output}" == "" ]] 38 | 39 | runc exec test_busybox rm -f /testfile 40 | [ "$status" -eq 1 ] 41 | [[ "${output}" == *"Device or resource busy"* ]] 42 | 43 | # TODO: this operation passes in sys containers, but problably should 44 | # fail; we don't want to allow unmasking of a masked path. 45 | 46 | runc exec test_busybox umount /testfile 47 | [ "$status" -eq 1 ] 48 | [[ "${output}" == *"Device or resource busy"* ]] 49 | } 50 | 51 | @test "mask paths [directory]" { 52 | # run busybox detached 53 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 54 | [ "$status" -eq 0 ] 55 | 56 | runc exec test_busybox ls /testdir 57 | [ "$status" -eq 0 ] 58 | [[ "${output}" == "" ]] 59 | 60 | runc exec test_busybox touch /testdir/foo 61 | [ "$status" -eq 1 ] 62 | [[ "${output}" == *"Read-only file system"* ]] 63 | 64 | runc exec test_busybox rm -rf /testdir 65 | [ "$status" -eq 1 ] 66 | [[ "${output}" == *"Device or resource busy"* ]] 67 | } 68 | 69 | # sysbox-runc: this test is expected to fail until sysbox can intercept 70 | # the mount syscall to prevent umounting of mounts for masked paths 71 | # @test "mask path umounting" { 72 | # run busybox detached 73 | # runc run -d --console-socket $CONSOLE_SOCKET test_busybox 74 | # [ "$status" -eq 0 ] 75 | # 76 | # runc exec test_busybox umount /testfile 77 | # [ "$status" -eq 1 ] 78 | # [[ "${output}" == *"Operation not permitted"* ]] 79 | # 80 | # runc exec test_busybox umount /testdir 81 | # [ "$status" -eq 1 ] 82 | # [[ "${output}" == *"Operation not permitted"* ]] 83 | # } 84 | -------------------------------------------------------------------------------- /tests/integration/multi-arch.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | get_busybox() { 3 | case $(go env GOARCH) in 4 | arm64) 5 | echo 'https://github.com/docker-library/busybox/raw/dist-arm64v8/stable/glibc/busybox.tar.xz' 6 | ;; 7 | *) 8 | echo 'https://github.com/docker-library/busybox/raw/dist-amd64/stable/glibc/busybox.tar.xz' 9 | ;; 10 | esac 11 | } 12 | 13 | get_hello() { 14 | case $(go env GOARCH) in 15 | arm64) 16 | echo 'hello-world-aarch64.tar' 17 | ;; 18 | *) 19 | echo 'hello-world.tar' 20 | ;; 21 | esac 22 | } 23 | 24 | get_and_extract_debian() { 25 | tmp=$(mktemp -d) 26 | cd "$tmp" 27 | 28 | debian="debian:3.11.6" 29 | 30 | case $(go env GOARCH) in 31 | arm64) 32 | skopeo copy docker://arm64v8/debian:buster "oci:$debian" 33 | ;; 34 | *) 35 | skopeo copy docker://amd64/debian:buster "oci:$debian" 36 | ;; 37 | esac 38 | 39 | args="$([ -z "${ROOTLESS_TESTPATH+x}" ] && echo "--rootless")" 40 | umoci unpack $args --image "$debian" "$1" 41 | 42 | cd - 43 | rm -rf "$tmp" 44 | } 45 | -------------------------------------------------------------------------------- /tests/integration/no_pivot.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "runc run --no-pivot must not expose bare /proc" { 15 | requires root 16 | 17 | update_config '.process.args |= ["unshare", "-mrpf", "sh", "-euxc", "mount -t proc none /proc && echo h > /proc/sysrq-trigger"]' 18 | 19 | runc run --no-pivot test_no_pivot 20 | [ "$status" -eq 1 ] 21 | [[ "$output" == *"mount: permission denied"* ]] 22 | } 23 | -------------------------------------------------------------------------------- /tests/integration/pause.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "runc pause and resume" { 15 | if [[ "$ROOTLESS" -ne 0 ]]; then 16 | requires rootless_cgroup 17 | set_cgroups_path "$BUSYBOX_BUNDLE" 18 | fi 19 | requires cgroups_freezer 20 | 21 | # run busybox detached 22 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 23 | [ "$status" -eq 0 ] 24 | 25 | testcontainer test_busybox running 26 | 27 | # pause busybox 28 | runc pause test_busybox 29 | [ "$status" -eq 0 ] 30 | 31 | # test state of busybox is paused 32 | testcontainer test_busybox paused 33 | 34 | # resume busybox 35 | runc resume test_busybox 36 | [ "$status" -eq 0 ] 37 | 38 | # test state of busybox is back to running 39 | testcontainer test_busybox running 40 | } 41 | 42 | @test "runc pause and resume with nonexist container" { 43 | if [[ "$ROOTLESS" -ne 0 ]]; then 44 | requires rootless_cgroup 45 | set_cgroups_path "$BUSYBOX_BUNDLE" 46 | fi 47 | requires cgroups_freezer 48 | 49 | # run test_busybox detached 50 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 51 | [ "$status" -eq 0 ] 52 | 53 | testcontainer test_busybox running 54 | 55 | # pause test_busybox and nonexistent container 56 | runc pause test_busybox 57 | [ "$status" -eq 0 ] 58 | runc pause nonexistent 59 | [ "$status" -ne 0 ] 60 | 61 | # test state of test_busybox is paused 62 | testcontainer test_busybox paused 63 | 64 | # resume test_busybox and nonexistent container 65 | runc resume test_busybox 66 | [ "$status" -eq 0 ] 67 | runc resume nonexistent 68 | [ "$status" -ne 0 ] 69 | 70 | # test state of test_busybox is back to running 71 | testcontainer test_busybox running 72 | 73 | # delete test_busybox 74 | runc delete --force test_busybox 75 | 76 | runc state test_busybox 77 | [ "$status" -ne 0 ] 78 | } 79 | -------------------------------------------------------------------------------- /tests/integration/ps.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "ps" { 15 | # ps is not supported, it requires cgroups 16 | requires root 17 | 18 | # start busybox detached 19 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 20 | [ "$status" -eq 0 ] 21 | 22 | # check state 23 | testcontainer test_busybox running 24 | 25 | runc ps test_busybox 26 | [ "$status" -eq 0 ] 27 | [[ ${lines[0]} =~ UID\ +PID\ +PPID\ +C\ +STIME\ +TTY\ +TIME\ +CMD+ ]] 28 | [[ "${lines[1]}" == *"$UID_MAP"*[0-9]* ]] 29 | } 30 | 31 | @test "ps -f json" { 32 | # ps is not supported, it requires cgroups 33 | requires root 34 | 35 | # start busybox detached 36 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 37 | [ "$status" -eq 0 ] 38 | 39 | # check state 40 | testcontainer test_busybox running 41 | 42 | runc ps -f json test_busybox 43 | [ "$status" -eq 0 ] 44 | [[ ${lines[0]} =~ [0-9]+ ]] 45 | } 46 | 47 | @test "ps -e" { 48 | 49 | # Note: in the OCI runc, this test uses "ps -e -x"; but the "-x" flag 50 | # causes no processes to be listed because the process doing ps does 51 | # no have the same UID as the process inside the sys container (due 52 | # to sysbox's user-namespace usage). 53 | 54 | # ps is not supported, it requires cgroups 55 | requires root 56 | 57 | # start busybox detached 58 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 59 | [ "$status" -eq 0 ] 60 | 61 | # check state 62 | testcontainer test_busybox running 63 | 64 | runc ps test_busybox -e 65 | [ "$status" -eq 0 ] 66 | [[ ${lines[0]} =~ \ +PID\ +TTY\ +TIME\ +CMD+ ]] 67 | [[ "${lines[1]}" =~ [0-9]+ ]] 68 | } 69 | 70 | @test "ps after the container stopped" { 71 | # ps requires cgroups 72 | [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup 73 | set_cgroups_path "$BUSYBOX_BUNDLE" 74 | 75 | # start busybox detached 76 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 77 | [ "$status" -eq 0 ] 78 | 79 | # check state 80 | testcontainer test_busybox running 81 | 82 | runc ps test_busybox 83 | [ "$status" -eq 0 ] 84 | 85 | runc kill test_busybox KILL 86 | [ "$status" -eq 0 ] 87 | 88 | retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" 89 | 90 | runc ps test_busybox 91 | [ "$status" -eq 0 ] 92 | } 93 | -------------------------------------------------------------------------------- /tests/integration/root.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_running_container_inroot test_dotbox "$HELLO_BUNDLE" 7 | teardown_busybox 8 | setup_busybox 9 | } 10 | 11 | function teardown() { 12 | teardown_running_container_inroot test_dotbox "$HELLO_BUNDLE" 13 | teardown_busybox 14 | } 15 | 16 | @test "global --root" { 17 | # run busybox detached using $HELLO_BUNDLE for state 18 | ROOT=$HELLO_BUNDLE runc run -d --console-socket "$CONSOLE_SOCKET" test_dotbox 19 | [ "$status" -eq 0 ] 20 | 21 | # run busybox detached in default root 22 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 23 | [ "$status" -eq 0 ] 24 | 25 | runc state test_busybox 26 | [ "$status" -eq 0 ] 27 | [[ "${output}" == *"running"* ]] 28 | 29 | ROOT=$HELLO_BUNDLE runc state test_dotbox 30 | [ "$status" -eq 0 ] 31 | [[ "${output}" == *"running"* ]] 32 | 33 | ROOT=$HELLO_BUNDLE runc state test_busybox 34 | [ "$status" -ne 0 ] 35 | 36 | runc state test_dotbox 37 | [ "$status" -ne 0 ] 38 | 39 | runc kill test_busybox KILL 40 | [ "$status" -eq 0 ] 41 | retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" 42 | runc delete test_busybox 43 | [ "$status" -eq 0 ] 44 | 45 | ROOT=$HELLO_BUNDLE runc kill test_dotbox KILL 46 | [ "$status" -eq 0 ] 47 | retry 10 1 eval "ROOT='$HELLO_BUNDLE' __runc state test_dotbox | grep -q 'stopped'" 48 | ROOT=$HELLO_BUNDLE runc delete test_dotbox 49 | [ "$status" -eq 0 ] 50 | } 51 | -------------------------------------------------------------------------------- /tests/integration/run.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | setup_hello 7 | } 8 | 9 | function teardown() { 10 | teardown_bundle 11 | } 12 | 13 | @test "runc run" { 14 | runc run test_hello 15 | [ "$status" -eq 0 ] 16 | 17 | runc state test_hello 18 | [ "$status" -ne 0 ] 19 | } 20 | 21 | @test "runc run --keep" { 22 | runc run --keep test_run_keep 23 | [ "$status" -eq 0 ] 24 | 25 | testcontainer test_run_keep stopped 26 | 27 | runc state test_run_keep 28 | [ "$status" -eq 0 ] 29 | 30 | runc delete test_run_keep 31 | 32 | runc state test_run_keep 33 | [ "$status" -ne 0 ] 34 | } 35 | 36 | @test "runc run --keep (check cgroup exists)" { 37 | # for systemd driver, the unit's cgroup path will be auto removed if container's all processes exited 38 | requires no_systemd 39 | 40 | [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup 41 | 42 | set_cgroups_path 43 | 44 | runc run --keep test_run_keep 45 | [ "$status" -eq 0 ] 46 | 47 | testcontainer test_run_keep stopped 48 | 49 | runc state test_run_keep 50 | [ "$status" -eq 0 ] 51 | 52 | # check that cgroup exists 53 | check_cgroup_value "pids.max" "max" 54 | 55 | runc delete test_run_keep 56 | 57 | runc state test_run_keep 58 | [ "$status" -ne 0 ] 59 | } 60 | -------------------------------------------------------------------------------- /tests/integration/spec.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | setup_hello 7 | 8 | # sysbox-runc: bundle must have same uid/gid as that passed to 9 | # "runc spec" (see runc_spec()) 10 | chown -R "$UID_MAP":"$GID_MAP" "$HELLO_BUNDLE" 11 | } 12 | 13 | function teardown() { 14 | teardown_hello 15 | } 16 | 17 | @test "spec generation cwd" { 18 | runc run test_hello 19 | [ "$status" -eq 0 ] 20 | } 21 | 22 | @test "spec generation --bundle" { 23 | runc run --bundle "$HELLO_BUNDLE" test_hello 24 | [ "$status" -eq 0 ] 25 | } 26 | 27 | @test "spec validator" { 28 | 29 | requires rootless_no_features 30 | 31 | SPEC_VERSION=$(awk '$1 == "github.com/opencontainers/runtime-spec" {print $2}' "$BATS_TEST_DIRNAME"/../../go.mod) 32 | # Will look like this when not pinned to specific tag: "v0.0.0-20190207185410-29686dbc5559", otherwise "v1.0.0" 33 | SPEC_COMMIT=$(cut -d "-" -f 3 <<<"$SPEC_VERSION") 34 | SPEC_REF=$([[ -z "$SPEC_COMMIT" ]] && echo "$SPEC_VERSION" || echo "$SPEC_COMMIT") 35 | 36 | git clone https://github.com/opencontainers/runtime-spec.git 37 | (cd runtime-spec && git reset --hard "$SPEC_REF") 38 | SCHEMA='runtime-spec/schema/config-schema.json' 39 | [ -e "$SCHEMA" ] 40 | 41 | runc spec "$UID_MAP" "$GID_MAP" "$ID_MAP_SIZE" 42 | [ -e config.json ] 43 | 44 | GO111MODULE=auto go get github.com/xeipuuv/gojsonschema 45 | GO111MODULE=auto go build runtime-spec/schema/validate.go 46 | 47 | ./validate "$SCHEMA" config.json 48 | } 49 | -------------------------------------------------------------------------------- /tests/integration/start.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "runc start" { 15 | runc create --console-socket "$CONSOLE_SOCKET" test_busybox 16 | [ "$status" -eq 0 ] 17 | 18 | testcontainer test_busybox created 19 | 20 | # start container test_busybox 21 | runc start test_busybox 22 | [ "$status" -eq 0 ] 23 | 24 | testcontainer test_busybox running 25 | 26 | # delete test_busybox 27 | runc delete --force test_busybox 28 | 29 | runc state test_busybox 30 | [ "$status" -ne 0 ] 31 | } 32 | -------------------------------------------------------------------------------- /tests/integration/start_detached.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "runc run detached" { 15 | # run busybox detached 16 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 17 | [ "$status" -eq 0 ] 18 | 19 | # check state 20 | testcontainer test_busybox running 21 | } 22 | 23 | @test "runc run detached ({u,g}id != 0)" { 24 | # cannot start containers as another user in rootless setup without idmap 25 | [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap 26 | 27 | # replace "uid": 0 with "uid": 1000 28 | # and do a similar thing for gid. 29 | update_config ' (.. | select(.uid? == 0)) .uid |= 1000 30 | | (.. | select(.gid? == 0)) .gid |= 100' 31 | 32 | # run busybox detached 33 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 34 | [ "$status" -eq 0 ] 35 | 36 | # check state 37 | testcontainer test_busybox running 38 | } 39 | 40 | @test "runc run detached --pid-file" { 41 | # run busybox detached 42 | runc run --pid-file pid.txt -d --console-socket "$CONSOLE_SOCKET" test_busybox 43 | [ "$status" -eq 0 ] 44 | 45 | # check state 46 | testcontainer test_busybox running 47 | 48 | # check pid.txt was generated 49 | [ -e pid.txt ] 50 | 51 | [[ "$(cat pid.txt)" == $(__runc state test_busybox | jq '.pid') ]] 52 | } 53 | 54 | @test "runc run detached --pid-file with new CWD" { 55 | # create pid_file directory as the CWD 56 | mkdir pid_file 57 | cd pid_file 58 | 59 | # run busybox detached 60 | runc run --pid-file pid.txt -d -b "$BUSYBOX_BUNDLE" --console-socket "$CONSOLE_SOCKET" test_busybox 61 | [ "$status" -eq 0 ] 62 | 63 | # check state 64 | testcontainer test_busybox running 65 | 66 | # check pid.txt was generated 67 | [ -e pid.txt ] 68 | 69 | [[ "$(cat pid.txt)" == $(__runc state test_busybox | jq '.pid') ]] 70 | } 71 | -------------------------------------------------------------------------------- /tests/integration/start_hello.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_hello 7 | setup_hello 8 | } 9 | 10 | function teardown() { 11 | teardown_hello 12 | } 13 | 14 | @test "runc run" { 15 | # run hello-world 16 | runc run test_hello 17 | [ "$status" -eq 0 ] 18 | 19 | # check expected output 20 | [[ "${output}" == *"Hello"* ]] 21 | } 22 | 23 | @test "runc run ({u,g}id != 0)" { 24 | # cannot start containers as another user in rootless setup without idmap 25 | [[ "$ROOTLESS" -ne 0 ]] && requires rootless_idmap 26 | 27 | # replace "uid": 0 with "uid": 1000 28 | # and do a similar thing for gid. 29 | update_config ' (.. | select(.uid? == 0)) .uid |= 1000 30 | | (.. | select(.gid? == 0)) .gid |= 100' 31 | 32 | # run hello-world 33 | runc run test_hello 34 | [ "$status" -eq 0 ] 35 | 36 | # check expected output 37 | [[ "${output}" == *"Hello"* ]] 38 | } 39 | 40 | @test "runc run with rootfs set to ." { 41 | cp config.json rootfs/. 42 | rm config.json 43 | cd rootfs 44 | update_config '(.. | select(. == "rootfs")) |= "."' 45 | 46 | # run hello-world 47 | runc run test_hello 48 | [ "$status" -eq 0 ] 49 | [[ "${output}" == *"Hello"* ]] 50 | } 51 | 52 | @test "runc run --pid-file" { 53 | # run hello-world 54 | runc run --pid-file pid.txt test_hello 55 | [ "$status" -eq 0 ] 56 | [[ "${output}" == *"Hello"* ]] 57 | 58 | # check pid.txt was generated 59 | [ -e pid.txt ] 60 | 61 | [[ "$(cat pid.txt)" =~ [0-9]+ ]] 62 | } 63 | -------------------------------------------------------------------------------- /tests/integration/state.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "state (kill + delete)" { 15 | runc state test_busybox 16 | [ "$status" -ne 0 ] 17 | 18 | # run busybox detached 19 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 20 | [ "$status" -eq 0 ] 21 | 22 | # check state 23 | testcontainer test_busybox running 24 | 25 | runc kill test_busybox KILL 26 | [ "$status" -eq 0 ] 27 | 28 | # wait for busybox to be in the destroyed state 29 | retry 10 1 eval "__runc state test_busybox | grep -q 'stopped'" 30 | 31 | # delete test_busybox 32 | runc delete test_busybox 33 | [ "$status" -eq 0 ] 34 | 35 | runc state test_busybox 36 | [ "$status" -ne 0 ] 37 | } 38 | 39 | @test "state (pause + resume)" { 40 | # XXX: pause and resume require cgroups. 41 | requires root 42 | 43 | runc state test_busybox 44 | [ "$status" -ne 0 ] 45 | 46 | # run busybox detached 47 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 48 | [ "$status" -eq 0 ] 49 | 50 | # check state 51 | testcontainer test_busybox running 52 | 53 | # pause busybox 54 | runc pause test_busybox 55 | [ "$status" -eq 0 ] 56 | 57 | # test state of busybox is paused 58 | testcontainer test_busybox paused 59 | 60 | # resume busybox 61 | runc resume test_busybox 62 | [ "$status" -eq 0 ] 63 | 64 | # test state of busybox is back to running 65 | testcontainer test_busybox running 66 | } 67 | -------------------------------------------------------------------------------- /tests/integration/syscont-cgroup.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | # Verify the cgroup mounts inside the sys container 15 | @test "syscont: cgroup mounts" { 16 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 17 | [ "$status" -eq 0 ] 18 | 19 | # verify /sys/fs/cgroup has root:root ownership 20 | # 21 | # (dev note: single quotes in a single-quote delimited script is '\'' ; use 22 | # 'echo' instead of 'sh -c' to see shell interpretation) 23 | 24 | runc exec test_busybox sh -c 'ls -l /sys/fs/cgroup/ | grep -v rdma | grep -v misc | awk '\''{print $3}'\'' | tr '\''\n'\'' '\'' '\'' ' 25 | [ "$status" -eq 0 ] 26 | 27 | for i in ${lines[0]}; do 28 | [ "$i" == "root" ] 29 | done 30 | 31 | runc exec test_busybox sh -c 'ls -l /sys/fs/cgroup/ | grep -v rdma | grep -v misc | awk '\''{print $4}'\'' | tr '\''\n'\'' '\'' '\'' ' 32 | [ "$status" -eq 0 ] 33 | 34 | for i in ${lines[0]}; do 35 | [ "$i" == "root" ] 36 | done 37 | 38 | # verify sys container cgroup root in /proc/$$/cgroup is "/" 39 | runc exec test_busybox sh -c 'cat /proc/1/cgroup | cut -d":" -f3 | tr '\''\n'\'' '\'' '\'' ' 40 | [ "$status" -eq 0 ] 41 | 42 | for i in ${lines[0]}; do 43 | [ "$i" == "/" ] 44 | done 45 | 46 | # verify sys container cgroup root in /proc/$$/mountinfo 47 | runc exec test_busybox sh -c 'cat /proc/1/mountinfo | grep "/sys/fs/cgroup/" | cut -d" " -f4 | tr '\''\n'\'' '\'' '\'' ' 48 | [ "$status" -eq 0 ] 49 | 50 | for i in ${lines[0]}; do 51 | [ "$i" == "/" ] 52 | done 53 | 54 | # verify cgroup is mounted read-write 55 | runc exec test_busybox sh -c 'cat /proc/1/mountinfo | grep "cgroup" | cut -d" " -f6 | tr '\''\n'\'' '\'' '\'' ' 56 | [ "$status" -eq 0 ] 57 | 58 | for i in ${lines[0]}; do 59 | [[ "$i" =~ "rw," ]] 60 | done 61 | } 62 | 63 | # Verify that sys container root can create cgroups 64 | @test "syscont: cgroup create" { 65 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 66 | [ "$status" -eq 0 ] 67 | 68 | cgList=$(runc exec test_busybox ls /sys/fs/cgroup) 69 | for cg in $cgList; do 70 | runc exec test_busybox mkdir /sys/fs/cgroup/$cg/subCgroup 71 | [ "$status" -eq 0 ] 72 | 73 | runc exec test_busybox ls /sys/fs/cgroup/$cg/subCgroup/cgroup.procs 74 | [ "$status" -eq 0 ] 75 | 76 | runc exec test_busybox rmdir /sys/fs/cgroup/$cg/subCgroup 77 | [ "$status" -eq 0 ] 78 | done 79 | } 80 | -------------------------------------------------------------------------------- /tests/integration/syscont-ns.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "syscont: uses all namespaces" { 15 | 16 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 17 | [ "$status" -eq 0 ] 18 | 19 | # For each ns, check that the sys container's init process is in a 20 | # different namespace than the test script. 21 | 22 | for nsType in cgroup ipc mnt net pid user uts; do 23 | syscont_ns=$(runc exec test_busybox ls -l /proc/1/ns | grep -i "$nsType" | cut -d":" -f3) 24 | [ "$status" -eq 0 ] 25 | test_ns=$(ls -l /proc/self/ns | grep -i "$nsType" | cut -d":" -f3) 26 | [ "$status" -eq 0 ] 27 | [ "$syscont_ns" != "$test_ns" ] 28 | done 29 | } 30 | 31 | @test "syscont: unshare" { 32 | 33 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 34 | [ "$status" -eq 0 ] 35 | 36 | # check that unshare(2) works inside a system container 37 | runc exec test_busybox sh -c "unshare -i -m -n -p -u -f --mount-proc echo 1 > /dev/null" 38 | [ "$status" -eq 0 ] 39 | 40 | # TODO: test that nsenter(2) also works 41 | } 42 | -------------------------------------------------------------------------------- /tests/integration/syscont-syscalls.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "syscont: syscall: mount and umount" { 15 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 16 | [ "$status" -eq 0 ] 17 | 18 | runc exec test_busybox sh -c "mkdir /root/test" 19 | [ "$status" -eq 0 ] 20 | 21 | runc exec test_busybox sh -c "mount --bind /root/test /root/test" 22 | [ "$status" -eq 0 ] 23 | 24 | runc exec test_busybox sh -c 'mount | grep "test"' 25 | [ "$status" -eq 0 ] 26 | [[ "${output}" =~ "/root/test" ]] 27 | 28 | runc exec test_busybox sh -c "umount /root/test" 29 | [ "$status" -eq 0 ] 30 | 31 | runc exec test_busybox sh -c "rmdir /root/test" 32 | [ "$status" -eq 0 ] 33 | } 34 | -------------------------------------------------------------------------------- /tests/integration/syscont-uid.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "syscont uid/gid mappings" { 15 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 16 | [ "$status" -eq 0 ] 17 | 18 | runc exec test_busybox cat /proc/1/uid_map 19 | [ "$status" -eq 0 ] 20 | 21 | uid_int=$(echo "${lines[0]}" | awk '{print $1}') 22 | uid_ext=$(echo "${lines[0]}" | awk '{print $2}') 23 | uid_size=$(echo "${lines[0]}" | awk '{print $3}') 24 | 25 | [[ "$uid_int" == "0" ]] 26 | [[ "$uid_ext" == "$UID_MAP" ]] 27 | [[ "$uid_size" == "$ID_MAP_SIZE" ]] 28 | } 29 | -------------------------------------------------------------------------------- /tests/integration/testdata/hello-world-aarch64.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestybox/sysbox-runc/c58eba1be027c762c495bc4eeba7c0984beda1ab/tests/integration/testdata/hello-world-aarch64.tar -------------------------------------------------------------------------------- /tests/integration/testdata/hello-world.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestybox/sysbox-runc/c58eba1be027c762c495bc4eeba7c0984beda1ab/tests/integration/testdata/hello-world.tar -------------------------------------------------------------------------------- /tests/integration/umask.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | function setup() { 6 | teardown_busybox 7 | setup_busybox 8 | } 9 | 10 | function teardown() { 11 | teardown_busybox 12 | } 13 | 14 | @test "umask" { 15 | update_config '.process.user += {"umask":63}' 16 | 17 | # run busybox detached 18 | runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox 19 | [ "$status" -eq 0 ] 20 | 21 | runc exec test_busybox grep '^Umask:' "/proc/1/status" 22 | [ "$status" -eq 0 ] 23 | # umask 63 decimal = umask 77 octal 24 | [[ "${output}" == *"77"* ]] 25 | } 26 | -------------------------------------------------------------------------------- /tests/integration/version.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load helpers 4 | 5 | @test "runc version" { 6 | skip "fails when git change is pending" 7 | 8 | runc -v 9 | [ "$status" -eq 0 ] 10 | [[ ${lines[0]} =~ runc\ version\ [0-9]+\.[0-9]+\.[0-9]+ ]] 11 | [[ ${lines[1]} =~ commit:+ ]] 12 | [[ ${lines[2]} =~ spec:\ [0-9]+\.[0-9]+\.[0-9]+ ]] 13 | } 14 | --------------------------------------------------------------------------------