Track actual bytes read in read operations
Add functionality to determine the actual number of bytes read during read operations, making it possible to detect when a read was truncated because it went past the end of the file. Changes: - Modified file_tree_read() to calculate and return actual_bytes via new output parameter - Updated metadata server to send actual_bytes in READ_SUCCESS messages - Added bytes_read field to ToastyResult structure - Modified client to parse, store, and report actual bytes read - Updated toasty_read() to return the actual number of bytes read instead of always returning 0 - Fixed web server to use bytes_read field instead of non-existent count field This allows clients to distinguish between: - Reading zeros because the file is sparse (has holes) - Reading past the end of the file (truncated read)
This commit is contained in:
@@ -159,6 +159,7 @@ typedef struct {
|
|||||||
ToastyResultType type;
|
ToastyResultType type;
|
||||||
ToastyListing listing;
|
ToastyListing listing;
|
||||||
void *user;
|
void *user;
|
||||||
|
int bytes_read; // For read operations: actual number of bytes read
|
||||||
} ToastyResult;
|
} ToastyResult;
|
||||||
|
|
||||||
// If the operation specified by "handle" is complete,
|
// If the operation specified by "handle" is complete,
|
||||||
|
|||||||
+16
-4
@@ -116,6 +116,7 @@ typedef struct {
|
|||||||
int ranges_head;
|
int ranges_head;
|
||||||
int ranges_count;
|
int ranges_count;
|
||||||
int num_pending;
|
int num_pending;
|
||||||
|
int actual_bytes; // For reads: actual number of bytes that can be read
|
||||||
|
|
||||||
// Write fields
|
// Write fields
|
||||||
SHA256 *hashes;
|
SHA256 *hashes;
|
||||||
@@ -1006,12 +1007,22 @@ static void process_event_for_read(ToastyFS *toasty,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read actual bytes that can be read
|
||||||
|
uint32_t actual_bytes;
|
||||||
|
if (!binary_read(&reader, &actual_bytes, sizeof(actual_bytes))) {
|
||||||
|
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_ERROR, .user=toasty->operations[opidx].user };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store actual_bytes for later use
|
||||||
|
toasty->operations[opidx].actual_bytes = actual_bytes;
|
||||||
|
|
||||||
// Calculate which chunks we need
|
// Calculate which chunks we need
|
||||||
int off = toasty->operations[opidx].off;
|
int off = toasty->operations[opidx].off;
|
||||||
int len = toasty->operations[opidx].len;
|
int len = toasty->operations[opidx].len;
|
||||||
|
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_SUCCESS, .user=toasty->operations[opidx].user };
|
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_SUCCESS, .user=toasty->operations[opidx].user, .bytes_read=0 };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1166,7 +1177,7 @@ static void process_event_for_read(ToastyFS *toasty,
|
|||||||
} else {
|
} else {
|
||||||
// No chunks to download
|
// No chunks to download
|
||||||
sys_free(ranges);
|
sys_free(ranges);
|
||||||
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_SUCCESS, .user=toasty->operations[opidx].user };
|
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_SUCCESS, .user=toasty->operations[opidx].user, .bytes_read=toasty->operations[opidx].actual_bytes };
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -1241,7 +1252,7 @@ static void process_event_for_read(ToastyFS *toasty,
|
|||||||
if (toasty->operations[opidx].num_pending == 0) {
|
if (toasty->operations[opidx].num_pending == 0) {
|
||||||
sys_free(toasty->operations[opidx].ranges);
|
sys_free(toasty->operations[opidx].ranges);
|
||||||
toasty->operations[opidx].ranges = NULL;
|
toasty->operations[opidx].ranges = NULL;
|
||||||
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_SUCCESS, .user=toasty->operations[opidx].user };
|
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_SUCCESS, .user=toasty->operations[opidx].user, .bytes_read=toasty->operations[opidx].actual_bytes };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2360,8 +2371,9 @@ int toasty_read(ToastyFS *toasty, ToastyString path,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int bytes_read = result.bytes_read;
|
||||||
toasty_free_result(&result);
|
toasty_free_result(&result);
|
||||||
return 0; // TODO: return the number of bytes read?
|
return bytes_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
int toasty_write(ToastyFS *toasty, ToastyString path,
|
int toasty_write(ToastyFS *toasty, ToastyString path,
|
||||||
|
|||||||
+13
-1
@@ -411,7 +411,7 @@ int file_tree_write(FileTree *ft, string path,
|
|||||||
|
|
||||||
int file_tree_read(FileTree *ft, string path,
|
int file_tree_read(FileTree *ft, string path,
|
||||||
uint64_t off, uint64_t len, uint64_t *chunk_size,
|
uint64_t off, uint64_t len, uint64_t *chunk_size,
|
||||||
SHA256 *hashes, int max_hashes)
|
SHA256 *hashes, int max_hashes, uint64_t *actual_bytes)
|
||||||
{
|
{
|
||||||
int num_comps;
|
int num_comps;
|
||||||
string comps[MAX_COMPS];
|
string comps[MAX_COMPS];
|
||||||
@@ -432,6 +432,18 @@ int file_tree_read(FileTree *ft, string path,
|
|||||||
|
|
||||||
*chunk_size = f->chunk_size;
|
*chunk_size = f->chunk_size;
|
||||||
|
|
||||||
|
// Calculate file size (number of chunks * chunk size)
|
||||||
|
uint64_t file_size = f->num_chunks * f->chunk_size;
|
||||||
|
|
||||||
|
// Calculate actual bytes that can be read
|
||||||
|
if (off >= file_size) {
|
||||||
|
*actual_bytes = 0;
|
||||||
|
} else if (off + len > file_size) {
|
||||||
|
*actual_bytes = file_size - off;
|
||||||
|
} else {
|
||||||
|
*actual_bytes = len;
|
||||||
|
}
|
||||||
|
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ int file_tree_list (FileTree *ft, string path, ListItem *items, int
|
|||||||
int file_tree_create_entity (FileTree *ft, string path, bool is_dir, uint64_t chunk_size);
|
int file_tree_create_entity (FileTree *ft, string path, bool is_dir, uint64_t chunk_size);
|
||||||
int file_tree_delete_entity (FileTree *ft, string path);
|
int file_tree_delete_entity (FileTree *ft, string path);
|
||||||
int file_tree_write (FileTree *ft, string path, uint64_t off, uint64_t len, uint32_t num_chunks, uint32_t chunk_size, SHA256 *prev_hashes, SHA256 *hashes, SHA256 *removed_hashes, int *num_removed);
|
int file_tree_write (FileTree *ft, string path, uint64_t off, uint64_t len, uint32_t num_chunks, uint32_t chunk_size, SHA256 *prev_hashes, SHA256 *hashes, SHA256 *removed_hashes, int *num_removed);
|
||||||
int file_tree_read (FileTree *ft, string path, uint64_t off, uint64_t len, uint64_t *chunk_size, SHA256 *hashes, int max_hashes);
|
int file_tree_read (FileTree *ft, string path, uint64_t off, uint64_t len, uint64_t *chunk_size, SHA256 *hashes, int max_hashes, uint64_t *actual_bytes);
|
||||||
string file_tree_strerror (int code);
|
string file_tree_strerror (int code);
|
||||||
int file_tree_serialize (FileTree *ft, int (*flush_fn)(char*,int,void*), void *flush_data);
|
int file_tree_serialize (FileTree *ft, int (*flush_fn)(char*,int,void*), void *flush_data);
|
||||||
int file_tree_deserialize (FileTree *ft, int (*read_fn)(char*,int,void*), void *read_data);
|
int file_tree_deserialize (FileTree *ft, int (*read_fn)(char*,int,void*), void *read_data);
|
||||||
|
|||||||
+10
-1
@@ -406,8 +406,9 @@ process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
|
|||||||
#define MAX_READ_HASHES 128
|
#define MAX_READ_HASHES 128
|
||||||
|
|
||||||
uint64_t chunk_size;
|
uint64_t chunk_size;
|
||||||
|
uint64_t actual_bytes;
|
||||||
SHA256 hashes[MAX_READ_HASHES];
|
SHA256 hashes[MAX_READ_HASHES];
|
||||||
int ret = file_tree_read(&state->file_tree, path, offset, length, &chunk_size, hashes, MAX_READ_HASHES);
|
int ret = file_tree_read(&state->file_tree, path, offset, length, &chunk_size, hashes, MAX_READ_HASHES, &actual_bytes);
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
|
||||||
@@ -439,6 +440,14 @@ process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
|
|||||||
uint32_t tmp = chunk_size;
|
uint32_t tmp = chunk_size;
|
||||||
message_write(&writer, &tmp, sizeof(tmp));
|
message_write(&writer, &tmp, sizeof(tmp));
|
||||||
|
|
||||||
|
// Send the actual number of bytes that can be read
|
||||||
|
if (actual_bytes > UINT32_MAX) {
|
||||||
|
message_writer_free(&writer);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint32_t tmp_actual = actual_bytes;
|
||||||
|
message_write(&writer, &tmp_actual, sizeof(tmp_actual));
|
||||||
|
|
||||||
uint32_t num_hashes = ret;
|
uint32_t num_hashes = ret;
|
||||||
message_write(&writer, &num_hashes, sizeof(num_hashes));
|
message_write(&writer, &num_hashes, sizeof(num_hashes));
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -197,13 +197,13 @@ int main(void)
|
|||||||
// First, ACK the byte we just read, even if it's
|
// First, ACK the byte we just read, even if it's
|
||||||
// just 0 bytes (every bodybuf must by paired with
|
// just 0 bytes (every bodybuf must by paired with
|
||||||
// a bodyack).
|
// a bodyack).
|
||||||
proxied[i].transferred += result.count;
|
proxied[i].transferred += result.bytes_read;
|
||||||
int ack = proxied[i].head_only ? 0 : result.count;
|
int ack = proxied[i].head_only ? 0 : result.bytes_read;
|
||||||
http_response_builder_bodyack(proxied[i].builder, ack);
|
http_response_builder_bodyack(proxied[i].builder, ack);
|
||||||
|
|
||||||
// If we didn't reach the end of the file, start
|
// If we didn't reach the end of the file, start
|
||||||
// a new read.
|
// a new read.
|
||||||
if (result.count > 0) {
|
if (result.bytes_read > 0) {
|
||||||
|
|
||||||
// Make sure there is some free space in the buffer
|
// Make sure there is some free space in the buffer
|
||||||
int mincap = 1<<10; // TODO: Choose based on overall file size
|
int mincap = 1<<10; // TODO: Choose based on overall file size
|
||||||
|
|||||||
Reference in New Issue
Block a user