torrent-verifier/src/verify.c

479 lines
14 KiB
C

#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "verify.h"
#include "sha1.h"
#include "opts.h"
#ifdef MT
#include <sys/sysinfo.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct {
uint8_t* piece_data;
int piece_data_size;
int piece_index;
const sha1sum_t* expected_result;
int results_match;
int done;
sem_t sem_filled_buffer;
} verify_thread_data_t;
typedef struct {
pthread_t thread;
verify_thread_data_t thread_data;
} verify_thread_t;
static int mt_max_thread = 0;
static verify_thread_t* mt_threads = NULL;
/* Which thread data to fill? */
static verify_thread_data_t* mt_td_tofill = NULL;
static pthread_cond_t mt_cond_tofill;
/* Mutex for _tofill */
static pthread_mutex_t mt_mut_tofill;
/* Worker thread signals to main thread to fill it's buffer */
static sem_t mt_sem_needs_fill;
#endif
/*
* Check if file, or directory exists
* Return 0 if yes, or errno
*/
static int verify_file_exists(const char* path) {
if (access(path, F_OK|R_OK) == 0)
return 0;
return errno;
}
/*
* Uselessly complex function to get the file path into a stack
* buffer if the size is enough, or allocate one and copy it there
* heap_str needs to be freed, if it's not null
* Returns a pointer to the path string
*/
static char* verify_get_path(fileinfo_t* finfo, const char* data_dir, \
size_t data_dir_len, const char* torrent_name, int torrent_name_len, \
char* stack_str, size_t stack_str_size, \
char** heap_str, size_t* heap_str_size) {
int path_len = metainfo_fileinfo_path(finfo, NULL);
int req_len = path_len + data_dir_len + torrent_name_len + 1 + 1;
char* path_ptr = stack_str;
if (req_len > stack_str_size) {
/* Stack is not large enough, use the heap */
if (!(*heap_str)) {
/* Heap is not yet allocated */
*heap_str_size = req_len;
path_ptr = *heap_str = malloc(*heap_str_size);
} else if (path_len > *heap_str_size) {
/* Heap size is not large enough, reallocate */
*heap_str_size = req_len;
path_ptr = *heap_str = realloc(*heap_str, *heap_str_size);
} else {
/* Heap is allocated, and is large enough */
path_ptr = *heap_str;
}
}
char* path_ptr_curr = path_ptr;
memcpy(path_ptr_curr, data_dir, data_dir_len);
path_ptr_curr += data_dir_len;
/* WARNING: Not portable here */
*path_ptr_curr++ = '/';
memcpy(path_ptr_curr, torrent_name, torrent_name_len);
path_ptr_curr += torrent_name_len;
/* This may include multiple /'s but idc lol */
*path_ptr_curr++ = '/';
path_ptr_curr += metainfo_fileinfo_path(finfo, path_ptr_curr);
*path_ptr_curr = '\0';
return path_ptr;
}
typedef int (*fullpath_iter_cb)(const char* path, void* data);
/*
* Call the callback function with every full path
* in the torrent. If callbacks returns non-zero, terminate the iter
* If append_torrent_folder is 1 and the torrent is a multi file one,
* the torrent name will be appended after data_dir
*/
static int verify_fullpath_iter(metainfo_t* m, const char* data_dir, \
int append_torrent_folder, fullpath_iter_cb cb, void* cb_data) {
/* A sensible default on the stack */
char path_buffer[512];
/* If the above buffer is too small, malloc one */
char* path_heap_ptr = NULL;
size_t path_heap_size;
const char* torrent_folder = "";
int torrent_folder_len = 0;
int result = 0;
size_t data_dir_len = strlen(data_dir);
fileinfo_t finfo;
if (metainfo_is_multi_file(m)) {
fileiter_t fiter;
if (append_torrent_folder)
metainfo_name(m, &torrent_folder, &torrent_folder_len);
metainfo_fileiter_create(m, &fiter);
while (result == 0 && metainfo_file_next(&fiter, &finfo) == 0) {
char* path = verify_get_path(&finfo, data_dir, data_dir_len, \
torrent_folder, torrent_folder_len, path_buffer, \
sizeof(path_buffer), &path_heap_ptr, &path_heap_size);
result = cb(path, cb_data);
}
} else {
metainfo_fileinfo(m, &finfo);
char* path = verify_get_path(&finfo, data_dir, data_dir_len, \
torrent_folder, torrent_folder_len, path_buffer, \
sizeof(path_buffer), &path_heap_ptr, &path_heap_size);
result = cb(path, cb_data);
}
if (path_heap_ptr)
free(path_heap_ptr);
return result;
}
static int verify_is_files_exists_cb(const char* path, void* data) {
return verify_file_exists(path);
}
/*
* Check if the files in the torrent exists, or not
* If append_torrent_folder is 1 and the torrent is a multi file one,
* the torrent name will be appended after data_dir
* Return 0 if yes, and is readable, or an errno
*/
static int verify_is_files_exists(metainfo_t* m, const char* data_dir, \
int append_torrent_folder) {
return verify_fullpath_iter(m, data_dir, append_torrent_folder, \
verify_is_files_exists_cb, NULL);
}
/*
* Read in 1 piece size amount of data
* Returns 0 if buffer got filled, -1 if error and 1 if end of the file
*/
static int verify_read_piece(const char* path, FILE** f, int piece_size, \
uint8_t* out_bytes, int* out_bytes_size) {
if (!*f) {
/* If first file, open it */
*f = fopen(path, "rb");
if (!*f)
return -1;
}
int read;
out_bytes += *out_bytes_size;
while (*out_bytes_size != piece_size && (read = fread(out_bytes, \
1, piece_size - *out_bytes_size, *f)) > 0) {
*out_bytes_size += read;
out_bytes += read;
}
if (ferror(*f)) {
/* If end because of an error */
fclose(*f);
*f = NULL;
return -1;
}
if (feof(*f)) {
/* If we reached the end of the current file */
fclose(*f);
*f = NULL;
return 1;
}
/* If we filled the buffer */
return 0;
}
typedef struct {
metainfo_t* metai;
int piece_size;
uint8_t* piece_data;
int piece_data_size;
#ifdef MT
const sha1sum_t* expected_piece_hash;
#endif
int piece_index;
int file_count, file_index;
} verify_files_data_t;
#ifdef MT
static void verify_piece_hash_mt_cond_cleanup(void* arg) {
pthread_mutex_unlock(&mt_mut_tofill);
}
static void* verify_piece_hash_mt(void* param) {
verify_thread_data_t* data = (verify_thread_data_t*)param;
for (;;) {
/* Wait until we can put out pointer into the tofill pointer */
pthread_mutex_lock(&mt_mut_tofill);
while (mt_td_tofill != NULL) {
/* If we got cancelled when waiting on cond, mutex remains
* locked and thus, deadlock ensures */
pthread_cleanup_push(verify_piece_hash_mt_cond_cleanup, NULL);
pthread_cond_wait(&mt_cond_tofill, &mt_mut_tofill);
pthread_cleanup_pop(0);
}
mt_td_tofill = data;
/* Ask main to fill out buffer */
sem_post(&mt_sem_needs_fill);
pthread_mutex_unlock(&mt_mut_tofill);
/* Wait for it to be filled */
if (sem_wait(&data->sem_filled_buffer) == -1) {
if (errno == EINTR)
break;
}
/* Work on the data */
sha1sum_t result;
SHA1_CTX ctx;
SHA1Init(&ctx);
SHA1Update(&ctx, data->piece_data, data->piece_data_size);
SHA1Final(result, &ctx);
/* Compare the data */
if (memcmp(result, data->expected_result, sizeof(sha1sum_t)) != 0) {
data->results_match = 0;
} else {
data->results_match = 1;
}
}
return 0;
}
#else
static void verify_piece_hash(uint8_t* piece_data, int piece_size, sha1sum_t result) {
SHA1_CTX ctx;
SHA1Init(&ctx);
SHA1Update(&ctx, piece_data, piece_size);
SHA1Final(result, &ctx);
}
#endif
#ifdef MT
#include <time.h>
/* MT magic oOoOOoOOoOoo */
static int verify_files_cb(const char* path, void* data) {
verify_files_data_t* vfi = (verify_files_data_t*)data;
FILE* f = NULL;
int read_piece_result = 0;
int result = 0;
if (!opt_silent) {
vfi->file_index++;
printf("[%d/%d] Verifying file: %s\n", vfi->file_index, vfi->file_count, path);
}
for (;;) {
/* If we don't have enough data to give to the threads, read it here */
if (vfi->piece_data_size != vfi->piece_size) {
read_piece_result = verify_read_piece(path, &f, vfi->piece_size, \
vfi->piece_data, &vfi->piece_data_size);
if (read_piece_result == 1) {
/* End of file, try the next one */
break;
} else if (read_piece_result == -1) {
/* Something failed */
result = -1;
fprintf(stderr, "Reading piece: %d failed\n", vfi->piece_index);
break;
}
/* Else, buffer got filled, read target piece hash and continue */
if (metainfo_piece_index(vfi->metai, vfi->piece_index, &vfi->expected_piece_hash) == -1) {
fprintf(stderr, "Piece meta hash reading failed at %d\n", vfi->piece_index);
break;
}
/* BUT don't increment piece_index here, because it will be copied into a thread */
}
/* Wait until a thread signals us to fill it's buffer, and check result */
sem_wait(&mt_sem_needs_fill);
pthread_mutex_lock(&mt_mut_tofill);
if (mt_td_tofill->piece_data_size == vfi->piece_size && \
!mt_td_tofill->results_match) {
/* If there was a hash at least once and vertif failed */
fprintf(stderr, "Error at piece: %d\n", \
mt_td_tofill->piece_index);
pthread_mutex_unlock(&mt_mut_tofill);
result = -1;
goto end;
}
mt_td_tofill->piece_index = vfi->piece_index;
mt_td_tofill->piece_data_size = vfi->piece_data_size;
mt_td_tofill->expected_result = vfi->expected_piece_hash;
vfi->piece_index++;
/* Reset variable so we will read next piece */
vfi->piece_data_size = 0;
/* Swap buffers */
uint8_t* tmp = vfi->piece_data;
vfi->piece_data = mt_td_tofill->piece_data;
mt_td_tofill->piece_data = tmp;
/* Send thread to work */
sem_post(&mt_td_tofill->sem_filled_buffer);
mt_td_tofill = NULL;
/* Tell threads its okay to fill the tofill pointer */
pthread_cond_signal(&mt_cond_tofill);
pthread_mutex_unlock(&mt_mut_tofill);
}
end:
if (f)
fclose(f);
if (read_piece_result == -1) {
return -1;
}
return result;
}
#else
static int verify_files_cb(const char* path, void* data) {
verify_files_data_t* vfi = (verify_files_data_t*)data;
FILE* f = NULL;
int ver_res;
if (!opt_silent) {
vfi->file_index++;
printf("[%d/%d] Verifying file: %s\n", vfi->file_index, vfi->file_count, path);
}
while ((ver_res = verify_read_piece(path, &f, vfi->piece_size, \
vfi->piece_data, &vfi->piece_data_size)) == 0) {
if (vfi->piece_size != vfi->piece_data_size) {
fprintf(stderr, "piece_size != piece_data_size at hash\n");
}
sha1sum_t curr_piece_sum;
verify_piece_hash(vfi->piece_data, vfi->piece_data_size, curr_piece_sum);
const sha1sum_t* target_piece_sum;
if (metainfo_piece_index(vfi->metai, vfi->piece_index, &target_piece_sum) == -1)
goto error;
if (memcmp(curr_piece_sum, target_piece_sum, sizeof(sha1sum_t)) != 0)
goto error;
vfi->piece_index++;
vfi->piece_data_size = 0;
}
if (ver_res == -1)
return -1;
return 0;
error:
fprintf(stderr, "Error at piece: %d\n", vfi->piece_index);
if (f)
fclose(f);
return -1;
}
#endif
/*
* Returns 0 if all files match
*/
static int verify_files(metainfo_t* m, const char* data_dir, \
int append_torrent_folder) {
int result = 0;
#if MT
mt_max_thread = get_nprocs_conf();
pthread_mutex_init(&mt_mut_tofill, 0);
sem_init(&mt_sem_needs_fill, 0, 0);
pthread_cond_init(&mt_cond_tofill, 0);
mt_threads = calloc(mt_max_thread, sizeof(verify_thread_t));
for (int i = 0; i < mt_max_thread; i++) {
mt_threads[i].thread_data.piece_data = malloc(metainfo_piece_size(m));
sem_init(&mt_threads[i].thread_data.sem_filled_buffer, 0, 0);
if (pthread_create(&mt_threads[i].thread, NULL, verify_piece_hash_mt, &mt_threads[i].thread_data) != 0) {
perror("Thread creation failed: ");
exit(EXIT_FAILURE);
}
}
#endif
verify_files_data_t data;
data.piece_size = metainfo_piece_size(m);
data.piece_data = malloc(data.piece_size);
data.piece_data_size = 0;
data.piece_index = 0;
data.metai = m;
if (!opt_silent) {
data.file_count = metainfo_file_count(m);
data.file_index = 0;
}
int vres = verify_fullpath_iter(m, data_dir, append_torrent_folder, \
verify_files_cb, &data);
if (vres != 0) {
result = vres;
goto end;
}
/* Here, we still have one piece left */
sha1sum_t curr_piece_sum;
SHA1_CTX ctx;
SHA1Init(&ctx);
SHA1Update(&ctx, data.piece_data, data.piece_data_size);
SHA1Final(curr_piece_sum, &ctx);
const sha1sum_t* target_piece_sum;
if (metainfo_piece_index(m, data.piece_index, &target_piece_sum) == -1) {
result = -1;
goto end;
}
if (memcmp(curr_piece_sum, target_piece_sum, sizeof(sha1sum_t)) != 0) {
result = -1;
goto end;
}
end:
#ifdef MT
for (int i = 0; i < mt_max_thread; i++) {
pthread_cancel(mt_threads[i].thread);
pthread_join(mt_threads[i].thread, NULL);
sem_destroy(&mt_threads[i].thread_data.sem_filled_buffer);
free(mt_threads[i].thread_data.piece_data);
}
free(mt_threads);
pthread_cond_destroy(&mt_cond_tofill);
pthread_mutex_destroy(&mt_mut_tofill);
sem_destroy(&mt_sem_needs_fill);
#endif
free(data.piece_data);
return result;
}
int verify(metainfo_t* metai, const char* data_dir, int append_folder) {
int files_no_exists = verify_is_files_exists(metai, data_dir, append_folder);
if (files_no_exists)
return files_no_exists;
return verify_files(metai, data_dir, append_folder);
}