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
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