diff --git a/server/backup/backup_local.go b/server/backup/backup_local.go index dd421f0..d76213a 100644 --- a/server/backup/backup_local.go +++ b/server/backup/backup_local.go @@ -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) }) } diff --git a/server/filesystem/compress.go b/server/filesystem/compress.go index 9782c67..74cf7df 100644 --- a/server/filesystem/compress.go +++ b/server/filesystem/compress.go @@ -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 } diff --git a/server/filesystem/disk_space.go b/server/filesystem/disk_space.go index b9bc879..3f21db4 100644 --- a/server/filesystem/disk_space.go +++ b/server/filesystem/disk_space.go @@ -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 } diff --git a/server/filesystem/errors.go b/server/filesystem/errors.go index ac8159e..a15d3be 100644 --- a/server/filesystem/errors.go +++ b/server/filesystem/errors.go @@ -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) } \ No newline at end of file diff --git a/server/filesystem/errors_test.go b/server/filesystem/errors_test.go index 0104e4c..5f93368 100644 --- a/server/filesystem/errors_test.go +++ b/server/filesystem/errors_test.go @@ -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 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: ") }) }) diff --git a/server/filesystem/filesystem.go b/server/filesystem/filesystem.go index 141240e..d492bcc 100644 --- a/server/filesystem/filesystem.go +++ b/server/filesystem/filesystem.go @@ -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() } diff --git a/server/filesystem/path.go b/server/filesystem/path.go index 470246a..6e5e735 100644 --- a/server/filesystem/path.go +++ b/server/filesystem/path.go @@ -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 diff --git a/server/filesystem/path_test.go b/server/filesystem/path_test.go index 49fb50b..ada1dfb 100644 --- a/server/filesystem/path_test.go +++ b/server/filesystem/path_test.go @@ -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) {