Add MISSING_FILE_GENERATION special value for "file does not exist"

Implements a new special generation counter value (UINT64_MAX) that
allows clients to assert that a file should NOT exist when performing
operations.

Primary use case: Atomic "create-if-not-exists" operations
When combined with TOASTY_WRITE_CREATE_IF_MISSING flag:
- File doesn't exist → server creates file and writes (SUCCESS)
- File exists → gen_match fails → BADGEN error (prevents overwrite)

This enables race-condition-free file creation where you want to
create a NEW file or fail if it already exists.

Key changes:
- Added MISSING_FILE_GENERATION constant to file_tree.h
- Updated gen_match() to return false when checking existing entities
  against MISSING_FILE_GENERATION
- Modified file_tree_delete_entity() to succeed (no-op) when file is
  missing and MISSING_FILE_GENERATION is specified (idempotent delete)
- Modified file_tree_write() to validate against the new value
- Added documentation in metadata_server.c explaining how
  MISSING_FILE_GENERATION works WITH CREATE_IF_MISSING
- Updated protocol documentation in PROTOCOL.txt
- Added comprehensive feature documentation in MISSING_FILE_GENERATION.md

Additional use cases:
- Idempotent delete operations (delete only if not already gone)
- Detecting unexpected file creation in validation/testing scenarios

The implementation is backward compatible and type-safe. Files are
never assigned MISSING_FILE_GENERATION as their actual generation value.
This commit is contained in:
Claude
2025-12-05 09:10:17 +01:00
committed by cozis
parent e73bb3a7d8
commit 6746b0d5fb
6 changed files with 85 additions and 11 deletions
+6 -1
View File
@@ -430,6 +430,7 @@ process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
uint64_t actual_bytes;
SHA256 hashes[MAX_READ_HASHES];
int ret = file_tree_read(&state->file_tree, path, offset, length, &gen, &chunk_size, hashes, MAX_READ_HASHES, &actual_bytes);
assert(gen != NO_GENERATION);
if (ret < 0) {
@@ -476,6 +477,7 @@ process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_READ_SUCCESS);
assert(gen != NO_GENERATION);
message_write(&writer, &gen, sizeof(gen));
if (chunk_size > UINT32_MAX) {
@@ -668,7 +670,10 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
num_chunks, expect_gen, &new_gen, new_hashes, removed_hashes, &num_removed, truncate_after);
// If write failed because file doesn't exist and CREATE_IF_MISSING flag is set,
// create the file and retry the write
// create the file and retry the write.
// Note: MISSING_FILE_GENERATION works WITH CREATE_IF_MISSING to implement
// atomic "create-only-if-not-exists" semantics: creates if missing (NOENT),
// but fails if file already exists (BADGEN from gen_match).
if (ret == FILETREE_NOENT && (flags & TOASTY_WRITE_CREATE_IF_MISSING)) {
// Create the file with default chunk size of 4096 bytes
uint64_t chunk_size = 4096;