mirror of
https://github.com/versity/versitygw.git
synced 2026-08-16 20:26:16 +00:00
fix: prevent connection errors in space/quotas error paths
When uploads hit ENOSPC or EDQUOT, the server was returning the correct S3 error but could close the connection while unread request bytes were still in flight, which caused TCP resets and surfaced as broken pipe/connection reset errors in SDKs instead of a clean Insufficient Storage response. This change drains the remaining upload body before returning the error so the response can be delivered and the connection can close gracefully, preserving correct client-visible behavior under disk-full and quota-exceeded conditions. Fixes #2209
This commit is contained in:
@@ -3024,9 +3024,11 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
|
||||
bucket, partPath, length, acct, doFalloc, p.forceNoTmpFile)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EDQUOT) {
|
||||
drainBody(r)
|
||||
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
|
||||
}
|
||||
if errors.Is(err, syscall.ENOSPC) {
|
||||
drainBody(r)
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
|
||||
}
|
||||
return nil, fmt.Errorf("open temp file: %w", err)
|
||||
@@ -3144,9 +3146,11 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart
|
||||
_, err = io.Copy(f, tr)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EDQUOT) {
|
||||
drainBody(tr)
|
||||
return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded)
|
||||
}
|
||||
if errors.Is(err, syscall.ENOSPC) {
|
||||
drainBody(tr)
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
|
||||
}
|
||||
// Return the error itself, if it implements the s3err.S3Error interface
|
||||
@@ -3793,9 +3797,11 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje
|
||||
*po.Bucket, *po.Key, contentLength, acct, doFalloc, p.forceNoTmpFile)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EDQUOT) {
|
||||
drainBody(po.Body)
|
||||
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
|
||||
}
|
||||
if errors.Is(err, syscall.ENOSPC) {
|
||||
drainBody(po.Body)
|
||||
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
|
||||
}
|
||||
return s3response.PutObjectOutput{}, fmt.Errorf("open temp file: %w", err)
|
||||
@@ -3820,9 +3826,11 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje
|
||||
_, err = io.Copy(f, rdr)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EDQUOT) {
|
||||
drainBody(rdr)
|
||||
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded)
|
||||
}
|
||||
if errors.Is(err, syscall.ENOSPC) {
|
||||
drainBody(rdr)
|
||||
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice)
|
||||
}
|
||||
// Return the error itself, if it implements the s3err.S3Error interface
|
||||
@@ -6764,6 +6772,17 @@ func getString(str *string) string {
|
||||
return *str
|
||||
}
|
||||
|
||||
// drainBody consumes and discards all remaining bytes from r.
|
||||
// It is called after a server error is detected mid-stream so that
|
||||
// the client can read the error response before the write-side of the
|
||||
// connection is shut down.
|
||||
func drainBody(r io.Reader) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, r)
|
||||
}
|
||||
|
||||
func joinPathWithTrailer(paths ...string) string {
|
||||
joined := filepath.Join(paths...)
|
||||
if strings.HasSuffix(paths[len(paths)-1], "/") {
|
||||
|
||||
Reference in New Issue
Block a user