Skip to content

Commit

Permalink
Report correct error when restore fails
Browse files Browse the repository at this point in the history
When file corruption is detected, report vfs.ErrCorruption to
distinguish corruption error from other restore errors.

Updates #1035

PiperOrigin-RevId: 404402516
  • Loading branch information
fvoznika authored and gvisor-bot committed Oct 20, 2021
1 parent 6dde3d5 commit 3bc75d6
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 21 deletions.
16 changes: 8 additions & 8 deletions pkg/sentry/fsimpl/gofer/save_restore.go
Expand Up @@ -277,18 +277,18 @@ func (d *dentry) restoreFile(ctx context.Context, file p9file, qid p9.QID, attrM
if d.isRegularFile() {
if opts.ValidateFileSizes {
if !attrMask.Size {
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d))
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d))}
}
if d.size != attr.Size {
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d), d.size, attr.Size)
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d), d.size, attr.Size)}
}
}
if opts.ValidateFileModificationTimestamps {
if !attrMask.MTime {
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d))
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d))}
}
if want := dentryTimestampFromP9(attr.MTimeSeconds, attr.MTimeNanoSeconds); d.mtime != want {
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d), linux.NsecToStatxTimestamp(d.mtime), linux.NsecToStatxTimestamp(want))
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d), linux.NsecToStatxTimestamp(d.mtime), linux.NsecToStatxTimestamp(want))}
}
}
}
Expand Down Expand Up @@ -326,18 +326,18 @@ func (d *dentry) restoreFileLisa(ctx context.Context, inode *lisafs.Inode, opts
if d.isRegularFile() {
if opts.ValidateFileSizes {
if inode.Stat.Mask&linux.STATX_SIZE != 0 {
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d))
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d))}
}
if d.size != inode.Stat.Size {
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d), d.size, inode.Stat.Size)
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d), d.size, inode.Stat.Size)}
}
}
if opts.ValidateFileModificationTimestamps {
if inode.Stat.Mask&linux.STATX_MTIME != 0 {
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d))
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d))}
}
if want := dentryTimestampFromLisa(inode.Stat.Mtime); d.mtime != want {
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d), linux.NsecToStatxTimestamp(d.mtime), linux.NsecToStatxTimestamp(want))
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d), linux.NsecToStatxTimestamp(d.mtime), linux.NsecToStatxTimestamp(want))}
}
}
}
Expand Down
29 changes: 16 additions & 13 deletions pkg/sentry/vfs/save_restore.go
Expand Up @@ -15,7 +15,6 @@
package vfs

import (
"fmt"
"sync/atomic"

"gvisor.dev/gvisor/pkg/abi/linux"
Expand All @@ -24,6 +23,18 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)

// 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 restore error.
func (e ErrCorruption) Error() string {
return "restore failed due to external file system state in corruption: " + e.Err.Error()
}

// FilesystemImplSaveRestoreExtension is an optional extension to
// FilesystemImpl.
type FilesystemImplSaveRestoreExtension interface {
Expand All @@ -37,38 +48,30 @@ type FilesystemImplSaveRestoreExtension interface {

// PrepareSave prepares all filesystems for serialization.
func (vfs *VirtualFilesystem) PrepareSave(ctx context.Context) error {
failures := 0
for fs := range vfs.getFilesystems() {
if ext, ok := fs.impl.(FilesystemImplSaveRestoreExtension); ok {
if err := ext.PrepareSave(ctx); err != nil {
ctx.Warningf("%T.PrepareSave failed: %v", fs.impl, err)
failures++
fs.DecRef(ctx)
return err
}
}
fs.DecRef(ctx)
}
if failures != 0 {
return fmt.Errorf("%d filesystems failed to prepare for serialization", failures)
}
return nil
}

// CompleteRestore completes restoration from checkpoint for all filesystems
// after deserialization.
func (vfs *VirtualFilesystem) CompleteRestore(ctx context.Context, opts *CompleteRestoreOptions) error {
failures := 0
for fs := range vfs.getFilesystems() {
if ext, ok := fs.impl.(FilesystemImplSaveRestoreExtension); ok {
if err := ext.CompleteRestore(ctx, *opts); err != nil {
ctx.Warningf("%T.CompleteRestore failed: %v", fs.impl, err)
failures++
fs.DecRef(ctx)
return err
}
}
fs.DecRef(ctx)
}
if failures != 0 {
return fmt.Errorf("%d filesystems failed to complete restore after deserialization", failures)
}
return nil
}

Expand Down

0 comments on commit 3bc75d6

Please sign in to comment.