// Copyright 2018 Google LLC // // 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)) }