snapshot: sagernet/gvisor v0.0.0-20260727.0-sing-box-mod.1 + SPEC 048 guard
Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to 20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии, на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его бампом. За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go, stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack. Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии: handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h, а performHandshake так же зануляет h и отпускает мьютекс до Close(). Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк). Red/green проверен на новой базе: без guard'а тест падает с той же nil-паникой, что в полевом крашдампе; с ним зелёный.
This commit is contained in:
parent
ffebe42860
commit
117243aa02
293 changed files with 16413 additions and 2842 deletions
835
sandboxexec/proto/sandbox_options_go_proto/sandbox_options.pb.go
Normal file
835
sandboxexec/proto/sandbox_options_go_proto/sandbox_options.pb.go
Normal file
|
|
@ -0,0 +1,835 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.4
|
||||
// source: sandboxexec/proto/sandbox_options.proto
|
||||
|
||||
package sandbox_options_go_proto
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type EnvVar_EnvVarPolicy int32
|
||||
|
||||
const (
|
||||
EnvVar_ENV_VAR_POLICY_UNSPECIFIED EnvVar_EnvVarPolicy = 0
|
||||
EnvVar_ENV_VAR_POLICY_FORWARD EnvVar_EnvVarPolicy = 1
|
||||
EnvVar_ENV_VAR_POLICY_UNSET EnvVar_EnvVarPolicy = 2
|
||||
)
|
||||
|
||||
// Enum value maps for EnvVar_EnvVarPolicy.
|
||||
var (
|
||||
EnvVar_EnvVarPolicy_name = map[int32]string{
|
||||
0: "ENV_VAR_POLICY_UNSPECIFIED",
|
||||
1: "ENV_VAR_POLICY_FORWARD",
|
||||
2: "ENV_VAR_POLICY_UNSET",
|
||||
}
|
||||
EnvVar_EnvVarPolicy_value = map[string]int32{
|
||||
"ENV_VAR_POLICY_UNSPECIFIED": 0,
|
||||
"ENV_VAR_POLICY_FORWARD": 1,
|
||||
"ENV_VAR_POLICY_UNSET": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x EnvVar_EnvVarPolicy) Enum() *EnvVar_EnvVarPolicy {
|
||||
p := new(EnvVar_EnvVarPolicy)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x EnvVar_EnvVarPolicy) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (EnvVar_EnvVarPolicy) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (EnvVar_EnvVarPolicy) Type() protoreflect.EnumType {
|
||||
return &file_sandboxexec_proto_sandbox_options_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x EnvVar_EnvVarPolicy) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EnvVar_EnvVarPolicy.Descriptor instead.
|
||||
func (EnvVar_EnvVarPolicy) EnumDescriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{3, 0}
|
||||
}
|
||||
|
||||
type PortMapping_PortMappingType int32
|
||||
|
||||
const (
|
||||
PortMapping_PORT_MAPPING_TYPE_TCP PortMapping_PortMappingType = 0
|
||||
PortMapping_PORT_MAPPING_TYPE_UDP PortMapping_PortMappingType = 1
|
||||
)
|
||||
|
||||
// Enum value maps for PortMapping_PortMappingType.
|
||||
var (
|
||||
PortMapping_PortMappingType_name = map[int32]string{
|
||||
0: "PORT_MAPPING_TYPE_TCP",
|
||||
1: "PORT_MAPPING_TYPE_UDP",
|
||||
}
|
||||
PortMapping_PortMappingType_value = map[string]int32{
|
||||
"PORT_MAPPING_TYPE_TCP": 0,
|
||||
"PORT_MAPPING_TYPE_UDP": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x PortMapping_PortMappingType) Enum() *PortMapping_PortMappingType {
|
||||
p := new(PortMapping_PortMappingType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x PortMapping_PortMappingType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (PortMapping_PortMappingType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (PortMapping_PortMappingType) Type() protoreflect.EnumType {
|
||||
return &file_sandboxexec_proto_sandbox_options_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x PortMapping_PortMappingType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PortMapping_PortMappingType.Descriptor instead.
|
||||
func (PortMapping_PortMappingType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{4, 0}
|
||||
}
|
||||
|
||||
type SandboxOptions struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Mounts []*Mount `protobuf:"bytes,1,rep,name=mounts,proto3" json:"mounts,omitempty"`
|
||||
NetworkingOptions *NetworkingOptions `protobuf:"bytes,2,opt,name=networking_options,json=networkingOptions,proto3" json:"networking_options,omitempty"`
|
||||
StdoutFile string `protobuf:"bytes,3,opt,name=stdout_file,json=stdoutFile,proto3" json:"stdout_file,omitempty"`
|
||||
StderrFile string `protobuf:"bytes,4,opt,name=stderr_file,json=stderrFile,proto3" json:"stderr_file,omitempty"`
|
||||
EnvVars []*EnvVar `protobuf:"bytes,5,rep,name=env_vars,json=envVars,proto3" json:"env_vars,omitempty"`
|
||||
DebugOptions *DebugOptions `protobuf:"bytes,6,opt,name=debug_options,json=debugOptions,proto3" json:"debug_options,omitempty"`
|
||||
StartTimeoutMs int64 `protobuf:"varint,7,opt,name=start_timeout_ms,json=startTimeoutMs,proto3" json:"start_timeout_ms,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) Reset() {
|
||||
*x = SandboxOptions{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SandboxOptions) ProtoMessage() {}
|
||||
|
||||
func (x *SandboxOptions) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SandboxOptions.ProtoReflect.Descriptor instead.
|
||||
func (*SandboxOptions) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetMounts() []*Mount {
|
||||
if x != nil {
|
||||
return x.Mounts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetNetworkingOptions() *NetworkingOptions {
|
||||
if x != nil {
|
||||
return x.NetworkingOptions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetStdoutFile() string {
|
||||
if x != nil {
|
||||
return x.StdoutFile
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetStderrFile() string {
|
||||
if x != nil {
|
||||
return x.StderrFile
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetEnvVars() []*EnvVar {
|
||||
if x != nil {
|
||||
return x.EnvVars
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetDebugOptions() *DebugOptions {
|
||||
if x != nil {
|
||||
return x.DebugOptions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SandboxOptions) GetStartTimeoutMs() int64 {
|
||||
if x != nil {
|
||||
return x.StartTimeoutMs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type NetworkingOptions struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
EnableExternalNetworking bool `protobuf:"varint,1,opt,name=enable_external_networking,json=enableExternalNetworking,proto3" json:"enable_external_networking,omitempty"`
|
||||
PortMappings []*PortMapping `protobuf:"bytes,2,rep,name=port_mappings,json=portMappings,proto3" json:"port_mappings,omitempty"`
|
||||
DomainAllowlist []string `protobuf:"bytes,3,rep,name=domain_allowlist,json=domainAllowlist,proto3" json:"domain_allowlist,omitempty"`
|
||||
DomainDenylist []string `protobuf:"bytes,4,rep,name=domain_denylist,json=domainDenylist,proto3" json:"domain_denylist,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NetworkingOptions) Reset() {
|
||||
*x = NetworkingOptions{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NetworkingOptions) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NetworkingOptions) ProtoMessage() {}
|
||||
|
||||
func (x *NetworkingOptions) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NetworkingOptions.ProtoReflect.Descriptor instead.
|
||||
func (*NetworkingOptions) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *NetworkingOptions) GetEnableExternalNetworking() bool {
|
||||
if x != nil {
|
||||
return x.EnableExternalNetworking
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NetworkingOptions) GetPortMappings() []*PortMapping {
|
||||
if x != nil {
|
||||
return x.PortMappings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NetworkingOptions) GetDomainAllowlist() []string {
|
||||
if x != nil {
|
||||
return x.DomainAllowlist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NetworkingOptions) GetDomainDenylist() []string {
|
||||
if x != nil {
|
||||
return x.DomainDenylist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DebugOptions struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
EnableDebugLogs string `protobuf:"bytes,1,opt,name=enable_debug_logs,json=enableDebugLogs,proto3" json:"enable_debug_logs,omitempty"`
|
||||
DebugLogFile string `protobuf:"bytes,2,opt,name=debug_log_file,json=debugLogFile,proto3" json:"debug_log_file,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DebugOptions) Reset() {
|
||||
*x = DebugOptions{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DebugOptions) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DebugOptions) ProtoMessage() {}
|
||||
|
||||
func (x *DebugOptions) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugOptions.ProtoReflect.Descriptor instead.
|
||||
func (*DebugOptions) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *DebugOptions) GetEnableDebugLogs() string {
|
||||
if x != nil {
|
||||
return x.EnableDebugLogs
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DebugOptions) GetDebugLogFile() string {
|
||||
if x != nil {
|
||||
return x.DebugLogFile
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type EnvVar struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
// Types that are valid to be assigned to PolicyOrValue:
|
||||
//
|
||||
// *EnvVar_Value
|
||||
// *EnvVar_Policy
|
||||
PolicyOrValue isEnvVar_PolicyOrValue `protobuf_oneof:"policy_or_value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EnvVar) Reset() {
|
||||
*x = EnvVar{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EnvVar) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EnvVar) ProtoMessage() {}
|
||||
|
||||
func (x *EnvVar) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EnvVar.ProtoReflect.Descriptor instead.
|
||||
func (*EnvVar) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *EnvVar) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EnvVar) GetPolicyOrValue() isEnvVar_PolicyOrValue {
|
||||
if x != nil {
|
||||
return x.PolicyOrValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *EnvVar) GetValue() string {
|
||||
if x != nil {
|
||||
if x, ok := x.PolicyOrValue.(*EnvVar_Value); ok {
|
||||
return x.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *EnvVar) GetPolicy() EnvVar_EnvVarPolicy {
|
||||
if x != nil {
|
||||
if x, ok := x.PolicyOrValue.(*EnvVar_Policy); ok {
|
||||
return x.Policy
|
||||
}
|
||||
}
|
||||
return EnvVar_ENV_VAR_POLICY_UNSPECIFIED
|
||||
}
|
||||
|
||||
type isEnvVar_PolicyOrValue interface {
|
||||
isEnvVar_PolicyOrValue()
|
||||
}
|
||||
|
||||
type EnvVar_Value struct {
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3,oneof"`
|
||||
}
|
||||
|
||||
type EnvVar_Policy struct {
|
||||
Policy EnvVar_EnvVarPolicy `protobuf:"varint,3,opt,name=policy,proto3,enum=cloud_gvisor_sandboxexec.EnvVar_EnvVarPolicy,oneof"`
|
||||
}
|
||||
|
||||
func (*EnvVar_Value) isEnvVar_PolicyOrValue() {}
|
||||
|
||||
func (*EnvVar_Policy) isEnvVar_PolicyOrValue() {}
|
||||
|
||||
type PortMapping struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
SandboxPort uint32 `protobuf:"varint,1,opt,name=sandbox_port,json=sandboxPort,proto3" json:"sandbox_port,omitempty"`
|
||||
HostPort uint32 `protobuf:"varint,2,opt,name=host_port,json=hostPort,proto3" json:"host_port,omitempty"`
|
||||
PortMappingType PortMapping_PortMappingType `protobuf:"varint,3,opt,name=port_mapping_type,json=portMappingType,proto3,enum=cloud_gvisor_sandboxexec.PortMapping_PortMappingType" json:"port_mapping_type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PortMapping) Reset() {
|
||||
*x = PortMapping{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PortMapping) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PortMapping) ProtoMessage() {}
|
||||
|
||||
func (x *PortMapping) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PortMapping.ProtoReflect.Descriptor instead.
|
||||
func (*PortMapping) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *PortMapping) GetSandboxPort() uint32 {
|
||||
if x != nil {
|
||||
return x.SandboxPort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PortMapping) GetHostPort() uint32 {
|
||||
if x != nil {
|
||||
return x.HostPort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *PortMapping) GetPortMappingType() PortMapping_PortMappingType {
|
||||
if x != nil {
|
||||
return x.PortMappingType
|
||||
}
|
||||
return PortMapping_PORT_MAPPING_TYPE_TCP
|
||||
}
|
||||
|
||||
type MountOptions struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Passthrough bool `protobuf:"varint,1,opt,name=passthrough,proto3" json:"passthrough,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *MountOptions) Reset() {
|
||||
*x = MountOptions{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MountOptions) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MountOptions) ProtoMessage() {}
|
||||
|
||||
func (x *MountOptions) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MountOptions.ProtoReflect.Descriptor instead.
|
||||
func (*MountOptions) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *MountOptions) GetPassthrough() bool {
|
||||
if x != nil {
|
||||
return x.Passthrough
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Mount struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Target string `protobuf:"bytes,1,opt,name=target,proto3" json:"target,omitempty"`
|
||||
// Types that are valid to be assigned to Mount:
|
||||
//
|
||||
// *Mount_HostMount
|
||||
// *Mount_SandboxTmpfsMount
|
||||
Mount isMount_Mount `protobuf_oneof:"mount"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Mount) Reset() {
|
||||
*x = Mount{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Mount) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Mount) ProtoMessage() {}
|
||||
|
||||
func (x *Mount) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Mount.ProtoReflect.Descriptor instead.
|
||||
func (*Mount) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *Mount) GetTarget() string {
|
||||
if x != nil {
|
||||
return x.Target
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Mount) GetMount() isMount_Mount {
|
||||
if x != nil {
|
||||
return x.Mount
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Mount) GetHostMount() *HostMount {
|
||||
if x != nil {
|
||||
if x, ok := x.Mount.(*Mount_HostMount); ok {
|
||||
return x.HostMount
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Mount) GetSandboxTmpfsMount() *SandboxTmpfsMount {
|
||||
if x != nil {
|
||||
if x, ok := x.Mount.(*Mount_SandboxTmpfsMount); ok {
|
||||
return x.SandboxTmpfsMount
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isMount_Mount interface {
|
||||
isMount_Mount()
|
||||
}
|
||||
|
||||
type Mount_HostMount struct {
|
||||
HostMount *HostMount `protobuf:"bytes,2,opt,name=host_mount,json=hostMount,proto3,oneof"`
|
||||
}
|
||||
|
||||
type Mount_SandboxTmpfsMount struct {
|
||||
SandboxTmpfsMount *SandboxTmpfsMount `protobuf:"bytes,3,opt,name=sandbox_tmpfs_mount,json=sandboxTmpfsMount,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*Mount_HostMount) isMount_Mount() {}
|
||||
|
||||
func (*Mount_SandboxTmpfsMount) isMount_Mount() {}
|
||||
|
||||
type HostMount struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
HostPath string `protobuf:"bytes,1,opt,name=host_path,json=hostPath,proto3" json:"host_path,omitempty"`
|
||||
Options *MountOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *HostMount) Reset() {
|
||||
*x = HostMount{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *HostMount) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*HostMount) ProtoMessage() {}
|
||||
|
||||
func (x *HostMount) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use HostMount.ProtoReflect.Descriptor instead.
|
||||
func (*HostMount) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *HostMount) GetHostPath() string {
|
||||
if x != nil {
|
||||
return x.HostPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *HostMount) GetOptions() *MountOptions {
|
||||
if x != nil {
|
||||
return x.Options
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SandboxTmpfsMount struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SandboxTmpfsMount) Reset() {
|
||||
*x = SandboxTmpfsMount{}
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SandboxTmpfsMount) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SandboxTmpfsMount) ProtoMessage() {}
|
||||
|
||||
func (x *SandboxTmpfsMount) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sandboxexec_proto_sandbox_options_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SandboxTmpfsMount.ProtoReflect.Descriptor instead.
|
||||
func (*SandboxTmpfsMount) Descriptor() ([]byte, []int) {
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
var File_sandboxexec_proto_sandbox_options_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_sandboxexec_proto_sandbox_options_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"'sandboxexec/proto/sandbox_options.proto\x12\x18cloud_gvisor_sandboxexec\"\x9b\x03\n" +
|
||||
"\x0eSandboxOptions\x127\n" +
|
||||
"\x06mounts\x18\x01 \x03(\v2\x1f.cloud_gvisor_sandboxexec.MountR\x06mounts\x12Z\n" +
|
||||
"\x12networking_options\x18\x02 \x01(\v2+.cloud_gvisor_sandboxexec.NetworkingOptionsR\x11networkingOptions\x12\x1f\n" +
|
||||
"\vstdout_file\x18\x03 \x01(\tR\n" +
|
||||
"stdoutFile\x12\x1f\n" +
|
||||
"\vstderr_file\x18\x04 \x01(\tR\n" +
|
||||
"stderrFile\x12;\n" +
|
||||
"\benv_vars\x18\x05 \x03(\v2 .cloud_gvisor_sandboxexec.EnvVarR\aenvVars\x12K\n" +
|
||||
"\rdebug_options\x18\x06 \x01(\v2&.cloud_gvisor_sandboxexec.DebugOptionsR\fdebugOptions\x12(\n" +
|
||||
"\x10start_timeout_ms\x18\a \x01(\x03R\x0estartTimeoutMs\"\xf1\x01\n" +
|
||||
"\x11NetworkingOptions\x12<\n" +
|
||||
"\x1aenable_external_networking\x18\x01 \x01(\bR\x18enableExternalNetworking\x12J\n" +
|
||||
"\rport_mappings\x18\x02 \x03(\v2%.cloud_gvisor_sandboxexec.PortMappingR\fportMappings\x12)\n" +
|
||||
"\x10domain_allowlist\x18\x03 \x03(\tR\x0fdomainAllowlist\x12'\n" +
|
||||
"\x0fdomain_denylist\x18\x04 \x03(\tR\x0edomainDenylist\"`\n" +
|
||||
"\fDebugOptions\x12*\n" +
|
||||
"\x11enable_debug_logs\x18\x01 \x01(\tR\x0fenableDebugLogs\x12$\n" +
|
||||
"\x0edebug_log_file\x18\x02 \x01(\tR\fdebugLogFile\"\xf6\x01\n" +
|
||||
"\x06EnvVar\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
|
||||
"\x05value\x18\x02 \x01(\tH\x00R\x05value\x12G\n" +
|
||||
"\x06policy\x18\x03 \x01(\x0e2-.cloud_gvisor_sandboxexec.EnvVar.EnvVarPolicyH\x00R\x06policy\"d\n" +
|
||||
"\fEnvVarPolicy\x12\x1e\n" +
|
||||
"\x1aENV_VAR_POLICY_UNSPECIFIED\x10\x00\x12\x1a\n" +
|
||||
"\x16ENV_VAR_POLICY_FORWARD\x10\x01\x12\x18\n" +
|
||||
"\x14ENV_VAR_POLICY_UNSET\x10\x02B\x11\n" +
|
||||
"\x0fpolicy_or_value\"\xf9\x01\n" +
|
||||
"\vPortMapping\x12!\n" +
|
||||
"\fsandbox_port\x18\x01 \x01(\rR\vsandboxPort\x12\x1b\n" +
|
||||
"\thost_port\x18\x02 \x01(\rR\bhostPort\x12a\n" +
|
||||
"\x11port_mapping_type\x18\x03 \x01(\x0e25.cloud_gvisor_sandboxexec.PortMapping.PortMappingTypeR\x0fportMappingType\"G\n" +
|
||||
"\x0fPortMappingType\x12\x19\n" +
|
||||
"\x15PORT_MAPPING_TYPE_TCP\x10\x00\x12\x19\n" +
|
||||
"\x15PORT_MAPPING_TYPE_UDP\x10\x01\"0\n" +
|
||||
"\fMountOptions\x12 \n" +
|
||||
"\vpassthrough\x18\x01 \x01(\bR\vpassthrough\"\xcd\x01\n" +
|
||||
"\x05Mount\x12\x16\n" +
|
||||
"\x06target\x18\x01 \x01(\tR\x06target\x12D\n" +
|
||||
"\n" +
|
||||
"host_mount\x18\x02 \x01(\v2#.cloud_gvisor_sandboxexec.HostMountH\x00R\thostMount\x12]\n" +
|
||||
"\x13sandbox_tmpfs_mount\x18\x03 \x01(\v2+.cloud_gvisor_sandboxexec.SandboxTmpfsMountH\x00R\x11sandboxTmpfsMountB\a\n" +
|
||||
"\x05mount\"j\n" +
|
||||
"\tHostMount\x12\x1b\n" +
|
||||
"\thost_path\x18\x01 \x01(\tR\bhostPath\x12@\n" +
|
||||
"\aoptions\x18\x02 \x01(\v2&.cloud_gvisor_sandboxexec.MountOptionsR\aoptions\"\x13\n" +
|
||||
"\x11SandboxTmpfsMountb\x06proto3"
|
||||
|
||||
var (
|
||||
file_sandboxexec_proto_sandbox_options_proto_rawDescOnce sync.Once
|
||||
file_sandboxexec_proto_sandbox_options_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_sandboxexec_proto_sandbox_options_proto_rawDescGZIP() []byte {
|
||||
file_sandboxexec_proto_sandbox_options_proto_rawDescOnce.Do(func() {
|
||||
file_sandboxexec_proto_sandbox_options_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_sandboxexec_proto_sandbox_options_proto_rawDesc), len(file_sandboxexec_proto_sandbox_options_proto_rawDesc)))
|
||||
})
|
||||
return file_sandboxexec_proto_sandbox_options_proto_rawDescData
|
||||
}
|
||||
|
||||
var (
|
||||
file_sandboxexec_proto_sandbox_options_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
file_sandboxexec_proto_sandbox_options_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
file_sandboxexec_proto_sandbox_options_proto_goTypes = []any{
|
||||
(EnvVar_EnvVarPolicy)(0), // 0: cloud_gvisor_sandboxexec.EnvVar.EnvVarPolicy
|
||||
(PortMapping_PortMappingType)(0), // 1: cloud_gvisor_sandboxexec.PortMapping.PortMappingType
|
||||
(*SandboxOptions)(nil), // 2: cloud_gvisor_sandboxexec.SandboxOptions
|
||||
(*NetworkingOptions)(nil), // 3: cloud_gvisor_sandboxexec.NetworkingOptions
|
||||
(*DebugOptions)(nil), // 4: cloud_gvisor_sandboxexec.DebugOptions
|
||||
(*EnvVar)(nil), // 5: cloud_gvisor_sandboxexec.EnvVar
|
||||
(*PortMapping)(nil), // 6: cloud_gvisor_sandboxexec.PortMapping
|
||||
(*MountOptions)(nil), // 7: cloud_gvisor_sandboxexec.MountOptions
|
||||
(*Mount)(nil), // 8: cloud_gvisor_sandboxexec.Mount
|
||||
(*HostMount)(nil), // 9: cloud_gvisor_sandboxexec.HostMount
|
||||
(*SandboxTmpfsMount)(nil), // 10: cloud_gvisor_sandboxexec.SandboxTmpfsMount
|
||||
}
|
||||
)
|
||||
|
||||
var file_sandboxexec_proto_sandbox_options_proto_depIdxs = []int32{
|
||||
8, // 0: cloud_gvisor_sandboxexec.SandboxOptions.mounts:type_name -> cloud_gvisor_sandboxexec.Mount
|
||||
3, // 1: cloud_gvisor_sandboxexec.SandboxOptions.networking_options:type_name -> cloud_gvisor_sandboxexec.NetworkingOptions
|
||||
5, // 2: cloud_gvisor_sandboxexec.SandboxOptions.env_vars:type_name -> cloud_gvisor_sandboxexec.EnvVar
|
||||
4, // 3: cloud_gvisor_sandboxexec.SandboxOptions.debug_options:type_name -> cloud_gvisor_sandboxexec.DebugOptions
|
||||
6, // 4: cloud_gvisor_sandboxexec.NetworkingOptions.port_mappings:type_name -> cloud_gvisor_sandboxexec.PortMapping
|
||||
0, // 5: cloud_gvisor_sandboxexec.EnvVar.policy:type_name -> cloud_gvisor_sandboxexec.EnvVar.EnvVarPolicy
|
||||
1, // 6: cloud_gvisor_sandboxexec.PortMapping.port_mapping_type:type_name -> cloud_gvisor_sandboxexec.PortMapping.PortMappingType
|
||||
9, // 7: cloud_gvisor_sandboxexec.Mount.host_mount:type_name -> cloud_gvisor_sandboxexec.HostMount
|
||||
10, // 8: cloud_gvisor_sandboxexec.Mount.sandbox_tmpfs_mount:type_name -> cloud_gvisor_sandboxexec.SandboxTmpfsMount
|
||||
7, // 9: cloud_gvisor_sandboxexec.HostMount.options:type_name -> cloud_gvisor_sandboxexec.MountOptions
|
||||
10, // [10:10] is the sub-list for method output_type
|
||||
10, // [10:10] is the sub-list for method input_type
|
||||
10, // [10:10] is the sub-list for extension type_name
|
||||
10, // [10:10] is the sub-list for extension extendee
|
||||
0, // [0:10] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_sandboxexec_proto_sandbox_options_proto_init() }
|
||||
func file_sandboxexec_proto_sandbox_options_proto_init() {
|
||||
if File_sandboxexec_proto_sandbox_options_proto != nil {
|
||||
return
|
||||
}
|
||||
file_sandboxexec_proto_sandbox_options_proto_msgTypes[3].OneofWrappers = []any{
|
||||
(*EnvVar_Value)(nil),
|
||||
(*EnvVar_Policy)(nil),
|
||||
}
|
||||
file_sandboxexec_proto_sandbox_options_proto_msgTypes[6].OneofWrappers = []any{
|
||||
(*Mount_HostMount)(nil),
|
||||
(*Mount_SandboxTmpfsMount)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_sandboxexec_proto_sandbox_options_proto_rawDesc), len(file_sandboxexec_proto_sandbox_options_proto_rawDesc)),
|
||||
NumEnums: 2,
|
||||
NumMessages: 9,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_sandboxexec_proto_sandbox_options_proto_goTypes,
|
||||
DependencyIndexes: file_sandboxexec_proto_sandbox_options_proto_depIdxs,
|
||||
EnumInfos: file_sandboxexec_proto_sandbox_options_proto_enumTypes,
|
||||
MessageInfos: file_sandboxexec_proto_sandbox_options_proto_msgTypes,
|
||||
}.Build()
|
||||
File_sandboxexec_proto_sandbox_options_proto = out.File
|
||||
file_sandboxexec_proto_sandbox_options_proto_goTypes = nil
|
||||
file_sandboxexec_proto_sandbox_options_proto_depIdxs = nil
|
||||
}
|
||||
165
sandboxexec/sandbox/oci.go
Normal file
165
sandboxexec/sandbox/oci.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// 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
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// BundleConfig holds configuration for creating an OCI bundle.
|
||||
type BundleConfig struct {
|
||||
ID string
|
||||
RuntimeDir string
|
||||
EnableNetworking bool
|
||||
Mounts []Mount
|
||||
Env []string
|
||||
Annotations map[string]string
|
||||
WorkingDir string
|
||||
Hostname string
|
||||
}
|
||||
|
||||
// NewBundle creates a temporary OCI bundle on the fly with the given configuration.
|
||||
func NewBundle(cfg BundleConfig) (string, error) {
|
||||
// Create a bundle directory for the sandbox.
|
||||
bundleDir := filepath.Join(cfg.RuntimeDir, cfg.ID)
|
||||
rootfsDir := filepath.Join(bundleDir, "rootfs")
|
||||
|
||||
if err := os.MkdirAll(rootfsDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("failed to create bundle directories: %w", err)
|
||||
}
|
||||
|
||||
// Define the OCI Specification programmatically.
|
||||
namespaces := []specs.LinuxNamespace{
|
||||
{Type: specs.PIDNamespace},
|
||||
{Type: specs.MountNamespace},
|
||||
{Type: specs.UTSNamespace},
|
||||
{Type: specs.IPCNamespace},
|
||||
}
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UserNamespace})
|
||||
}
|
||||
if cfg.EnableNetworking {
|
||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace})
|
||||
}
|
||||
|
||||
spec := &specs.Spec{
|
||||
Version: "1.0.0",
|
||||
Annotations: cfg.Annotations,
|
||||
Root: &specs.Root{
|
||||
Path: "rootfs",
|
||||
Readonly: false,
|
||||
},
|
||||
Process: &specs.Process{
|
||||
Terminal: false,
|
||||
User: specs.User{UID: 0, GID: 0},
|
||||
// Keeps the sandbox alive in the background.
|
||||
Args: []string{"sleep", "infinity"},
|
||||
Cwd: cfg.WorkingDir,
|
||||
},
|
||||
Mounts: []specs.Mount{
|
||||
// Mandatory Linux API Filesystems
|
||||
{Destination: "/proc", Type: "proc", Source: "proc"},
|
||||
{Destination: "/dev", Type: "tmpfs", Source: "tmpfs"},
|
||||
},
|
||||
// enable basic namespaces for gVisor.
|
||||
Linux: &specs.Linux{
|
||||
Namespaces: namespaces,
|
||||
},
|
||||
Hostname: cfg.Hostname,
|
||||
}
|
||||
|
||||
baseEnv := []string{"PATH=/bin:/usr/bin:/usr/local/bin"}
|
||||
spec.Process.Env = append(baseEnv, cfg.Env...)
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
spec.Linux.UIDMappings = []specs.LinuxIDMapping{
|
||||
{ContainerID: 0, HostID: uint32(os.Geteuid()), Size: 1},
|
||||
}
|
||||
spec.Linux.GIDMappings = []specs.LinuxIDMapping{
|
||||
{ContainerID: 0, HostID: uint32(os.Getegid()), Size: 1},
|
||||
}
|
||||
}
|
||||
|
||||
// Map host binaries & libraries as readonly. The binaries will be
|
||||
// executed in gVisor sandbox, not on the host.
|
||||
for _, p := range []string{"/bin", "/usr", "/lib", "/lib64", "/etc/alternatives"} {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
opts := []string{"rbind", "ro", "nosuid", "nodev"}
|
||||
if p == "/etc/alternatives" {
|
||||
opts = []string{"rbind", "ro"}
|
||||
}
|
||||
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||
Destination: p,
|
||||
Type: "bind",
|
||||
Source: p,
|
||||
Options: opts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom mounts. Custom mounts overriding default host mounts create duplicate OCI
|
||||
// entries. The later entry overrides the earlier one, as expected by OCI specs.
|
||||
for _, m := range cfg.Mounts {
|
||||
switch m.Type {
|
||||
case MountTypeBind:
|
||||
opts := []string{"rbind"}
|
||||
if m.ReadOnly {
|
||||
opts = append(opts, "ro")
|
||||
} else {
|
||||
opts = append(opts, "rw")
|
||||
}
|
||||
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||
Destination: filepath.Clean(m.Destination),
|
||||
Source: filepath.Clean(m.Source),
|
||||
Type: "bind",
|
||||
Options: opts,
|
||||
})
|
||||
case MountTypeTmpfs:
|
||||
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||
Destination: filepath.Clean(m.Destination),
|
||||
Source: "tmpfs",
|
||||
Type: "tmpfs",
|
||||
})
|
||||
case MountTypeProc:
|
||||
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||
Destination: filepath.Clean(m.Destination),
|
||||
Source: "proc",
|
||||
Type: "proc",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Write the spec to config.json
|
||||
configPath := filepath.Join(bundleDir, "config.json")
|
||||
configFile, err := os.Create(configPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create config.json: %w", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
|
||||
encoder := json.NewEncoder(configFile)
|
||||
encoder.SetIndent("", " ")
|
||||
if err := encoder.Encode(spec); err != nil {
|
||||
return "", fmt.Errorf("failed to encode config.json: %w", err)
|
||||
}
|
||||
|
||||
return bundleDir, nil
|
||||
}
|
||||
528
sandboxexec/sandbox/sandbox.go
Normal file
528
sandboxexec/sandbox/sandbox.go
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
// 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
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package sandbox provides a simple Go API for creating gVisor sandbox
|
||||
// and executing commands in the sandbox.
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Options holds the configuration for a Sandbox.
|
||||
type Options struct {
|
||||
runtimeDir string
|
||||
id string
|
||||
enableNetworking bool
|
||||
mounts []Mount
|
||||
snapshot *Snapshot
|
||||
env []string
|
||||
err error
|
||||
workingDir string
|
||||
hostname string
|
||||
}
|
||||
|
||||
// Option configures the Options struct.
|
||||
type Option func(*Options)
|
||||
|
||||
// MountType represents the type of a mount point inside the sandbox.
|
||||
type MountType int
|
||||
|
||||
const (
|
||||
// MountTypeBind represents a host bind mount.
|
||||
MountTypeBind MountType = iota
|
||||
// MountTypeTmpfs represents an in-memory tmpfs mount.
|
||||
MountTypeTmpfs
|
||||
// MountTypeProc represents a procfs mount.
|
||||
MountTypeProc
|
||||
)
|
||||
|
||||
// Mount holds settings for a custom host bind directory or in-memory mount.
|
||||
type Mount struct {
|
||||
Source string
|
||||
Destination string
|
||||
Type MountType
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// WithRuntimeDir sets a custom runtime directory where bundle and state files are written.
|
||||
func WithRuntimeDir(runtimeDir string) Option {
|
||||
return func(o *Options) {
|
||||
o.runtimeDir = runtimeDir
|
||||
}
|
||||
}
|
||||
|
||||
// WithID sets a specific sandbox ID. If not set, a unique ID will be generated automatically.
|
||||
func WithID(id string) Option {
|
||||
return func(o *Options) {
|
||||
o.id = id
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetworking configures whether networking is enabled inside the sandbox.
|
||||
func WithNetworking(enabled bool) Option {
|
||||
return func(o *Options) {
|
||||
o.enableNetworking = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// WithBindMount adds a custom bind mount from host's source path to the sandbox's destination path.
|
||||
func WithBindMount(source, destination string, readOnly bool) Option {
|
||||
return func(o *Options) {
|
||||
o.mounts = append(o.mounts, Mount{
|
||||
Source: filepath.Clean(source),
|
||||
Destination: filepath.Clean(destination),
|
||||
Type: MountTypeBind,
|
||||
ReadOnly: readOnly,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithTmpfsMount adds an in-memory tmpfs filesystem at the destination path inside the sandbox.
|
||||
func WithTmpfsMount(destination string) Option {
|
||||
return func(o *Options) {
|
||||
o.mounts = append(o.mounts, Mount{
|
||||
Destination: filepath.Clean(destination),
|
||||
Type: MountTypeTmpfs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithProcMount adds a procfs mount at the destination path inside the sandbox.
|
||||
func WithProcMount(destination string) Option {
|
||||
return func(o *Options) {
|
||||
o.mounts = append(o.mounts, Mount{
|
||||
Destination: filepath.Clean(destination),
|
||||
Type: MountTypeProc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithHostname sets the hostname for the sandbox.
|
||||
func WithHostname(hostname string) Option {
|
||||
return func(o *Options) {
|
||||
o.hostname = hostname
|
||||
}
|
||||
}
|
||||
|
||||
// WithSnapshot configures the sandbox to restore state from the given snapshot.
|
||||
// The sandbox automatically reads the snapshot metadata to determine if it is a
|
||||
// full Checkpoint/Restore, Filesystem snapshot, or Rootfs Tar snapshot.
|
||||
func WithSnapshot(snapshot *Snapshot) Option {
|
||||
return func(o *Options) {
|
||||
o.snapshot = snapshot
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnv sets one or more environment variables in the sandbox process.
|
||||
// Each env string must be in the "KEY=VALUE" format.
|
||||
func WithEnv(envs ...string) Option {
|
||||
return func(o *Options) {
|
||||
for _, env := range envs {
|
||||
if !strings.Contains(env, "=") {
|
||||
o.err = fmt.Errorf("invalid environment variable format, expected KEY=VALUE: %q", env)
|
||||
return
|
||||
}
|
||||
}
|
||||
o.env = append(o.env, envs...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithWorkingDir sets the current working directory for the sandbox process.
|
||||
// If the path is relative, it will be resolved as an absolute path from the root directory "/".
|
||||
// This is not a bind mount; it is simply setting the cwd inside the sandbox process.
|
||||
// Defaults to "/".
|
||||
func WithWorkingDir(cwd string) Option {
|
||||
return func(o *Options) {
|
||||
if cwd == "" {
|
||||
o.err = fmt.Errorf("working directory cannot be empty")
|
||||
return
|
||||
}
|
||||
// ensure absolute path inside sandbox
|
||||
if !filepath.IsAbs(cwd) {
|
||||
cwd = filepath.Join("/", cwd)
|
||||
}
|
||||
cwd = filepath.Clean(cwd)
|
||||
o.workingDir = cwd
|
||||
}
|
||||
}
|
||||
|
||||
// Sandbox represents a running gVisor sandbox where applications
|
||||
// run inside.
|
||||
type Sandbox struct {
|
||||
id string
|
||||
bundleDir string
|
||||
runscPath string
|
||||
rootState string
|
||||
}
|
||||
|
||||
// newID returns a unique ID for the sandbox.
|
||||
func newID() string {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
// rand.Read never returns an error, and always fills b entirely.
|
||||
panic(fmt.Sprintf("failed to generate random bytes for sandbox ID: %v", err))
|
||||
}
|
||||
return fmt.Sprintf("%x", b)
|
||||
}
|
||||
|
||||
// Look for runsc binary from the environment variable RUNSC_PATH,
|
||||
// then in system PATH.
|
||||
func runscPath() string {
|
||||
if path := os.Getenv("RUNSC_PATH"); path != "" {
|
||||
return path
|
||||
}
|
||||
path, err := exec.LookPath("runsc")
|
||||
if err == nil {
|
||||
return path
|
||||
}
|
||||
panic("runsc binary is not found")
|
||||
}
|
||||
|
||||
// New spawns a new sandbox as a subprocess, the sandbox
|
||||
// will be started and running in detached mode.
|
||||
func New(ctx context.Context, opts ...Option) (*Sandbox, error) {
|
||||
options := Options{
|
||||
enableNetworking: true,
|
||||
workingDir: "/",
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
if options.err != nil {
|
||||
return nil, options.err
|
||||
}
|
||||
|
||||
if options.runtimeDir == "" {
|
||||
dir, err := os.MkdirTemp("", "gvisor-sandbox-*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create runtime directory: %v", err)
|
||||
}
|
||||
options.runtimeDir = dir
|
||||
}
|
||||
|
||||
if options.id == "" {
|
||||
options.id = newID()
|
||||
}
|
||||
|
||||
if os.Geteuid() != 0 && options.enableNetworking {
|
||||
return nil, fmt.Errorf("enabling networking requires running as root")
|
||||
}
|
||||
|
||||
runDir := options.runtimeDir
|
||||
stateDir := filepath.Join(runDir, "state")
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create sandbox state directory: %v", err)
|
||||
}
|
||||
// Verify that the state directory actually has 0700 permissions.
|
||||
fi, err := os.Stat(stateDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stat sandbox state directory: %v", err)
|
||||
}
|
||||
if fi.Mode().Perm() != 0o700 {
|
||||
return nil, fmt.Errorf("sandbox state directory has incorrect permissions: got %v, want %v", fi.Mode().Perm(), os.FileMode(0o700))
|
||||
}
|
||||
|
||||
var annotations map[string]string
|
||||
var globalFlags []string
|
||||
var runFlags []string
|
||||
var isCheckpointRestore bool
|
||||
var checkpointRestoreDir string
|
||||
|
||||
if options.snapshot != nil {
|
||||
store := options.snapshot.Storage
|
||||
snapshotID := options.snapshot.ID
|
||||
if store == nil {
|
||||
return nil, fmt.Errorf("no snapshot storage configured for restore")
|
||||
}
|
||||
|
||||
// Fetch metadata.json from store.
|
||||
metaReader, err := store.GetReader(ctx, snapshotID, MetadataAsset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read snapshot metadata: %w", err)
|
||||
}
|
||||
defer metaReader.Close()
|
||||
|
||||
var meta SnapshotMetadata
|
||||
if err := json.NewDecoder(metaReader).Decode(&meta); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse snapshot metadata: %w", err)
|
||||
}
|
||||
|
||||
// Perform restore based on type.
|
||||
switch meta.Type {
|
||||
case RootfsTarSnapshot:
|
||||
tarPath, err := readRootfsTar(ctx, snapshotID, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(tarPath)
|
||||
|
||||
annotations = map[string]string{
|
||||
"dev.gvisor.tar.rootfs.upper": tarPath,
|
||||
}
|
||||
globalFlags = append(globalFlags, "--allow-rootfs-tar-annotation")
|
||||
|
||||
case FilesystemSnapshot:
|
||||
fsRestoreDir := filepath.Join(stateDir, "fs-restore")
|
||||
if err := os.MkdirAll(fsRestoreDir, 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: List assets in store and download all filesystem image assets to fsRestoreDir.
|
||||
runFlags = append(runFlags, fmt.Sprintf("--fs-restore-image-path=%s", fsRestoreDir))
|
||||
|
||||
case CheckpointRestore:
|
||||
checkpointRestoreDir = filepath.Join(stateDir, "checkpoint-restore")
|
||||
if err := os.MkdirAll(checkpointRestoreDir, 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: List assets in store and download all checkpoint image assets to checkpointRestoreDir.
|
||||
isCheckpointRestore = true
|
||||
}
|
||||
}
|
||||
bundleDir, err := NewBundle(BundleConfig{
|
||||
ID: options.id,
|
||||
RuntimeDir: runDir,
|
||||
EnableNetworking: options.enableNetworking,
|
||||
Mounts: options.mounts,
|
||||
Env: options.env,
|
||||
Annotations: annotations,
|
||||
WorkingDir: options.workingDir,
|
||||
Hostname: options.hostname,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create OCI bundle: %v", err)
|
||||
}
|
||||
|
||||
sb := &Sandbox{
|
||||
id: options.id,
|
||||
bundleDir: bundleDir,
|
||||
runscPath: runscPath(),
|
||||
rootState: stateDir,
|
||||
}
|
||||
|
||||
// Launch the sandbox in detached mode via os/exec.
|
||||
args := []string{"--root", sb.rootState}
|
||||
if os.Geteuid() != 0 {
|
||||
args = append(args, "--ignore-cgroups")
|
||||
}
|
||||
if !options.enableNetworking {
|
||||
args = append(args, "--network=none")
|
||||
}
|
||||
args = append(args, globalFlags...)
|
||||
|
||||
if isCheckpointRestore {
|
||||
args = append(args, "restore", "--image-path", checkpointRestoreDir, "--detach", sb.id)
|
||||
} else {
|
||||
args = append(args, "run")
|
||||
args = append(args, runFlags...)
|
||||
args = append(args, "--bundle", sb.bundleDir, "--detach", sb.id)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, sb.runscPath, args...)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create sandbox via subprocess: %v", err)
|
||||
}
|
||||
|
||||
return sb, nil
|
||||
}
|
||||
|
||||
// Exec runs the given command inside the running sandbox and returns the output.
|
||||
func (s *Sandbox) Exec(ctx context.Context, cmd string, opts ...string) (stdout string, stderr string, err error) {
|
||||
args := append([]string{"--root", s.rootState, "exec", s.id, cmd}, opts...)
|
||||
|
||||
commandContext := exec.CommandContext(ctx, s.runscPath, args...)
|
||||
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
|
||||
commandContext.Stdout = io.MultiWriter(&stdoutBuf)
|
||||
commandContext.Stderr = io.MultiWriter(&stderrBuf)
|
||||
|
||||
err = commandContext.Run()
|
||||
if err != nil {
|
||||
return "", stderrBuf.String(), fmt.Errorf("exec failed: %v", err)
|
||||
}
|
||||
|
||||
return stdoutBuf.String(), stderrBuf.String(), err
|
||||
}
|
||||
|
||||
// Close kills the sandbox processes and cleans up the state directory.
|
||||
func (s *Sandbox) Close(ctx context.Context) error {
|
||||
killArgs := []string{"--root", s.rootState, "kill", s.id, "SIGKILL"}
|
||||
_ = exec.CommandContext(ctx, s.runscPath, killArgs...).Run()
|
||||
|
||||
deleteArgs := []string{"--root", s.rootState, "delete", "--force", s.id}
|
||||
if err := exec.CommandContext(ctx, s.runscPath, deleteArgs...).Run(); err != nil {
|
||||
return fmt.Errorf("failed to clean up sandbox state: %v", err)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(s.bundleDir); err != nil {
|
||||
return fmt.Errorf("failed to clean up sandbox bundle directory: %v", err)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(s.rootState); err != nil {
|
||||
return fmt.Errorf("failed to clean up sandbox state directory: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bundle returns the path to the OCI bundle directory for this sandbox.
|
||||
func (s *Sandbox) Bundle() string {
|
||||
return s.bundleDir
|
||||
}
|
||||
|
||||
// SnapshotOptions holds configuration for taking a snapshot.
|
||||
type SnapshotOptions struct {
|
||||
LeaveRunning bool
|
||||
}
|
||||
|
||||
// SnapshotOption configures SnapshotOptions.
|
||||
type SnapshotOption func(*SnapshotOptions)
|
||||
|
||||
// WithLeaveRunning keeps the sandbox running after taking the snapshot.
|
||||
func WithLeaveRunning(leaveRunning bool) SnapshotOption {
|
||||
return func(o *SnapshotOptions) {
|
||||
o.LeaveRunning = leaveRunning
|
||||
}
|
||||
}
|
||||
|
||||
func newSnapshotID() SnapshotID {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(fmt.Sprintf("failed to generate random bytes for snapshot ID: %v", err))
|
||||
}
|
||||
return SnapshotID(fmt.Sprintf("snap-%x", b))
|
||||
}
|
||||
|
||||
// Snapshot serializes and saves the sandbox state to storage, returning the generated snapshot.
|
||||
// Depending on the snapshotType, it will perform a full Checkpoint, a Filesystem Snapshot, or a Rootfs Tar Snapshot.
|
||||
// It also automatically generates and writes "metadata.json" into the storage.
|
||||
func (s *Sandbox) Snapshot(ctx context.Context, snapshotType SnapshotType, storage SnapshotStorage, opts ...SnapshotOption) (*Snapshot, error) {
|
||||
options := SnapshotOptions{
|
||||
LeaveRunning: false, // Default is false.
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
snapshotID := newSnapshotID()
|
||||
|
||||
switch snapshotType {
|
||||
case RootfsTarSnapshot:
|
||||
if err := s.snapshotRootfsTar(ctx, snapshotID, storage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case FilesystemSnapshot:
|
||||
// TODO: Run `runsc fscheckpoint --image-path=<localTempDir> [--leave-running] <sandboxID>`.
|
||||
// TODO: Walk `<localTempDir>` and upload each file to storage.
|
||||
|
||||
case CheckpointRestore:
|
||||
// TODO: Run `runsc checkpoint --image-path=<localTempDir> [--leave-running] <sandboxID>`.
|
||||
// TODO: Walk `<localTempDir>` and upload each file to storage.
|
||||
}
|
||||
|
||||
meta := SnapshotMetadata{
|
||||
Type: snapshotType,
|
||||
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
metaWriter, err := storage.PutWriter(ctx, snapshotID, MetadataAsset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create metadata.json in storage: %w", err)
|
||||
}
|
||||
defer metaWriter.Close()
|
||||
|
||||
if err := json.NewEncoder(metaWriter).Encode(&meta); err != nil {
|
||||
return nil, fmt.Errorf("failed to write metadata.json to storage: %w", err)
|
||||
}
|
||||
|
||||
return &Snapshot{
|
||||
ID: snapshotID,
|
||||
Storage: storage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Sandbox) snapshotRootfsTar(ctx context.Context, snapshotID SnapshotID, storage SnapshotStorage) error {
|
||||
tarFile, err := os.CreateTemp(os.TempDir(), "rootfs-*.tar")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp tar file: %w", err)
|
||||
}
|
||||
tarPath := tarFile.Name()
|
||||
tarFile.Close()
|
||||
defer os.Remove(tarPath)
|
||||
|
||||
cmd := exec.CommandContext(ctx, s.runscPath, "--root", s.rootState, "tar", "rootfs-upper", "--file", tarPath, s.id)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("runsc tar failed: %v (stderr: %q)", err, stderr.String())
|
||||
}
|
||||
|
||||
localFile, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open temp tar file: %w", err)
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
storageWriter, err := storage.PutWriter(ctx, snapshotID, RootfsAsset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create storage writer: %w", err)
|
||||
}
|
||||
defer storageWriter.Close()
|
||||
|
||||
if _, err := io.Copy(storageWriter, localFile); err != nil {
|
||||
return fmt.Errorf("failed to upload rootfs tar: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readRootfsTar(ctx context.Context, snapshotID SnapshotID, store SnapshotStorage) (string, error) {
|
||||
tarFile, err := os.CreateTemp(os.TempDir(), "rootfs-*.tar")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create temp tar file: %w", err)
|
||||
}
|
||||
tarPath := tarFile.Name()
|
||||
defer tarFile.Close()
|
||||
|
||||
cleanup := true
|
||||
defer func() {
|
||||
if cleanup {
|
||||
os.Remove(tarPath)
|
||||
}
|
||||
}()
|
||||
|
||||
storageReader, err := store.GetReader(ctx, snapshotID, RootfsAsset)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get rootfs reader from storage: %w", err)
|
||||
}
|
||||
defer storageReader.Close()
|
||||
|
||||
if _, err := io.Copy(tarFile, storageReader); err != nil {
|
||||
return "", fmt.Errorf("failed to download rootfs asset: %w", err)
|
||||
}
|
||||
|
||||
cleanup = false
|
||||
return tarPath, nil
|
||||
}
|
||||
3
sandboxexec/sandbox/sandbox_state_autogen.go
Normal file
3
sandboxexec/sandbox/sandbox_state_autogen.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package sandbox
|
||||
195
sandboxexec/sandbox/storage.go
Normal file
195
sandboxexec/sandbox/storage.go
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// 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
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// SnapshotType defines the type of snapshot.
|
||||
type SnapshotType string
|
||||
|
||||
const (
|
||||
// CheckpointRestore represents a full process state checkpoint and restore.
|
||||
CheckpointRestore SnapshotType = "CheckpointRestore"
|
||||
|
||||
// FilesystemSnapshot represents a snapshot of the container's filesystems.
|
||||
FilesystemSnapshot SnapshotType = "FilesystemSnapshot"
|
||||
|
||||
// RootfsTarSnapshot represents a tar file snapshot of rootfs changes.
|
||||
RootfsTarSnapshot SnapshotType = "RootfsTarSnapshot"
|
||||
)
|
||||
|
||||
// SnapshotID defines the type for snapshot IDs.
|
||||
type SnapshotID string
|
||||
|
||||
// SnapshotMetadata stores the metadata of a snapshot.
|
||||
type SnapshotMetadata struct {
|
||||
Type SnapshotType `json:"type"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// Snapshot groups SnapshotID and SnapshotStorage together.
|
||||
type Snapshot struct {
|
||||
ID SnapshotID
|
||||
Storage SnapshotStorage
|
||||
}
|
||||
|
||||
// Asset defines the type for snapshot asset names.
|
||||
type Asset string
|
||||
|
||||
const (
|
||||
// MetadataAsset is the name of the metadata file.
|
||||
MetadataAsset Asset = "metadata.json"
|
||||
// RootfsAsset is the name of the rootfs tarball (if using RootfsTarSnapshot).
|
||||
RootfsAsset Asset = "rootfs.tar"
|
||||
// CheckpointAsset is the main checkpoint state file.
|
||||
CheckpointAsset Asset = "checkpoint.img"
|
||||
// PagesAsset is the memory pages file.
|
||||
PagesAsset Asset = "pages.img"
|
||||
// PagesMetaAsset is the memory pages metadata file.
|
||||
PagesMetaAsset Asset = "pages_meta.img"
|
||||
)
|
||||
|
||||
// SnapshotStorage defines a pluggable storage interface for snapshots.
|
||||
type SnapshotStorage interface {
|
||||
// PutWriter returns a WriteCloser to write a file asset of a snapshot.
|
||||
PutWriter(ctx context.Context, snapshotID SnapshotID, assetName Asset) (io.WriteCloser, error)
|
||||
|
||||
// GetReader returns a ReadCloser to read a file asset of a snapshot.
|
||||
GetReader(ctx context.Context, snapshotID SnapshotID, assetName Asset) (io.ReadCloser, error)
|
||||
|
||||
// Delete deletes all assets associated with a snapshot ID.
|
||||
Delete(ctx context.Context, snapshotID SnapshotID) error
|
||||
|
||||
// List returns all snapshot IDs known to this storage.
|
||||
List(ctx context.Context) ([]SnapshotID, error)
|
||||
|
||||
// Lookup verifies that the snapshot ID exists in this storage and returns a Snapshot.
|
||||
Lookup(ctx context.Context, snapshotID SnapshotID) (*Snapshot, error)
|
||||
}
|
||||
|
||||
// ErrSnapshotNotFound is returned when the snapshot ID is not found.
|
||||
var ErrSnapshotNotFound = errors.New("snapshot not found")
|
||||
|
||||
// FilesystemStorage implements SnapshotStorage using a local directory.
|
||||
type FilesystemStorage struct {
|
||||
rootDir string
|
||||
}
|
||||
|
||||
// NewFilesystemStorage creates a new FilesystemStorage at the given root directory.
|
||||
// The root directory must already exist.
|
||||
func NewFilesystemStorage(rootDir string) (*FilesystemStorage, error) {
|
||||
fi, err := os.Stat(rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, fmt.Errorf("root directory %q is not a directory", rootDir)
|
||||
}
|
||||
return &FilesystemStorage{rootDir: rootDir}, nil
|
||||
}
|
||||
|
||||
func sanitizeName(s string) (string, error) {
|
||||
base := filepath.Base(s)
|
||||
if base == "." || base == ".." || base == "/" || base == "\\" {
|
||||
return "", fmt.Errorf("invalid name: %q", s)
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// PutWriter returns a WriteCloser to write a file asset of a snapshot.
|
||||
func (f *FilesystemStorage) PutWriter(ctx context.Context, snapshotID SnapshotID, assetName Asset) (io.WriteCloser, error) {
|
||||
safeID, err := sanitizeName(string(snapshotID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid snapshot ID: %w", err)
|
||||
}
|
||||
safeAsset, err := sanitizeName(string(assetName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid asset name: %w", err)
|
||||
}
|
||||
path := filepath.Join(f.rootDir, safeID, safeAsset)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Create(path)
|
||||
}
|
||||
|
||||
// GetReader returns a ReadCloser to read a file asset of a snapshot.
|
||||
func (f *FilesystemStorage) GetReader(ctx context.Context, snapshotID SnapshotID, assetName Asset) (io.ReadCloser, error) {
|
||||
safeID, err := sanitizeName(string(snapshotID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid snapshot ID: %w", err)
|
||||
}
|
||||
safeAsset, err := sanitizeName(string(assetName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid asset name: %w", err)
|
||||
}
|
||||
path := filepath.Join(f.rootDir, safeID, safeAsset)
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// Delete deletes all assets associated with a snapshot ID.
|
||||
func (f *FilesystemStorage) Delete(ctx context.Context, snapshotID SnapshotID) error {
|
||||
safeID, err := sanitizeName(string(snapshotID))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid snapshot ID: %w", err)
|
||||
}
|
||||
path := filepath.Join(f.rootDir, safeID)
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// List returns all snapshot IDs known to this storage.
|
||||
func (f *FilesystemStorage) List(ctx context.Context) ([]SnapshotID, error) {
|
||||
entries, err := os.ReadDir(f.rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make([]SnapshotID, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
ids = append(ids, SnapshotID(entry.Name()))
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// Lookup returns a Snapshot by a given snapshot ID.
|
||||
func (f *FilesystemStorage) Lookup(ctx context.Context, snapshotID SnapshotID) (*Snapshot, error) {
|
||||
safeID, err := sanitizeName(string(snapshotID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid snapshot ID: %w", err)
|
||||
}
|
||||
dir := filepath.Join(f.rootDir, safeID)
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, ErrSnapshotNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return nil, fmt.Errorf("snapshot path %q is not a directory", dir)
|
||||
}
|
||||
return &Snapshot{
|
||||
ID: SnapshotID(safeID),
|
||||
Storage: f,
|
||||
}, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue