gvisor/pkg/sentry/fs/gofer/inode_state.go

173 lines
5.5 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.googlesource.com/gvisor/pkg/p9"
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/device"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
"gvisor.googlesource.com/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))
}