479 lines
14 KiB
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);
|
||
|
}
|