Files
Konstantin BogomolovandgVisor bot b81e5fd5df Move fscheckpoint manifest from json to proto.
The idea is to make fscheckpoints be forward compatible (newer runsc versions
will successfully read fscheckpoints generated by an older runsc).

The exact details of how this plays out will obviously depend on ongoing runsc
runsc development (for how long to keep work-arounds for breaking changes, etc).
While this CL does enable forward compatible fscheckpoints by eliminating the
hard-matching requirement on runsc-version, please don't rely on it just yet.

PiperOrigin-RevId: 962306264
2026-08-10 12:39:54 -07:00

164 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
//
// 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.
// Package fscheckpoint defines the format of a gVisor filesystem checkpoint.
//
// Filesystem checkpoints can only be taken in sandboxes with runsc flag
// `-overlay2` enabled, which causes sandboxed filesystems to be overlaid with
// a tmpfs "upper layer" that captures filesystem changes. Each filesystem
// checkpoint contains the state of all upper layers in the sandbox.
//
// A filesystem checkpoint comprises four files:
//
// - A "pages metadata" file and a "pages" file are generated by
// pgalloc.MemoryFile save/restore. These contain the state of MemoryFiles
// that store the contents of files in checkpointed tmpfs filesystems.
//
// - A "multi-tar" file contains tar archives representing the filesystem
// structure of checkpointed tmpfs filesystems. For regular files, these
// tar archives store the MemoryFile offsets containing file data, avoiding
// duplication of MemoryFile content and ensuring a dense representation of
// possibly-sparse files.
//
// - A "manifest" file contains information about each MemoryFile and tmpfs
// filesystem required to load state from other checkpoint files. See type
// definitions below.
//
// TODO: NOLINT - The tar file format involves a lot of padding to 512-byte
// boundaries. Consider gzip/zstd compression of (each archive in) the
// multi-tar file, as well as something simpler like run-length encoding of
// zero bytes.
package fscheckpoint
import (
"gvisor.dev/gvisor/pkg/sentry/checkpoint"
fspb "gvisor.dev/gvisor/pkg/sentry/fscheckpoint/fscheckpoint_proto_go_proto"
)
// AllTmpfsPath is a sentinel value for the Path option of a filesystem
// checkpoint save (kernel.FSSaveOpts.Path) indicating that all tmpfs
// filesystems with a ResourceID should be checkpointed, rather than only the
// tmpfs filesystems mounted at a specific path.
const AllTmpfsPath = "all-tmpfs"
// Manifest is the type of the JSON object stored in the manifest file.
type Manifest struct {
// Version is the checkpoint format version.
Version uint64 `json:"version,omitzero"`
// RunscVersion is the version of the runsc binary that produced this
// checkpoint.
RunscVersion string `json:"runsc_version,omitzero"`
// PageSize is hostarch.PageSize for the sandbox that produced this
// checkpoint.
//
// TODO: NOLINT - Currently this must match during restore. Technically,
// it's already permissible for PageSize to shrink (as long as both old and
// new sizes remain powers of 2). Allowing PageSize to increase may require
// being able to relocate pgalloc.MemoryFile pages during restore based on
// information provided by tmpfs.
PageSize uint64 `json:"page_size"`
// Endian is hostarch.EndianString() for the sandbox that produced this
// checkpoint.
//
// TODO: NOLINT - Currently this must match during restore. Removing this
// requirement requires fixing an endianness for
// tmpfs.fsckptRegularFileSegment.
Endian string `json:"endian"`
// MemoryFiles contains information about pgalloc.MemoryFiles stored in the
// checkpoint, in order of increasing file offsets in the pages metadata
// and pages files.
MemoryFiles []MemoryFile `json:"memory_files"`
// Information about filesystems stored in the checkpoint, in order of
// increasing file offsets in the multi-tar file.
Tmpfs []Tmpfs `json:"tmpfs"`
}
// MemoryFile represents a pgalloc.MemoryFile stored in a filesystem
// checkpoint.
type MemoryFile struct {
// ResourceID is the value of pgalloc.MemoryFile.ResourceID().
ResourceID checkpoint.ResourceID `json:"resource_id"`
// PagesMetadataStart and PagesMetadataEnd are the offsets in the pages
// metadata file at which the pages metadata for this MemoryFile begin and
// end respectively.
PagesMetadataStart uint64 `json:"pages_metadata_start"`
PagesMetadataEnd uint64 `json:"pages_metadata_end"`
// PagesStart is the offset in the pages file at which the pages for this
// MemoryFile begin.
PagesStart uint64 `json:"pages_start"`
}
// Tmpfs represents a tmpfs filesystem stored in a filesystem checkpoint.
type Tmpfs struct {
// ResourceID is the value of tmpfs.filesystem.mf.ResourceID().
//
// TODO: NOLINT - Assign ResourceIDs to tmpfs filesystems directly so that
// we can also checkpoint filesystems using the main MemoryFile.
ResourceID checkpoint.ResourceID `json:"resource_id"`
// TarStart and TarEnd are the offsets in the multi-tar file at which the
// tar archive for this filesystem begin and end respectively.
TarStart uint64 `json:"tar_start"`
TarEnd uint64 `json:"tar_end"`
}
// FromProto converts a proto Manifest to a Manifest struct.
func FromProto(pb *fspb.Manifest) Manifest {
m := Manifest{
Version: pb.Version,
RunscVersion: pb.RunscVersion,
PageSize: pb.PageSize,
Endian: pb.Endian,
}
if len(pb.MemoryFiles) > 0 {
m.MemoryFiles = make([]MemoryFile, len(pb.MemoryFiles))
for i, mf := range pb.MemoryFiles {
m.MemoryFiles[i] = MemoryFile{
ResourceID: fromProtoResourceID(mf.ResourceId),
PagesMetadataStart: mf.PagesMetadataStart,
PagesMetadataEnd: mf.PagesMetadataEnd,
PagesStart: mf.PagesStart,
}
}
}
if len(pb.Tmpfs) > 0 {
m.Tmpfs = make([]Tmpfs, len(pb.Tmpfs))
for i, t := range pb.Tmpfs {
m.Tmpfs[i] = Tmpfs{
ResourceID: fromProtoResourceID(t.ResourceId),
TarStart: t.TarStart,
TarEnd: t.TarEnd,
}
}
}
return m
}
func fromProtoResourceID(pb *fspb.ResourceID) checkpoint.ResourceID {
if pb == nil {
return checkpoint.ResourceID{}
}
return checkpoint.ResourceID{
ContainerName: pb.ContainerName,
Path: pb.Path,
}
}