Skip to content

Local filesystem writes can retain stale tail data when overwriting shorter objects #3642

Description

@LuisLin17

Problem

The Local FS storage backend opens existing objects with os.O_RDWR|os.O_CREATE but without os.O_TRUNC:

handle, err := os.OpenFile(o.path, os.O_RDWR|os.O_CREATE, 0o644)

fsObject.Put writes from offset zero, so overwriting an existing object with shorter content leaves the old tail in the file. The same behavior affects the uncompressed StoreFile path that uses getHandle(false).

Example:

existing object: abcdefgh
new object:      xy
read result:     xycdefgh

This makes the Local FS backend inconsistent with object-storage backends, where a put replaces the complete object. It can corrupt local snapshots, cache files, or metadata when an object is rewritten with a smaller payload.

Proposed fix

Use truncation for complete-object writes, either by adding os.O_TRUNC to the write handle or by explicitly truncating and resetting the offset before copying:

handle, err := os.OpenFile(o.path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
    return nil, err
}

The truncating mode should only be used for complete replacement writes. Read, range-read, append, and multipart/random-offset paths should retain their existing semantics.

Tests

Please add two separate regression tests:

  1. Put path:

    • Write a longer payload to an object.
    • Overwrite it with a shorter payload using Put.
    • Verify Size equals the new payload length.
    • Verify reading returns exactly the new payload, with no stale suffix.
  2. Direct, uncompressed StoreFile path:

    • Store a longer source file into an object.
    • Overwrite it with a shorter source file using uncompressed StoreFile.
    • Verify Size equals the new source file length.
    • Verify reading returns exactly the new source content, with no stale suffix.

The compressed StoreFile path is a separate implementation and should not be conflated with this regression.

Relevant code:

  • packages/shared/pkg/storage/storage_fs.go
  • fsObject.Put
  • fsObject.StoreFile
  • fsObject.getHandle

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions