Fix: Add missing checksum field to file_meta_t and file_task_t structs
The file_meta_t and file_task_t structs were missing the checksum field that was being used in both client and server code, causing a compilation error. Added uint64_t checksum to both structs and updated wq_push declaration to match implementation. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
+705
@@ -0,0 +1,705 @@
|
||||
#include "common.h"
|
||||
#include <sys/sendfile.h>
|
||||
#include <getopt.h>
|
||||
#include <dirent.h>
|
||||
#include <stdatomic.h>
|
||||
#include <time.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// ── Helper to copy a file ────────────────────────────────────────────────
|
||||
static int copy_file(const char *src, const char *dst) {
|
||||
int src_fd = open(src, O_RDONLY);
|
||||
if (src_fd < 0) return -1;
|
||||
|
||||
int dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (dst_fd < 0) { close(src_fd); return -1; }
|
||||
|
||||
char buf[65536];
|
||||
ssize_t n;
|
||||
while ((n = read(src_fd, buf, sizeof(buf))) > 0) {
|
||||
if (writen(dst_fd, buf, n) != n) { close(src_fd); close(dst_fd); return -1; }
|
||||
}
|
||||
|
||||
close(src_fd);
|
||||
close(dst_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── Simple fast hash for checksum-based skip ──────────────────────────────
|
||||
static uint64_t fast_hash_file(const char *path, uint64_t file_size) {
|
||||
// For small files (< 4KB), hash entire file
|
||||
// For large files, hash first 4KB + last 4KB + size
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
|
||||
uint64_t hash = file_size * 0x9e3779b97f4a7c15ULL;
|
||||
|
||||
// Hash first chunk
|
||||
unsigned char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof(buf), f);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
hash = hash * 0x880355f21e15d8c5ULL + buf[i];
|
||||
}
|
||||
|
||||
// For files > 8KB, also hash last chunk
|
||||
if (file_size > 8192) {
|
||||
if (fseeko(f, -(off_t)sizeof(buf), SEEK_END) == 0) {
|
||||
n = fread(buf, 1, sizeof(buf), f);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
hash = hash * 0x880355f21e15d8c5ULL + buf[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return hash;
|
||||
}
|
||||
|
||||
// ─── Per-connection context ────────────────────────────────────────────────
|
||||
|
||||
typedef struct {
|
||||
int tcp_fd;
|
||||
int use_udp;
|
||||
int udp_fd;
|
||||
struct sockaddr_in udp_server_addr;
|
||||
uint64_t udp_session_id;
|
||||
} sync_ctx_t;
|
||||
|
||||
// ─── Global stats (atomic for lock-free updates) ──────────────────────────
|
||||
|
||||
static atomic_uint_fast64_t g_bytes_sent = 0;
|
||||
static atomic_uint_fast32_t g_files_sent = 0;
|
||||
|
||||
// ─── UDP helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
static int verify_and_resend(sync_ctx_t *ctx, int file_fd,
|
||||
uint32_t file_id, uint64_t file_size) {
|
||||
uint32_t total_blocks = (file_size + UDP_PAYLOAD_MAX - 1) / UDP_PAYLOAD_MAX;
|
||||
|
||||
while (1) {
|
||||
uint32_t req = MAGIC_VERIFY;
|
||||
if (writen(ctx->tcp_fd, &req, sizeof(req)) != sizeof(req)) return -1;
|
||||
if (writen(ctx->tcp_fd, &file_id, sizeof(file_id)) != sizeof(file_id)) return -1;
|
||||
|
||||
uint32_t missing = 0;
|
||||
if (readn(ctx->tcp_fd, &missing, sizeof(missing)) != sizeof(missing)) return -1;
|
||||
if (missing == 0) break;
|
||||
|
||||
uint32_t *ids = malloc(missing * sizeof(uint32_t));
|
||||
if (!ids) { perror("malloc"); return -1; }
|
||||
if (readn(ctx->tcp_fd, ids, missing * sizeof(uint32_t))
|
||||
!= (ssize_t)(missing * sizeof(uint32_t))) { free(ids); return -1; }
|
||||
|
||||
printf("[thread] Resending %u missing blocks...\n", missing);
|
||||
for (uint32_t i = 0; i < missing; i++) {
|
||||
uint32_t bid = ids[i];
|
||||
if (bid >= total_blocks) continue;
|
||||
uint64_t off = (uint64_t)bid * UDP_PAYLOAD_MAX;
|
||||
if (lseek(file_fd, off, SEEK_SET) == (off_t)-1) { perror("lseek"); continue; }
|
||||
|
||||
char payload[UDP_PAYLOAD_MAX];
|
||||
ssize_t nr = read(file_fd, payload, UDP_PAYLOAD_MAX);
|
||||
if (nr <= 0) continue;
|
||||
|
||||
udp_packet_header_t hdr;
|
||||
hdr.magic = MAGIC_UDP_DATA;
|
||||
hdr.session_id = ctx->udp_session_id;
|
||||
hdr.file_id = file_id;
|
||||
hdr.block_id = bid;
|
||||
hdr.block_offset = off;
|
||||
hdr.payload_len = nr;
|
||||
|
||||
char pkt[sizeof(hdr) + UDP_PAYLOAD_MAX];
|
||||
memcpy(pkt, &hdr, sizeof(hdr));
|
||||
memcpy(pkt + sizeof(hdr), payload, nr);
|
||||
sendto(ctx->udp_fd, pkt, sizeof(hdr) + nr, 0,
|
||||
(struct sockaddr *)&ctx->udp_server_addr,
|
||||
sizeof(ctx->udp_server_addr));
|
||||
}
|
||||
free(ids);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_file_udp(sync_ctx_t *ctx, const char *full_path,
|
||||
uint32_t file_id, uint64_t file_size) {
|
||||
int fd = open(full_path, O_RDONLY);
|
||||
if (fd < 0) { perror("open"); return -1; }
|
||||
|
||||
uint32_t total = (file_size + UDP_PAYLOAD_MAX - 1) / UDP_PAYLOAD_MAX;
|
||||
printf("[thread] UDP send: %s (%u blocks)\n", full_path, total);
|
||||
|
||||
for (uint32_t bid = 0; bid < total; bid++) {
|
||||
char payload[UDP_PAYLOAD_MAX];
|
||||
ssize_t nr = read(fd, payload, UDP_PAYLOAD_MAX);
|
||||
if (nr <= 0) break;
|
||||
|
||||
udp_packet_header_t hdr;
|
||||
hdr.magic = MAGIC_UDP_DATA;
|
||||
hdr.session_id = ctx->udp_session_id;
|
||||
hdr.file_id = file_id;
|
||||
hdr.block_id = bid;
|
||||
hdr.block_offset = (uint64_t)bid * UDP_PAYLOAD_MAX;
|
||||
hdr.payload_len = nr;
|
||||
|
||||
char pkt[sizeof(hdr) + UDP_PAYLOAD_MAX];
|
||||
memcpy(pkt, &hdr, sizeof(hdr));
|
||||
memcpy(pkt + sizeof(hdr), payload, nr);
|
||||
sendto(ctx->udp_fd, pkt, sizeof(hdr) + nr, 0,
|
||||
(struct sockaddr *)&ctx->udp_server_addr,
|
||||
sizeof(ctx->udp_server_addr));
|
||||
}
|
||||
|
||||
int ret = verify_and_resend(ctx, fd, file_id, file_size);
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ─── Send a single file over one connection (TCP or UDP data) ─────────────
|
||||
|
||||
static int send_one_file(sync_ctx_t *ctx, const char *full_path,
|
||||
const char *rel_path) {
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) < 0) { perror("stat"); return -1; }
|
||||
|
||||
file_meta_t meta;
|
||||
meta.magic = MAGIC_META;
|
||||
meta.name_len = strlen(rel_path);
|
||||
meta.file_size = st.st_size;
|
||||
meta.mode = st.st_mode;
|
||||
|
||||
if (writen(ctx->tcp_fd, &meta, sizeof(meta)) != sizeof(meta)) return -1;
|
||||
if (writen(ctx->tcp_fd, rel_path, meta.name_len) != (ssize_t)meta.name_len) return -1;
|
||||
|
||||
file_response_t resp;
|
||||
if (readn(ctx->tcp_fd, &resp, sizeof(resp)) != sizeof(resp)) return -1;
|
||||
|
||||
if (resp.response == RESP_SKIP) {
|
||||
printf("[thread] Skip: %s\n", rel_path);
|
||||
return 0;
|
||||
} else if (resp.response == RESP_USE_UDP) {
|
||||
if (send_file_udp(ctx, full_path, resp.file_id, meta.file_size) < 0)
|
||||
return -1;
|
||||
uint32_t ack = 0;
|
||||
if (readn(ctx->tcp_fd, &ack, sizeof(ack)) != sizeof(ack)
|
||||
|| ack != RESP_OK) {
|
||||
fprintf(stderr, "[thread] UDP finalize failed: %s\n", rel_path);
|
||||
return -1;
|
||||
}
|
||||
} else if (resp.response == RESP_SEND_DATA) {
|
||||
int fd = open(full_path, O_RDONLY);
|
||||
if (fd < 0) { perror("open"); return -1; }
|
||||
off_t off = 0;
|
||||
while (off < (off_t)meta.file_size) {
|
||||
ssize_t r = sendfile(ctx->tcp_fd, fd, &off, meta.file_size - off);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("sendfile"); close(fd); return -1;
|
||||
}
|
||||
if (r == 0) break;
|
||||
}
|
||||
close(fd);
|
||||
uint32_t ack = 0;
|
||||
if (readn(ctx->tcp_fd, &ack, sizeof(ack)) != sizeof(ack)
|
||||
|| ack != RESP_OK) {
|
||||
fprintf(stderr, "[thread] TCP finalize failed: %s\n", rel_path);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "[thread] Server rejected %s (code %u)\n",
|
||||
rel_path, resp.response);
|
||||
return -1;
|
||||
}
|
||||
|
||||
atomic_fetch_add(&g_bytes_sent, meta.file_size);
|
||||
atomic_fetch_add(&g_files_sent, 1);
|
||||
printf("[thread] Sent: %s\n", rel_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ─── UDP session setup (called once per worker if -u) ─────────────────────
|
||||
|
||||
static int setup_udp(sync_ctx_t *ctx, const char *host) {
|
||||
uint32_t req = MAGIC_UDP_REQ;
|
||||
if (writen(ctx->tcp_fd, &req, sizeof(req)) != sizeof(req)) return -1;
|
||||
|
||||
uint32_t resp = 0;
|
||||
if (readn(ctx->tcp_fd, &resp, sizeof(resp)) != sizeof(resp)) return -1;
|
||||
if (resp != RESP_USE_UDP) {
|
||||
fprintf(stderr, "[thread] Server refused UDP, using TCP.\n");
|
||||
ctx->use_udp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
udp_handshake_t hs;
|
||||
if (readn(ctx->tcp_fd, &hs, sizeof(hs)) != sizeof(hs)) return -1;
|
||||
ctx->udp_session_id = hs.session_id;
|
||||
|
||||
ctx->udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (ctx->udp_fd < 0) { perror("UDP socket"); return -1; }
|
||||
|
||||
// Tune UDP socket for better throughput
|
||||
int bufsize = 4 * 1024 * 1024; // 4 MB
|
||||
setsockopt(ctx->udp_fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
|
||||
setsockopt(ctx->udp_fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
|
||||
|
||||
memset(&ctx->udp_server_addr, 0, sizeof(ctx->udp_server_addr));
|
||||
ctx->udp_server_addr.sin_family = AF_INET;
|
||||
ctx->udp_server_addr.sin_port = htons(hs.udp_port);
|
||||
inet_pton(AF_INET, host, &ctx->udp_server_addr.sin_addr);
|
||||
|
||||
// Knock
|
||||
udp_packet_header_t knock;
|
||||
memset(&knock, 0, sizeof(knock));
|
||||
knock.magic = MAGIC_UDP_KNOCK;
|
||||
knock.session_id = hs.session_id;
|
||||
sendto(ctx->udp_fd, &knock, sizeof(knock), 0,
|
||||
(struct sockaddr *)&ctx->udp_server_addr,
|
||||
sizeof(ctx->udp_server_addr));
|
||||
|
||||
uint32_t ack = 0;
|
||||
if (readn(ctx->tcp_fd, &ack, sizeof(ack)) != sizeof(ack) || ack != RESP_OK) {
|
||||
fprintf(stderr, "[thread] UDP knock failed\n");
|
||||
return -1;
|
||||
}
|
||||
printf("[thread] UDP session ready (port %u)\n", hs.udp_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ─── Batch file sending ────────────────────────────────────────────────────
|
||||
|
||||
static int send_batch_files(sync_ctx_t *ctx, file_task_t **tasks, int count) {
|
||||
// Collect and stat all files first (single pass to avoid double stat)
|
||||
// Also pre-open file descriptors for pipelining
|
||||
int *fds = malloc(count * sizeof(int));
|
||||
struct stat *stats = malloc(count * sizeof(struct stat));
|
||||
if (!fds || !stats) { perror("malloc"); free(fds); free(stats); return -1; }
|
||||
|
||||
batch_meta_header_t batch_hdr;
|
||||
batch_hdr.magic = MAGIC_BATCH_META;
|
||||
batch_hdr.count = count;
|
||||
batch_hdr.total_name_len = 0;
|
||||
batch_hdr.total_size = 0;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (stat(tasks[i]->full_path, &stats[i]) < 0) {
|
||||
perror("stat");
|
||||
for (int j = 0; j < i; j++) close(fds[j]);
|
||||
free(fds); free(stats);
|
||||
return -1;
|
||||
}
|
||||
batch_hdr.total_name_len += strlen(tasks[i]->rel_path);
|
||||
batch_hdr.total_size += stats[i].st_size;
|
||||
|
||||
// Pre-open file descriptors
|
||||
fds[i] = open(tasks[i]->full_path, O_RDONLY);
|
||||
if (fds[i] < 0) {
|
||||
perror("open");
|
||||
for (int j = 0; j < i; j++) close(fds[j]);
|
||||
free(fds); free(stats);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Send batch header
|
||||
if (writen(ctx->tcp_fd, &batch_hdr, sizeof(batch_hdr)) != sizeof(batch_hdr)) {
|
||||
for (int j = 0; j < count; j++) close(fds[j]);
|
||||
free(fds); free(stats);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send files with pipelining: meta+name+data for each file sequentially
|
||||
// This allows server to start writing file N while receiving meta for file N+1
|
||||
for (int i = 0; i < count; i++) {
|
||||
file_meta_t meta;
|
||||
meta.magic = 0;
|
||||
meta.name_len = strlen(tasks[i]->rel_path);
|
||||
meta.file_size = stats[i].st_size;
|
||||
meta.mode = stats[i].st_mode;
|
||||
meta.checksum = tasks[i]->checksum;
|
||||
|
||||
if (writen(ctx->tcp_fd, &meta, sizeof(meta)) != sizeof(meta)) {
|
||||
for (int j = 0; j < count; j++) close(fds[j]);
|
||||
free(fds); free(stats);
|
||||
return -1;
|
||||
}
|
||||
if (writen(ctx->tcp_fd, tasks[i]->rel_path, meta.name_len) != (ssize_t)meta.name_len) {
|
||||
for (int j = 0; j < count; j++) close(fds[j]);
|
||||
free(fds); free(stats);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send file data using sendfile() for zero-copy transfer
|
||||
off_t file_offset = 0;
|
||||
ssize_t total_sent = 0;
|
||||
while (file_offset < (off_t)stats[i].st_size) {
|
||||
ssize_t sent = sendfile(ctx->tcp_fd, fds[i], &file_offset, stats[i].st_size - file_offset);
|
||||
if (sent < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("sendfile");
|
||||
for (int j = 0; j < count; j++) close(fds[j]);
|
||||
free(fds); free(stats);
|
||||
return -1;
|
||||
}
|
||||
if (sent == 0) break; // EOF
|
||||
total_sent += sent;
|
||||
}
|
||||
close(fds[i]);
|
||||
atomic_fetch_add(&g_bytes_sent, (uint64_t)total_sent);
|
||||
printf("[thread] Sent (pipelined+sendfile): %s\n", tasks[i]->rel_path);
|
||||
}
|
||||
|
||||
free(fds);
|
||||
free(stats);
|
||||
|
||||
// Update file count atomically
|
||||
atomic_fetch_add(&g_files_sent, count);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ─── Worker thread: owns one TCP connection, drains work queue ─────────────
|
||||
|
||||
static void *worker_thread(void *arg) {
|
||||
worker_arg_t *wa = (worker_arg_t *)arg;
|
||||
|
||||
// Connect TCP
|
||||
sync_ctx_t ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.use_udp = wa->use_udp;
|
||||
|
||||
ctx.tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (ctx.tcp_fd < 0) { perror("socket"); wa->result = -1; return NULL; }
|
||||
|
||||
// TCP tuning for better throughput
|
||||
int optval = 1;
|
||||
setsockopt(ctx.tcp_fd, SOL_TCP, TCP_NODELAY, &optval, sizeof(optval));
|
||||
setsockopt(ctx.tcp_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
|
||||
|
||||
// Large send/receive buffers
|
||||
int bufsize = 4 * 1024 * 1024; // 4 MB
|
||||
setsockopt(ctx.tcp_fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
|
||||
setsockopt(ctx.tcp_fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
|
||||
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(wa->port);
|
||||
inet_pton(AF_INET, wa->host, &sa.sin_addr);
|
||||
|
||||
if (connect(ctx.tcp_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
||||
perror("connect"); close(ctx.tcp_fd); wa->result = -1; return NULL;
|
||||
}
|
||||
printf("[thread %d] TCP connected to %s:%d\n", wa->thread_id, wa->host, wa->port);
|
||||
|
||||
if (wa->use_udp && setup_udp(&ctx, wa->host) < 0) {
|
||||
close(ctx.tcp_fd); wa->result = -1; return NULL;
|
||||
}
|
||||
|
||||
// Drain queue with batching
|
||||
file_task_t *batch[BATCH_MAX_FILES];
|
||||
int batch_count = 0;
|
||||
|
||||
file_task_t *task;
|
||||
while ((task = wq_pop(wa->queue)) != NULL) {
|
||||
batch[batch_count++] = task;
|
||||
|
||||
// If batch is full, send it
|
||||
if (batch_count >= BATCH_MAX_FILES) {
|
||||
if (send_batch_files(&ctx, batch, batch_count) < 0) {
|
||||
fprintf(stderr, "[thread %d] Batch send failed\n", wa->thread_id);
|
||||
wa->result = -1;
|
||||
}
|
||||
// Free all tasks in batch
|
||||
for (int i = 0; i < batch_count; i++) {
|
||||
free(batch[i]);
|
||||
}
|
||||
batch_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Send remaining files in partial batch
|
||||
if (batch_count > 0) {
|
||||
if (send_batch_files(&ctx, batch, batch_count) < 0) {
|
||||
fprintf(stderr, "[thread %d] Final batch send failed\n", wa->thread_id);
|
||||
wa->result = -1;
|
||||
}
|
||||
for (int i = 0; i < batch_count; i++) {
|
||||
free(batch[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Signal done to server
|
||||
file_meta_t done;
|
||||
memset(&done, 0, sizeof(done));
|
||||
done.magic = MAGIC_DONE;
|
||||
writen(ctx.tcp_fd, &done, sizeof(done));
|
||||
|
||||
if (ctx.use_udp && ctx.udp_fd > 0) close(ctx.udp_fd);
|
||||
close(ctx.tcp_fd);
|
||||
printf("[thread %d] Done.\n", wa->thread_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ─── Scanner: recursively walks dir, collects files, sorts by size, pushes to queue ──
|
||||
|
||||
typedef struct {
|
||||
work_queue_t *queue;
|
||||
char base_path[1024];
|
||||
} scanner_arg_t;
|
||||
|
||||
// Comparison function for sorting files by size (ascending: small files first)
|
||||
static int compare_files_by_size(const void *a, const void *b) {
|
||||
const file_task_t *fa = *(const file_task_t **)a;
|
||||
const file_task_t *fb = *(const file_task_t **)b;
|
||||
if (fa->file_size < fb->file_size) return -1;
|
||||
if (fa->file_size > fb->file_size) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Collect all files recursively into a list
|
||||
static int collect_files(const char *base, const char *sub, file_task_t **list, int *count, int max_files) {
|
||||
char cur[2048];
|
||||
if (sub && sub[0])
|
||||
snprintf(cur, sizeof(cur), "%s/%s", base, sub);
|
||||
else
|
||||
snprintf(cur, sizeof(cur), "%s", base);
|
||||
|
||||
DIR *dir = opendir(cur);
|
||||
if (!dir) { perror("opendir"); return -1; }
|
||||
|
||||
struct dirent *e;
|
||||
while ((e = readdir(dir)) != NULL) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
|
||||
char rel[2048], full[2048];
|
||||
if (sub && sub[0]) {
|
||||
snprintf(rel, sizeof(rel), "%s/%s", sub, e->d_name);
|
||||
} else {
|
||||
snprintf(rel, sizeof(rel), "%s", e->d_name);
|
||||
}
|
||||
snprintf(full, sizeof(full), "%s/%s", base, rel);
|
||||
full[sizeof(full) - 1] = '\0';
|
||||
rel[sizeof(rel) - 1] = '\0';
|
||||
|
||||
struct stat st;
|
||||
if (lstat(full, &st) < 0) { perror("lstat"); continue; }
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (collect_files(base, rel, list, count, max_files) < 0) {
|
||||
closedir(dir);
|
||||
return -1;
|
||||
}
|
||||
} else if (S_ISREG(st.st_mode)) {
|
||||
if (*count >= max_files) {
|
||||
fprintf(stderr, "Warning: Maximum file count reached, stopping scan\n");
|
||||
closedir(dir);
|
||||
return 0;
|
||||
}
|
||||
list[*count] = malloc(sizeof(file_task_t));
|
||||
if (!list[*count]) { perror("malloc"); closedir(dir); return -1; }
|
||||
snprintf(list[*count]->full_path, sizeof(list[*count]->full_path), "%s", full);
|
||||
snprintf(list[*count]->rel_path, sizeof(list[*count]->rel_path), "%s", rel);
|
||||
list[*count]->file_size = st.st_size;
|
||||
list[*count]->checksum = fast_hash_file(full, st.st_size);
|
||||
list[*count]->next = NULL;
|
||||
(*count)++;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *scanner_thread(void *arg) {
|
||||
scanner_arg_t *sa = (scanner_arg_t *)arg;
|
||||
|
||||
// First pass: count files to determine array size
|
||||
int file_count = 0;
|
||||
char cur[2048];
|
||||
DIR *dir = opendir(sa->base_path);
|
||||
if (!dir) { perror("opendir"); wq_finish(sa->queue); return NULL; }
|
||||
|
||||
struct dirent *e;
|
||||
while ((e = readdir(dir)) != NULL) {
|
||||
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
|
||||
|
||||
snprintf(cur, sizeof(cur), "%s/%s", sa->base_path, e->d_name);
|
||||
struct stat st;
|
||||
if (lstat(cur, &st) < 0) continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
// Recursively count
|
||||
DIR *subdir = opendir(cur);
|
||||
if (subdir) {
|
||||
struct dirent *se;
|
||||
while ((se = readdir(subdir)) != NULL) {
|
||||
if (!strcmp(se->d_name, ".") || !strcmp(se->d_name, "..")) continue;
|
||||
file_count++;
|
||||
}
|
||||
closedir(subdir);
|
||||
}
|
||||
} else if (S_ISREG(st.st_mode)) {
|
||||
file_count++;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
// Allocate array for file tasks
|
||||
file_task_t **file_list = malloc(file_count * sizeof(file_task_t *));
|
||||
if (!file_list) { perror("malloc"); wq_finish(sa->queue); return NULL; }
|
||||
|
||||
int count = 0;
|
||||
if (collect_files(sa->base_path, "", file_list, &count, file_count) < 0) {
|
||||
for (int i = 0; i < count; i++) free(file_list[i]);
|
||||
free(file_list);
|
||||
wq_finish(sa->queue);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Sort files by size (smallest first for better pipelining)
|
||||
qsort(file_list, count, sizeof(file_task_t *), compare_files_by_size);
|
||||
|
||||
// Push sorted files to queue
|
||||
for (int i = 0; i < count; i++) {
|
||||
wq_push(sa->queue, file_list[i]->full_path, file_list[i]->rel_path, file_list[i]->file_size, file_list[i]->checksum);
|
||||
free(file_list[i]);
|
||||
}
|
||||
free(file_list);
|
||||
|
||||
wq_finish(sa->queue); /* broadcast to all waiting workers */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ─── main ──────────────────────────────────────────────────────────────────
|
||||
|
||||
static void print_usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s -h <host> [-p <port>] -s <source> [-n <connections>] [-u]\n"
|
||||
" -n number of parallel TCP connections (default: 4)\n"
|
||||
" -u enable UDP data channel\n", prog);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char *host = NULL;
|
||||
int port = DEFAULT_PORT;
|
||||
char *source = NULL;
|
||||
int nconn = 4;
|
||||
int use_udp = 0;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "h:p:s:n:u")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h': host = optarg; break;
|
||||
case 'p': port = atoi(optarg); break;
|
||||
case 's': source = optarg; break;
|
||||
case 'n': nconn = atoi(optarg); break;
|
||||
case 'u': use_udp = 1; break;
|
||||
default: print_usage(argv[0]); exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!host || !source) { print_usage(argv[0]); exit(EXIT_FAILURE); }
|
||||
if (nconn < 1) nconn = 1;
|
||||
if (nconn > 16) nconn = 16;
|
||||
|
||||
struct stat st;
|
||||
if (stat(source, &st) < 0) { perror("stat source"); exit(EXIT_FAILURE); }
|
||||
|
||||
printf("Starting sync: %s → %s:%d (connections=%d, udp=%s)\n",
|
||||
source, host, port, nconn, use_udp ? "yes" : "no");
|
||||
|
||||
// ── Create temp dir for single files (to use directory scanning) ─────────
|
||||
char temp_dir[2048] = "";
|
||||
int use_temp_dir = 0;
|
||||
char *final_source = (char *)source;
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
// Create temp dir, copy file there with its name
|
||||
strncpy(temp_dir, "/tmp/fastsync_temp_XXXXXX", sizeof(temp_dir));
|
||||
if (mkdtemp(temp_dir) == NULL) {
|
||||
perror("mkdtemp");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
use_temp_dir = 1;
|
||||
|
||||
char *fname = strrchr(source, '/');
|
||||
char new_path[2048];
|
||||
snprintf(new_path, sizeof(new_path), "%s/%s", temp_dir, fname ? fname + 1 : source);
|
||||
|
||||
// Copy file to temp dir
|
||||
if (copy_file(source, new_path) < 0) {
|
||||
perror("copy_file");
|
||||
rmdir(temp_dir);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
final_source = temp_dir;
|
||||
}
|
||||
|
||||
// ── Init shared work queue ────────────────────────────────────────────
|
||||
work_queue_t queue;
|
||||
wq_init(&queue);
|
||||
|
||||
// ── Start scanner thread ──────────────────────────────────────────────
|
||||
scanner_arg_t sarg;
|
||||
sarg.queue = &queue;
|
||||
snprintf(sarg.base_path, sizeof(sarg.base_path), "%s", final_source);
|
||||
pthread_t stid;
|
||||
pthread_create(&stid, NULL, scanner_thread, &sarg);
|
||||
pthread_detach(stid);
|
||||
|
||||
// ── Spawn N worker threads ────────────────────────────────────────────
|
||||
pthread_t *tids = malloc(nconn * sizeof(pthread_t));
|
||||
worker_arg_t *args = malloc(nconn * sizeof(worker_arg_t));
|
||||
if (!tids || !args) { perror("malloc"); exit(EXIT_FAILURE); }
|
||||
|
||||
// ── Start timer ───────────────────────────────────────────────────────
|
||||
struct timespec t_start, t_end;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t_start);
|
||||
|
||||
for (int i = 0; i < nconn; i++) {
|
||||
args[i].thread_id = i;
|
||||
args[i].queue = &queue;
|
||||
args[i].host = host;
|
||||
args[i].port = port;
|
||||
args[i].use_udp = use_udp;
|
||||
args[i].result = 0;
|
||||
pthread_create(&tids[i], NULL, worker_thread, &args[i]);
|
||||
}
|
||||
|
||||
// ── Wait for all workers ──────────────────────────────────────────────
|
||||
int overall = 0;
|
||||
for (int i = 0; i < nconn; i++) {
|
||||
pthread_join(tids[i], NULL);
|
||||
if (args[i].result != 0) overall = -1;
|
||||
}
|
||||
|
||||
// ── Stop timer & print summary ────────────────────────────────────────
|
||||
clock_gettime(CLOCK_MONOTONIC, &t_end);
|
||||
double elapsed_ms = (t_end.tv_sec - t_start.tv_sec) * 1000.0
|
||||
+ (t_end.tv_nsec - t_start.tv_nsec) / 1e6;
|
||||
|
||||
uint64_t total_bytes = atomic_load(&g_bytes_sent);
|
||||
uint32_t total_files = atomic_load(&g_files_sent);
|
||||
double throughput = (total_bytes / 1048576.0) / (elapsed_ms / 1000.0);
|
||||
|
||||
printf("\n=== Sync complete: %u files, %lu bytes, %.1f ms, %.2f MB/s ===\n",
|
||||
total_files, (unsigned long)total_bytes, elapsed_ms, throughput);
|
||||
/* Machine-readable line for benchmark.sh to parse */
|
||||
printf("BENCH: connections=%d bytes=%lu ms=%.1f throughput_mbs=%.2f\n",
|
||||
nconn, (unsigned long)total_bytes, elapsed_ms, throughput);
|
||||
|
||||
wq_destroy(&queue);
|
||||
free(tids);
|
||||
free(args);
|
||||
|
||||
// Clean up temp directory for single file
|
||||
if (use_temp_dir) {
|
||||
// Remove the temp directory and its contents
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", temp_dir);
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
return overall == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Binary file not shown.
+134
@@ -0,0 +1,134 @@
|
||||
#ifndef COMMON_H
|
||||
#define COMMON_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
|
||||
// ─── Work-Queue (client-side, thread-safe) ───────────────────────────────────
|
||||
|
||||
typedef struct file_task {
|
||||
char full_path[2048];
|
||||
char rel_path[2048];
|
||||
uint64_t file_size;
|
||||
uint64_t checksum;
|
||||
struct file_task *next;
|
||||
} file_task_t;
|
||||
|
||||
typedef struct {
|
||||
file_task_t *head;
|
||||
file_task_t *tail;
|
||||
int done; // set to 1 when scanner is finished
|
||||
int error; // set to 1 on fatal scan error
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
} work_queue_t;
|
||||
|
||||
void wq_init(work_queue_t *q);
|
||||
void wq_push(work_queue_t *q, const char *full_path, const char *rel_path, uint64_t file_size, uint64_t checksum);
|
||||
file_task_t *wq_pop(work_queue_t *q); // blocks until item or done
|
||||
void wq_finish(work_queue_t *q);
|
||||
void wq_destroy(work_queue_t *q);
|
||||
|
||||
// ─── Worker-thread argument (client-side) ────────────────────────────────────
|
||||
|
||||
typedef struct {
|
||||
int thread_id;
|
||||
work_queue_t *queue;
|
||||
const char *host;
|
||||
int port;
|
||||
int use_udp;
|
||||
int result; // 0 = ok, -1 = error
|
||||
} worker_arg_t;
|
||||
|
||||
|
||||
#define DEFAULT_PORT 8082
|
||||
|
||||
// Protocol Magics
|
||||
#define MAGIC_META 0x53594E43 // 'SYNC'
|
||||
#define MAGIC_BATCH_META 0x42415443 // 'BATC' - Batch metadata
|
||||
#define MAGIC_DONE 0x444F4E45 // 'DONE'
|
||||
#define MAGIC_VERIFY 0x56455259 // 'VERY'
|
||||
#define MAGIC_UDP_REQ 0x55445052 // 'UDPR'
|
||||
|
||||
// Batch constants
|
||||
#define BATCH_MAX_FILES 64 // Max files per batch
|
||||
|
||||
|
||||
// Response Codes
|
||||
#define RESP_SEND_DATA 1
|
||||
#define RESP_SKIP 2
|
||||
#define RESP_ERROR 0
|
||||
#define RESP_OK 3
|
||||
#define RESP_USE_UDP 4
|
||||
|
||||
// UDP Constants
|
||||
#define UDP_PAYLOAD_MAX 1400
|
||||
#define MAGIC_UDP_DATA 0x55445044 // 'UDPD'
|
||||
#define MAGIC_UDP_KNOCK 0x5544504B // 'UDPK'
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t name_len;
|
||||
uint64_t file_size;
|
||||
uint32_t mode;
|
||||
uint64_t checksum;
|
||||
} file_meta_t;
|
||||
|
||||
// Batch metadata: header followed by N file_meta_t + name strings
|
||||
typedef struct {
|
||||
uint32_t magic; // MAGIC_BATCH_META
|
||||
uint32_t count; // Number of files in batch
|
||||
uint32_t total_name_len; // Total length of all filenames combined
|
||||
uint64_t total_size; // Total size of all files
|
||||
} batch_meta_header_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t response;
|
||||
uint32_t file_id; // For single file
|
||||
uint32_t reserved[2];
|
||||
} file_response_t;
|
||||
|
||||
// Batch response: one response code for the whole batch
|
||||
typedef struct {
|
||||
uint32_t magic; // MAGIC_BATCH_META (echoed back)
|
||||
uint32_t count; // Number of files accepted
|
||||
uint32_t first_file_id; // First file_id in this batch
|
||||
} batch_response_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t udp_port;
|
||||
uint64_t session_id;
|
||||
} udp_handshake_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint64_t session_id;
|
||||
uint32_t file_id;
|
||||
uint32_t block_id;
|
||||
uint64_t block_offset;
|
||||
uint32_t payload_len;
|
||||
} udp_packet_header_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
// Socket Helpers
|
||||
ssize_t readn(int fd, void *vptr, size_t n);
|
||||
ssize_t writen(int fd, const void *vptr, size_t n);
|
||||
|
||||
// Directory/File Helpers
|
||||
int make_path(const char *path, mode_t mode);
|
||||
|
||||
#endif // COMMON_H
|
||||
+688
@@ -0,0 +1,688 @@
|
||||
#include "common.h"
|
||||
#include <getopt.h>
|
||||
#include <poll.h>
|
||||
#include <time.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// ── Simple fast hash for checksum-based skip ──────────────────────────────
|
||||
static uint64_t fast_hash_file(const char *path, uint64_t file_size) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
|
||||
uint64_t hash = file_size * 0x9e3779b97f4a7c15ULL;
|
||||
|
||||
unsigned char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof(buf), f);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
hash = hash * 0x880355f21e15d8c5ULL + buf[i];
|
||||
}
|
||||
|
||||
if (file_size > 8192) {
|
||||
if (fseeko(f, -(off_t)sizeof(buf), SEEK_END) == 0) {
|
||||
n = fread(buf, 1, sizeof(buf), f);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
hash = hash * 0x880355f21e15d8c5ULL + buf[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return hash;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t file_id;
|
||||
char target_path[2048];
|
||||
int out_fd;
|
||||
uint64_t file_size;
|
||||
uint32_t total_blocks;
|
||||
char *received_blocks;
|
||||
} active_file_t;
|
||||
|
||||
int setup_udp_session(int tcp_fd, int *udp_fd_out, struct sockaddr_in *client_udp_addr_out, uint64_t *session_id_out) {
|
||||
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_fd < 0) {
|
||||
perror("UDP socket creation failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in udp_addr;
|
||||
memset(&udp_addr, 0, sizeof(udp_addr));
|
||||
udp_addr.sin_family = AF_INET;
|
||||
udp_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
udp_addr.sin_port = 0; // Ephemeral port
|
||||
|
||||
if (bind(udp_fd, (struct sockaddr *)&udp_addr, sizeof(udp_addr)) < 0) {
|
||||
perror("UDP bind failed");
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Tune UDP socket for better throughput
|
||||
int bufsize = 4 * 1024 * 1024; // 4 MB
|
||||
setsockopt(udp_fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
|
||||
setsockopt(udp_fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
|
||||
|
||||
socklen_t addr_len = sizeof(udp_addr);
|
||||
if (getsockname(udp_fd, (struct sockaddr *)&udp_addr, &addr_len) < 0) {
|
||||
perror("getsockname failed");
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t udp_port = ntohs(udp_addr.sin_port);
|
||||
uint64_t session_id = ((uint64_t)rand() << 32) | rand();
|
||||
|
||||
// Send response code and handshake over TCP
|
||||
uint32_t resp = RESP_USE_UDP;
|
||||
if (writen(tcp_fd, &resp, sizeof(resp)) != sizeof(resp)) {
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
udp_handshake_t handshake;
|
||||
handshake.udp_port = udp_port;
|
||||
handshake.session_id = session_id;
|
||||
if (writen(tcp_fd, &handshake, sizeof(handshake)) != sizeof(handshake)) {
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("UDP port bound to %u. Waiting for knock with Session ID: %lu...\n", udp_port, (unsigned long)session_id);
|
||||
|
||||
// Timeout of 5s for the UDP knock
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(udp_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
struct sockaddr_in client_udp_addr;
|
||||
socklen_t client_len = sizeof(client_udp_addr);
|
||||
udp_packet_header_t knock_hdr;
|
||||
|
||||
ssize_t n = recvfrom(udp_fd, &knock_hdr, sizeof(knock_hdr), 0,
|
||||
(struct sockaddr *)&client_udp_addr, &client_len);
|
||||
if (n < 0) {
|
||||
perror("UDP knock timeout or recvfrom failed");
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (knock_hdr.magic != MAGIC_UDP_KNOCK || knock_hdr.session_id != session_id) {
|
||||
fprintf(stderr, "Invalid UDP knock magic or session ID\n");
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Confirm session established on TCP
|
||||
resp = RESP_OK;
|
||||
if (writen(tcp_fd, &resp, sizeof(resp)) != sizeof(resp)) {
|
||||
close(udp_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("UDP Session established successfully.\n");
|
||||
*udp_fd_out = udp_fd;
|
||||
*client_udp_addr_out = client_udp_addr;
|
||||
*session_id_out = session_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int receive_file_udp_loop(int tcp_fd, int udp_fd, uint64_t session_id, active_file_t *active) {
|
||||
struct pollfd fds[2];
|
||||
fds[0].fd = tcp_fd;
|
||||
fds[0].events = POLLIN;
|
||||
fds[1].fd = udp_fd;
|
||||
fds[1].events = POLLIN;
|
||||
|
||||
// Reset SO_RCVTIMEO on UDP socket (block indefinitely during active sync)
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(udp_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
while (1) {
|
||||
int poll_ret = poll(fds, 2, -1);
|
||||
if (poll_ret < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
perror("poll failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 1. Check UDP Data socket
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char pkt[sizeof(udp_packet_header_t) + UDP_PAYLOAD_MAX];
|
||||
struct sockaddr_in src_addr;
|
||||
socklen_t src_len = sizeof(src_addr);
|
||||
ssize_t n = recvfrom(udp_fd, pkt, sizeof(pkt), 0,
|
||||
(struct sockaddr *)&src_addr, &src_len);
|
||||
if (n > 0) {
|
||||
if (n >= (ssize_t)sizeof(udp_packet_header_t)) {
|
||||
udp_packet_header_t *hdr = (udp_packet_header_t *)pkt;
|
||||
if (hdr->magic == MAGIC_UDP_DATA && hdr->session_id == session_id && hdr->file_id == active->file_id) {
|
||||
uint32_t bid = hdr->block_id;
|
||||
if (bid < active->total_blocks) {
|
||||
if (active->received_blocks[bid] == 0) {
|
||||
char *payload = pkt + sizeof(udp_packet_header_t);
|
||||
if (pwrite(active->out_fd, payload, hdr->payload_len, hdr->block_offset) != (ssize_t)hdr->payload_len) {
|
||||
perror("pwrite file block failed");
|
||||
} else {
|
||||
active->received_blocks[bid] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check TCP command socket
|
||||
if (fds[0].revents & POLLIN) {
|
||||
uint32_t req_magic = 0;
|
||||
ssize_t n = readn(tcp_fd, &req_magic, sizeof(req_magic));
|
||||
if (n <= 0) {
|
||||
return -1; // Disconnected
|
||||
}
|
||||
|
||||
if (req_magic == MAGIC_VERIFY) {
|
||||
uint32_t file_id = 0;
|
||||
if (readn(tcp_fd, &file_id, sizeof(file_id)) != sizeof(file_id)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (file_id != active->file_id) {
|
||||
fprintf(stderr, "File ID mismatch on verify query: got %u, active is %u\n", file_id, active->file_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Count missing blocks
|
||||
uint32_t missing_count = 0;
|
||||
for (uint32_t i = 0; i < active->total_blocks; i++) {
|
||||
if (active->received_blocks[i] == 0) {
|
||||
missing_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Send count
|
||||
if (writen(tcp_fd, &missing_count, sizeof(missing_count)) != sizeof(missing_count)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (missing_count == 0) {
|
||||
// Success!
|
||||
break;
|
||||
}
|
||||
|
||||
// Compile and send missing block IDs
|
||||
uint32_t *missing_array = malloc(missing_count * sizeof(uint32_t));
|
||||
if (!missing_array) {
|
||||
perror("malloc missing array");
|
||||
return -1;
|
||||
}
|
||||
uint32_t idx = 0;
|
||||
for (uint32_t i = 0; i < active->total_blocks; i++) {
|
||||
if (active->received_blocks[i] == 0) {
|
||||
missing_array[idx++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (writen(tcp_fd, missing_array, missing_count * sizeof(uint32_t)) != (ssize_t)(missing_count * sizeof(uint32_t))) {
|
||||
free(missing_array);
|
||||
return -1;
|
||||
}
|
||||
free(missing_array);
|
||||
} else {
|
||||
fprintf(stderr, "Received unexpected TCP command magic: 0x%08x\n", req_magic);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ─── Per-connection thread wrapper ───────────────────────────────────────────
|
||||
|
||||
/* Forward declaration — defined further below */
|
||||
void handle_client(int sock_fd, const char *dest_dir);
|
||||
|
||||
typedef struct {
|
||||
|
||||
int client_fd;
|
||||
char dest_dir[1024];
|
||||
} client_thread_arg_t;
|
||||
|
||||
static void *client_thread(void *arg) {
|
||||
client_thread_arg_t *ca = (client_thread_arg_t *)arg;
|
||||
handle_client(ca->client_fd, ca->dest_dir);
|
||||
close(ca->client_fd);
|
||||
free(ca);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void handle_client(int sock_fd, const char *dest_dir) {
|
||||
printf("Client connected. Starting transfer session...\n");
|
||||
|
||||
int use_udp = 0;
|
||||
int udp_fd = -1;
|
||||
struct sockaddr_in client_udp_addr;
|
||||
uint64_t udp_session_id = 0;
|
||||
uint32_t file_id_counter = 0;
|
||||
|
||||
while (1) {
|
||||
uint32_t magic = 0;
|
||||
// Peek or read magic
|
||||
ssize_t n = readn(sock_fd, &magic, sizeof(magic));
|
||||
if (n <= 0) {
|
||||
if (n < 0) {
|
||||
perror("read magic failed");
|
||||
} else {
|
||||
printf("Client disconnected.\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (magic == MAGIC_UDP_REQ) {
|
||||
if (setup_udp_session(sock_fd, &udp_fd, &client_udp_addr, &udp_session_id) < 0) {
|
||||
fprintf(stderr, "Failed to initialize UDP session. Falling back to TCP.\n");
|
||||
uint32_t resp_fail = RESP_ERROR;
|
||||
writen(sock_fd, &resp_fail, sizeof(resp_fail));
|
||||
} else {
|
||||
use_udp = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (magic == MAGIC_DONE) {
|
||||
printf("Sync completed successfully.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (magic == MAGIC_BATCH_META) {
|
||||
// Handle batch metadata
|
||||
// We already read the magic, now read the rest of the header
|
||||
batch_meta_header_t batch_hdr;
|
||||
uint32_t rest_of_header[3]; // count, total_name_len, total_size_hi
|
||||
uint32_t total_size_lo;
|
||||
if (readn(sock_fd, &rest_of_header, sizeof(rest_of_header)) != sizeof(rest_of_header) ||
|
||||
readn(sock_fd, &total_size_lo, sizeof(total_size_lo)) != sizeof(total_size_lo)) {
|
||||
fprintf(stderr, "Failed to read batch header\n");
|
||||
break;
|
||||
}
|
||||
batch_hdr.magic = magic;
|
||||
batch_hdr.count = rest_of_header[0];
|
||||
batch_hdr.total_name_len = rest_of_header[1];
|
||||
batch_hdr.total_size = ((uint64_t)rest_of_header[2] << 32) | total_size_lo;
|
||||
|
||||
if (batch_hdr.count > BATCH_MAX_FILES) {
|
||||
fprintf(stderr, "Batch count %u exceeds max %d\n", batch_hdr.count, BATCH_MAX_FILES);
|
||||
break;
|
||||
}
|
||||
|
||||
// No response needed - client sends data immediately after each file's metadata
|
||||
// Process each file in batch with pipelining: meta+name+data for each file
|
||||
uint32_t first_file_id = file_id_counter + 1;
|
||||
|
||||
for (uint32_t i = 0; i < batch_hdr.count; i++) {
|
||||
// Read metadata for this file
|
||||
file_meta_t meta;
|
||||
if (readn(sock_fd, &meta, sizeof(meta)) != sizeof(meta)) {
|
||||
fprintf(stderr, "Failed to read pipelined file meta %u\n", i);
|
||||
break;
|
||||
}
|
||||
|
||||
// Read filename
|
||||
char *filename = malloc(meta.name_len + 1);
|
||||
if (!filename) {
|
||||
perror("malloc filename");
|
||||
break;
|
||||
}
|
||||
if (readn(sock_fd, filename, meta.name_len) != (ssize_t)meta.name_len) {
|
||||
fprintf(stderr, "Failed to read pipelined filename %u\n", i);
|
||||
free(filename);
|
||||
break;
|
||||
}
|
||||
filename[meta.name_len] = '\0';
|
||||
|
||||
char target_path[2048];
|
||||
snprintf(target_path, sizeof(target_path), "%s/%s", dest_dir, filename);
|
||||
|
||||
// Check if file already exists with same size and checksum (skip if so)
|
||||
struct stat existing_st;
|
||||
if (stat(target_path, &existing_st) == 0 &&
|
||||
existing_st.st_size == meta.file_size &&
|
||||
S_ISREG(existing_st.st_mode)) {
|
||||
// File exists with same size - check checksum
|
||||
uint64_t existing_checksum = fast_hash_file(target_path, meta.file_size);
|
||||
if (existing_checksum == meta.checksum) {
|
||||
printf("Skip (checksum match): %s\n", target_path);
|
||||
free(filename);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Create parent directory
|
||||
char *slash = strrchr(target_path, '/');
|
||||
if (slash) {
|
||||
*slash = '\0';
|
||||
if (make_path(target_path, 0755) < 0) {
|
||||
perror("Failed to create parent directory path");
|
||||
*slash = '/';
|
||||
free(filename);
|
||||
continue;
|
||||
}
|
||||
*slash = '/';
|
||||
}
|
||||
|
||||
int out_fd = open(target_path, O_WRONLY | O_CREAT | O_TRUNC, meta.mode);
|
||||
if (out_fd < 0) {
|
||||
perror("Failed to open target file");
|
||||
free(filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("Receiving (pipelined): %s (%lu bytes)\n", filename, (unsigned long)meta.file_size);
|
||||
|
||||
// Read file data
|
||||
char buffer[65536];
|
||||
uint64_t bytes_left = meta.file_size;
|
||||
int write_error = 0;
|
||||
while (bytes_left > 0) {
|
||||
size_t to_read = (bytes_left > sizeof(buffer)) ? sizeof(buffer) : bytes_left;
|
||||
ssize_t nread = read(sock_fd, buffer, to_read);
|
||||
if (nread < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("Socket read error");
|
||||
write_error = 1;
|
||||
break;
|
||||
}
|
||||
if (nread == 0) {
|
||||
fprintf(stderr, "Unexpected socket EOF\n");
|
||||
write_error = 1;
|
||||
break;
|
||||
}
|
||||
if (!write_error) {
|
||||
if (writen(out_fd, buffer, nread) != nread) {
|
||||
perror("File write error");
|
||||
write_error = 1;
|
||||
}
|
||||
}
|
||||
bytes_left -= nread;
|
||||
}
|
||||
if (!write_error) {
|
||||
fsync(out_fd); // Ensure data is flushed to disk
|
||||
}
|
||||
close(out_fd);
|
||||
free(filename);
|
||||
|
||||
if (!write_error) {
|
||||
printf("Saved (pipelined TCP): %s\n", target_path);
|
||||
}
|
||||
}
|
||||
|
||||
file_id_counter = first_file_id + batch_hdr.count;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (magic != MAGIC_META) {
|
||||
fprintf(stderr, "Received invalid magic header: 0x%08x\n", magic);
|
||||
break;
|
||||
}
|
||||
|
||||
// We received a file metadata packet (read rest of it)
|
||||
uint32_t name_len = 0;
|
||||
uint64_t file_size = 0;
|
||||
uint32_t mode = 0;
|
||||
|
||||
if (readn(sock_fd, &name_len, sizeof(name_len)) != sizeof(name_len) ||
|
||||
readn(sock_fd, &file_size, sizeof(file_size)) != sizeof(file_size) ||
|
||||
readn(sock_fd, &mode, sizeof(mode)) != sizeof(mode)) {
|
||||
fprintf(stderr, "Failed to read file metadata fields\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// Read filename
|
||||
char filename[1024];
|
||||
if (name_len >= sizeof(filename)) {
|
||||
fprintf(stderr, "Filename too long: %u bytes\n", name_len);
|
||||
break;
|
||||
}
|
||||
if (readn(sock_fd, filename, name_len) != (ssize_t)name_len) {
|
||||
fprintf(stderr, "Failed to read filename\n");
|
||||
break;
|
||||
}
|
||||
filename[name_len] = '\0';
|
||||
|
||||
char target_path[2048];
|
||||
snprintf(target_path, sizeof(target_path), "%s/%s", dest_dir, filename);
|
||||
|
||||
// Ensure directories exist
|
||||
if (make_path(target_path, 0755) < 0) {
|
||||
perror("Failed to create parent directory path");
|
||||
file_response_t resp_err = {RESP_ERROR, 0, {0, 0}};
|
||||
writen(sock_fd, &resp_err, sizeof(resp_err));
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t file_id = ++file_id_counter;
|
||||
|
||||
if (use_udp) {
|
||||
file_response_t resp_udp = {RESP_USE_UDP, file_id, {0, 0}};
|
||||
if (writen(sock_fd, &resp_udp, sizeof(resp_udp)) != sizeof(resp_udp)) {
|
||||
perror("Failed to send file response header");
|
||||
break;
|
||||
}
|
||||
|
||||
active_file_t active;
|
||||
active.file_id = file_id;
|
||||
active.file_size = file_size;
|
||||
active.total_blocks = (file_size + UDP_PAYLOAD_MAX - 1) / UDP_PAYLOAD_MAX;
|
||||
active.received_blocks = calloc(active.total_blocks, 1);
|
||||
snprintf(active.target_path, sizeof(active.target_path), "%s", target_path);
|
||||
|
||||
active.out_fd = open(target_path, O_WRONLY | O_CREAT | O_TRUNC, mode);
|
||||
if (active.out_fd < 0) {
|
||||
perror("Failed to open target file for UDP sync");
|
||||
free(active.received_blocks);
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Receiving over UDP: %s (%lu bytes, %u blocks)\n", filename, (unsigned long)file_size, active.total_blocks);
|
||||
|
||||
int loop_ret = receive_file_udp_loop(sock_fd, udp_fd, udp_session_id, &active);
|
||||
close(active.out_fd);
|
||||
free(active.received_blocks);
|
||||
|
||||
if (loop_ret < 0) {
|
||||
fprintf(stderr, "UDP receive loop failed\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// Send final ACK over TCP
|
||||
uint32_t ack_ok = RESP_OK;
|
||||
if (writen(sock_fd, &ack_ok, sizeof(ack_ok)) != sizeof(ack_ok)) {
|
||||
perror("Failed to send final file ACK");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Saved (UDP): %s\n", target_path);
|
||||
} else {
|
||||
// TCP fallback
|
||||
file_response_t resp_tcp = {RESP_SEND_DATA, 0, {0, 0}};
|
||||
if (writen(sock_fd, &resp_tcp, sizeof(resp_tcp)) != sizeof(resp_tcp)) {
|
||||
perror("Failed to send file response header");
|
||||
break;
|
||||
}
|
||||
|
||||
int out_fd = open(target_path, O_WRONLY | O_CREAT | O_TRUNC, mode);
|
||||
if (out_fd < 0) {
|
||||
perror("Failed to open target file for TCP sync");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Receiving over TCP: %s (%lu bytes)\n", filename, (unsigned long)file_size);
|
||||
|
||||
char buffer[65536];
|
||||
uint64_t bytes_left = file_size;
|
||||
int write_error = 0;
|
||||
while (bytes_left > 0) {
|
||||
size_t to_read = (bytes_left > sizeof(buffer)) ? sizeof(buffer) : bytes_left;
|
||||
ssize_t nread = read(sock_fd, buffer, to_read);
|
||||
if (nread < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
perror("Socket read error");
|
||||
write_error = 1;
|
||||
break;
|
||||
}
|
||||
if (nread == 0) {
|
||||
fprintf(stderr, "Unexpected socket EOF\n");
|
||||
write_error = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!write_error) {
|
||||
if (writen(out_fd, buffer, nread) != nread) {
|
||||
perror("File write error");
|
||||
write_error = 1;
|
||||
}
|
||||
}
|
||||
bytes_left -= nread;
|
||||
}
|
||||
close(out_fd);
|
||||
|
||||
uint32_t ack = write_error ? RESP_ERROR : RESP_OK;
|
||||
if (writen(sock_fd, &ack, sizeof(ack)) != sizeof(ack)) {
|
||||
perror("Failed to send final file ACK");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!write_error) {
|
||||
printf("Saved (TCP): %s\n", target_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (udp_fd >= 0) {
|
||||
close(udp_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void print_usage(const char *prog) {
|
||||
fprintf(stderr, "Usage: %s [-p <port>] -d <destination_dir>\n", prog);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
srand(time(NULL));
|
||||
int port = DEFAULT_PORT;
|
||||
char *dest_dir = NULL;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "p:d:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'p':
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
dest_dir = optarg;
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!dest_dir) {
|
||||
print_usage(argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (stat(dest_dir, &st) < 0) {
|
||||
if (mkdir(dest_dir, 0755) < 0) {
|
||||
perror("mkdir dest_dir");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!S_ISDIR(st.st_mode)) {
|
||||
fprintf(stderr, "Destination path '%s' is not a directory.\n", dest_dir);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) {
|
||||
perror("socket creation failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int optval = 1;
|
||||
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
|
||||
perror("setsockopt SO_REUSEADDR failed");
|
||||
close(server_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct sockaddr_in address;
|
||||
memset(&address, 0, sizeof(address));
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
address.sin_port = htons(port);
|
||||
|
||||
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
|
||||
perror("bind failed");
|
||||
close(server_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (listen(server_fd, 32) < 0) {
|
||||
perror("listen failed");
|
||||
close(server_fd);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Server listening on port %d, writing to directory: %s\n", port, dest_dir);
|
||||
|
||||
while (1) {
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
|
||||
if (client_fd < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
perror("accept failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
char client_ip[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
|
||||
printf("\nConnection accepted from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
|
||||
|
||||
client_thread_arg_t *ca = malloc(sizeof(client_thread_arg_t));
|
||||
if (!ca) { perror("malloc"); close(client_fd); continue; }
|
||||
ca->client_fd = client_fd;
|
||||
snprintf(ca->dest_dir, sizeof(ca->dest_dir), "%s", dest_dir);
|
||||
|
||||
// TCP tuning for the accepted socket
|
||||
int optval = 1;
|
||||
setsockopt(client_fd, SOL_TCP, TCP_NODELAY, &optval, sizeof(optval));
|
||||
int bufsize = 4 * 1024 * 1024; // 4 MB
|
||||
setsockopt(client_fd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
|
||||
setsockopt(client_fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
|
||||
|
||||
pthread_t tid;
|
||||
if (pthread_create(&tid, NULL, client_thread, ca) != 0) {
|
||||
perror("pthread_create");
|
||||
close(client_fd);
|
||||
free(ca);
|
||||
} else {
|
||||
pthread_detach(tid); /* fire-and-forget; thread cleans up itself */
|
||||
}
|
||||
}
|
||||
|
||||
close(server_fd);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Binary file not shown.
+130
@@ -0,0 +1,130 @@
|
||||
#include "common.h"
|
||||
|
||||
ssize_t readn(int fd, void *vptr, size_t n) {
|
||||
size_t nleft;
|
||||
ssize_t nread;
|
||||
char *ptr;
|
||||
|
||||
ptr = vptr;
|
||||
nleft = n;
|
||||
while (nleft > 0) {
|
||||
if ((nread = read(fd, ptr, nleft)) < 0) {
|
||||
if (errno == EINTR) {
|
||||
nread = 0; /* call read() again */
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
break; /* EOF */
|
||||
}
|
||||
nleft -= nread;
|
||||
ptr += nread;
|
||||
}
|
||||
return n - nleft;
|
||||
}
|
||||
|
||||
ssize_t writen(int fd, const void *vptr, size_t n) {
|
||||
size_t nleft;
|
||||
ssize_t nwritten;
|
||||
const char *ptr;
|
||||
|
||||
ptr = vptr;
|
||||
nleft = n;
|
||||
while (nleft > 0) {
|
||||
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
|
||||
if (nwritten < 0 && errno == EINTR)
|
||||
nwritten = 0; /* and call write() again */
|
||||
else
|
||||
return -1; /* error */
|
||||
}
|
||||
nleft -= nwritten;
|
||||
ptr += nwritten;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// ─── Simple path creation ───────────────────────────────────────────────
|
||||
int make_path(const char *path, mode_t mode) {
|
||||
char tmp[2048], *p = NULL;
|
||||
size_t len;
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||
len = strlen(tmp);
|
||||
if (tmp[len - 1] == '/')
|
||||
tmp[len - 1] = '\0';
|
||||
for (p = tmp + 1; *p; p++)
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (mkdir(tmp, mode) == -1 && errno != EEXIST)
|
||||
return -1;
|
||||
*p = '/';
|
||||
}
|
||||
if (mkdir(tmp, mode) == -1 && errno != EEXIST)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ─── Work-Queue (client-side, thread-safe) ───────────────────────────────────
|
||||
|
||||
void wq_init(work_queue_t *q) {
|
||||
q->head = NULL;
|
||||
q->tail = NULL;
|
||||
q->done = 0;
|
||||
q->error = 0;
|
||||
pthread_mutex_init(&q->lock, NULL);
|
||||
pthread_cond_init(&q->cond, NULL);
|
||||
}
|
||||
|
||||
void wq_push(work_queue_t *q, const char *full_path, const char *rel_path, uint64_t file_size, uint64_t checksum) {
|
||||
file_task_t *t = malloc(sizeof(file_task_t));
|
||||
if (!t) { perror("wq_push malloc"); return; }
|
||||
snprintf(t->full_path, sizeof(t->full_path), "%s", full_path);
|
||||
snprintf(t->rel_path, sizeof(t->rel_path), "%s", rel_path);
|
||||
t->file_size = file_size;
|
||||
t->checksum = checksum;
|
||||
t->next = NULL;
|
||||
|
||||
pthread_mutex_lock(&q->lock);
|
||||
if (q->tail) {
|
||||
q->tail->next = t;
|
||||
} else {
|
||||
q->head = t;
|
||||
}
|
||||
q->tail = t;
|
||||
pthread_cond_signal(&q->cond);
|
||||
pthread_mutex_unlock(&q->lock);
|
||||
}
|
||||
|
||||
/* Returns NULL when queue is empty AND done=1 (no more work ever). */
|
||||
file_task_t *wq_pop(work_queue_t *q) {
|
||||
pthread_mutex_lock(&q->lock);
|
||||
while (q->head == NULL && !q->done) {
|
||||
pthread_cond_wait(&q->cond, &q->lock);
|
||||
}
|
||||
file_task_t *t = q->head;
|
||||
if (t) {
|
||||
q->head = t->next;
|
||||
if (!q->head) q->tail = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&q->lock);
|
||||
return t; /* NULL means done */
|
||||
}
|
||||
|
||||
void wq_finish(work_queue_t *q) {
|
||||
pthread_mutex_lock(&q->lock);
|
||||
q->done = 1;
|
||||
pthread_cond_broadcast(&q->cond); /* wake ALL waiting workers */
|
||||
pthread_mutex_unlock(&q->lock);
|
||||
}
|
||||
|
||||
void wq_destroy(work_queue_t *q) {
|
||||
/* drain any leftover tasks */
|
||||
file_task_t *t = q->head;
|
||||
while (t) {
|
||||
file_task_t *next = t->next;
|
||||
free(t);
|
||||
t = next;
|
||||
}
|
||||
pthread_mutex_destroy(&q->lock);
|
||||
pthread_cond_destroy(&q->cond);
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
#ifndef XXHASH_H
|
||||
#define XXHASH_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* xxHash64 — fast 64-bit non-cryptographic hash */
|
||||
uint64_t xxhash64(const void *data, size_t len, uint64_t seed);
|
||||
|
||||
#endif /* XXHASH_H */
|
||||
Reference in New Issue
Block a user