From 0e35235908c779678308552b86a9a5623534f28e Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 30 May 2026 21:09:53 -0700 Subject: [PATCH] s3: return NoSuchVersion (not NoSuchKey) for a missing versionId (#9749) GET/HEAD object with an explicit versionId that does not exist returned NoSuchKey. AWS S3 returns NoSuchVersion (404) for this case; tools that distinguish "key gone" from "this version gone" rely on that code. Add the ErrNoSuchVersion error code and use it on the GET and HEAD specific-version lookups. Only a genuine not-found maps to NoSuchVersion; a transient or internal filer error now maps to InternalError (500) instead of a misleading 404. getSpecificObjectVersion wraps its lookup error with %w so callers can detect filer_pb.ErrNotFound. --- weed/s3api/s3api_object_handlers.go | 12 ++++++++++-- weed/s3api/s3api_object_versioning.go | 4 ++-- weed/s3api/s3err/s3api_errors.go | 6 ++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index 9871db850..04d510061 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -641,7 +641,11 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) entry, err = s3a.getSpecificObjectVersion(bucket, object, versionId) if err != nil { glog.Errorf("Failed to get specific version %s: %v", versionId, err) - s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) + if errors.Is(err, filer_pb.ErrNotFound) { + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchVersion) + } else { + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + } return } targetVersionId = versionId @@ -2149,7 +2153,11 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request entry, err = s3a.getSpecificObjectVersion(bucket, object, versionId) if err != nil { glog.Errorf("Failed to get specific version %s for %s/%s: %v", versionId, bucket, object, err) - s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchKey) + if errors.Is(err, filer_pb.ErrNotFound) { + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchVersion) + } else { + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + } return } targetVersionId = versionId diff --git a/weed/s3api/s3api_object_versioning.go b/weed/s3api/s3api_object_versioning.go index 43d268b85..36a741584 100644 --- a/weed/s3api/s3api_object_versioning.go +++ b/weed/s3api/s3api_object_versioning.go @@ -984,7 +984,7 @@ func (s3a *S3ApiServer) getSpecificObjectVersion(bucket, object, versionId strin bucketDir := s3a.bucketDir(bucket) entry, err := s3a.getEntry(bucketDir, normalizedObject) if err != nil { - return nil, fmt.Errorf("null version object %s not found: %v", normalizedObject, err) + return nil, fmt.Errorf("null version object %s not found: %w", normalizedObject, err) } return entry, nil } @@ -995,7 +995,7 @@ func (s3a *S3ApiServer) getSpecificObjectVersion(bucket, object, versionId strin entry, err := s3a.getEntry(versionsDir, versionFile) if err != nil { - return nil, fmt.Errorf("version %s not found: %v", versionId, err) + return nil, fmt.Errorf("version %s not found: %w", versionId, err) } return entry, nil diff --git a/weed/s3api/s3err/s3api_errors.go b/weed/s3api/s3err/s3api_errors.go index dcc8a2b84..76cd81075 100644 --- a/weed/s3api/s3err/s3api_errors.go +++ b/weed/s3api/s3err/s3api_errors.go @@ -57,6 +57,7 @@ const ( ErrNoSuchCORSConfiguration ErrNoSuchLifecycleConfiguration ErrNoSuchKey + ErrNoSuchVersion ErrNoSuchUpload ErrInvalidBucketName ErrInvalidBucketState @@ -281,6 +282,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The specified key does not exist.", HTTPStatusCode: http.StatusNotFound, }, + ErrNoSuchVersion: { + Code: "NoSuchVersion", + Description: "The specified version does not exist.", + HTTPStatusCode: http.StatusNotFound, + }, ErrNoSuchUpload: { Code: "NoSuchUpload", Description: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",