Обновление снапшота с 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-паникой, что в полевом крашдампе; с ним зелёный.
195 lines
6.1 KiB
Go
195 lines
6.1 KiB
Go
// 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
|
|
}
|