173 lines
5.4 KiB
Go
173 lines
5.4 KiB
Go
// Copyright 2018 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 gofer
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gvisor.dev/gvisor/pkg/p9"
|
|
"gvisor.dev/gvisor/pkg/sentry/context"
|
|
"gvisor.dev/gvisor/pkg/sentry/device"
|
|
"gvisor.dev/gvisor/pkg/sentry/fs"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/time"
|
|
)
|
|
|
|
// Some fs implementations may not support atime, ctime, or mtime in getattr.
|
|
// The unstable() logic would try to use clock time for them. However, we do not
|
|
// want to use such time during S/R as that would cause restore timestamp
|
|
// checking failure. Hence a dummy stable-time clock is needed.
|
|
//
|
|
// Note that application-visible UnstableAttrs either come from CachingInodeOps
|
|
// (in which case they are saved), or they are requested from the gofer on each
|
|
// stat (for non-caching), so the dummy time only affects the modification
|
|
// timestamp check.
|
|
type dummyClock struct {
|
|
time.Clock
|
|
}
|
|
|
|
// Now returns a stable dummy time.
|
|
func (d *dummyClock) Now() time.Time {
|
|
return time.Time{}
|
|
}
|
|
|
|
type dummyClockContext struct {
|
|
context.Context
|
|
}
|
|
|
|
// Value implements context.Context
|
|
func (d *dummyClockContext) Value(key interface{}) interface{} {
|
|
switch key {
|
|
case time.CtxRealtimeClock:
|
|
return &dummyClock{}
|
|
default:
|
|
return d.Context.Value(key)
|
|
}
|
|
}
|
|
|
|
// beforeSave is invoked by stateify.
|
|
func (i *inodeFileState) beforeSave() {
|
|
if _, ok := i.s.inodeMappings[i.sattr.InodeID]; !ok {
|
|
panic(fmt.Sprintf("failed to find path for inode number %d. Device %s contains %s", i.sattr.InodeID, i.s.connID, fs.InodeMappings(i.s.inodeMappings)))
|
|
}
|
|
if i.sattr.Type == fs.RegularFile {
|
|
uattr, err := i.unstableAttr(&dummyClockContext{context.Background()})
|
|
if err != nil {
|
|
panic(fs.ErrSaveRejection{fmt.Errorf("failed to get unstable atttribute of %s: %v", i.s.inodeMappings[i.sattr.InodeID], err)})
|
|
}
|
|
i.savedUAttr = &uattr
|
|
}
|
|
}
|
|
|
|
// saveLoading is invoked by stateify.
|
|
func (i *inodeFileState) saveLoading() struct{} {
|
|
return struct{}{}
|
|
}
|
|
|
|
// splitAbsolutePath splits the path on slashes ignoring the leading slash.
|
|
func splitAbsolutePath(path string) []string {
|
|
if len(path) == 0 {
|
|
panic("There is no path!")
|
|
}
|
|
if path != filepath.Clean(path) {
|
|
panic(fmt.Sprintf("path %q is not clean", path))
|
|
}
|
|
// This case is to return {} rather than {""}
|
|
if path == "/" {
|
|
return []string{}
|
|
}
|
|
if path[0] != '/' {
|
|
panic(fmt.Sprintf("path %q is not absolute", path))
|
|
}
|
|
|
|
s := strings.Split(path, "/")
|
|
|
|
// Since p is absolute, the first component of s
|
|
// is an empty string. We must remove that.
|
|
return s[1:]
|
|
}
|
|
|
|
// loadLoading is invoked by stateify.
|
|
func (i *inodeFileState) loadLoading(_ struct{}) {
|
|
i.loading.Lock()
|
|
}
|
|
|
|
// afterLoad is invoked by stateify.
|
|
func (i *inodeFileState) afterLoad() {
|
|
load := func() (err error) {
|
|
// See comment on i.loading().
|
|
defer func() {
|
|
if err == nil {
|
|
i.loading.Unlock()
|
|
}
|
|
}()
|
|
|
|
// Manually restore the p9.File.
|
|
name, ok := i.s.inodeMappings[i.sattr.InodeID]
|
|
if !ok {
|
|
// This should be impossible, see assertion in
|
|
// beforeSave.
|
|
return fmt.Errorf("failed to find path for inode number %d. Device %s contains %s", i.sattr.InodeID, i.s.connID, fs.InodeMappings(i.s.inodeMappings))
|
|
}
|
|
// TODO(b/38173783): Context is not plumbed to save/restore.
|
|
ctx := &dummyClockContext{context.Background()}
|
|
|
|
_, i.file, err = i.s.attach.walk(ctx, splitAbsolutePath(name))
|
|
if err != nil {
|
|
return fs.ErrCorruption{fmt.Errorf("failed to walk to %q: %v", name, err)}
|
|
}
|
|
|
|
// Remap the saved inode number into the gofer device using the
|
|
// actual device and actual inode that exists in our new
|
|
// environment.
|
|
qid, mask, attrs, err := i.file.getAttr(ctx, p9.AttrMaskAll())
|
|
if err != nil {
|
|
return fs.ErrCorruption{fmt.Errorf("failed to get file attributes of %s: %v", name, err)}
|
|
}
|
|
if !mask.RDev {
|
|
return fs.ErrCorruption{fmt.Errorf("file %s lacks device", name)}
|
|
}
|
|
i.key = device.MultiDeviceKey{
|
|
Device: attrs.RDev,
|
|
SecondaryDevice: i.s.connID,
|
|
Inode: qid.Path,
|
|
}
|
|
if !goferDevice.Load(i.key, i.sattr.InodeID) {
|
|
return fs.ErrCorruption{fmt.Errorf("gofer device %s -> %d conflict in gofer device mappings: %s", i.key, i.sattr.InodeID, goferDevice)}
|
|
}
|
|
|
|
if i.sattr.Type == fs.RegularFile {
|
|
env, ok := fs.CurrentRestoreEnvironment()
|
|
if !ok {
|
|
return errors.New("missing restore environment")
|
|
}
|
|
uattr := unstable(ctx, mask, attrs, i.s.mounter, i.s.client)
|
|
if env.ValidateFileSize && uattr.Size != i.savedUAttr.Size {
|
|
return fs.ErrCorruption{fmt.Errorf("file size has changed for %s: previously %d, now %d", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.Size, uattr.Size)}
|
|
}
|
|
if env.ValidateFileTimestamp && uattr.ModificationTime != i.savedUAttr.ModificationTime {
|
|
return fs.ErrCorruption{fmt.Errorf("file modification time has changed for %s: previously %v, now %v", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.ModificationTime, uattr.ModificationTime)}
|
|
}
|
|
i.savedUAttr = nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
fs.Async(fs.CatchError(load))
|
|
}
|