Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions router/router_server_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,20 +447,38 @@ func postServerDecompressFiles(c *gin.Context) {

s := middleware.ExtractServer(c)
lg := middleware.ExtractLogger(c).WithFields(log.Fields{"root_path": data.RootPath, "file": data.File})
lg.Debug("checking if space is available for file decompression")
err := s.Filesystem().SpaceAvailableForDecompression(context.Background(), data.RootPath, data.File)

// Check if there's enough space for decompression. This uses a 5-second timeout
// to avoid delays on large archives - if it times out, decompression proceeds
// with incremental space checking during extraction.
err := s.Filesystem().SpaceAvailableForDecompression(c.Request.Context(), data.RootPath, data.File)
if err != nil {
if filesystem.IsErrorCode(err, filesystem.ErrCodeUnknownArchive) {
lg.WithField("error", err).Warn("failed to decompress file: unknown archive format")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "The archive provided is in a format Wings does not understand."})
return
}
if filesystem.IsErrorCode(err, filesystem.ErrCodeDiskSpace) {
lg.WithField("error", err).Warn("failed to decompress file: not enough disk space")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "There is not enough disk space available to decompress this archive."})
return
}
middleware.CaptureAndAbort(c, err)
return
}

lg.Info("starting file decompression")
if err := s.Filesystem().DecompressFile(context.Background(), data.RootPath, data.File); err != nil {
if err := s.Filesystem().DecompressFile(c.Request.Context(), data.RootPath, data.File); err != nil {
if filesystem.IsErrorCode(err, filesystem.ErrCodeUnknownArchive) {
lg.WithField("error", err).Warn("failed to decompress file: unknown archive format")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "The archive provided is in a format Wings does not understand."})
return
}
if filesystem.IsErrorCode(err, filesystem.ErrCodeDiskSpace) {
lg.WithField("error", err).Warn("failed to decompress file: not enough disk space")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "There is not enough disk space available to decompress this archive."})
return
}
// If the file is busy for some reason just return a nicer error to the user since there is not
// much we specifically can do. They'll need to stop the running server process in order to overwrite
// a file like this.
Expand Down
24 changes: 20 additions & 4 deletions server/filesystem/compress.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ func (fs *Filesystem) archiverFileSystem(ctx context.Context, p string) (iofs.FS

// SpaceAvailableForDecompression looks through a given archive and determines
// if decompressing it would put the server over its allocated disk space limit.
// To avoid long delays on large archives, this function will timeout after 5 seconds
// and allow decompression to proceed (space will still be checked incrementally during extraction).
func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir string, file string) error {
// Don't waste time trying to determine this if we know the server will have the space for
// it since there is no limit.
Expand All @@ -113,16 +115,22 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
return err
}

// Create a context with timeout to prevent long delays on large archives
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

var size atomic.Int64
return iofs.WalkDir(fsys, ".", func(path string, d iofs.DirEntry, err error) error {
err = iofs.WalkDir(fsys, ".", func(path string, d iofs.DirEntry, err error) error {
if err != nil {
return err
}

select {
case <-ctx.Done():
// Stop walking if the context is canceled.
return ctx.Err()
case <-timeoutCtx.Done():
// Stop walking if the timeout is reached or context is canceled.
// We'll check below whether to ignore the error (for timeouts)
// or propagate it (for cancellations).
return timeoutCtx.Err()
default:
info, err := d.Info()
if err != nil {
Expand All @@ -134,6 +142,14 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
return nil
}
})

// If the error is a timeout, ignore it and allow decompression to proceed.
// Space will still be checked incrementally during the actual extraction.
if errors.Is(err, context.DeadlineExceeded) {
return nil
}

return err
}

// DecompressFile will decompress a file in a given directory by using the
Expand Down