Skip to content

v0.10.0 — WITHDRAWN, do not use

Choose a tag to compare

@github-actions github-actions released this 24 Feb 05:03

⚠️ This release is WITHDRAWN — do not use it

A deep audit of v0.10.0 found defects that prevent the shipped default configuration from mounting
and that silently lose or corrupt user data. v0.10.1 is in progress. Until it ships, no tagged
version of ObjectFS should be used for data you care about.

The three that matter most

C1 — the default configuration cannot mount.
internal/config/config.go defaults compression.algorithm to gzip, but
internal/compression/codec.go implements only none, zstd, and lz4. Every layer that reads
config treats gzip as valid — only the codec factory disagrees — so objectfs s3://bucket /mnt
exits with Failed to start adapter. examples/config.yaml ships the same broken value.

H7 — offset writes truncate the object.
The write-buffer flush callback in internal/adapter/adapter.go is handed (key, data, offset) and
calls backend.PutObject(ctx, key, data), discarding the offset. PutObject is a whole-object
replace, so:

$ dd if=/dev/zero of=f bs=1M count=1        # 1 MiB file
$ printf X | dd of=f bs=1 seek=1048575 conv=notrunc
$ ls -l f
-rw-r--r--  1 byte                          # the other 1,048,575 are gone

Non-contiguous writes — SQLite, mmap writeback, tar, HDF5 — return EIO instead. Flush errors are
recorded to a stats counter and never returned, so close(2) reports success after a failed upload.

C4 — read amplification on every object when compression is enabled.
internal/storage/s3/backend.go decides whole-object-versus-ranged fetch from the compression
configuration rather than from the object being read. A ranged read of any object — including
objects never compressed, objects below min_size, and objects written by other tools — downloads
the whole object, and parallel reads are disabled bucket-wide. Measured against real S3 in
us-west-2 with a fixed 4 KiB read:

object size compression off compression on penalty
16 MiB 123 ms 1.92 s 15.6×
64 MiB 117 ms 5.03 s 43.0×
256 MiB 227 ms 49.2 s 216.5×

A 4 KiB read of a 10 GiB object transfers 10 GiB.

Also withdrawn for

  • Silent corruption when the codec configuration changes. Decompress in
    internal/compression/s3_integration.go returns the payload unchanged when the stored
    Content-Encoding doesn't match the configured codec — so an object written with zstd and read
    after switching to lz4 emits the raw compressed frame with exit status 0. The objectfs-sha256
    metadata this very release added is written and never read, so nothing catches it.
  • The read cache cannot hit and is never invalidated. The cache key includes the requested
    length, so the Lookup metadata cache never hits (one S3 HEAD per path component per stat,
    forever), short reads at EOF are uncacheable, and the 16 MB chunked cache population added in this
    release is unreachable. There are no cache.Delete calls anywhere in internal/fuse, so a read
    after a write on the same descriptor returns pre-write bytes for up to the 5-minute TTL.
  • A reachable panic that unmounts the filesystem. GetObject with a negative size slices
    data[offset:offset+size] with neither bounds arm firing — slice bounds out of range [100:99].
    This kills the mount process and takes every open file descriptor with it.
  • This release's headline feature is inactive in production. buildS3Config maps 6 of roughly 30
    s3.Config fields and does not map ParallelReadThreshold; NewBackend does not backfill it. The
    parallel range GET path is gated on threshold > 0, so it never runs on a real mount. PoolSize
    is likewise unmapped, leaving a zero-capacity semaphore that blocks forever in
    GetObjects/PutObjects.
  • rm and rmdir reported success without deleting. Fixed after this tag (#163): the operations
    now fail loudly with EROFS rather than silently lying.
  • Windows is not supported. The cgofuse build tag has never compiled. Any Windows claim in the
    v0.10.0 documentation is wrong.

Why the test suite didn't catch this

Every defect above is a seam defect: a value correctly produced at one layer and silently dropped
at the boundary to the next. The suite's 32,680 lines across 90 files mock the neighbouring layer in
each case, so these are invisible to it by construction. v0.10.1 adds a differential-testing oracle —
identical operation sequences run against ObjectFS and against the local OS filesystem, asserted
byte-for-byte — plus fuzz targets over the write path, the range/slice domain, config loading, and
compression round-trips. Those tests are being written before the fixes, so each fix lands with a
failing-then-passing test.

Track progress: issues ·
milestones