Add better error handling for filesystem

This commit is contained in:
Dane Everitt 2021-04-17 13:29:18 -07:00
parent a0ae5fd131
commit 0676a82a21
8 changed files with 86 additions and 43 deletions

View File

@ -2,13 +2,13 @@ package backup
import (
"errors"
"github.com/pterodactyl/wings/server/filesystem"
"io"
"os"
"github.com/pterodactyl/wings/server/filesystem"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/system"
)
type LocalBackup struct {
@ -78,10 +78,6 @@ func (b *LocalBackup) Restore(_ io.Reader, callback RestoreCallback) error {
if f.IsDir() {
return nil
}
name, err := system.ExtractArchiveSourceName(f, "/")
if err != nil {
return err
}
return callback(name, f)
return callback(f.Name(), f)
})
}

View File

@ -9,8 +9,8 @@ import (
"sync/atomic"
"time"
"emperror.dev/errors"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/system"
)
// CompressFiles compresses all of the files matching the given paths in the
@ -86,13 +86,13 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) er
// Walk over the archive and figure out just how large the final output would be from unarchiving it.
err = archiver.Walk(source, func(f archiver.File) error {
if atomic.AddInt64(&size, f.Size())+dirSize > fs.MaxDisk() {
return &Error{code: ErrCodeDiskSpace}
return newFilesystemError(ErrCodeDiskSpace, nil)
}
return nil
})
if err != nil {
if strings.HasPrefix(err.Error(), "format ") {
return &Error{code: ErrCodeUnknownArchive}
if IsUnknownArchiveFormatError(err) {
return newFilesystemError(ErrCodeUnknownArchive, err)
}
return err
}
@ -111,7 +111,7 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
}
// Ensure that the source archive actually exists on the system.
if _, err := os.Stat(source); err != nil {
return err
return errors.WithStack(err)
}
// Walk all of the files in the archiver file and write them to the disk. If any
@ -127,13 +127,13 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
return nil
}
if err := fs.Writefile(p, f); err != nil {
return &Error{code: ErrCodeUnknownError, err: err, resolved: source}
return wrapError(err, source)
}
return nil
})
if err != nil {
if strings.HasPrefix(err.Error(), "format ") {
return &Error{code: ErrCodeUnknownArchive}
if IsUnknownArchiveFormatError(err) {
return newFilesystemError(ErrCodeUnknownArchive, err)
}
return err
}

View File

@ -1,13 +1,14 @@
package filesystem
import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/karrick/godirwalk"
"sync"
"sync/atomic"
"syscall"
"time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/karrick/godirwalk"
)
type SpaceCheckingOpts struct {
@ -48,7 +49,7 @@ func (fs *Filesystem) SetDiskLimit(i int64) {
// no space, rather than a boolean value.
func (fs *Filesystem) HasSpaceErr(allowStaleValue bool) error {
if !fs.HasSpaceAvailable(allowStaleValue) {
return &Error{code: ErrCodeDiskSpace}
return newFilesystemError(ErrCodeDiskSpace, nil)
}
return nil
}
@ -200,16 +201,13 @@ func (fs *Filesystem) HasSpaceFor(size int64) error {
if fs.MaxDisk() == 0 {
return nil
}
s, err := fs.DiskUsage(true)
if err != nil {
return err
}
if (s + size) > fs.MaxDisk() {
return &Error{code: ErrCodeDiskSpace}
return newFilesystemError(ErrCodeDiskSpace, nil)
}
return nil
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"emperror.dev/errors"
"github.com/apex/log"
@ -34,6 +35,14 @@ type Error struct {
path string
}
// newFilesystemError returns a new error instance with a stack trace associated.
func newFilesystemError(code ErrorCode, err error) error {
if err != nil {
return errors.WithStackDepth(&Error{code: code, err: err}, 1)
}
return errors.WithStackDepth(&Error{code: code}, 1)
}
// Code returns the ErrorCode for this specific error instance.
func (e *Error) Code() ErrorCode {
return e.code
@ -63,13 +72,13 @@ func (e *Error) Error() string {
case ErrCodeUnknownError:
fallthrough
default:
return fmt.Sprintf("filesystem: an error occurred: %s", e.Cause())
return fmt.Sprintf("filesystem: an error occurred: %s", e.Unwrap())
}
}
// Cause returns the underlying cause of this filesystem error. In some causes
// Unwrap returns the underlying cause of this filesystem error. In some causes
// there may not be a cause present, in which case nil will be returned.
func (e *Error) Cause() error {
func (e *Error) Unwrap() error {
return e.err
}
@ -113,20 +122,26 @@ func IsErrorCode(err error, code ErrorCode) bool {
return false
}
// NewBadPathResolution returns a new BadPathResolution error.
func NewBadPathResolution(path string, resolved string) *Error {
return &Error{code: ErrCodePathResolution, path: path, resolved: resolved}
// IsUnknownArchiveFormatError checks if the error is due to the archive being
// in an unexpected file format.
func IsUnknownArchiveFormatError(err error) bool {
if err != nil && strings.HasPrefix(err.Error(), "format ") {
return true
}
return false
}
// WrapError wraps the provided error as a Filesystem error and attaches the
// NewBadPathResolution returns a new BadPathResolution error.
func NewBadPathResolution(path string, resolved string) error {
return errors.WithStackDepth(&Error{code: ErrCodePathResolution, path: path, resolved: resolved}, 1)
}
// wrapError wraps the provided error as a Filesystem error and attaches the
// provided resolved source to it. If the error is already a Filesystem error
// no action is taken.
func WrapError(err error, resolved string) *Error {
if err == nil {
return nil
func wrapError(err error, resolved string) error {
if err == nil || IsFilesystemError(err) {
return err
}
if IsFilesystemError(err) {
return err.(*Error)
}
return &Error{code: ErrCodeUnknownError, err: err, resolved: resolved}
return errors.WithStackDepth(&Error{code: ErrCodeUnknownError, err: err, resolved: resolved}, 1)
}

View File

@ -1,13 +1,45 @@
package filesystem
import (
. "github.com/franela/goblin"
"io"
"testing"
"emperror.dev/errors"
. "github.com/franela/goblin"
)
type stackTracer interface {
StackTrace() errors.StackTrace
}
func TestFilesystem_PathResolutionError(t *testing.T) {
g := Goblin(t)
g.Describe("NewFilesystemError", func() {
g.It("includes a stack trace for the error", func() {
err := newFilesystemError(ErrCodeUnknownError, nil)
_, ok := err.(stackTracer)
g.Assert(ok).IsTrue()
})
g.It("properly wraps the underlying error cause", func() {
underlying := io.EOF
err := newFilesystemError(ErrCodeUnknownError, underlying)
_, ok := err.(stackTracer)
g.Assert(ok).IsTrue()
_, ok = err.(*Error)
g.Assert(ok).IsFalse()
fserr, ok := errors.Unwrap(err).(*Error)
g.Assert(ok).IsTrue()
g.Assert(fserr.Unwrap()).IsNotNil()
g.Assert(fserr.Unwrap()).Equal(underlying)
})
})
g.Describe("NewBadPathResolutionError", func() {
g.It("is can detect itself as an error correctly", func() {
err := NewBadPathResolution("foo", "bar")
@ -18,6 +50,7 @@ func TestFilesystem_PathResolutionError(t *testing.T) {
g.It("returns <empty> if no destination path is provided", func() {
err := NewBadPathResolution("foo", "")
g.Assert(err).IsNotNil()
g.Assert(err.Error()).Equal("filesystem: server path [foo] resolves to a location outside the server root: <empty>")
})
})

View File

@ -67,7 +67,7 @@ func (fs *Filesystem) File(p string) (*os.File, Stat, error) {
return nil, Stat{}, err
}
if st.IsDir() {
return nil, Stat{}, &Error{code: ErrCodeIsDirectory}
return nil, Stat{}, newFilesystemError(ErrCodeIsDirectory, nil)
}
f, err := os.Open(cleaned)
if err != nil {
@ -144,7 +144,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
return errors.Wrap(err, "server/filesystem: writefile: failed to stat file")
} else if err == nil {
if stat.IsDir() {
return &Error{code: ErrCodeIsDirectory, resolved: cleaned}
return errors.WithStack(&Error{code: ErrCodeIsDirectory, resolved: cleaned})
}
currentSize = stat.Size()
}

View File

@ -20,7 +20,7 @@ func (fs *Filesystem) IsIgnored(paths ...string) error {
return err
}
if fs.denylist.MatchesPath(sp) {
return &Error{code: ErrCodeDenylistFile, path: p, resolved: sp}
return errors.WithStack(&Error{code: ErrCodeDenylistFile, path: p, resolved: sp})
}
}
return nil

View File

@ -2,11 +2,12 @@ package filesystem
import (
"bytes"
"emperror.dev/errors"
. "github.com/franela/goblin"
"os"
"path/filepath"
"testing"
"emperror.dev/errors"
. "github.com/franela/goblin"
)
func TestFilesystem_Path(t *testing.T) {