Capture restore file system corruption errors in exit error.

PiperOrigin-RevId: 195850822
Change-Id: I4d7bdd8fe129c5ed461b73e1d7458be2cf5680c2
This commit is contained in:
Zhaozhong Ni
2018-05-08 11:36:59 -07:00
committed by Shentubot
parent 3ac3ea1d6a
commit 174161013d
6 changed files with 90 additions and 24 deletions
+5 -4
View File
@@ -63,7 +63,7 @@ func (p *pipeOperations) loadFlags(flags fs.FileFlags) {
// afterLoad is invoked by stateify.
func (p *pipeOperations) afterLoad() {
load := func() {
load := func() error {
if !p.flags.Read {
readPipeOperationsLoading.Wait()
} else {
@@ -75,14 +75,15 @@ func (p *pipeOperations) afterLoad() {
Write: p.flags.Write,
})
if err != nil {
panic(fmt.Sprintf("unable to open pipe %v: %v", p, err))
return fmt.Errorf("unable to open pipe %v: %v", p, err)
}
if err := p.init(); err != nil {
panic(fmt.Sprintf("unable to initialize pipe %v: %v", p, err))
return fmt.Errorf("unable to initialize pipe %v: %v", p, err)
}
return nil
}
// Do background opening of pipe ends. Note for write-only pipe ends we
// have to do it asynchronously to avoid blocking the restore.
fs.Async(load)
fs.Async(fs.CatchError(load))
}
+60 -3
View File
@@ -55,11 +55,19 @@ package fs
import (
"sync"
"gvisor.googlesource.com/gvisor/pkg/log"
)
// work is a sync.WaitGroup that can be used to queue asynchronous operations
// via Do. Callers can use Barrier to ensure no operations are outstanding.
var work sync.WaitGroup
var (
// work is a sync.WaitGroup that can be used to queue asynchronous
// operations via Do. Callers can use Barrier to ensure no operations
// are outstanding.
work sync.WaitGroup
// asyncError is used to store up to one asynchronous execution error.
asyncError = make(chan error, 1)
)
// AsyncBarrier waits for all outstanding asynchronous work to complete.
func AsyncBarrier() {
@@ -75,6 +83,43 @@ func Async(f func()) {
}()
}
// AsyncErrorBarrier waits for all outstanding asynchronous work to complete, or
// the first async error to arrive. Other unfinished async executions will
// continue in the background. Other past and future async errors are ignored.
func AsyncErrorBarrier() error {
wait := make(chan struct{}, 1)
go func() { // S/R-SAFE: Does not touch persistent state.
work.Wait()
wait <- struct{}{}
}()
select {
case <-wait:
select {
case err := <-asyncError:
return err
default:
return nil
}
case err := <-asyncError:
return err
}
}
// CatchError tries to capture the potential async error returned by the
// function. At most one async error will be captured globally so excessive
// errors will be dropped.
func CatchError(f func() error) func() {
return func() {
if err := f(); err != nil {
select {
case asyncError <- err:
default:
log.Warningf("excessive async error dropped: %v", err)
}
}
}
}
// ErrSaveRejection indicates a failed save due to unsupported file system state
// such as dangling open fd, etc.
type ErrSaveRejection struct {
@@ -86,3 +131,15 @@ type ErrSaveRejection struct {
func (e ErrSaveRejection) Error() string {
return "save rejected due to unsupported file system state: " + e.Err.Error()
}
// ErrCorruption indicates a failed restore due to external file system state in
// corruption.
type ErrCorruption struct {
// Err is the wrapped error.
Err error
}
// Error returns a sensible description of the save rejection error.
func (e ErrCorruption) Error() string {
return "restore failed due to external file system state in corruption: " + e.Err.Error()
}
+6 -3
View File
@@ -15,13 +15,15 @@
package gofer
import (
"fmt"
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
)
// afterLoad is invoked by stateify.
func (f *fileOperations) afterLoad() {
load := func() {
load := func() error {
f.inodeOperations.fileState.waitForLoad()
// Manually load the open handles.
@@ -29,9 +31,10 @@ func (f *fileOperations) afterLoad() {
// TODO: Context is not plumbed to save/restore.
f.handles, err = newHandles(context.Background(), f.inodeOperations.fileState.file, f.flags)
if err != nil {
panic("failed to re-open handle: " + err.Error())
return fmt.Errorf("failed to re-open handle: %v", err)
}
f.inodeOperations.fileState.setHandlesForCachedIO(f.flags, f.handles)
return nil
}
fs.Async(load)
fs.Async(fs.CatchError(load))
}
+13 -10
View File
@@ -15,6 +15,7 @@
package gofer
import (
"errors"
"fmt"
"strings"
@@ -83,7 +84,7 @@ func (i *inodeFileState) loadLoading(_ struct{}) {
// afterLoad is invoked by stateify.
func (i *inodeFileState) afterLoad() {
load := func() {
load := func() error {
// See comment on i.loading().
defer i.loading.Unlock()
@@ -92,14 +93,14 @@ func (i *inodeFileState) afterLoad() {
if !ok {
// This should be impossible, see assertion in
// beforeSave.
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)))
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: Context is not plumbed to save/restore.
ctx := &dummyClockContext{context.Background()}
var err error
_, i.file, err = i.s.attach.walk(ctx, strings.Split(name, "/"))
if err != nil {
panic(fmt.Sprintf("failed to walk to %q: %v", name, err))
return fmt.Errorf("failed to walk to %q: %v", name, err)
}
// Remap the saved inode number into the gofer device using the
@@ -107,10 +108,10 @@ func (i *inodeFileState) afterLoad() {
// environment.
qid, mask, attrs, err := i.file.getAttr(ctx, p9.AttrMaskAll())
if err != nil {
panic(fmt.Sprintf("failed to get file attributes of %s: %v", name, err))
return fmt.Errorf("failed to get file attributes of %s: %v", name, err)
}
if !mask.RDev {
panic(fmt.Sprintf("file %s lacks device", name))
return fs.ErrCorruption{fmt.Errorf("file %s lacks device", name)}
}
i.key = device.MultiDeviceKey{
Device: attrs.RDev,
@@ -118,24 +119,26 @@ func (i *inodeFileState) afterLoad() {
Inode: qid.Path,
}
if !goferDevice.Load(i.key, i.sattr.InodeID) {
panic(fmt.Sprintf("gofer device %s -> %d conflict in gofer device mappings: %s", i.key, i.sattr.InodeID, goferDevice))
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 {
panic("missing restore environment")
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 {
panic(fmt.Errorf("file size has changed for %s: previously %d, now %d", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.Size, uattr.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 {
panic(fmt.Errorf("file modification time has changed for %s: previously %v, now %v", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.ModificationTime, uattr.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(load)
fs.Async(fs.CatchError(load))
}
+3 -3
View File
@@ -59,7 +59,7 @@ func (i *inodeFileState) afterLoad() {
// saved filesystem are no longer unique on this filesystem.
// Since this violates the contract that filesystems cannot
// change across save and restore, error out.
panic(fmt.Sprintf("host %s conflict in host device mappings: %s", key, hostFileDevice))
panic(fs.ErrCorruption{fmt.Errorf("host %s conflict in host device mappings: %s", key, hostFileDevice)})
}
if !i.descriptor.donated && i.sattr.Type == fs.RegularFile {
@@ -69,10 +69,10 @@ func (i *inodeFileState) afterLoad() {
}
uattr := unstableAttr(i.mops, &s)
if env.ValidateFileSize && uattr.Size != i.savedUAttr.Size {
panic(fmt.Errorf("file size has changed for %s: previously %d, now %d", i.mops.inodeMappings[i.sattr.InodeID], i.savedUAttr.Size, uattr.Size))
panic(fs.ErrCorruption{fmt.Errorf("file size has changed for %s: previously %d, now %d", i.mops.inodeMappings[i.sattr.InodeID], i.savedUAttr.Size, uattr.Size)})
}
if env.ValidateFileTimestamp && uattr.ModificationTime != i.savedUAttr.ModificationTime {
panic(fmt.Errorf("file modification time has changed for %s: previously %v, now %v", i.mops.inodeMappings[i.sattr.InodeID], i.savedUAttr.ModificationTime, uattr.ModificationTime))
panic(fs.ErrCorruption{fmt.Errorf("file modification time has changed for %s: previously %v, now %v", i.mops.inodeMappings[i.sattr.InodeID], i.savedUAttr.ModificationTime, uattr.ModificationTime)})
}
i.savedUAttr = nil
}
+3 -1
View File
@@ -418,7 +418,9 @@ func (k *Kernel) LoadFrom(r io.Reader, p platform.Platform, net inet.Stack) erro
// Ensure that all pending asynchronous work is complete:
// - namedpipe opening
// - inode file opening
fs.AsyncBarrier()
if err := fs.AsyncErrorBarrier(); err != nil {
return err
}
log.Infof("Overall load took [%s]", time.Since(loadStart))