mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Procfs mounts used a per-mount major number for /proc/gvisor/checkpoint. Since Linux reserves a limited number of dynamic major numbers, running many isolated container actions concurrently would exhaust the pool and crash the sentry with EBUSY. This adds VFS.GetSharedDynamicCharDevMajor(key) to allocate a major on first use and share it across all subsequent mounts for that key. The sentry now consumes exactly two majors for these devices regardless of how many procfs instances are mounted. PiperOrigin-RevId: 967985107
587 lines
17 KiB
Go
587 lines
17 KiB
Go
// Copyright 2024 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 proc
|
|
|
|
import (
|
|
"bytes"
|
|
goContext "context"
|
|
"fmt"
|
|
"io"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/atomicbitops"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/usermem"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
func (fs *filesystem) newGvisorInode(ctx context.Context, root *auth.Credentials, internalData *InternalData, k *kernel.Kernel) (kernfs.Inode, error) {
|
|
gvisorFiles := make(map[string]kernfs.Inode)
|
|
if internalData.GVisorMarkerFile {
|
|
gvisorFiles["kernel_is_gvisor"] = fs.newInode(ctx, root, 0444, newStaticFile("gvisor\n"))
|
|
}
|
|
log.Infof("Setting up checkpoint files under [procfs]/gvisor")
|
|
checkpoint, err := newCheckpointInode(ctx, k, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), internalData.SaveTriggerEnabled)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gvisorFiles["checkpoint"] = checkpoint
|
|
gvisorFiles["spec_environ"] = fs.newInode(ctx, root, 0444, &specEnvironData{k: k})
|
|
if internalData.FSCheckpointEnabled {
|
|
log.Infof("Setting up fscheckpoint files under [procfs]/gvisor")
|
|
fsCheckpoint, err := newFSCheckpointInode(ctx, k, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
gvisorFiles["fscheckpoint"] = fsCheckpoint
|
|
}
|
|
if len(gvisorFiles) == 0 {
|
|
return nil, nil
|
|
}
|
|
return fs.newStaticDir(ctx, root, gvisorFiles), nil
|
|
}
|
|
|
|
// checkpointInode represents the inode for /proc/gvisor/checkpoint.
|
|
//
|
|
// +stateify savable
|
|
type checkpointInode struct {
|
|
kernfs.InodeAttrs
|
|
kernfs.InodeNoStatFS
|
|
kernfs.InodeNoopRefCount
|
|
kernfs.InodeNotAnonymous
|
|
kernfs.InodeNotDirectory
|
|
kernfs.InodeNotSymlink
|
|
kernfs.InodeWatches
|
|
kernfs.InodeFSOwned
|
|
locks vfs.FileLocks
|
|
|
|
k *kernel.Kernel
|
|
rdevMajor uint32
|
|
saveTriggerEnabled bool
|
|
}
|
|
|
|
var _ kernfs.Inode = (*checkpointInode)(nil)
|
|
|
|
func newCheckpointInode(ctx context.Context, k *kernel.Kernel, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64, saveTriggerEnabled bool) (*checkpointInode, error) {
|
|
rdevMajor, err := k.VFS().GetSharedDynamicCharDevMajor(vfs.CheckpointDeviceMajorKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to allocate device number for /proc/gvisor/checkpoint: %w", err)
|
|
}
|
|
perm := linux.FileMode(0444)
|
|
if saveTriggerEnabled {
|
|
perm = linux.FileMode(0666)
|
|
}
|
|
f := &checkpointInode{
|
|
k: k,
|
|
rdevMajor: rdevMajor,
|
|
saveTriggerEnabled: saveTriggerEnabled,
|
|
}
|
|
// Appear to be a character device so that applications don't carelessly
|
|
// try to read from us.
|
|
f.InodeAttrs.Init(ctx, creds, devMajor, devMinor, ino, linux.ModeCharacterDevice|perm)
|
|
return f, nil
|
|
}
|
|
|
|
// Stat implements kernfs.Inode.Stat.
|
|
func (f *checkpointInode) Stat(ctx context.Context, vfsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
|
|
stat, err := f.InodeAttrs.Stat(ctx, vfsfs, opts)
|
|
stat.RdevMajor = f.rdevMajor
|
|
stat.RdevMinor = 0
|
|
return stat, err
|
|
}
|
|
|
|
// CheckPermissions implements Inode.CheckPermissions.
|
|
func (f *checkpointInode) CheckPermissions(ctx context.Context, creds *auth.Credentials, ats vfs.AccessTypes) error {
|
|
if ats.MayWrite() && !f.saveTriggerEnabled {
|
|
// Even though file mode is 0444, the root user can still open the file
|
|
// for writing because it has CAP_DAC_OVERRIDE. Explicitly reject here.
|
|
return linuxerr.EPERM
|
|
}
|
|
return f.InodeAttrs.CheckPermissions(ctx, creds, ats)
|
|
}
|
|
|
|
// Open implements kernfs.Inode.Open.
|
|
func (f *checkpointInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
data := &checkpointFD{
|
|
k: f.k,
|
|
inode: f,
|
|
countToWaitFor: f.k.CheckpointGen().Count + 1,
|
|
}
|
|
data.initKey()
|
|
|
|
data.vfsfd.Init(data, opts.Flags, rp.Credentials(), rp.Mount(), d.VFSDentry(), &vfs.FileDescriptionOptions{})
|
|
return &data.vfsfd, nil
|
|
}
|
|
|
|
// checkpointFD implements vfs.FileDescription for /proc/gvisor/checkpoint.
|
|
//
|
|
// +stateify savable
|
|
type checkpointFD struct {
|
|
vfsfd vfs.FileDescription
|
|
vfs.FileDescriptionDefaultImpl
|
|
vfs.NoLockFD
|
|
|
|
k *kernel.Kernel
|
|
inode kernfs.Inode
|
|
countToWaitFor uint32
|
|
waiters waiter.Queue
|
|
|
|
// This is the key used to unregister the checkpoint wait. It must be acquired again
|
|
// after a restore.
|
|
key any `state:"nosave"`
|
|
|
|
mu sync.Mutex `state:"nosave"`
|
|
result string
|
|
off int64
|
|
}
|
|
|
|
var _ vfs.FileDescriptionImpl = (*checkpointFD)(nil)
|
|
|
|
func (fd *checkpointFD) initKey() {
|
|
fd.key = fd.k.CheckpointWait.Register(fd.onRestoreOrResume, fd.countToWaitFor)
|
|
}
|
|
|
|
func (fd *checkpointFD) afterLoad(_ goContext.Context) {
|
|
fd.initKey()
|
|
}
|
|
|
|
// Release implements vfs.FileDescription.Release. It stops waiting for the checkpoint.
|
|
func (fd *checkpointFD) Release(context.Context) {
|
|
fd.k.CheckpointWait.Unregister(fd.key)
|
|
}
|
|
|
|
func (fd *checkpointFD) onRestoreOrResume(gen kernel.CheckpointGeneration, err error) {
|
|
fd.mu.Lock()
|
|
defer fd.mu.Unlock()
|
|
|
|
if len(fd.result) > 0 {
|
|
// Result is already determined, nothing else to do.
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case err != nil:
|
|
fd.result = "error\n"
|
|
case gen.Restore:
|
|
// This could race if there is another checkpoint done before the caller can
|
|
// read it. In the worst case, the caller may get "restore" more than once,
|
|
// but it would be getting "restore" from one of the other FDs anyway.
|
|
// Also, checkpoint is not called in quick succession.
|
|
fd.result = "restore\n"
|
|
default:
|
|
fd.result = "resume\n"
|
|
}
|
|
|
|
fd.waiters.Notify(waiter.ReadableEvents)
|
|
}
|
|
|
|
// PRead implements vfs.FileDescription.PRead.
|
|
func (fd *checkpointFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, _ vfs.ReadOptions) (int64, error) {
|
|
fd.mu.Lock()
|
|
defer fd.mu.Unlock()
|
|
return fd.preadLocked(ctx, dst, offset)
|
|
}
|
|
|
|
// PRead implements vfs.FileDescription.Read.
|
|
func (fd *checkpointFD) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
|
|
fd.mu.Lock()
|
|
defer fd.mu.Unlock()
|
|
|
|
n, err := fd.preadLocked(ctx, dst, fd.off)
|
|
fd.off += n
|
|
return n, err
|
|
}
|
|
|
|
// preadLocked returns WouldBlock until the next checkpoint is done and returns the result. Which
|
|
// checkpoint to wait is determined when the FD is first opened. This allows the caller to first
|
|
// trigger the checkpoint asynchronously, by writing to the file, and then reading the result
|
|
// without racing. The read can also happen before writing to the checkpoint file. And writing isn't
|
|
// even required. This allows for independent processes to wait for the checkpoint to complete.
|
|
//
|
|
// After the checkpoint is completed, reading always returns the same result. To wait the next
|
|
// checkpoint, the file must be opened again.
|
|
//
|
|
// The result is one of:
|
|
// * "resume": the checkpoint has completed (or failed), and the workload is running again.
|
|
// * "restore": the cloned instance was restored and is running again.
|
|
// * "error": the checkpoint has failed. Note that the workload also resumes in this case.
|
|
func (fd *checkpointFD) preadLocked(ctx context.Context, dst usermem.IOSequence, offset int64) (int64, error) {
|
|
if len(fd.result) == 0 {
|
|
// If the result is empty, it means the checkpoint has not completed yet.
|
|
return 0, linuxerr.ErrWouldBlock
|
|
}
|
|
|
|
if offset >= int64(len(fd.result)) {
|
|
return 0, io.EOF
|
|
}
|
|
n, err := dst.CopyOut(ctx, []byte(fd.result[offset:]))
|
|
return int64(n), err
|
|
}
|
|
|
|
// PWrite implements vfs.FileDescription.PWrite.
|
|
func (fd *checkpointFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
|
return fd.write(ctx, src, offset)
|
|
}
|
|
|
|
// Write implements vfs.FileDescription.Write.
|
|
func (fd *checkpointFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
|
return fd.write(ctx, src, 0)
|
|
}
|
|
|
|
// write triggers a checkpoint when "1" is written.
|
|
func (fd *checkpointFD) write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if offset != 0 || src.NumBytes() > 2 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if src.NumBytes() == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
var buf [2]byte
|
|
n, err := src.CopyIn(ctx, buf[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// Accept new line at the end to allow `echo 1` for convenience.
|
|
if in := string(buf[:n]); in != "1" && in != "1\n" {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
|
|
log.Infof("Checkpoint triggered by user")
|
|
if err := fd.k.Saver().SaveAsync(); err != nil {
|
|
return 0, err
|
|
}
|
|
return src.NumBytes(), nil
|
|
}
|
|
|
|
// Seek implements vfs.FileDescriptionImpl.Seek.
|
|
func (fd *checkpointFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
|
fd.mu.Lock()
|
|
defer fd.mu.Unlock()
|
|
|
|
switch whence {
|
|
case linux.SEEK_SET:
|
|
// Use offset as given.
|
|
case linux.SEEK_CUR:
|
|
offset += fd.off
|
|
case linux.SEEK_END:
|
|
offset += int64(len(fd.result))
|
|
default:
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if offset < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
fd.off = offset
|
|
return offset, nil
|
|
}
|
|
|
|
// Stat implements vfs.FileDescriptionImpl.Stat.
|
|
func (fd *checkpointFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
|
fs := fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
|
return fd.inode.Stat(ctx, fs, opts)
|
|
}
|
|
|
|
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
|
func (fd *checkpointFD) SetStat(context.Context, vfs.SetStatOptions) error {
|
|
return linuxerr.EPERM
|
|
}
|
|
|
|
// Readiness implements vfs.FileDescriptionImpl.Readiness.
|
|
func (fd *checkpointFD) Readiness(mask waiter.EventMask) waiter.EventMask {
|
|
if (mask & waiter.ReadableEvents) != 0 {
|
|
fd.mu.Lock()
|
|
defer fd.mu.Unlock()
|
|
|
|
if len(fd.result) > 0 {
|
|
return waiter.ReadableEvents
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// EventRegister implements vfs.FileDescriptionImpl.EventRegister.
|
|
func (fd *checkpointFD) EventRegister(e *waiter.Entry) error {
|
|
fd.waiters.EventRegister(e)
|
|
return nil
|
|
}
|
|
|
|
// EventUnregister implements vfs.FileDescriptionImpl.EventUnregister.
|
|
func (fd *checkpointFD) EventUnregister(e *waiter.Entry) {
|
|
fd.waiters.EventUnregister(e)
|
|
}
|
|
|
|
// specEnvironData implements vfs.DynamicBytesSource for /proc/gvisor/spec_environ.
|
|
//
|
|
// +stateify savable
|
|
type specEnvironData struct {
|
|
dynamicBytesFileSetAttr
|
|
|
|
k *kernel.Kernel
|
|
}
|
|
|
|
var _ dynamicInode = (*specEnvironData)(nil)
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (r *specEnvironData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
t := kernel.TaskFromContext(ctx)
|
|
if t == nil {
|
|
// The caller is not a task. Return an empty string.
|
|
return nil
|
|
}
|
|
cid := t.ContainerID()
|
|
cname := r.k.ContainerName(cid)
|
|
if cname == "" {
|
|
log.Warningf("Container name not found for container ID %q", cid)
|
|
return nil
|
|
}
|
|
env := r.k.Saver().SpecEnviron(cname)
|
|
for _, e := range env {
|
|
buf.WriteString(e)
|
|
buf.WriteByte(0)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// fsCheckpointInode implements kernfs.Inode for /proc/gvisor/fscheckpoint.
|
|
//
|
|
// +stateify savable
|
|
type fsCheckpointInode struct {
|
|
kernfs.InodeAttrs
|
|
kernfs.InodeNoStatFS
|
|
kernfs.InodeNoopRefCount
|
|
kernfs.InodeNotAnonymous
|
|
kernfs.InodeNotDirectory
|
|
kernfs.InodeNotSymlink
|
|
kernfs.InodeWatches
|
|
kernfs.InodeFSOwned
|
|
locks vfs.FileLocks
|
|
|
|
k *kernel.Kernel
|
|
rdevMajor uint32
|
|
}
|
|
|
|
func newFSCheckpointInode(ctx context.Context, k *kernel.Kernel, creds *auth.Credentials, devMajor, devMinor uint32, ino uint64) (*fsCheckpointInode, error) {
|
|
rdevMajor, err := k.VFS().GetSharedDynamicCharDevMajor(vfs.FSCheckpointDeviceMajorKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to allocate device number for /proc/gvisor/fscheckpoint: %w", err)
|
|
}
|
|
i := &fsCheckpointInode{
|
|
k: k,
|
|
rdevMajor: rdevMajor,
|
|
}
|
|
// Appear to be a character device so that applications don't carelessly
|
|
// try to read from us.
|
|
i.InodeAttrs.Init(ctx, creds, devMajor, devMinor, ino, linux.ModeCharacterDevice|0o666)
|
|
return i, nil
|
|
}
|
|
|
|
// Stat implements kernfs.Inode.Stat.
|
|
func (i *fsCheckpointInode) Stat(ctx context.Context, vfsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
|
|
stat, err := i.InodeAttrs.Stat(ctx, vfsfs, opts)
|
|
stat.RdevMajor = i.rdevMajor
|
|
stat.RdevMinor = 0
|
|
return stat, err
|
|
}
|
|
|
|
// Open implements kernfs.Inode.Open.
|
|
func (i *fsCheckpointInode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
f := &fsCheckpointFile{
|
|
k: i.k,
|
|
inode: i,
|
|
}
|
|
f.vfsfd.Init(f, opts.Flags, rp.Credentials(), rp.Mount(), d.VFSDentry(), &vfs.FileDescriptionOptions{
|
|
DenyPWrite: true,
|
|
UseDentryMetadata: true,
|
|
})
|
|
return &f.vfsfd, nil
|
|
}
|
|
|
|
// fsCheckpointFile implements vfs.FileDescriptionImpl for
|
|
// /proc/gvisor/fscheckpoint.
|
|
//
|
|
// +stateify savable
|
|
type fsCheckpointFile struct {
|
|
vfsfd vfs.FileDescription
|
|
vfs.FileDescriptionDefaultImpl
|
|
vfs.DentryMetadataFileDescriptionImpl
|
|
vfs.NoLockFD
|
|
|
|
k *kernel.Kernel
|
|
inode *fsCheckpointInode
|
|
|
|
state atomicbitops.Uint32 `state:"nosave"` // monotonically increasing
|
|
q waiter.Queue
|
|
result string `state:"nosave"` // immutable once state becomes fsSaveFinished
|
|
resultSaved string
|
|
|
|
mu sync.Mutex `state:"nosave"`
|
|
off int64
|
|
}
|
|
|
|
// Possible values for fsCheckpointFile.state.
|
|
const (
|
|
fsSaveUnstarted uint32 = iota
|
|
fsSaveStarted
|
|
fsSaveFinished
|
|
)
|
|
|
|
// Release implements vfs.FileDescription.Release.
|
|
func (f *fsCheckpointFile) Release(context.Context) {
|
|
// no-op
|
|
}
|
|
|
|
// PRead implements vfs.FileDescriptionImpl.PRead.
|
|
func (f *fsCheckpointFile) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
|
if f.state.Load() != fsSaveFinished {
|
|
return 0, linuxerr.ErrWouldBlock
|
|
}
|
|
if offset >= int64(len(f.result)) {
|
|
return 0, io.EOF
|
|
}
|
|
n, err := dst.CopyOut(ctx, []byte(f.result[offset:]))
|
|
return int64(n), err
|
|
}
|
|
|
|
// Read implements vfs.FileDescriptionImpl.Read.
|
|
func (f *fsCheckpointFile) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
n, err := f.PRead(ctx, dst, f.off, opts)
|
|
f.off += n
|
|
return n, err
|
|
}
|
|
|
|
// Write implements vfs.FileDescriptionImpl.Write.
|
|
func (f *fsCheckpointFile) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
|
if src.NumBytes() > 2 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if src.NumBytes() == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
var buf [2]byte
|
|
n, err := src.CopyIn(ctx, buf[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// Accept new line at the end to allow `echo 1` for convenience.
|
|
if in := string(buf[:n]); in != "1" && in != "1\n" {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
|
|
if f.state.Load() != fsSaveUnstarted || !f.state.CompareAndSwap(fsSaveUnstarted, fsSaveStarted) {
|
|
return 0, linuxerr.EBUSY
|
|
}
|
|
saver := f.k.Saver()
|
|
if saver == nil {
|
|
f.result = "error\n"
|
|
f.state.Store(fsSaveFinished)
|
|
return 0, linuxerr.ENXIO // consistent with runsc/boot.Loader.SaveAsync
|
|
}
|
|
log.Infof("Filesystem checkpoint saving triggered by user")
|
|
go func() {
|
|
if err := saver.FSSave(); err != nil {
|
|
log.Warningf("Filesystem checkpoint saving failed: %v", err)
|
|
f.result = "error\n"
|
|
} else {
|
|
log.Infof("Filesystem checkpoint saving succeeded")
|
|
f.result = "resume\n"
|
|
}
|
|
f.state.Store(fsSaveFinished)
|
|
f.q.Notify(waiter.ReadableEvents)
|
|
}()
|
|
return int64(n), nil
|
|
}
|
|
|
|
// Seek implements vfs.FileDescriptionImpl.Seek.
|
|
func (f *fsCheckpointFile) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
|
if f.state.Load() != fsSaveFinished {
|
|
return 0, linuxerr.ErrWouldBlock
|
|
}
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
switch whence {
|
|
case linux.SEEK_SET:
|
|
// Use offset as given.
|
|
case linux.SEEK_CUR:
|
|
offset += f.off
|
|
case linux.SEEK_END:
|
|
offset += int64(len(f.result))
|
|
default:
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if offset < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
f.off = offset
|
|
return offset, nil
|
|
}
|
|
|
|
// Readiness implements waiter.Waitable.Readiness.
|
|
func (f *fsCheckpointFile) Readiness(mask waiter.EventMask) waiter.EventMask {
|
|
switch f.state.Load() {
|
|
case fsSaveUnstarted:
|
|
return waiter.WritableEvents
|
|
case fsSaveFinished:
|
|
return waiter.ReadableEvents
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// EventRegister implements waiter.Waitable.EventRegister.
|
|
func (f *fsCheckpointFile) EventRegister(e *waiter.Entry) error {
|
|
f.q.EventRegister(e)
|
|
return nil
|
|
}
|
|
|
|
// EventUnregister implements waiter.Waitable.EventUnregister.
|
|
func (f *fsCheckpointFile) EventUnregister(e *waiter.Entry) {
|
|
f.q.EventUnregister(e)
|
|
}
|
|
|
|
// beforeSave is invoked by stateify.
|
|
func (f *fsCheckpointFile) beforeSave() {
|
|
// Note that we can't wait for filesystem saving to complete, because it
|
|
// may be blocked on Kernel.extMu, held by kernel saving; but we also can't
|
|
// assume that f.state and f.result won't be mutated by filesystem saving,
|
|
// since the filesystem saving goroutine may have already passed the
|
|
// Kernel.extMu critical section.
|
|
switch f.state.Load() {
|
|
case fsSaveStarted:
|
|
f.resultSaved = "unknown\n"
|
|
case fsSaveFinished:
|
|
f.resultSaved = f.result
|
|
}
|
|
}
|
|
|
|
// afterLoad is invoked by stateify.
|
|
func (f *fsCheckpointFile) afterLoad(ctx goContext.Context) {
|
|
if f.resultSaved != "" {
|
|
f.result = f.resultSaved
|
|
f.state.Store(fsSaveFinished)
|
|
f.q.Notify(waiter.ReadableEvents)
|
|
}
|
|
}
|