Merge pull request #13929 from EtiennePerot:sentry-binary

PiperOrigin-RevId: 959992675
This commit is contained in:
gVisor bot
2026-08-05 19:00:10 -07:00
39 changed files with 496 additions and 158 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ _templates:
- exit_status: "*"
limit: 2
common: &common
timeout_in_minutes: 30
timeout_in_minutes: 55
retry:
<<: *retry_settings
source_test: &source_test
+1
View File
@@ -29,6 +29,7 @@ release_files(
"//shim:containerd-shim-runsc-v1",
],
sidecars = SIDECARS,
visibility = ["//visibility:public"],
)
nogo_config(
+11 -8
View File
@@ -115,12 +115,13 @@ RUNTIME ?= runsc
else
RUNTIME ?= $(BRANCH_NAME)
endif
RUNTIME_DIR ?= $(shell dirname $(shell mktemp -u))/$(RUNTIME)
RUNSC_TARGET ?= //runsc
RUNTIME_BIN ?= $(RUNTIME_DIR)/runsc
RUNTIME_LOG_DIR ?= $(RUNTIME_DIR)/logs
RUNTIME_LOGS ?= $(RUNTIME_LOG_DIR)/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%
RUNTIME_ARGS ?=
RUNTIME_DIR ?= $(shell dirname $(shell mktemp -u))/$(RUNTIME)
RUNSC_TARGET ?= //runsc
RUNTIME_BIN ?= $(RUNTIME_DIR)/runsc
EXTRA_SIDECAR_TARGETS ?= # Extra binaries to install under gvisor-bin/.
RUNTIME_LOG_DIR ?= $(RUNTIME_DIR)/logs
RUNTIME_LOGS ?= $(RUNTIME_LOG_DIR)/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%
RUNTIME_ARGS ?=
DOCKER_DAEMON_CONFIG_PATH ?= /etc/docker/daemon.json
DOCKER_RELOAD_COMMAND ?= sudo systemctl reload docker
@@ -139,8 +140,9 @@ ifeq (,$(STAGED_BINARIES))
@$(call copy,//debian:gvisor-bin-tar,$(RUNTIME_DIR))
@tar -C "$(RUNTIME_DIR)" -xf "$(RUNTIME_DIR)/gvisor-bin-tar.tar"
@rm -f "$(RUNTIME_DIR)/gvisor-bin-tar.tar"
@$(if $(EXTRA_SIDECAR_TARGETS),$(call copy,$(EXTRA_SIDECAR_TARGETS),$(RUNTIME_DIR)/gvisor-bin))
else
gcloud storage cat "${STAGED_BINARIES}" | \
@gcloud storage cat "${STAGED_BINARIES}" | \
tar -C "$(RUNTIME_DIR)" -zxvf - ./runsc ./gvisor-bin && \
chmod -R a+rx "$(RUNTIME_BIN)" "$(RUNTIME_DIR)/gvisor-bin"
endif
@@ -274,7 +276,7 @@ network-tests: iptables-tests packetdrill-tests packetimpact-tests
# To run multiple specific syscall tests:
# make syscall-tests TARGETS="//test/syscalls:signalfd_test_runsc_systrap_shared //test/syscalls:link_test_runsc_systrap_shared"
syscall-tests: $(RUNTIME_BIN)
@$(call test,$(OPTIONS) --test_env=RUNTIME=$(RUNTIME_BIN) --cxxopt=-Werror $(PARTITIONS) $(if $(TARGETS),-- $(TARGETS),test/syscalls/... test/rtnetlink/...))
@$(call test,$(OPTIONS) --test_env=RUNTIME=$(RUNTIME_BIN) --test_env=GVISOR_SIDECAR_BINARIES_DIR=$(RUNTIME_DIR)/gvisor-bin --cxxopt=-Werror $(PARTITIONS) $(if $(TARGETS),-- $(TARGETS),test/syscalls/... test/rtnetlink/...))
.PHONY: syscall-tests
packetimpact-tests:
@@ -430,6 +432,7 @@ plugin-network-tests: integration-test-images $(RUNTIME_BIN)
@$(call test_runtime_cached,$(RUNTIME)-dpdk, --test_arg=-test.run=ConnectToSelf $(INTEGRATION_TARGETS))
plugin-network-tests: RUNSC_TARGET=--config plugin-tldk //runsc:runsc-plugin-stack
plugin-network-tests: EXTRA_SIDECAR_TARGETS=--config plugin-tldk //runsc/cmd/sentry:gvisor_sentry_plugin_stack
overlay-tests: integration-test-images $(RUNTIME_BIN)
@$(call install_runtime_noreload,$(RUNTIME)-overlay,--overlay2=all:dir=/tmp)
+11 -6
View File
@@ -177,7 +177,7 @@ type ExpectedOpts struct {
// Sandbox config options follow.
DirectFS bool
HostNetwork bool
NetworkMode string
EnableRaw bool
RequiresCapSysPtrace bool
SpecHasUserNamespace bool
@@ -204,11 +204,16 @@ type ExpectedOpts struct {
ExtraMounts []string
}
// HostNetwork returns true if the sandbox is using hostinet.
func (e *ExpectedOpts) HostNetwork() bool {
return e.NetworkMode == "host"
}
// Expected returns the posture a sandbox started with the given configuration
// must have.
func Expected(opts ExpectedOpts) *Posture {
applyCaps := opts.DirectFS || opts.HostNetwork
newUserNS := !applyCaps || opts.SpecHasUserNamespace || (opts.DirectFS && !opts.HostNetwork)
applyCaps := opts.DirectFS || opts.HostNetwork()
newUserNS := !applyCaps || opts.SpecHasUserNamespace || (opts.DirectFS && !opts.HostNetwork())
return &Posture{
Creds: expectedCreds(applyCaps),
Caps: expectedCaps(opts, applyCaps),
@@ -242,7 +247,7 @@ func expectedCaps(opts ExpectedOpts, applyCaps bool) Capabilities {
if opts.DirectFS {
caps = append(caps, directfsCaps...)
}
if opts.HostNetwork {
if opts.HostNetwork() {
held := make(map[string]bool, len(opts.Ref.PermittedCaps))
for _, c := range opts.Ref.PermittedCaps {
held[c] = true
@@ -281,7 +286,7 @@ func expectedNamespaces(opts ExpectedOpts, newUserNS bool) Namespaces {
switch {
case opts.NetNamespaceTarget != "":
ns.Net = NSJoined(opts.NetNamespaceTarget)
case opts.HostNetwork:
case opts.HostNetwork():
ns.Net = NSInherited
default:
ns.Net = NSNew
@@ -343,7 +348,7 @@ func expectedAttrs(opts ExpectedOpts, applyCaps, newUserNS bool) Attrs {
}
var env []string
if opts.CgoEnabled {
if opts.NetworkMode == "plugin" {
env = append(env, "GLIBC_TUNABLES=glibc.pthread.rseq=0")
}
if opts.GoDebug != "" {
+6 -6
View File
@@ -311,8 +311,8 @@ type Attrs struct {
// process. False under shim.
ChildOfRef bool
// Environ is the environment, sorted, with `RUNSC_START_TIME_NANOS`
// removed.
// Environ is the environment, sorted, with variables like
// `RUNSC_START_TIME_NANOS` and `GVISOR_ENFORCE_RELEASE` removed.
Environ []string
// Argv0 is argv[0], which `runsc` should set to "runsc-sandbox".
@@ -1105,8 +1105,8 @@ func parseLimits(s string) (map[string]string, error) {
return out, nil
}
// startTimeEnvRE matches the start-time env variable `runsc` uses.
var startTimeEnvRE = regexp.MustCompile(`^RUNSC_START_TIME_NANOS=\d+$`)
// ignoredEnvRegExp matches the start-time env variable `runsc` uses.
var ignoredEnvRegExp = regexp.MustCompile(`^(RUNSC_START_TIME_NANOS=\d+|GVISOR_ENFORCE_RELEASE=.*)$`)
func collectAttrs(p, ref *proc, status, refStatus map[string]string, userNS string) (Attrs, error) {
var a Attrs
@@ -1134,8 +1134,8 @@ func collectAttrs(p, ref *proc, status, refStatus map[string]string, userNS stri
return a, fmt.Errorf("reading environ: %w", err)
}
for _, e := range env {
if startTimeEnvRE.MatchString(e) {
continue // Skip start time env var
if ignoredEnvRegExp.MatchString(e) {
continue
}
a.Environ = append(a.Environ, e)
}
+10 -2
View File
@@ -146,13 +146,21 @@ func ImageByName(name string) string {
// ConfigureExePath configures the executable for runsc in the test environment.
func ConfigureExePath() error {
if *runscPath == "" {
path, err := FindFile("runsc/runsc")
path, err := FindFile("release/runsc")
if err != nil {
return err
path, err = FindFile("runsc/runsc")
if err != nil {
return err
}
}
*runscPath = path
}
specutils.ExePath = *runscPath
if os.Getenv("GVISOR_SIDECAR_BINARIES_DIR") == "" {
if dir, err := FindFile("release/gvisor-bin"); err == nil {
os.Setenv("GVISOR_SIDECAR_BINARIES_DIR", dir)
}
}
return nil
}
+1
View File
@@ -19,6 +19,7 @@ go_library(
"//runsc/cmd",
"//runsc/cmd/alias",
"//runsc/cmd/nvproxy",
"//runsc/cmd/sentry/sentrycmd",
"//runsc/cmd/trace",
"//runsc/cmd/util",
"//runsc/gvisorbinaries/embed",
+4 -3
View File
@@ -21,6 +21,7 @@ import (
"gvisor.dev/gvisor/runsc/cmd"
"gvisor.dev/gvisor/runsc/cmd/alias"
"gvisor.dev/gvisor/runsc/cmd/nvproxy"
"gvisor.dev/gvisor/runsc/cmd/sentry/sentrycmd"
"gvisor.dev/gvisor/runsc/cmd/trace"
"gvisor.dev/gvisor/runsc/cmd/util"
@@ -94,9 +95,9 @@ func commands() (map[util.SubCommand]string, []subcommands.Command) {
new(cmd.MetricServer): metricGroup,
// Internal commands.
new(cmd.Boot): internalGroup,
new(cmd.Gofer): internalGroup,
new(cmd.Umount): internalGroup,
new(sentrycmd.Boot): internalGroup,
new(cmd.Gofer): internalGroup,
new(sentrycmd.Umount): internalGroup,
}
// Merge alias commands.
+3 -18
View File
@@ -8,9 +8,7 @@ package(
go_library(
name = "cmd",
srcs = [
"boot.go",
"checkpoint.go",
"chroot.go",
"cmd.go",
"cpu_features.go",
"create.go",
@@ -19,7 +17,6 @@ go_library(
"do.go",
"events.go",
"exec.go",
"fd_mapping.go",
"features.go",
"fscheckpoint.go",
"gofer.go",
@@ -51,7 +48,6 @@ go_library(
"symbolize.go",
"syscalls.go",
"tar.go",
"umount_unsafe.go",
"update.go",
"usage.go",
"wait.go",
@@ -60,31 +56,20 @@ go_library(
force_add_state_pkg = True,
visibility = ["//runsc:__subpackages__"],
deps = [
"//pkg/abi/linux",
"//pkg/abi/tpu",
"//pkg/cleanup",
"//pkg/coretag",
"//pkg/coverage",
"//pkg/cpuid",
"//pkg/fd",
"//pkg/hostarch",
"//pkg/lisafs",
"//pkg/log",
"//pkg/metric",
"//pkg/prometheus",
"//pkg/rdma",
"//pkg/ring0",
"//pkg/sentry/checkpoint",
"//pkg/sentry/control",
"//pkg/sentry/devices/nvproxy/nvconf",
"//pkg/sentry/hostmm",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/platform",
"//pkg/sentry/syscalls/linux",
"//pkg/state/pretty",
"//pkg/state/statefile",
"//pkg/tcpip/nftables",
"//pkg/unet",
"//pkg/urpc",
"//runsc/boot",
@@ -107,7 +92,6 @@ go_library(
"//runsc/version",
"//sandboxexec/proto:sandbox_options_go_proto",
"@com_github_google_subcommands//:go_default_library",
"@com_github_moby_sys_capability//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@org_golang_google_protobuf//encoding/prototext:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
@@ -121,7 +105,6 @@ go_test(
srcs = [
"bootposture_test.go",
"capability_test.go",
"chroot_test.go",
"delete_test.go",
"exec_test.go",
"features_test.go",
@@ -135,7 +118,8 @@ go_test(
"spec_test.go",
],
data = [
"//runsc",
"//:release", # keep
"//runsc", # keep
],
library = ":cmd",
deps = [
@@ -148,6 +132,7 @@ go_test(
"//pkg/test/testutil",
"//runsc/boot",
"//runsc/cmd/sandboxsetup",
"//runsc/cmd/sentry/sentrycmd",
"//runsc/cmd/util",
"//runsc/config",
"//runsc/container",
+1 -1
View File
@@ -153,7 +153,7 @@ func testBootPosture(t *testing.T, tc postureCase) {
}
opts := sandboxposture.ExpectedOpts{
DirectFS: tc.directfs,
HostNetwork: isHostNetwork,
NetworkMode: tc.network.String(),
EnableRaw: conf.EnableRaw,
RequiresCapSysPtrace: sandboxposture.PlatformCapSysPtrace[conf.Platform],
GoDebug: sandboxposture.PlatformGoDebug[conf.Platform],
+2 -1
View File
@@ -26,6 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/cmd/sandboxsetup"
"gvisor.dev/gvisor/runsc/cmd/sentry/sentrycmd"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/specutils"
@@ -131,7 +132,7 @@ func testCapabilities(t *testing.T, directfs bool) {
}
if directfs {
// With directfs, the sandbox has additional capabilities.
wantSandboxCaps = specutils.MergeCapabilities(wantSandboxCaps, directfsSandboxLinuxCaps)
wantSandboxCaps = specutils.MergeCapabilities(wantSandboxCaps, sentrycmd.DirectfsSandboxLinuxCaps)
}
// Check that sandbox and gofer have the proper capabilities.
if err := checkProcessCaps(c.Sandbox.Getpid(), wantSandboxCaps); err != nil {
+2 -1
View File
@@ -31,6 +31,7 @@ import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/runsc/cmd/sandboxsetup"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/console"
@@ -60,7 +61,7 @@ type Exec struct {
// passFDs are user-supplied FDs from the host to be exposed to the
// sandboxed app.
passFDs fdMappings
passFDs sandboxsetup.FDMappings
// execFD is the host file descriptor used for program execution.
execFD int
+1 -22
View File
@@ -19,7 +19,6 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"github.com/google/subcommands"
@@ -553,30 +552,10 @@ func (g *goferSyncFDs) syncUsernsForRootless(uid, gid uint32) {
if g.usernsFD < 0 {
return
}
syncUsernsForRootless(g.usernsFD, uid, gid)
sandboxsetup.SyncUsernsForRootless(g.usernsFD, uid, gid)
g.usernsFD = -1
}
// syncUsernsForRootless waits on usernsFD to be closed and then sets
// UID/GID to uid/gid. Note that this function calls runtime.LockOSThread().
//
// Postcondition: All callers must re-exec themselves after this returns.
func syncUsernsForRootless(fd int, uid uint32, gid uint32) {
if err := sandboxsetup.WaitForFD(fd, "userns sync FD"); err != nil {
util.Fatalf("failed to sync on userns FD: %v", err)
}
// SETUID changes UID on the current system thread, so we have
// to re-execute current binary.
runtime.LockOSThread()
if _, _, errno := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0); errno != 0 {
util.Fatalf("failed to set UID: %v", errno)
}
if _, _, errno := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0); errno != 0 {
util.Fatalf("failed to set GID: %v", errno)
}
}
// syncChroot waits on chrootFD to be closed.
// Used for synchronization during container filesystem setup which is done
// from the non-gofer process.
+2 -1
View File
@@ -23,6 +23,7 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/runsc/cmd/sandboxsetup"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
@@ -40,7 +41,7 @@ type Run struct {
// passFDs are user-supplied FDs from the host to be exposed to the
// sandboxed app.
passFDs fdMappings
passFDs sandboxsetup.FDMappings
// execFD is the host file descriptor used for program execution.
execFD int
+2
View File
@@ -9,6 +9,7 @@ go_library(
name = "sandboxsetup",
srcs = [
"caps.go",
"fdmappings.go",
"flags.go",
"fs.go",
"gofer_mount.go",
@@ -19,6 +20,7 @@ go_library(
"//pkg/fd",
"//pkg/log",
"//pkg/unet",
"//runsc/boot",
"//runsc/cmd/util",
"//runsc/config",
"//runsc/flag",
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
package sandboxsetup
import (
"fmt"
@@ -22,11 +22,11 @@ import (
"gvisor.dev/gvisor/runsc/boot"
)
// fdMappings can be used with flags that appear multiple times.
type fdMappings []boot.FDMapping
// FDMappings can be used with flags that appear multiple times.
type FDMappings []boot.FDMapping
// String implements flag.Value.
func (i *fdMappings) String() string {
func (i *FDMappings) String() string {
var mappings []string
for _, m := range *i {
mappings = append(mappings, fmt.Sprintf("%v:%v", m.Host, m.Guest))
@@ -35,18 +35,18 @@ func (i *fdMappings) String() string {
}
// Get implements flag.Value.
func (i *fdMappings) Get() any {
func (i *FDMappings) Get() any {
return i
}
// GetArray returns an array of mappings.
func (i *fdMappings) GetArray() []boot.FDMapping {
func (i *FDMappings) GetArray() []boot.FDMapping {
return *i
}
// Set implements flag.Value and appends a mapping from the command line to the
// mappings array. Set(String()) should be idempotent.
func (i *fdMappings) Set(s string) error {
func (i *FDMappings) Set(s string) error {
for _, m := range strings.Split(s, ",") {
split := strings.Split(m, ":")
if len(split) != 2 {
+20
View File
@@ -112,6 +112,26 @@ func PrepareArgs(subCmdName string, fSet *flag.FlagSet, override map[string]stri
return args
}
// SyncUsernsForRootless waits on usernsFD to be closed and then sets
// UID/GID to uid/gid. Note that this function calls runtime.LockOSThread().
//
// Postcondition: All callers must re-exec themselves after this returns.
func SyncUsernsForRootless(fd int, uid uint32, gid uint32) {
if err := WaitForFD(fd, "userns sync FD"); err != nil {
util.Fatalf("failed to sync on userns FD: %v", err)
}
// SETUID changes UID on the current system thread, so we have
// to re-execute current binary.
runtime.LockOSThread()
if _, _, errno := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0); errno != 0 {
util.Fatalf("failed to set UID: %v", errno)
}
if _, _, errno := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0); errno != 0 {
util.Fatalf("failed to set GID: %v", errno)
}
}
// ExecProcUmounter executes a child process that umounts /proc when the
// returned pipe is closed.
func ExecProcUmounter() (*exec.Cmd, *os.File) {
+99
View File
@@ -0,0 +1,99 @@
load("//tools:defs.bzl", "go_binary")
load("//tools:deps.bzl", "deps_test")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
go_binary(
name = "gvisor_sentry",
srcs = ["sentry_main.go"],
noasan = True,
pure = True,
visibility = ["//visibility:public"],
x_defs = {"gvisor.dev/gvisor/runsc/version.version": "{STABLE_VERSION}"},
deps = [
"//runsc/cli",
"//runsc/cmd/sentry/sentrycmd",
"//runsc/cmd/util",
"//runsc/gvisorbinaries",
],
)
go_binary(
name = "gvisor_sentry_plugin_stack",
srcs = ["sentry_main_plugin_stack.go"],
gotags = ["network_plugins"],
nogo = False,
pure = False,
static = True,
tags = ["network_plugins"],
visibility = ["//visibility:public"],
x_defs = {"gvisor.dev/gvisor/runsc/version.version": "{STABLE_VERSION}"},
deps = [
"//pkg/sentry/socket/plugin/stack",
"//runsc/cli",
"//runsc/cmd/sentry/sentrycmd",
"//runsc/cmd/util",
"//runsc/gvisorbinaries",
],
)
deps_test(
name = "gvisor_sentry_deps_test",
# The sentry binaries contain the sentry, and nothing else.
# Keep this tight.
allowed = [
"//runsc/cli",
"//runsc/cmd/sandboxsetup",
"//runsc/cmd/sentry/sentrycmd",
"//runsc/cmd/util",
"//runsc/config",
"//runsc/donation",
"//runsc/flag",
"//runsc/gvisorbinaries",
"//runsc/profile",
"//runsc/starttime",
"//runsc/version",
# External deps.
"@com_github_cenkalti_backoff//:go_default_library",
"@com_github_google_btree//:go_default_library",
"@com_github_google_subcommands//:go_default_library",
"@com_github_moby_sys_capability//:go_default_library",
"@com_github_mohae_deepcopy//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go/features:go_default_library",
"@io_bazel_rules_go//go/tools/coverdata",
"@org_golang_x_exp//constraints:go_default_library",
"@org_golang_x_mod//semver:go_default_library",
"@org_golang_x_sync//errgroup:go_default_library",
"@org_golang_x_sys//cpu:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
"@org_golang_x_time//rate:go_default_library",
],
allowed_prefixes = [
"@org_golang_google_protobuf//",
"//pkg",
"//runsc/boot",
"//runsc/specutils",
],
definitely_not_allowed_prefixes = [
# Container-management side of runsc:
"//runsc/container",
"//runsc/sandbox",
# Gofers:
"//runsc/fsgofer",
# Other sidecar binaries:
"//runsc/checkpointgofer",
"//runsc/cmd/metricserver",
"//runsc/metricserver",
],
targets = [
":gvisor_sentry",
":gvisor_sentry_plugin_stack",
],
)
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The `gvisor_sentry` binary runs the gVisor sentry, aka the gVisor kernel.
package main
import (
"gvisor.dev/gvisor/runsc/cli"
"gvisor.dev/gvisor/runsc/cmd/sentry/sentrycmd"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/gvisorbinaries"
)
func main() {
cli.Run(&gvisorbinaries.GvisorSentry, map[util.SubCommand]string{
new(sentrycmd.Boot): "internal use only",
new(sentrycmd.Umount): "internal use only",
}, nil)
}
@@ -0,0 +1,35 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !false && network_plugins
// +build !false,network_plugins
// The `gvisor_sentry_plugin_stack` binary runs the gVisor sentry with a
// third-party plugin network stack linked in.
package main
import (
_ "gvisor.dev/gvisor/pkg/sentry/socket/plugin/stack"
"gvisor.dev/gvisor/runsc/cli"
"gvisor.dev/gvisor/runsc/cmd/sentry/sentrycmd"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/gvisorbinaries"
)
func main() {
cli.Run(&gvisorbinaries.GvisorSentryPluginStack, map[util.SubCommand]string{
new(sentrycmd.Boot): "internal use only",
new(sentrycmd.Umount): "internal use only",
}, nil)
}
+58
View File
@@ -0,0 +1,58 @@
load("//tools:defs.bzl", "go_library", "go_test")
package(
default_applicable_licenses = ["//:license"],
licenses = ["notice"],
)
go_library(
name = "sentrycmd",
srcs = [
"boot.go",
"chroot.go",
"umount_unsafe.go",
],
visibility = ["//runsc:__subpackages__"],
deps = [
"//pkg/abi/linux",
"//pkg/abi/tpu",
"//pkg/coretag",
"//pkg/cpuid",
"//pkg/fd",
"//pkg/hostarch",
"//pkg/log",
"//pkg/metric",
"//pkg/prometheus",
"//pkg/rdma",
"//pkg/ring0",
"//pkg/sentry/devices/nvproxy/nvconf",
"//pkg/sentry/hostmm",
"//pkg/sentry/platform",
"//pkg/sentry/syscalls/linux",
"//pkg/tcpip/nftables",
"//runsc/boot",
"//runsc/cmd/sandboxsetup",
"//runsc/cmd/util",
"//runsc/config",
"//runsc/flag",
"//runsc/profile",
"//runsc/specutils",
"@com_github_google_subcommands//:go_default_library",
"@com_github_moby_sys_capability//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
go_test(
name = "sentrycmd_test",
size = "small",
srcs = ["chroot_test.go"],
library = ":sentrycmd",
deps = [
"//runsc/config",
"//runsc/specutils",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
// Package sentrycmd holds the subcommands run by the sentry process.
package sentrycmd
import (
"context"
@@ -29,6 +30,7 @@ import (
"github.com/moby/sys/capability"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/coretag"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/fd"
@@ -63,9 +65,9 @@ var (
"CAP_FSETID",
}
// directfsSandboxLinuxCaps is the minimal set of capabilities needed by the
// DirectfsSandboxLinuxCaps is the minimal set of capabilities needed by the
// sandbox to operate on files in directfs mode.
directfsSandboxLinuxCaps = &specs.LinuxCapabilities{
DirectfsSandboxLinuxCaps = &specs.LinuxCapabilities{
Bounding: directfsSandboxCaps,
Effective: directfsSandboxCaps,
Permitted: directfsSandboxCaps,
@@ -116,7 +118,7 @@ type Boot struct {
stdioFDs sandboxsetup.IntFlags
// passFDs are mappings of user-supplied host to guest file descriptors.
passFDs fdMappings
passFDs sandboxsetup.FDMappings
// execFD is the host file descriptor used for program execution.
execFD int
@@ -365,7 +367,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
}
if b.syncUsernsFD >= 0 {
syncUsernsForRootless(b.syncUsernsFD, uint32(b.uid), uint32(b.gid))
sandboxsetup.SyncUsernsForRootless(b.syncUsernsFD, uint32(b.uid), uint32(b.gid))
argOverride["sync-userns-fd"] = "-1"
}
@@ -457,7 +459,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
}
if conf.DirectFS {
caps = specutils.MergeCapabilities(caps, directfsSandboxLinuxCaps)
caps = specutils.MergeCapabilities(caps, DirectfsSandboxLinuxCaps)
}
if conf.Network == config.NetworkHost {
curCaps, err := capability.NewPid2(0)
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
package sentrycmd
import (
"fmt"
@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
package sentrycmd
import (
"flag"
"fmt"
"os"
"path"
@@ -23,8 +24,18 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/specutils"
)
func TestMain(m *testing.M) {
flag.Parse()
if err := specutils.MaybeRunAsRoot(); err != nil {
fmt.Fprintf(os.Stderr, "Error running as root: %v", err)
os.Exit(123)
}
os.Exit(m.Run())
}
func setup(t *testing.T) (string, string) {
t.Helper()
testDir := t.TempDir()
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
package sentrycmd
import (
"context"
+1 -1
View File
@@ -67,7 +67,7 @@ go_test(
],
}),
data = [
"//runsc",
"//:release",
"//test/cmd/test_app",
],
library = ":container",
+31 -9
View File
@@ -59,6 +59,7 @@ import (
"syscall"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/runsc/config"
@@ -68,8 +69,10 @@ import (
// Sidecar binary filenames under the "gvisor-bin/" directory.
const (
metricServerName = "runsc-metric-server"
checkpointGoferName = "checkpointgofer"
metricServerName = "runsc-metric-server"
checkpointGoferName = "checkpointgofer"
gvisorSentryName = "gvisor_sentry"
gvisorSentryPluginStackName = "gvisor_sentry_plugin_stack"
)
// binDirName is the name of the directory holding sidecar binaries.
@@ -93,6 +96,9 @@ const (
enforceReleaseTestonlySkip = "TESTONLY_SKIP"
)
// Special release name used in CI that is also ignored.
const buildkiteTestVersion = "buildkite-test-branch-dirty"
// cutSkip returns the expected release embedded in `val` (env var).
func cutSkip(val, prefix string) (string, bool) {
if val == prefix {
@@ -106,9 +112,11 @@ func cutSkip(val, prefix string) (string, bool) {
// Set from config flag.
var ReleaseEnforcementPolicy = config.SidecarNever
// withEnforceRelease returns envv with `GVISOR_ENFORCE_RELEASE` set for
// sidecar processes.
func withEnforceRelease(envv []string) []string {
// WithEnforceRelease returns envv with `GVISOR_ENFORCE_RELEASE` set for
// sidecar processes. Exec and ForkExec apply it automatically
// Callers that spawn a sidecar through other means (manual `exec.Cmd`)
// must apply it to the sidecar process's env themselves.
func WithEnforceRelease(envv []string) []string {
val := os.Getenv(enforceReleaseEnv)
prefix, want := "", ""
if w, ok := cutSkip(val, enforceReleaseTestonlySkip); ok {
@@ -171,6 +179,10 @@ func VerifyMatchingRelease(b *Binary) {
if b == nil {
return
}
if got == buildkiteTestVersion {
log.Infof("Sidecar release match check not enforced%s; this build is %q.", who, got)
return
}
var msg string
info := testOnly
switch want {
@@ -199,10 +211,16 @@ var (
MetricServer = Binary{Name: metricServerName}
// CheckpointGofer is the checkpoint gofer sidecar binary.
CheckpointGofer = Binary{Name: checkpointGoferName}
// GvisorSentry is the sentry (kernel) sidecar binary.
GvisorSentry = Binary{Name: gvisorSentryName}
// GvisorSentryPluginStack is the sentry sidecar binary with a plugin
// network stack linked in.
// Only present in plugin-enabled installations, so not part of `All`.
GvisorSentryPluginStack = Binary{Name: gvisorSentryPluginStackName}
)
// All lists every known sidecar.
var All = []*Binary{&MetricServer, &CheckpointGofer}
// All lists every sidecar present in a standard installation.
var All = []*Binary{&MetricServer, &CheckpointGofer, &GvisorSentry}
// Options is the set of options used to execute a sidecar binary.
type Options struct {
@@ -262,6 +280,10 @@ func resolveDir() (string, error) {
if dir := os.Getenv(sidecarBinariesDirEnv); dir != "" {
return dir, nil
}
dir := filepath.Join(filepath.Dir(specutils.ExePath), binDirName)
if fi, err := os.Stat(dir); err == nil && fi.IsDir() {
return dir, nil
}
exe, err := filepath.EvalSymlinks(specutils.ExePath)
if err != nil {
return "", fmt.Errorf("cannot resolve path to runsc binary %q: %w", specutils.ExePath, err)
@@ -331,7 +353,7 @@ func (b *Binary) warnEmbeddedDeprecated(opts *Options) {
// Exec resolves the binary and replaces the current process with it. It only
// returns if execution could not be started.
func (b *Binary) Exec(opts Options) error {
opts.Envv = withEnforceRelease(opts.Envv)
opts.Envv = WithEnforceRelease(opts.Envv)
if p, err := b.Path(); err == nil {
log.Infof("sidecar %q found: executing %s (%v)", b.Name, p, &opts)
return execDisk(p, opts)
@@ -346,7 +368,7 @@ func (b *Binary) Exec(opts Options) error {
// ForkExec resolves the binary and runs it in a new process, returning the
// child's PID.
func (b *Binary) ForkExec(opts Options) (int, error) {
opts.Envv = withEnforceRelease(opts.Envv)
opts.Envv = WithEnforceRelease(opts.Envv)
if p, err := b.Path(); err == nil {
log.Infof("sidecar %q: executing %s (%v)", b.Name, p, &opts)
return forkExecDisk(p, opts)
+24 -5
View File
@@ -942,8 +942,22 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
}
lfOpts.Command = "boot" // Revert command to "boot".
sentryBin := &gvisorbinaries.GvisorSentry
sentryUsesCgo := false
if conf.Network == config.NetworkPlugin {
sentryBin = &gvisorbinaries.GvisorSentryPluginStack
sentryUsesCgo = true
}
bootBinPath := specutils.ExePath
if p, err := sentryBin.Path(); err == nil {
log.Infof("Sidecar %q found: booting sandbox with %s", sentryBin.Name, p)
bootBinPath = p
} else {
log.Warningf("Sidecar %q not usable (%v): booting sandbox with runsc itself", sentryBin.Name, err)
}
// Relay all the config flags to the sandbox process.
cmd := exec.Command(specutils.ExePath, conf.ToFlags()...)
cmd := exec.Command(bootBinPath, conf.ToFlags()...)
cmd.SysProcAttr = &unix.SysProcAttr{
// Detach from this session, otherwise cmd will get SIGHUP and SIGCONT
// when re-parented.
@@ -963,12 +977,17 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
// All flags after this must be for the boot command
cmd.Args = append(cmd.Args, "boot", "--bundle="+args.BundleDir)
// Clear environment variables, unless --TESTONLY-unsafe-nonroot is set.
if !conf.TestOnlyAllowRunAsCurrentUserWithoutChroot {
// Setting cmd.Env = nil causes cmd to inherit the current process's env.
if conf.TestOnlyAllowRunAsCurrentUserWithoutChroot {
// --TESTONLY-unsafe-nonroot is set, so keep env.
cmd.Env = os.Environ()
} else {
// Clear environment variables, unless --TESTONLY-unsafe-nonroot is set.
cmd.Env = []string{}
}
if config.CgoEnabled {
if bootBinPath != specutils.ExePath {
cmd.Env = gvisorbinaries.WithEnforceRelease(cmd.Env)
}
if sentryUsesCgo {
// Platforms that use stub processes are not compatible with
// the glibc rseq, because they unmap everything from a process
// address space.
+6 -6
View File
@@ -13,8 +13,8 @@ go_test(
"integration_test.go",
],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
# Needed to invalidate the bazel cache in case of any code changes.
data = ["//:release"],
library = ":integration",
tags = [
# Requires docker and runsc to be configured before the test runs.
@@ -40,8 +40,8 @@ go_test(
"integration_runtime_test.go",
"systemd_test.go",
],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
# Needed to invalidate the bazel cache in case of any code changes.
data = ["//:release"],
library = ":integration",
tags = [
# Requires docker and runsc to be configured before the test runs.
@@ -65,8 +65,8 @@ go_test(
srcs = [
"runtime_in_docker_test.go",
],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
# Needed to invalidate the bazel cache in case of any code changes.
data = ["//:release"],
library = ":integration",
tags = [
# Requires docker and runsc to be configured before the test runs.
+5 -2
View File
@@ -132,9 +132,12 @@ func (test testVariant) failureCases() []testVariant {
// https://gvisor.dev/blog/2024/09/23/safe-ride-into-the-dangerzone/
func TestGVisorInDocker(t *testing.T) {
ctx := context.Background()
runscPath, err := testutil.FindFile("runsc/runsc")
runscPath, err := testutil.FindFile("release/runsc")
if err != nil {
t.Fatalf("Cannot locate runsc binary: %v", err)
runscPath, err = testutil.FindFile("runsc/runsc")
if err != nil {
t.Fatalf("Cannot locate runsc binary: %v", err)
}
}
for _, test := range []testVariant{
{
+12 -12
View File
@@ -8,7 +8,7 @@ package(
go_test(
name = "smoke_test",
srcs = ["smoke_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
# Needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
tags = [
"manual",
@@ -22,8 +22,8 @@ go_test(
go_test(
name = "pytorch_test",
srcs = ["pytorch_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
# Needed to invalidate the bazel cache in case of any code changes.
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -36,8 +36,8 @@ go_test(
go_test(
name = "textgen_test",
srcs = ["textgen_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
# Needed to invalidate the bazel cache in case of any code changes.
data = ["//:release"],
embedsrcs = ["gvisor.png"],
tags = [
"manual",
@@ -56,7 +56,7 @@ go_test(
name = "sglang_test",
srcs = ["sglang_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -73,7 +73,7 @@ go_test(
name = "sr_test",
srcs = ["sr_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -92,7 +92,7 @@ go_test(
timeout = "eternal", # YES_I_REALLY_NEED_AN_ETERNAL_TEST
srcs = ["cuda_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -110,7 +110,7 @@ go_test(
timeout = "eternal", # YES_I_REALLY_NEED_AN_ETERNAL_TEST
srcs = ["cuda_12_8_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -127,7 +127,7 @@ go_test(
name = "ffmpeg_test",
srcs = ["ffmpeg_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -141,7 +141,7 @@ go_test(
name = "imagegen_test",
srcs = ["imagegen_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
@@ -179,7 +179,7 @@ go_test(
name = "triton_test",
srcs = ["triton_test.go"],
# runsc is needed to invalidate the bazel cache in case of any code changes.
data = ["//runsc"],
data = ["//:release"],
tags = [
"manual",
"noguitar",
+2 -2
View File
@@ -14,8 +14,8 @@ go_test(
data = [
"latin10k.txt",
"mysql.sql",
# runsc is needed to invalidate the bazel cache in case of any code changes.
"//runsc",
# Needed to invalidate the bazel cache in case of any code changes.
"//:release",
],
library = ":image",
tags = [
+1 -1
View File
@@ -24,7 +24,7 @@ go_test(
"sandboxposture_test.go",
],
data = [
"//runsc",
"//:release",
],
library = ":root",
pure = True,
+1
View File
@@ -173,6 +173,7 @@ func TestSandboxProcessEnv(t *testing.T) {
t.Fatal(err)
}
got = regexp.MustCompile("(^|\x00)RUNSC_START_TIME_NANOS=\\d+\x00").ReplaceAll(got, []byte("$1"))
got = regexp.MustCompile("(^|\x00)GVISOR_ENFORCE_RELEASE=[^\x00]*\x00").ReplaceAll(got, []byte("$1"))
if len(got) != 0 && string(got) != "GLIBC_TUNABLES=glibc.pthread.rseq=0\x00" {
t.Errorf("sandbox process's environment is not empty: got %s (%v)", string(got), got)
}
+1 -1
View File
@@ -94,7 +94,7 @@ func TestSandboxPostureDocker(t *testing.T) {
want := sandboxposture.Expected(sandboxposture.ExpectedOpts{
DirectFS: conf.DirectFS,
HostNetwork: conf.Network == config.NetworkHost,
NetworkMode: conf.Network.String(),
EnableRaw: conf.EnableRaw,
RequiresCapSysPtrace: sandboxposture.PlatformCapSysPtrace[conf.Platform],
GoDebug: sandboxposture.PlatformGoDebug[conf.Platform],
+1 -1
View File
@@ -10,7 +10,7 @@ go_binary(
testonly = 1,
srcs = ["main.go"],
data = [
"//runsc",
"//:release",
"//test/runner/fuse",
"//test/runner/setup_container",
],
+2 -3
View File
@@ -63,10 +63,9 @@ _runtime_test = rule(
executable = True,
cfg = "target",
),
# runsc is needed to invalidate the bazel cache in case of any code changes.
# Needed to invalidate the bazel cache in case of any code changes.
"_runsc": attr.label(
default = "//runsc:runsc",
executable = True,
default = "//:release",
cfg = "target",
),
},
+58 -9
View File
@@ -11,30 +11,56 @@ DepsInfo = provider(
def _deps_check_impl(target, ctx):
# Check the target's dependencies and add any of our own deps.
deps = []
for dep in ctx.rule.attr.deps:
deps.append(dep)
if hasattr(ctx.rule.attr, "deps"):
for dep in ctx.rule.attr.deps:
deps.append(dep)
nodes = {}
if len(deps) != 0:
nodes[target] = deps
# Keep and propagate each dep's providers.
for dep in ctx.rule.attr.deps:
nodes.update(dep[DepsInfo].nodes)
if hasattr(ctx.rule.attr, "deps"):
for dep in ctx.rule.attr.deps:
if DepsInfo in dep:
nodes.update(dep[DepsInfo].nodes)
if hasattr(ctx.rule.attr, "actual_binary"):
dep = ctx.rule.attr.actual_binary
if dep and DepsInfo in dep:
nodes.update(dep[DepsInfo].nodes)
# Map target (the wrapper target) as depending on the wrapped target's deps.
if dep in dep[DepsInfo].nodes:
nodes[target] = dep[DepsInfo].nodes[dep]
return [DepsInfo(nodes = nodes)]
_deps_check = aspect(
implementation = _deps_check_impl,
attr_aspects = ["deps"],
attr_aspects = ["deps", "actual_binary"],
)
def _workspace_of(label):
# Under bzlmod, external labels have canonical workspace names like
# "gazelle++go_deps+org_golang_x_sys".
# Strip prefixes so that "@org_golang_x_sys//..." prefixes match.
return label.workspace_name.rsplit("+", 1)[-1]
def _parse_prefix(prefix):
workspace, pkg = prefix.split("//", 1)
if len(workspace) > 0 and workspace[0] == "@":
workspace = workspace[1:]
return workspace, pkg
def _matches_prefix(label, prefix):
workspace, pkg = _parse_prefix(prefix)
return _workspace_of(label) == workspace and label.package.startswith(pkg)
def _is_allowed(target, allowlist, prefixes):
# Check for allowed prefixes.
for prefix in prefixes:
workspace, pfx = prefix.split("//", 1)
if len(workspace) > 0 and workspace[0] == "@":
workspace = workspace[1:]
if target.workspace_name == workspace and target.package.startswith(pfx):
if _matches_prefix(target, prefix):
return True
# Check the allowlist.
@@ -44,7 +70,24 @@ def _is_allowed(target, allowlist, prefixes):
return False
def _check_definitely_not_allowed(ctx):
# `deps_test` already rejects any dependency that is not explicitly
# allowed, but this rejects additions to the allowlist itself, to make
# it obvious at review time that something that should definitely not
# be added is still being added. Defense in depth?
for denied in ctx.attr.definitely_not_allowed_prefixes:
denied_workspace, denied_pkg = _parse_prefix(denied)
for allowed in ctx.attr.allowed:
if _matches_prefix(allowed.label, denied):
fail("allowed entry %s matches definitely_not_allowed_prefixes entry %s" % (allowed.label, denied))
for prefix in ctx.attr.allowed_prefixes:
workspace, pkg = _parse_prefix(prefix)
if workspace == denied_workspace and (pkg.startswith(denied_pkg) or denied_pkg.startswith(pkg)):
fail("allowed_prefixes entry %s matches definitely_not_allowed_prefixes entry %s" % (prefix, denied))
def _deps_test_impl(ctx):
_check_definitely_not_allowed(ctx)
nodes = {}
for target in ctx.attr.targets:
for (node_target, node_deps) in target[DepsInfo].nodes.items():
@@ -114,6 +157,12 @@ deps_test = rule(
"allowed_prefixes": attr.string_list(
doc = "Any packages beginning with these prefixes are allowed.",
),
"definitely_not_allowed_prefixes": attr.string_list(
doc = "Packages beginning with these prefixes must not be " +
"covered by allowed or allowed_prefixes. Makes " +
"'never add these' possible to be defined explicitly in " +
"the BUILD file.",
),
},
test = True,
)
+22 -20
View File
@@ -5,6 +5,7 @@
SIDECARS = {
"//runsc/checkpointgofer:checkpointgofer_binary": "checkpointgofer",
"//runsc/cmd/metricserver:runsc-metric-server": "runsc-metric-server",
"//runsc/cmd/sentry:gvisor_sentry": "gvisor_sentry",
}
def _single_file(target):
@@ -15,37 +16,38 @@ def _single_file(target):
def _release_files_impl(ctx):
outputs = []
inputs = []
commands = []
# Top-level binaries
for target in ctx.attr.bins:
src = _single_file(target)
out = ctx.actions.declare_file("%s/%s" % (ctx.label.name, src.basename))
ctx.actions.run_shell(
inputs = [src],
outputs = [out],
command = 'cp -f "$1" "$2"',
arguments = [src.path, out.path],
mnemonic = "ReleaseFile",
)
inputs.append(src)
outputs.append(out)
commands.append('cp -f "%s" "%s"' % (src.path, out.path))
# Sidecar binaries
gvisor_bin = ctx.actions.declare_directory("%s/gvisor-bin" % ctx.label.name)
sidecar_files = []
commands = ['mkdir -p "%s"' % (gvisor_bin.path,)]
for target, name in ctx.attr.sidecars.items():
src = _single_file(target)
sidecar_files.append(src)
commands.append('cp -f "%s" "%s/%s"' % (src.path, gvisor_bin.path, name))
ctx.actions.run_shell(
inputs = sidecar_files,
outputs = [gvisor_bin],
command = "\n".join(commands),
mnemonic = "ReleaseSidecars",
)
outputs.append(gvisor_bin)
out = ctx.actions.declare_file("%s/gvisor-bin/%s" % (ctx.label.name, name))
inputs.append(src)
outputs.append(out)
commands.append('mkdir -p "$(dirname "%s")" && cp -f "%s" "%s"' % (out.path, src.path, out.path))
return [DefaultInfo(files = depset(outputs))]
ctx.actions.run_shell(
inputs = inputs,
outputs = outputs,
command = "\n".join(commands),
mnemonic = "ReleaseFiles",
)
runfiles = ctx.runfiles(files = outputs)
return [DefaultInfo(
files = depset(outputs),
default_runfiles = runfiles,
data_runfiles = runfiles,
)]
release_files = rule(
implementation = _release_files_impl,