fix: scanner mid-directory resume bug, -c -s protocol fix, 50 MB test data

- Fix scanner: when chunk fills up mid-directory, save DIR handle so
  remaining files in that directory are not skipped (pre-existing bug)
- Fix -c -s: add STATUS_CHUNK to protocol, send it before chunk data,
  handle it on the server side for both single and multithreaded paths
- Extract chunk_serialize/chunk_deserialize from chunk_compress/decompress
- Remove dead declarations (file_receive_from_buffer, etc.)
- Fix memory leak in receive_file_receive (free -> data_destroy)
- test.py generates ~50 MB of test data across bulk files
- All 8 integration tests + 7 unit tests pass
This commit is contained in:
2026-07-04 22:20:24 +02:00
parent 7f6fa968ee
commit 4867e04677
33 changed files with 10211 additions and 30 deletions
+53 -18
View File
@@ -7,10 +7,14 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include <unistd.h>
DirectoryScanner *directory_scanner_create(char *root_directory) { DirectoryScanner *directory_scanner_create(char *root_directory) {
DirectoryScanner *scanner = malloc(sizeof(DirectoryScanner)); DirectoryScanner *scanner = malloc(sizeof(DirectoryScanner));
scanner->directories = queue_create(100, free); scanner->directories = queue_create(100, free);
scanner->current_dir = NULL;
scanner->current_path = NULL;
queue_enqueue(scanner->directories, str_dup(root_directory)); queue_enqueue(scanner->directories, str_dup(root_directory));
return scanner; return scanner;
} }
@@ -18,11 +22,16 @@ DirectoryScanner *directory_scanner_create(char *root_directory) {
void directory_scanner_destroy(DirectoryScanner *scanner) { void directory_scanner_destroy(DirectoryScanner *scanner) {
if (scanner == NULL) if (scanner == NULL)
return; return;
if (scanner->current_dir) {
closedir(scanner->current_dir);
scanner->current_dir = NULL;
}
free(scanner->current_path);
queue_destroy(scanner->directories); queue_destroy(scanner->directories);
free(scanner); free(scanner);
} }
Chunk *chunk_data_to_chunk(ArrayList *chunk_data) { static Chunk *chunk_data_to_chunk(ArrayList *chunk_data) {
void **chunk_items = array_list_to_array(chunk_data); void **chunk_items = array_list_to_array(chunk_data);
Chunk *chunk = chunk_create((File **)chunk_items, chunk_data->size); Chunk *chunk = chunk_create((File **)chunk_items, chunk_data->size);
free(chunk_items); free(chunk_items);
@@ -31,29 +40,57 @@ Chunk *chunk_data_to_chunk(ArrayList *chunk_data) {
return chunk; return chunk;
} }
static int open_next_directory(DirectoryScanner *scanner) {
if (scanner->current_dir) {
closedir(scanner->current_dir);
scanner->current_dir = NULL;
}
free(scanner->current_path);
if (queue_is_empty(scanner->directories))
return 0;
scanner->current_path = (char *)queue_dequeue(scanner->directories);
scanner->current_dir = opendir(scanner->current_path);
if (scanner->current_dir == NULL) {
perror("Could not open directory!");
exit(EXIT_FAILURE);
}
return 1;
}
Chunk *directory_scanner_next(DirectoryScanner *scanner) { Chunk *directory_scanner_next(DirectoryScanner *scanner) {
ArrayList *chunk_data = array_list_create(file_destroy); ArrayList *chunk_data = array_list_create(file_destroy);
unsigned long long chunk_data_size = 0; unsigned long long chunk_data_size = 0;
while (!queue_is_empty(scanner->directories)) { while (1) {
char *path = (char *)queue_dequeue(scanner->directories); if (scanner->current_dir == NULL) {
DIR *dir; if (!open_next_directory(scanner))
struct dirent *entry; break;
dir = opendir(path);
if (dir == NULL) {
perror("Could not open directory!");
exit(EXIT_FAILURE);
} }
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { struct dirent *entry = readdir(scanner->current_dir);
if (entry == NULL) {
closedir(scanner->current_dir);
scanner->current_dir = NULL;
free(scanner->current_path);
scanner->current_path = NULL;
continue; continue;
} }
char *cur_path = path_cat(path, entry->d_name);
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char *cur_path = path_cat(scanner->current_path, entry->d_name);
struct stat stats; struct stat stats;
stat(cur_path, &stats); if (stat(cur_path, &stats) != 0) {
if (!S_ISREG(stats.st_mode)) free(cur_path);
continue;
}
if (!S_ISREG(stats.st_mode)) {
queue_enqueue(scanner->directories, (void *)cur_path); queue_enqueue(scanner->directories, (void *)cur_path);
else { } else {
File *file = file_create(cur_path, &stats); File *file = file_create(cur_path, &stats);
array_list_add(chunk_data, file); array_list_add(chunk_data, file);
chunk_data_size += file->stats.st_size; chunk_data_size += file->stats.st_size;
@@ -62,9 +99,7 @@ Chunk *directory_scanner_next(DirectoryScanner *scanner) {
free(cur_path); free(cur_path);
} }
} }
closedir(dir);
free(path);
}
if (chunk_data->size > 0) if (chunk_data->size > 0)
return chunk_data_to_chunk(chunk_data); return chunk_data_to_chunk(chunk_data);
return NULL; return NULL;
+4
View File
@@ -3,8 +3,12 @@
#include "chunk.h" #include "chunk.h"
#include "queue.h" #include "queue.h"
#include <dirent.h>
typedef struct { typedef struct {
Queue *directories; Queue *directories;
DIR *current_dir;
char *current_path;
} DirectoryScanner; } DirectoryScanner;
DirectoryScanner *directory_scanner_create(char *root_directory); DirectoryScanner *directory_scanner_create(char *root_directory);
+20 -2
View File
@@ -56,10 +56,13 @@ def generate_test_files(source_dir):
shutil.rmtree(source_dir) shutil.rmtree(source_dir)
os.makedirs(source_dir) os.makedirs(source_dir)
target_total = 50 * 1024 * 1024
written = 0
files = { files = {
"small.txt": b"hello world\n", "small.txt": b"hello world\n",
"medium.txt": b"line\n" * 1000, "medium.txt": b"the quick brown fox jumps over the lazy dog\n" * 5000,
"binary.bin": bytes(range(256)), "binary.bin": bytes(range(256)) * 1000,
"nested/subdir/deep.txt": b"deeply nested file\n", "nested/subdir/deep.txt": b"deeply nested file\n",
"nested/another.txt": b"another nested file\n" * 50, "nested/another.txt": b"another nested file\n" * 50,
} }
@@ -68,6 +71,21 @@ def generate_test_files(source_dir):
os.makedirs(os.path.dirname(full_path), exist_ok=True) os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "wb") as f: with open(full_path, "wb") as f:
f.write(content) f.write(content)
written += len(content)
i = 0
while written < target_total:
chunk_size = min(5 * 1024 * 1024, target_total - written)
rel_path = f"bulk/file_{i}.dat"
full_path = os.path.join(source_dir, rel_path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "wb") as f:
f.write(b"0" * chunk_size)
written += chunk_size
i += 1
total_mb = written / (1024 * 1024)
print(f" Generated {total_mb:.1f} MB of test data in {source_dir}")
def verify_transfer(source_dir, dest_dir): def verify_transfer(source_dir, dest_dir):
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,50 @@
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
@@ -0,0 +1 @@
deeply nested file
@@ -0,0 +1 @@
hello world
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
another nested file
+1
View File
@@ -0,0 +1 @@
deeply nested file
+1
View File
@@ -0,0 +1 @@
hello world