512 Commits

Author SHA1 Message Date
Ben McClelland
8c49a3335e fix: add explicit sidecar metadata cleanup on object/bucket deletion
Unlike xattr metadata which is tied to the filesystem object and removed
automatically, sidecar metadata lives in a parallel directory tree and
must be deleted explicitly. Add DeleteAttributes calls after removing
bucket directories, null-version files, and versioned object files.

Also add an os.Stat existence check in GetObjectTagging and
PutObjectTagging when no versionId is given, since sidecar's
StoreAttribute/RetrieveAttribute do not naturally return ErrNotExist
for missing objects the way xattr operations do.
2026-04-22 11:55:11 -07:00
niksis02
d6fb9547b8 fix: correct 206 Partial Content response status for ranged GetObject and HeadObject
Fixes #2052
Fixes #2056
Fixes #2057

Previously, GetObject and HeadObject used the request's `Range` header to determine the response status code, which caused incorrect 206 responses for invalid Range header values.

The status is now driven by whether res.ContentRange is set in the response, rather than by the presence of a range in the request. Backends (posix and azure) now set Content-Range for PartNumber=1 on non-multipart objects, skipping zero-size objects where no range applies.

HeadObject was also fixed to return 206 when Content-Range is present, and to only return checksums when the full object is requested.
2026-04-21 02:13:04 +04:00
Ben McClelland
8533bc1b60 Merge pull request #2054 from versity/sis/racing-complete-mp
fix: make CompleteMultipartUpload idempotent and add part-number support to GetObject/HeadObject
2026-04-20 10:23:09 -07:00
niksis02
62e8cddbc7 fix: make CompleteMultipartUpload idempotent and add part-number support to GetObject/HeadObject
Closes #1064

Use the multipart ETag as the in-progress directory suffix instead of the static `.inprogress` marker so that concurrent CompleteMultipartUpload calls for the same upload ID are all treated as successful (idempotent) rather than racing, where only one succeeded and the rest returned NoSuchUpload.

After finalizing the multipart upload, store an `mp-metadata` xattr on the assembled object that records the upload ID and cumulative byte offsets for each part. GetObject and HeadObject now use this metadata to serve individual part ranges via the `partNumber` query parameter, returning a successful response instead of returning NotImplemented.

Add two new S3 error codes:
- `ErrInvalidPartNumberRange` (416 RequestedRangeNotSatisfiable) — returned
  when the requested part number exceeds the number of parts in the upload.
- `ErrRangeAndPartNumber` (400 BadRequest) — returned when both a Range header
  and a partNumber query parameter are specified on the same request.
2026-04-20 20:45:58 +04:00
niksis02
b905554e06 fix: fix azure multipart upload objects masking
As multipart uploads are translated to blobs in azure blob storage, they were visible in ListObjects(V2) as complete objects. Now the blobs with multipart prefix are filtered out during listing.

The listing logic is rewritten client-side to implement proper S3 semantics: flat blob enumeration with manual delimiter handling, correct truncation (IsTruncated only set when more items genuinely exist beyond maxKeys), and StartAfter/Marker/ContinuationToken applied via the lexicographic max of both constraints in ListObjectsV2.

For the same reason bucket deletion was not allowed. Now multipart objects are explicitly checked on bucket deletion and any pending multipart upload doesn't block the bucket deletion anymore.
2026-04-16 23:56:18 +04:00
Ben McClelland
04e1468ce3 Merge pull request #2048 from versity/ben/misspell
chore: fix spellings and unused function args
2026-04-15 12:33:34 -07:00
Ben McClelland
e8680bde63 Merge pull request #2046 from versity/ben/windows-sidecar
fix: close temp file before rename in sidecar StoreAttribute
2026-04-15 12:33:06 -07:00
Ben McClelland
2b918d585e chore: fix spellings and unused function args
No logic changes, just janitorial cleanup
2026-04-15 10:32:14 -07:00
Ben McClelland
81f0158a48 fix: close temp file before rename in sidecar StoreAttribute
On Windows, a file apparently cannot be renamed while an open
handle to it exists.
The previous code used defer tempfile.Close(), which meant the
handle was still open when os.Rename was called, producing:

  failed to rename temporary file: The process cannot access
  the file because it is being used by another process.

Fix by closing the file explicitly before the rename.

Fixes #2021
2026-04-14 11:19:18 -07:00
Ben McClelland
9816c2fdb3 fix: add gcs compatibility flag to fix s3proxy GCS SigV4 signature mismatch
The AWS SDK v2 includes Accept-Encoding in SigV4 signed headers
which causes GCS to return a SignatureDoesNotMatch error because
GCS rewrites that header internally before verifying the signature.

Add a --gcs-compatibility / VGW_S3_GCS_COMPATIBILITY option for the s3proxy
backend that injects two Smithy finalize-layer middlewares: one removes
Accept-Encoding from the request immediately before the Signing step, and
a second restores it after signing so the header is still sent on the wire.

see: https://github.com/aws/aws-sdk-go-v2/issues/1816

This can be removed once GCS fixes this incompatibility.
2026-04-14 10:53:26 -07:00
Ben McClelland
efd1885d21 Merge pull request #2023 from versity/sis/move-versionid-validation-backend
fix: move versionId validation to backend
2026-04-10 11:23:20 -07:00
Ben McClelland
aa3c223adb Merge pull request #2027 from anaelorlinski/fix-multipart-upload
fix for multipart upload when using sidecar meta
2026-04-10 11:21:59 -07:00
niksis02
48bfa9f4cf fix: correct HeadObject restore status for offline objects in scoutfs
Fixes #2030

When an object has offline blocks, the restore status was incorrectly set to `ongoing-request="false"` instead of omitting the header entirely, which causes s3 clients fail on parsing the x-amz-restore header.
Remove the incorrect `stageNotInProgress` constant and simplify the `requestOngoing` initialization to reflect the correct default.
2026-04-09 19:15:31 +04:00
Anael ORLINSKI
a673900b51 fix for multipart upload when using sidecar meta 2026-04-07 23:37:47 +02:00
niksis02
b473aa0545 fix: move versionId validation to backend
Closes #1813

We use a specific `versionId` format(`ulid` package) to generate versionIds in posix, which is not compatible to S3. The versionId validation was performed in frontend which is a potential source of failure for s3 proxy configured on an s3 service which doesn't use ulid for versionId generation(e.g. aws S3). These changes move the specific `ulid` versionId validation to posix to not force any specific versionId format in the gateway.
2026-04-07 01:56:51 +04:00
Ben McClelland
1fca33e738 Merge pull request #2006 from versity/ben/racing-put-delete
fix: retry link on ENOENT caused by racing DeleteObject
2026-04-02 09:57:48 -07:00
Ben McClelland
c26012905c fix: retry link on ENOENT caused by racing DeleteObject
A concurrent PutObject and DeleteObject on the same prefix directory
can race:
PutObject opens an O_TMPFILE in MetaTmpDir (not yet visible in the fs)
DeleteObject removes the last visible object in the prefix directory
and calls removeParents(), which rmdir's the now-empty prefix
directory
PutObject's link() tries to link the fd into a parent directory that
no longer exists

Fix by detecting ENOENT in the final link step (Linkat, Rename, and
MoveFile) and retrying after recreating the parent directory.

Also extract linkatOTmpfile() to consolidate the Linkat+EEXIST→Renameat
logic that was previously inline in link().

Fixes #1988
2026-04-02 08:14:44 -07:00
niksis02
052f2364cc feat: implement x-amz-source-expected-bucket-owner for CopyObject and UploadPartCopy
Closes #1897

Extract the `X-Amz-Source-Expected-Bucket-Owner` header for CopyObject and UploadPartCopy. Verify the source bucket owner in the backend and if the provided access key id doesn't match, return an `AccessDenied` error.
2026-04-01 21:44:33 +04:00
Ben McClelland
e0209ebab4 Merge pull request #1997 from versity/sis/copyobject-threshold
fix: enforce 5gb copy source object size threshold.
2026-03-31 12:27:11 -07:00
niksis02
59002b2650 feat: implement integration tests for browser-based POST object 2026-03-31 22:47:04 +04:00
niksis02
bbe246e8ec fix: enforce 5gb copy source object size threshold.
Fixes #1896

Enforces the S3 `5 GiB` copy source size limit across the posix and azure
backends for `CopyObject` and `UploadPartCopy`, returning `InvalidRequest` when
the source object exceeds the threshold.

The limit is now configurable via `--copy-object-threshold`
(`VGW_COPY_OBJECT_THRESHOLD`, default 5 GiB).
A new `--mp-max-parts flag` (`VGW_MP_MAX_PARTS`, default `10000`) has been added to make multipart upload parts number limit configurable.

No integration test has been added, as GitHub Actions cannot reliably
handle large objects.
2026-03-31 22:44:03 +04:00
Ben McClelland
13b3dc5267 fix: serialize concurrent CompleteMultipartUpload calls via rename
When two requests raced to complete the same multipart upload, the first
caller to finish would remove the part files and upload directory. The
second caller, already past the initial existence check, would then fail
mid-flight with confusing errors such as ErrInvalidPart or an I/O error
when trying to open a part that no longer exists.

Fix this by atomically renaming the upload directory from <uploadID> to
<uploadID>.inprogress at the very start of CompleteMultipartUploadWithCopy,
before any part data is read. A concurrent caller will now find the
original directory absent and receive a clean NoSuchUpload error. A
deferred rename restores the original name if the complete does not
succeed, allowing the client to retry.

ListMultipartUploads is updated to skip any directories whose name ends
in .inprogress so in-flight completes do not appear as pending uploads.
2026-03-27 16:14:20 -07:00
Ben McClelland
0234f3ecc7 Merge pull request #1992 from versity/ben/cleanup-fd
fix: cleanup file descriptor leaks with chown fails
2026-03-27 11:43:31 -07:00
Ben McClelland
495b38a899 fix: abort scoutfs multipart uploads on error after successful moveblocks
The scoutfs backend uses the move blocks ioctl when combining parts into the
final multipart upload object. Once a move blocks from any part is successful,
the original data is no longer in the part file. If the multipart upload fails
and retries, future complete multipart upload calls will not have the correct
data within the part files anymore.

To prevent this case, once a move blocks call is successful for an upload, any
future failure for the complete upload is set to auto-abort the upload to force
clients to re-upload the part data again.
2026-03-26 16:28:42 -07:00
Ben McClelland
927d1d668a fix: cleanup file descriptor leaks with chown fails
We were missing a few cases of cleaning up temp files and file
descriptors in the openTmpFile Chown() error cases.
2026-03-26 15:45:20 -07:00
Jakob van Santen
22f04312a7 fix: cleanup tempfiles when error prevents calling link
When a client provides an invalid or incomplete body for a single-part
upload, the handler returns before the link stage.  Factor tempfile
removal into cleanup() to catch all cases.
2026-03-20 08:25:44 -07:00
Jakob van Santen
56cb36d45a feat: add option to disable copy-file-range for multipart uploads
The standard io.Copy() will attempt to use copy_file_range when available.
However, this can cause problems with certain filesystems. Add an option
that will prevent io.Copy() from being able to use copy_file_range to force
a standard data copy between file descriptors when needed for completing
multipart uploads.
2026-03-20 08:23:01 -07:00
Ben McClelland
ae411fa3c1 Merge pull request #1974 from jvansanten/no-truncate
Always write complete files in sidecar meta provider
2026-03-19 16:01:50 -07:00
Ben McClelland
88e6396950 Merge pull request #1889 from versity/ben/fast-walk
feat: implement quicker backend/posix walk algorithm
2026-03-19 16:00:54 -07:00
tonyipm
33917ad6f3 feat: implement quicker backend/posix walk algorithm
Rewrite the posix Walk implementation to avoid the extra ReadDir per
directory that was noted as a TODO in the old code.  The new algorithm
holds all traversal state in a walkState struct and uses processDir to
interleave sibling entries in correct S3 lexicographic order without a
second syscall.

Key changes:
prefix optimisation: jump directly into the deepest matching directory
rather than scanning from the root on every call

marker short-circuit: skip entire subtrees that are lexically before
the marker, making paginated listing faster

Co-authored-by: Ben McClelland <ben.mcclelland@versity.com>
2026-03-18 17:39:16 -07:00
Ben McClelland
02f925a84b Merge pull request #1978 from 57-Wolve/57-Wolve-patch-s3-glacier-response
Update scoutfs glacier stage constants for ongoing requests
2026-03-18 10:25:06 -07:00
c32ddfff1a Update stage constants for ongoing requests
Fix issue with incompatible S3 response for offline and staging status.

Resolves issue with Restic Glacier support.
2026-03-18 09:46:55 -05:00
Jakob van Santen
d8f927519a Do not expose directories that can't be buckets 2026-03-18 15:33:10 +01:00
Jakob van Santen
0559807783 Always write complete files in sidecar meta provider
Some filesystems like dCache don't allow file truncation
2026-03-18 15:29:29 +01:00
Ben McClelland
97cc6bf23b chore: run go modernize tool
This is a fixup of the codebase using:
go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./...

This has no bahvior changes, and only updates safe changes for
modern go features.
2026-03-10 09:47:37 -07:00
Ben McClelland
8795c15621 feat: s3proxy default to credential chain with optional anonymous access
When access/secret are not provided, let AWS SDK v2 resolve credentials
from the default provider chain (env vars, IRSA, ECS/EC2 roles, etc.)
instead of forcing anonymous credentials.

Add an explicit anonymous credentials option for s3 proxy to force
backend anonymous access.

Fixes #1955
2026-03-09 17:41:50 -07:00
Ben McClelland
0f92d0ef4f Merge pull request #1916 from versity/ben/azure-test-falures
azure test failure fixes
2026-03-05 17:07:41 -08:00
niksis02
97bb70509f fix: change the way object metadata is stored in posix
Fixes #1909

Previously, the mapping between object metadata and posix object was as follows: for each metadata key, we stored a separate xattr with the `user.X-Amz-Meta.<key>` prefix. This resulted in syscall overhead when storing and deleting large numbers of metadata keys.
In addition, very long metadata keys caused failures because most posix filesystems limit xattr key lengths to 127–255 bytes, while S3 does not enforce such a per-key limit.

The logic has now been changed so that all object metadata is stored in a single xattr, `user.metadata`, as a JSON key/value object. For backward compatibility, metadata GET operations still fall back to the old mechanism (`metadata key -> xattr key`) when `user.metadata` is not present.

A new CLI utility has been added to convert all legacy object metadata to the new metadata format within the provided directory.

**Example usage:**

```
versitygw utils convert-xattr-metadata path/to/bucket
```

or

```
versitygw utils cxm path/to/bucket
```

It is recommended to run this command on bucket directories to convert all legacy metadata for every object in the bucket.
2026-03-06 01:44:14 +04:00
Ben McClelland
afbeb7cb6e fix: azure modernize part number loop check
The part number loop check can be simplified with slices.Contains.
2026-03-03 08:58:31 -08:00
Ben McClelland
271313b036 fix: azure close download body in CopyObject to prevent resource leak
When copying between two different Azure blobs, the source download
stream body was only consumed by PutObject but never explicitly closed.
If PutObject or any subsequent step returned an error, the underlying
HTTP connection held by the Azure SDK was never released, leaking both
the connection and any internal SDK retry goroutines attached to it.
Added a deferred close on downloadResp.Body immediately after the
successful DownloadStream call to ensure the body is always drained and
released regardless of the outcome.
2026-03-03 08:58:31 -08:00
Ben McClelland
dc31696e53 fix: azure ListBuckets pagination to use client-side continuation tokens
Azure's ListContainers Marker parameter requires an opaque internal token
(e.g. /accountname/containername) rather than a plain container name, so
passing MaxResults and our ContinuationToken directly to the Azure API
caused 400 OutOfRangeInput errors. Rework ListBuckets to iterate all Azure
pages client-side, skip entries at or before the ContinuationToken (matching
the posix backend's "start after" semantics), and stop once MaxBuckets items
have been collected, setting ContinuationToken to the last returned bucket
name. This avoids using Azure's NextMarker entirely and correctly handles
both unpaginated and paginated requests.
2026-03-03 08:58:31 -08:00
Ben McClelland
929048cbee fix: azure PresignedAuth_UploadPart test failure
Azure Storage's StageBlock REST API rejects Content-Length: 0
with InvalidHeaderValue. The tests (PresignedAuth_UploadPart,
UploadPart_success) upload a nil/empty body, which causes the
Azure SDK to send Content-Length: 0. Azurite is lenient and
accepts it; real Azure Storage does not.

Use a new metadata key ("Zerobytesparts") sett on the
.sgwtmp/multipart/<uploadId>/<object-hash> blob to track and
0 length parts.
2026-03-03 08:58:22 -08:00
Ben McClelland
fc52052e33 Merge pull request #1910 from versity/sis/posix-dir-obj-metadata-tagging
feat: add tagging support for directory objects in posix
2026-03-02 13:05:51 -08:00
Ben McClelland
75f5db445f Merge pull request #1905 from versity/ben/test-walk-versions
fix: improve WalkVersions() ancestor-directory guard for prefix filtering
2026-03-02 12:55:33 -08:00
niksis02
8550dba36f feat: add tagging support for directory objects in posix
Closes #1857

Adds object Tagging support for directory objects in `PutObject` posix. Updates the integration tests to test object metadata and tagging both for file and directory objects.
2026-03-02 18:56:50 +04:00
Ben McClelland
62da7be9cd Merge pull request #1907 from versity/ben/posix-cleanup
chore: fix typos and error return wrapping types in posix
2026-03-02 05:44:07 -08:00
Ben McClelland
fc5c0a36a6 chore: fix typos and error return wrapping types in posix
This is just a cleanup of typos, error messages and error types
to correctly wrap the returned errors in the posix backend. No
major logic changes.
2026-02-28 10:38:29 -08:00
Ben McClelland
7695be56b0 fix: store part checksums at destination path in UploadPartCopy
In sidecar mode, the three StoreAttribute/storeChecksums calls after the
copy loop were using objPath (the source object path) instead of the
destination part's bucket and partPath. This caused checksums and the
internal part-crc64nvme to be written under the source object's sidecar
directory, making them unresolvable when CompleteMultipartUploadWithCopy
tried to retrieve them. All three stores now use *upi.Bucket and partPath,
consistent with the etag store directly below them.
2026-02-28 10:24:15 -08:00
Ben McClelland
760f252936 fix: improve WalkVersions() ancestor-directory guard for prefix filtering
Replace the length-comparison condition with an explicit predicate that
is both more readable and correctly scoped: skip only when the visited
directory is a strict ancestor of the specified prefix (not a descendant
and not when prefix is empty).

Adds tests from the original bug report (#1864) to verify the fix and
guard against future regressions.
2026-02-28 10:10:17 -08:00
Ben McClelland
ca3c76b0f9 Merge pull request #1902 from s3-on-win/fix-1864-ListObjectVersions
fix: not return parent keys for ListObjectVersions
2026-02-28 10:00:43 -08:00