diff --git a/backend/meta/sidecar.go b/backend/meta/sidecar.go index 98082af8..3056ae3e 100644 --- a/backend/meta/sidecar.go +++ b/backend/meta/sidecar.go @@ -20,6 +20,9 @@ import ( "io" "os" "path/filepath" + "syscall" + + "github.com/versity/versitygw/s3err" ) // SideCar is a metadata storer that uses sidecar files to store metadata. @@ -71,12 +74,18 @@ func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, va } err := os.MkdirAll(metadir, 0777) if err != nil { + if errors.Is(err, syscall.ENOSPC) { + return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return fmt.Errorf("failed to create metadata directory: %v", err) } attr := filepath.Join(metadir, attribute) tempfile, err := os.CreateTemp(metadir, attribute) if err != nil { + if errors.Is(err, syscall.ENOSPC) { + return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return fmt.Errorf("failed to create temporary file: %v", err) } defer os.Remove(tempfile.Name()) @@ -84,6 +93,9 @@ func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, va _, err = tempfile.Write(value) if err != nil { tempfile.Close() + if errors.Is(err, syscall.ENOSPC) { + return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return fmt.Errorf("failed to write attribute: %v", err) } @@ -176,6 +188,9 @@ func (s SideCar) RenameObject(bucket, oldObject, newObject string) error { newPath := filepath.Join(s.dir, bucket, newObject) if err := os.MkdirAll(filepath.Dir(newPath), 0777); err != nil { + if errors.Is(err, syscall.ENOSPC) { + return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return fmt.Errorf("create parent for renamed metadata: %w", err) } diff --git a/backend/meta/xattr.go b/backend/meta/xattr.go index c2d66cfd..7be71d7a 100644 --- a/backend/meta/xattr.go +++ b/backend/meta/xattr.go @@ -57,6 +57,9 @@ func (x XattrMeta) StoreAttribute(f *os.File, bucket, object, attribute string, if errors.Is(err, syscall.EROFS) { return s3err.GetAPIError(s3err.ErrMethodNotAllowed) } + if errors.Is(err, syscall.ENOSPC) { + return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return err } @@ -64,6 +67,9 @@ func (x XattrMeta) StoreAttribute(f *os.File, bucket, object, attribute string, if errors.Is(err, syscall.EROFS) { return s3err.GetAPIError(s3err.ErrMethodNotAllowed) } + if errors.Is(err, syscall.ENOSPC) { + return s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return err } diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 680dfa38..8ad71eed 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -1989,6 +1989,9 @@ func (p *Posix) CompleteMultipartUploadWithCopy(ctx context.Context, input *s3.C if errors.Is(err, syscall.EDQUOT) { return res, "", s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return res, "", s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return res, "", fmt.Errorf("open temp file: %w", err) } defer f.cleanup() @@ -2044,6 +2047,9 @@ func (p *Posix) CompleteMultipartUploadWithCopy(ctx context.Context, input *s3.C if errors.Is(err, syscall.EDQUOT) { return res, "", s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return res, "", s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return res, "", fmt.Errorf("copy part %v: %v", part.PartNumber, err) } } @@ -2877,6 +2883,9 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart if errors.Is(err, syscall.EDQUOT) { return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return nil, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return nil, fmt.Errorf("open temp file: %w", err) } defer f.cleanup() @@ -2989,6 +2998,9 @@ func (p *Posix) UploadPartWithPostFunc(ctx context.Context, input *s3.UploadPart if errors.Is(err, syscall.EDQUOT) { return nil, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return nil, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } // Return the error itself, if it's an 's3err.APIError' if _, ok := err.(s3err.APIError); ok { return nil, err @@ -3253,6 +3265,9 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput) if errors.Is(err, syscall.EDQUOT) { return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return s3response.CopyPartResult{}, fmt.Errorf("open temp file: %w", err) } defer f.cleanup() @@ -3298,6 +3313,9 @@ func (p *Posix) UploadPartCopy(ctx context.Context, upi *s3.UploadPartCopyInput) if errors.Is(err, syscall.EDQUOT) { return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return s3response.CopyPartResult{}, fmt.Errorf("copy part data: %w", err) } @@ -3494,6 +3512,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje if errors.Is(err, syscall.EDQUOT) { return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return s3response.PutObjectOutput{}, err } @@ -3622,6 +3643,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje if errors.Is(err, syscall.EDQUOT) { return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } return s3response.PutObjectOutput{}, fmt.Errorf("open temp file: %w", err) } defer f.cleanup() @@ -3646,6 +3670,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje if errors.Is(err, syscall.EDQUOT) { return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrQuotaExceeded) } + if errors.Is(err, syscall.ENOSPC) { + return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSpaceLeftOnDevice) + } // Return the error itself, if it's an 's3err.APIError' if _, ok := err.(s3err.APIError); ok { return s3response.PutObjectOutput{}, err diff --git a/s3err/s3err.go b/s3err/s3err.go index ae9de4fd..0787e326 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -199,6 +199,7 @@ const ( ErrQuotaExceeded ErrVersioningNotConfigured ErrACLsDisabled + ErrNoSpaceLeftOnDevice // Admin api errors ErrAdminAccessDenied @@ -904,6 +905,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "Access control lists are disabled at the gateway level", HTTPStatusCode: http.StatusBadRequest, }, + ErrNoSpaceLeftOnDevice: { + Code: "InsufficientStorage", + Description: "No space left on device.", + HTTPStatusCode: http.StatusInsufficientStorage, + }, // Admin api errors ErrAdminAccessDenied: {