Add TOASTY_WRITE_CREATE_IF_MISSING flag for server-side file auto-creation

Adds support for automatically creating files when write operations target
non-existent files. This is essential for REST PUT operations which should
be able to create new resources.

Implementation:
- Added TOASTY_WRITE_CREATE_IF_MISSING flag to ToastyFS API header
- Updated toasty_write() and toasty_begin_write() to accept flags parameter
- Client sends write flags in MESSAGE_TYPE_WRITE message to metadata server
- Metadata server parses flags and handles file auto-creation atomically:
  * When write fails with FILETREE_NOENT and flag is set
  * Logs creation to WAL for crash consistency
  * Creates file with default 4096-byte chunk size
  * Retries write with newly created file's generation tag
- Updated web proxy PUT handler to use TOASTY_WRITE_CREATE_IF_MISSING
- Updated examples and simulation client for new API signature

Benefits:
- Works for both sync and async APIs
- Single round trip (atomic server-side operation)
- Prevents race conditions
- Proper WAL logging ensures crash consistency
This commit is contained in:
Claude
2025-12-03 18:48:42 +00:00
parent a082f46b78
commit 8160c5ac51
9 changed files with 80 additions and 16 deletions
+7 -3
View File
@@ -124,6 +124,7 @@ typedef struct {
uint32_t num_chunks;
uint32_t chunk_size;
uint64_t expect_gen;
uint32_t flags; // Write operation flags (e.g., TOASTY_WRITE_CREATE_IF_MISSING)
UploadSchedule *uploads;
int num_uploads;
int cap_uploads;
@@ -744,7 +745,7 @@ unlock_and_exit:
ToastyHandle toasty_begin_write(ToastyFS *toasty,
ToastyString path, int off, void *src, int len,
ToastyVersionTag vtag)
ToastyVersionTag vtag, uint32_t flags)
{
int opidx = -1;
ToastyHandle handle = TOASTY_INVALID;
@@ -759,6 +760,7 @@ ToastyHandle toasty_begin_write(ToastyFS *toasty,
goto unlock_and_exit;
toasty->operations[opidx].path = path; // TODO: must be a copy
toasty->operations[opidx].flags = flags;
if (send_read_message(toasty, opidx, TAG_RETRIEVE_METADATA_FOR_WRITE, path, off, len, vtag) < 0) {
free_operation(toasty, opidx);
@@ -1974,6 +1976,7 @@ static void process_event_for_write(ToastyFS *toasty,
ToastyString path = toasty->operations[opidx].path;
uint32_t offset = toasty->operations[opidx].off;
uint32_t length = toasty->operations[opidx].len;
uint32_t flags = toasty->operations[opidx].flags;
if (path.len > UINT16_MAX) {
assert(0); // TODO
@@ -1984,6 +1987,7 @@ static void process_event_for_write(ToastyFS *toasty,
uint32_t num_chunks = num_upload_results;
message_write(&writer, &expect_gen, sizeof(expect_gen));
message_write(&writer, &flags, sizeof(flags));
message_write(&writer, &path_len, sizeof(path_len));
message_write(&writer, path.ptr, path.len);
message_write(&writer, &offset, sizeof(offset));
@@ -2449,9 +2453,9 @@ int toasty_read(ToastyFS *toasty, ToastyString path,
}
int toasty_write(ToastyFS *toasty, ToastyString path,
int off, void *src, int len, ToastyVersionTag *vtag)
int off, void *src, int len, ToastyVersionTag *vtag, uint32_t flags)
{
ToastyHandle handle = toasty_begin_write(toasty, path, off, src, len, vtag ? *vtag : 0);
ToastyHandle handle = toasty_begin_write(toasty, path, off, src, len, vtag ? *vtag : 0, flags);
if (handle == TOASTY_INVALID)
return -1;
+26
View File
@@ -537,6 +537,10 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
if (!binary_read(&reader, &expect_gen, sizeof(expect_gen)))
return -1;
uint32_t flags;
if (!binary_read(&reader, &flags, sizeof(flags)))
return -1;
char path_mem[1<<10];
uint16_t path_len;
@@ -634,6 +638,28 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
int ret = file_tree_write(&state->file_tree, path, offset, length,
num_chunks, expect_gen, &new_gen, new_hashes, removed_hashes, &num_removed);
// If write failed because file doesn't exist and CREATE_IF_MISSING flag is set,
// create the file and retry the write
#define TOASTY_WRITE_CREATE_IF_MISSING (1 << 0)
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;
// Log the creation in the WAL
if (wal_append_create(&state->wal, path, false, chunk_size) < 0) {
assert(0); // TODO
}
uint64_t create_gen;
int create_ret = file_tree_create_entity(&state->file_tree, path, false, chunk_size, &create_gen);
if (create_ret == 0) {
// File created successfully, retry the write with the new generation
ret = file_tree_write(&state->file_tree, path, offset, length,
num_chunks, create_gen, &new_gen, new_hashes, removed_hashes, &num_removed);
}
}
if (ret < 0) {
string desc = file_tree_strerror(ret);
+1 -1
View File
@@ -209,7 +209,7 @@ int simulation_client_step(SimulationClient *client, void **contexts,
ptr = malloc(len);
if (ptr == NULL) assert(0);
memset(ptr, 'a', len);
handle = toasty_begin_write(client->toasty, entry.path, off, ptr, len, TOASTY_VERSION_TAG_EMPTY);
handle = toasty_begin_write(client->toasty, entry.path, off, ptr, len, TOASTY_VERSION_TAG_EMPTY, 0);
//printf("[Client] submit write (path=%s, off=%d, len=%d)\n", entry.path, off, len);
break;
}