Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
defb5c0
feat: add .symlinks file support for AWS S3 compatibility
alexsavio Jan 22, 2026
2b79a9d
fixes
alexsavio Jan 26, 2026
439ebaf
restrict symlinks to s3-compatible systems
alexsavio Jan 27, 2026
e3b3931
rename .symlinks to .geesefs_symlinks
alexsavio Jan 29, 2026
7780cda
fixes
alexsavio Jan 29, 2026
a8fea61
docs and tests
alexsavio Jan 29, 2026
dc145dc
fixes
alexsavio Feb 1, 2026
f623a78
hide symlink files
alexsavio Feb 2, 2026
eedded4
fixes
alexsavio Feb 3, 2026
7f7e865
update doc
alexsavio Feb 3, 2026
bcd330f
split tests
alexsavio Feb 3, 2026
ed56850
add test for direct symlink access
alexsavio Feb 16, 2026
30ff732
fix: deep copy symlinks cache before mutation to prevent corruption o…
alexsavio Feb 17, 2026
9802f2e
fix: release parent.mu during symlinks I/O to prevent lock contention
alexsavio Feb 17, 2026
bf6c28a
fix: add TTL check to loadSymlinksCache to avoid excessive S3 requests
alexsavio Feb 17, 2026
f10a88b
fix: restore --symlink-attr CLI flag removed by symlinks-file PR
alexsavio Feb 17, 2026
99e3fdb
fix: use AWS SDK typed errors instead of fragile string matching
alexsavio Feb 17, 2026
c2e2ce4
test: add tests for AWS SDK typed error detection paths
alexsavio Feb 17, 2026
d187ac6
fix: update .geesefs_symlinks on symlink rename (same-dir and cross-dir)
alexsavio Feb 17, 2026
a701fc1
fix: use isVirtualSymlink field to distinguish virtual from S3-backed…
alexsavio Feb 17, 2026
03d2732
refactor: extract virtual symlink helpers to reduce duplication
alexsavio Feb 17, 2026
ab67ceb
fix: use conditional write before deleting empty symlinks file
alexsavio Feb 17, 2026
621eeca
perf: guard debug logging in LookUpCached with level check
alexsavio Feb 17, 2026
c43f554
cleanup: remove dead code (SymlinksFileCache, isSymlinkFromCache)
alexsavio Feb 17, 2026
3965b0c
fix: add jitter to retry backoff in SaveSymlinksFileWithRetry
alexsavio Feb 17, 2026
ed4ee63
perf: use compact JSON for symlinks file serialization
alexsavio Feb 17, 2026
83e7ec2
refactor: replace IsS3Compatible() with SupportsConditionalWrites cap…
alexsavio Feb 17, 2026
0071597
test: add cache corruption recovery and edge case tests
alexsavio Feb 17, 2026
97ea609
fix: clip copy ranges to source object size in copyUnmodifiedParts
alexsavio Feb 17, 2026
d30d74a
feat: batch symlink changes with configurable delay before S3 PUT
alexsavio Feb 17, 2026
86fa5c7
fix: capture finalSize under lock to prevent race with concurrent writes
alexsavio Feb 18, 2026
b0145a9
fix: use stable source size for UploadPartCopy range clipping
alexsavio Feb 18, 2026
d4f1ba1
fix: use HeadBlob to get actual source size for UploadPartCopy clipping
alexsavio Feb 18, 2026
933da63
fix: harden UploadPartCopy range handling
alexsavio Feb 18, 2026
bfcfab4
fix: zero-fill fallocate extensions
alexsavio Feb 18, 2026
d1ae566
perf: optimize symlink flush tracking and fallocate path
alexsavio Feb 19, 2026
ad04a74
fix: harden multipart copy source-size handling
alexsavio Feb 19, 2026
cb9d086
fix: restore fallocate extension zero-fill
alexsavio Feb 19, 2026
ae9d669
chore: set version number for this release
alexsavio Mar 2, 2026
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
19 changes: 15 additions & 4 deletions core/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type Capabilities struct {
// indicates that the blob store has native support for directories
DirBlob bool
Name string
// SupportsConditionalWrites indicates the backend supports If-Match/If-None-Match
// conditional headers on PutObject (used for optimistic locking of .geesefs_symlinks)
SupportsConditionalWrites bool
}

type HeadBlobInput struct {
Expand Down Expand Up @@ -116,10 +119,11 @@ type CopyBlobOutput struct {
}

type GetBlobInput struct {
Key string
Start uint64
Count uint64
IfMatch *string
Key string
Start uint64
Count uint64
IfMatch *string
IfNoneMatch *string // For conditional GET - returns 304 if ETag matches
}

type GetBlobOutput struct {
Expand All @@ -138,6 +142,13 @@ type PutBlobInput struct {

Body io.ReadSeeker
Size *uint64

// IfMatch specifies an ETag; the request succeeds only if the object's ETag matches.
// Used for optimistic locking / conditional updates.
IfMatch *string
// IfNoneMatch specifies that the request should succeed only if the object does not exist.
// Set to "*" to prevent overwriting an existing object.
IfNoneMatch *string
}

type PutBlobOutput struct {
Expand Down
24 changes: 21 additions & 3 deletions core/backend_s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ func NewS3(bucket string, flags *cfg.FlagStorage, config *cfg.S3Config) (*S3Back
flags: flags,
config: config,
cap: Capabilities{
Name: "s3",
MaxMultipartSize: 5 * 1024 * 1024 * 1024,
Name: "s3",
MaxMultipartSize: 5 * 1024 * 1024 * 1024,
SupportsConditionalWrites: true,
},
}

Expand Down Expand Up @@ -925,7 +926,14 @@ func (s *S3Backend) GetBlob(param *GetBlobInput) (*GetBlobOutput, error) {
}
get.Range = &bytes
}
// TODO handle IfMatch

if param.IfMatch != nil {
get.IfMatch = param.IfMatch
}

if param.IfNoneMatch != nil {
get.IfNoneMatch = param.IfNoneMatch
}

req, resp := s.GetObjectRequest(&get)
err := req.Send()
Expand Down Expand Up @@ -990,6 +998,16 @@ func (s *S3Backend) PutBlob(param *PutBlobInput) (*PutBlobOutput, error) {
put.ACL = &s.config.ACL
}

// Conditional write support (S3 feature since August 2024)
// IfMatch: only write if ETag matches (optimistic locking for updates)
// IfNoneMatch: only write if object doesn't exist (create-if-not-exists)
if param.IfMatch != nil {
put.IfMatch = param.IfMatch
}
if param.IfNoneMatch != nil {
put.IfNoneMatch = param.IfNoneMatch
}

req, resp := s.PutObjectRequest(put)
err := req.Send()
if err != nil {
Expand Down
Loading