Don't ignore disk space limits when copying/archiving; closes pterodactyl/panel#2400
This commit is contained in:
parent
1a3ba9efca
commit
bf1233def4
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestError struct {
|
type RequestError struct {
|
||||||
|
@ -95,6 +96,39 @@ func (e *RequestError) AbortWithServerError(c *gin.Context) {
|
||||||
e.AbortWithStatus(http.StatusInternalServerError, c)
|
e.AbortWithStatus(http.StatusInternalServerError, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle specific filesystem errors for a server.
|
||||||
|
func (e *RequestError) AbortFilesystemError(c *gin.Context) {
|
||||||
|
if errors.Is(e.Err, os.ErrNotExist) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "The requested resource was not found.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(e.Err, server.ErrNotEnoughDiskSpace) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": server.ErrNotEnoughDiskSpace.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(e.Err.Error(), "file name too long") {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "File name is too long.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := e.Err.(*os.SyscallError); ok && e.Syscall == "readdirent" {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||||
|
"error": "The requested directory does not exist.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.AbortWithServerError(c)
|
||||||
|
}
|
||||||
|
|
||||||
// Format the error to a string and include the UUID.
|
// Format the error to a string and include the UUID.
|
||||||
func (e *RequestError) Error() string {
|
func (e *RequestError) Error() string {
|
||||||
return fmt.Sprintf("%v (uuid: %s)", e.Err, e.Uuid)
|
return fmt.Sprintf("%v (uuid: %s)", e.Err, e.Uuid)
|
||||||
|
|
|
@ -82,14 +82,7 @@ func getServerListDirectory(c *gin.Context) {
|
||||||
|
|
||||||
stats, err := s.Filesystem.ListDirectory(d)
|
stats, err := s.Filesystem.ListDirectory(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if e, ok := err.(*os.SyscallError); ok && e.Syscall == "readdirent" {
|
TrackedServerError(err, s).AbortFilesystemError(c)
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": "The requested directory does not exist.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,14 +149,7 @@ func putServerRenameFiles(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(err.Error(), "file name too long") {
|
TrackedServerError(err, s).AbortFilesystemError(c)
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": "Cannot move or rename file, name is too long.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,15 +169,7 @@ func postServerCopyFile(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Filesystem.Copy(data.Location); err != nil {
|
if err := s.Filesystem.Copy(data.Location); err != nil {
|
||||||
// Check if the file does not exist.
|
TrackedServerError(err, s).AbortFilesystemError(c)
|
||||||
// NOTE: os.IsNotExist() does not work if the error is wrapped.
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackedServerError(err, s).AbortWithServerError(c)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
|
|
|
@ -47,11 +47,12 @@ func (s *Server) Config() *Configuration {
|
||||||
return &s.cfg
|
return &s.cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the amount of disk space available to a server in bytes.
|
||||||
func (s *Server) DiskSpace() int64 {
|
func (s *Server) DiskSpace() int64 {
|
||||||
s.cfg.mu.RLock()
|
s.cfg.mu.RLock()
|
||||||
defer s.cfg.mu.RUnlock()
|
defer s.cfg.mu.RUnlock()
|
||||||
|
|
||||||
return s.cfg.Build.DiskSpace
|
return s.cfg.Build.DiskSpace * 1000.0 * 1000.0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) MemoryLimit() int64 {
|
func (s *Server) MemoryLimit() int64 {
|
||||||
|
|
|
@ -243,7 +243,7 @@ func (fs *Filesystem) HasSpaceAvailable(allowStaleValue bool) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return (size / 1000.0 / 1000.0) <= space
|
return size <= space
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper function to allow other parts of the codebase to check the total used disk space
|
// Internal helper function to allow other parts of the codebase to check the total used disk space
|
||||||
|
@ -581,15 +581,14 @@ func (fs *Filesystem) Chown(path string) error {
|
||||||
|
|
||||||
// Copies a given file to the same location and appends a suffix to the file to indicate that
|
// Copies a given file to the same location and appends a suffix to the file to indicate that
|
||||||
// it has been copied.
|
// it has been copied.
|
||||||
//
|
|
||||||
// @todo need to get an exclusive lock on the file.
|
|
||||||
func (fs *Filesystem) Copy(p string) error {
|
func (fs *Filesystem) Copy(p string) error {
|
||||||
cleaned, err := fs.SafePath(p)
|
cleaned, err := fs.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, err := os.Stat(cleaned); err != nil {
|
s, err := os.Stat(cleaned);
|
||||||
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
} else if s.IsDir() || !s.Mode().IsRegular() {
|
} else if s.IsDir() || !s.Mode().IsRegular() {
|
||||||
// If this is a directory or not a regular file, just throw a not-exist error
|
// If this is a directory or not a regular file, just throw a not-exist error
|
||||||
|
@ -597,6 +596,12 @@ func (fs *Filesystem) Copy(p string) error {
|
||||||
return os.ErrNotExist
|
return os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that copying this file wouldn't put the server over its limit.
|
||||||
|
curSize := atomic.LoadInt64(&fs.disk)
|
||||||
|
if (curSize + s.Size()) > fs.Server.DiskSpace() {
|
||||||
|
return ErrNotEnoughDiskSpace
|
||||||
|
}
|
||||||
|
|
||||||
base := filepath.Base(cleaned)
|
base := filepath.Base(cleaned)
|
||||||
relative := strings.TrimSuffix(strings.TrimPrefix(cleaned, fs.Path()), base)
|
relative := strings.TrimSuffix(strings.TrimPrefix(cleaned, fs.Path()), base)
|
||||||
extension := filepath.Ext(base)
|
extension := filepath.Ext(base)
|
||||||
|
@ -665,6 +670,9 @@ func (fs *Filesystem) Copy(p string) error {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Once everything is done, increment the disk space used.
|
||||||
|
atomic.AddInt64(&fs.disk, s.Size())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -927,10 +935,25 @@ func (fs *Filesystem) CompressFiles(dir string, paths []string) (os.FileInfo, er
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &backup.Archive{TrimPrefix: fs.Path(), Files: inc}
|
a := &backup.Archive{TrimPrefix: fs.Path(), Files: inc}
|
||||||
|
|
||||||
d := path.Join(cleanedRootDir, fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")))
|
d := path.Join(cleanedRootDir, fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")))
|
||||||
|
|
||||||
return a.Create(d, context.Background())
|
f, err := a.Create(d, context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
curSize := atomic.LoadInt64(&fs.disk)
|
||||||
|
if (curSize + f.Size()) > fs.Server.DiskSpace() {
|
||||||
|
// Exceeding space limits, delete archive and return error. Kind of a waste of resources
|
||||||
|
// I suppose, but oh well.
|
||||||
|
_ = os.Remove(d)
|
||||||
|
|
||||||
|
return nil, ErrNotEnoughDiskSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt64(&fs.disk, f.Size())
|
||||||
|
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle errors encountered when walking through directories.
|
// Handle errors encountered when walking through directories.
|
||||||
|
|
|
@ -35,7 +35,7 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
|
||||||
dirSize, err := fs.DiskUsage(false)
|
dirSize, err := fs.DiskUsage(false)
|
||||||
|
|
||||||
var size int64
|
var size int64
|
||||||
var max = fs.Server.DiskSpace() * 1000.0 * 1000.0
|
var max = fs.Server.DiskSpace()
|
||||||
// Walk over the archive and figure out just how large the final output would be from unarchiving it.
|
// 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 {
|
err = archiver.Walk(source, func(f archiver.File) error {
|
||||||
if atomic.AddInt64(&size, f.Size())+dirSize > max {
|
if atomic.AddInt64(&size, f.Size())+dirSize > max {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user