@@ -0,0 +1,3 @@ | |||
*.o | |||
/torrent-verify | |||
tags |
@@ -0,0 +1,3 @@ | |||
[submodule "subm/heapless-bencode"] | |||
path = subm/heapless-bencode | |||
url = https://github.com/willemt/heapless-bencode |
@@ -0,0 +1,24 @@ | |||
MultiThread = Yes | |||
PROGNAME = torrent-verify | |||
CC = cc | |||
CFLAGS = -Wall -std=gnu99 -I./subm/heapless-bencode -Werror | |||
CPPFLAGS = -DPROGRAM_NAME='"$(PROGNAME)"' -DBUILD_INFO \ | |||
-DBUILD_HASH="\"`git rev-parse --abbrev-ref HEAD` -> `git rev-parse --short HEAD`\"" -DBUILD_DATE="\"`date -I`\"" | |||
ifeq ($(MultiThread), Yes) | |||
CFLAGS += -lpthread | |||
CPPFLAGS += -DMT | |||
endif | |||
SOURCE = $(wildcard subm/heapless-bencode/*.c) $(wildcard src/*.c) | |||
#OBJ = $(addsuffix .o,$(basename $(SOURCE))) | |||
OBJS = $(SOURCE:.c=.o) | |||
all: $(PROGNAME) | |||
$(PROGNAME): $(OBJS) | |||
$(CC) -o $@ $+ $(CFLAGS) $(CPPFLAGS) | |||
clean: | |||
-rm -- $(OBJS) $(PROGNAME) |
@@ -0,0 +1,95 @@ | |||
#include <unistd.h> | |||
#include <stdlib.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "metainfo.h" | |||
#include "verify.h" | |||
#include "showinfo.h" | |||
#include "opts.h" | |||
#ifndef PROGRAM_NAME | |||
#define PROGRAM_NAME "torrent-verify" | |||
#endif | |||
void usage() { | |||
fprintf(stderr, "Usage: " PROGRAM_NAME " [-h | -i | -s] [-n] [-v data_path] [--] .torrent_file...\n"); | |||
exit(EXIT_FAILURE); | |||
} | |||
void help() { | |||
printf( | |||
"Usage:\n" | |||
" " PROGRAM_NAME " [options] <.torrent file>\n" | |||
"\n" | |||
"OPTIONS:\n" | |||
" -h print this help text\n" | |||
" -i show info about the torrent file\n" | |||
" -v PATH verify the torrent file, pass in the path of the files\n" | |||
" -s don't write any output\n" | |||
" -n Don't use torrent name as a folder when verifying\n" | |||
"\n" | |||
"EXIT CODE\n" | |||
" If no error, exit code is 0. In verify mode exit code is 0 if it's\n" | |||
" verified correctly, otherwise non-zero\n" | |||
#ifdef BUILD_INFO | |||
"\n" | |||
BUILD_HASH " (" BUILD_DATE ")\n" | |||
#ifdef MT | |||
"MultiThread support\n" | |||
#endif | |||
#endif | |||
); | |||
exit(EXIT_SUCCESS); | |||
} | |||
int main(int argc, char** argv) { | |||
if (sizeof(long int) < 8) { | |||
fprintf(stderr, "long int is less than 8 bytes. Create an issue please.\n"); | |||
exit(1); | |||
} | |||
if (opts_parse(argc, argv) == -1) | |||
usage(); | |||
if (opt_help) | |||
help(); | |||
if (optind >= argc) { | |||
fprintf(stderr, "Provide at least one torrent file\n"); | |||
usage(); | |||
} | |||
/* | |||
if (data_path && optind != argc - 1) { | |||
fprintf(stderr, "If used with -v, only input 1 bittorrent file\n"); | |||
usage(); | |||
} | |||
*/ | |||
int exit_code = EXIT_SUCCESS; | |||
for (int i = optind; i < argc; i++) { | |||
metainfo_t m; | |||
if (metainfo_create(&m, argv[i]) == -1) { | |||
return EXIT_FAILURE; | |||
} | |||
if (opt_showinfo && !opt_silent) { | |||
showinfo(&m); | |||
} | |||
if (opt_data_path) { /* Verify */ | |||
int verify_result = verify(&m, opt_data_path, !opt_no_use_dir); | |||
if (verify_result != 0) { | |||
if (!opt_silent) | |||
printf("Torrent verify failed: %s\n", strerror(verify_result)); | |||
exit_code = EXIT_FAILURE; | |||
} else { | |||
if (!opt_silent) | |||
printf("Torrent verified successfully\n"); | |||
} | |||
} | |||
metainfo_destroy(&m); | |||
} | |||
return exit_code; | |||
} |
@@ -0,0 +1,393 @@ | |||
#include "metainfo.h" | |||
#include <errno.h> | |||
#include <stdlib.h> | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "sha1.h" | |||
/* 128 MiB */ | |||
#define MAX_TORRENT_SIZE 128*1024*1024 | |||
/* | |||
* Read the file in memory, and return the pointer to it (which needs to be | |||
* freed) in out_contents and the size in out_size. If the file is too big, | |||
* fail. Returns 0 on success and an errno on fail. | |||
*/ | |||
static int metainfo_read(const char* path, char** out_contents, int* out_size) { | |||
int ret = 0; | |||
FILE* f = NULL; | |||
long size; | |||
char* contents = NULL, *curr_contents = NULL; | |||
size_t read; | |||
f = fopen(path, "rb"); | |||
if (!f) { | |||
ret = errno; | |||
goto end; | |||
} | |||
/* Get the file size, and bail if it's too large */ | |||
if (fseek(f, 0, SEEK_END) == -1) { | |||
ret = errno; | |||
goto end; | |||
} | |||
size = ftell(f); | |||
if (size > MAX_TORRENT_SIZE) { | |||
ret = EFBIG; | |||
goto end; | |||
} | |||
rewind(f); | |||
contents = curr_contents = malloc(size); | |||
if (!contents) { | |||
ret = ENOMEM; | |||
goto end; | |||
} | |||
/* Read it in */ | |||
while ((read = fread(curr_contents, 1, \ | |||
size - (curr_contents - contents), f)) > 0) { | |||
curr_contents += read; | |||
} | |||
if (ferror(f)) { | |||
ret = errno; | |||
free(curr_contents); | |||
curr_contents = NULL; | |||
goto end; | |||
} | |||
end: | |||
if (f) | |||
fclose(f); | |||
*out_size = size; | |||
*out_contents = contents; | |||
return ret; | |||
} | |||
static int len_strcmp(const char* s1, int s1_len, const char* s2, int s2_len) { | |||
return (s1_len == s2_len) && (strncmp(s1, s2, s1_len) == 0); | |||
} | |||
static int read_benc_int_into(bencode_t* benc, long int* val, int def) { | |||
if (!bencode_int_value(benc, val)) { | |||
*val = def; | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
#define tkey(s) len_strcmp(key, klen, s, strlen(s)) | |||
#define ttype(t) bencode_is_##t(&item) | |||
static int metainfo_hash_info(metainfo_t* metai, bencode_t* info) { | |||
const char* info_start; | |||
int info_len; | |||
/* Almost as if this function was made for this, lol */ | |||
bencode_dict_get_start_and_len(info, &info_start, &info_len); | |||
SHA1_CTX ctx; | |||
SHA1Init(&ctx); | |||
SHA1Update(&ctx, (const unsigned char*)info_start, info_len); | |||
SHA1Final((unsigned char*)&metai->info_hash, &ctx); | |||
return 0; | |||
} | |||
static int metainfo_parse_info(metainfo_t* metai, bencode_t* benc) { | |||
metainfo_hash_info(metai, benc); | |||
while (bencode_dict_has_next(benc)) { | |||
const char* key; | |||
int klen; | |||
bencode_t item; | |||
bencode_dict_get_next(benc, &item, &key, &klen); | |||
if (tkey("name") && ttype(string)) { | |||
metai->name = item; | |||
} else if (tkey("piece length") && ttype(int)) { | |||
read_benc_int_into(&item, &metai->piece_length, -1); | |||
} else if (tkey("pieces") && ttype(string)) { | |||
int plen = -1; | |||
bencode_string_value(&item, (const char**)&metai->pieces, &plen); | |||
/* A piece hash is always 20 bytes */ | |||
if (plen % sizeof(sha1sum_t) != 0) { | |||
fprintf(stderr, "A piece hash is not 20 bytes somewhere\n" \ | |||
"\tHash string length is: %d\n", plen); | |||
/* This should never happen tho */ | |||
plen -= plen % sizeof(sha1sum_t); | |||
} | |||
metai->piece_count = plen / sizeof(sha1sum_t); | |||
} else if (tkey("length") && ttype(int)) { /* If single file */ | |||
metai->is_multi_file = 0; | |||
read_benc_int_into(&item, &metai->file_size, -1); | |||
} else if (tkey("files") && ttype(list)) { /* If multiple files */ | |||
metai->is_multi_file = 1; | |||
metai->files = item; | |||
} else if (tkey("private") && ttype(int)) { | |||
read_benc_int_into(&item, &metai->is_private, 0); | |||
} else if (tkey("source") && ttype(string)) { | |||
metai->source = item; | |||
} else { | |||
fprintf(stderr, "Unknown key in info dict: %.*s\n", klen, key); | |||
} | |||
} | |||
return 0; | |||
} | |||
static int metainfo_parse(metainfo_t* metai, bencode_t* benc) { | |||
int ret = 0; | |||
/* All .torrent files are dictionaries, so this is an error */ | |||
if (!bencode_is_dict(benc)) { | |||
metainfo_destroy(metai); | |||
fprintf(stderr, "File is not a valid .torrent file\n"); | |||
return -1; | |||
} | |||
while (bencode_dict_has_next(benc)) { | |||
const char* key; | |||
int klen; | |||
bencode_t item; | |||
bencode_dict_get_next(benc, &item, &key, &klen); | |||
if (tkey("announce")) { | |||
metai->announce = item; | |||
} else if (tkey("created by") && ttype(string)) { | |||
metai->created_by = item; | |||
} else if (tkey("creation date") && ttype(int)) { | |||
read_benc_int_into(&item, &metai->creation_date, -1); | |||
} else if (tkey("info") && ttype(dict)) { | |||
metainfo_parse_info(metai, &item); | |||
} else if (tkey("comment") && ttype(string)) { | |||
metai->comment = item; | |||
} else { | |||
fprintf(stderr, "Unknown dict key: %.*s\n", klen, key); | |||
} | |||
} | |||
return ret; | |||
} | |||
int metainfo_create(metainfo_t* metai, const char* path) { | |||
char* bytes; | |||
int size; | |||
int ret = metainfo_read(path, &bytes, &size); | |||
if (ret) { | |||
fprintf(stderr, "Metafile reading failed: %s\n", \ | |||
strerror(ret)); | |||
return -1; | |||
} | |||
memset(metai, 0, sizeof(metainfo_t)); | |||
metai->bytes = bytes; | |||
metai->bytes_size = size; | |||
bencode_t benc; | |||
bencode_init(&benc, bytes, size); | |||
if (metainfo_parse(metai, &benc) == -1) { | |||
metainfo_destroy(metai); | |||
fprintf(stderr, "Can't parse metainfo file\n"); | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
void metainfo_destroy(metainfo_t* metai) { | |||
if (metai->bytes) { | |||
free(metai->bytes); | |||
metai->bytes = NULL; | |||
} | |||
} | |||
const sha1sum_t* metainfo_infohash(metainfo_t* metai) { | |||
return &metai->info_hash; | |||
} | |||
static int metainfo_get_string(bencode_t* benc, const char** str, int* len) { | |||
if (!benc->start) | |||
return -1; | |||
return bencode_string_value(benc, str, len) - 1; | |||
} | |||
int metainfo_announce(metainfo_t* metai, const char** str, int* len) { | |||
return metainfo_get_string(&metai->announce, str, len); | |||
} | |||
int metainfo_created_by(metainfo_t* metai, const char** str, int* len) { | |||
return metainfo_get_string(&metai->created_by, str, len); | |||
} | |||
int metainfo_creation_date(metainfo_t* metai) { | |||
return metai->creation_date; | |||
} | |||
int metainfo_source(metainfo_t* metai, const char** str, int* len) { | |||
return metainfo_get_string(&metai->source, str, len); | |||
} | |||
int metainfo_is_private(metainfo_t* metai) { | |||
return metai->is_private; | |||
} | |||
int metainfo_name(metainfo_t* metai, const char** str, int* len) { | |||
return metainfo_get_string(&metai->name, str, len); | |||
} | |||
int metainfo_comment(metainfo_t* metai, const char** str, int* len) { | |||
return metainfo_get_string(&metai->comment, str, len); | |||
} | |||
int metainfo_pieces(metainfo_t* metai, const sha1sum_t** piece_hash) { | |||
if (!metai->pieces) | |||
return -1; | |||
*piece_hash = metai->pieces; | |||
return 0; | |||
} | |||
int metainfo_piece_index(metainfo_t* metai, int index, \ | |||
const sha1sum_t** piece_hash) { | |||
if (!metai->pieces || index >= metai->piece_count) | |||
return -1; | |||
*piece_hash = metai->pieces + index; | |||
return 0; | |||
} | |||
int metainfo_piece_size(metainfo_t* metai) { | |||
return metai->piece_length; | |||
} | |||
long int metainfo_piece_count(metainfo_t* metai) { | |||
return metai->piece_count; | |||
} | |||
int metainfo_is_multi_file(metainfo_t* metai) { | |||
return metai->is_multi_file; | |||
} | |||
long int metainfo_file_count(metainfo_t* metai) { | |||
if (!(metai->is_multi_file && bencode_is_list(&metai->files))) | |||
return 0; | |||
long int count = 0; | |||
bencode_t iterb = metai->files; | |||
while (bencode_list_get_next(&iterb, NULL) != 0) | |||
count++; | |||
return count; | |||
} | |||
static int metainfo_file_dict2fileinfo(bencode_t* f_dict, fileinfo_t* finfo) { | |||
int has_path = 0, has_size = 0; | |||
while (bencode_dict_has_next(f_dict) && (!has_path || !has_size)) { | |||
const char* key; | |||
int klen; | |||
bencode_t item; | |||
bencode_dict_get_next(f_dict, &item, &key, &klen); | |||
if (tkey("length") && ttype(int)) { | |||
has_size = 1; | |||
bencode_int_value(&item, &finfo->size); | |||
} else if (tkey("path") && ttype(list)) { | |||
has_path = 1; | |||
finfo->path = item; | |||
} else { | |||
fprintf(stderr, "Unknown key in files dict: %*.s\n", klen, key); | |||
} | |||
} | |||
return (has_path && has_size) ? 0 : -1; | |||
} | |||
int metainfo_file_index(metainfo_t* metai, int index, fileinfo_t* finfo) { | |||
if (!(metai->is_multi_file && bencode_is_list(&metai->files))) | |||
return -1; | |||
bencode_t iterb = metai->files; | |||
while (index-- && bencode_list_get_next(&iterb, NULL) != 0); | |||
if (!bencode_is_dict(&iterb)) | |||
return -1; | |||
return metainfo_file_dict2fileinfo(&iterb, finfo); | |||
} | |||
int metainfo_fileiter_create(const metainfo_t* metai, fileiter_t* fileiter) { | |||
if (!metai->is_multi_file || !bencode_is_list(&metai->files)) | |||
return -1; | |||
fileiter->filelist = metai->files; | |||
return 0; | |||
} | |||
int metainfo_file_next(fileiter_t* iter, fileinfo_t* finfo) { | |||
if (!bencode_list_has_next(&iter->filelist)) | |||
return -1; | |||
bencode_t f_dict; | |||
bencode_list_get_next(&iter->filelist, &f_dict); | |||
return metainfo_file_dict2fileinfo(&f_dict, finfo); | |||
} | |||
int metainfo_fileinfo(metainfo_t* metai, fileinfo_t* finfo) { | |||
if (metai->is_multi_file) | |||
return -1; | |||
finfo->size = metai->file_size; | |||
/* In the case of single files, the name is the filename */ | |||
finfo->path = metai->name; | |||
return 0; | |||
} | |||
#ifdef _WIN32 | |||
#define PATH_SEP '\\' | |||
#else | |||
#define PATH_SEP '/' | |||
#endif | |||
int metainfo_fileinfo_path(fileinfo_t* finfo, char* out_str) { | |||
int count = 0; | |||
if (bencode_is_list(&finfo->path)) { | |||
bencode_t local_copy = finfo->path; | |||
bencode_t item; | |||
while (bencode_list_has_next(&local_copy)) { | |||
/* If not in the first iter, append separator */ | |||
if (count > 0) { | |||
if (out_str) | |||
*out_str++ = PATH_SEP; | |||
count++; | |||
} | |||
bencode_list_get_next(&local_copy, &item); | |||
int slen; | |||
const char* s; | |||
bencode_string_value(&item, &s, &slen); | |||
count += slen; | |||
if (out_str) { | |||
memcpy(out_str, s, slen); | |||
out_str += slen; | |||
} | |||
} | |||
} else { | |||
/* Single file, we shouldn't even copy here, but it's easier... */ | |||
int slen; | |||
const char* s; | |||
bencode_string_value(&finfo->path, &s, &slen); | |||
if (out_str) { | |||
memcpy(out_str, s, slen); | |||
} | |||
count = slen; | |||
} | |||
return count; | |||
} | |||
long int metainfo_fileinfo_size(fileinfo_t* finfo) { | |||
return finfo->size; | |||
} | |||
#undef tkey | |||
#undef ttype | |||
@@ -0,0 +1,162 @@ | |||
#ifndef METAFILE_H | |||
#define METAFILE_H | |||
#include <bencode.h> | |||
/* This file will parse the .torrent file and make accessor functions */ | |||
typedef struct { | |||
const char* str; | |||
int len; | |||
} lenstr_t; | |||
typedef struct { | |||
bencode_t path; | |||
long int size; | |||
} fileinfo_t; | |||
typedef struct { | |||
bencode_t filelist; | |||
} fileiter_t; | |||
typedef unsigned char sha1sum_t[20]; | |||
/* | |||
typedef struct __attribute__((packed)) sha1sum_t { | |||
unsigned char hash[20]; | |||
} sha1sum_t; | |||
*/ | |||
typedef struct { | |||
char* bytes; | |||
int bytes_size; | |||
sha1sum_t info_hash; | |||
const sha1sum_t* pieces; | |||
int piece_count; | |||
long int piece_length, creation_date; | |||
bencode_t announce, created_by, name, source, comment; | |||
long int is_private; | |||
int is_multi_file; | |||
union { | |||
long int file_size; | |||
bencode_t files; | |||
}; | |||
} metainfo_t; | |||
/* | |||
* Returns 0 on success, -1 on error | |||
*/ | |||
int metainfo_create(metainfo_t* metai, const char* path); | |||
void metainfo_destroy(metainfo_t* metai); | |||
/* | |||
* Get the info_hash of the torrent as a pointer | |||
*/ | |||
const sha1sum_t* metainfo_infohash(metainfo_t* metai); | |||
/* | |||
* Get the announce url | |||
*/ | |||
int metainfo_announce(metainfo_t* metai, const char** str, int* len); | |||
/* | |||
* Get the created by string | |||
*/ | |||
int metainfo_created_by(metainfo_t* metai, const char** str, int* len); | |||
/* | |||
* Get the creation date, as a unix timestamp | |||
*/ | |||
int metainfo_creation_date(metainfo_t* metai); | |||
/* | |||
* Get the source | |||
*/ | |||
int metainfo_source(metainfo_t* metai, const char** str, int* len); | |||
/* | |||
* Get the name of the torrent | |||
*/ | |||
int metainfo_name(metainfo_t* metai, const char** str, int* len); | |||
/* | |||
* Get the comment of the torrent | |||
*/ | |||
int metainfo_comment(metainfo_t* metai, const char** str, int* len); | |||
/* | |||
* Is this torrent private? | |||
*/ | |||
int metainfo_is_private(metainfo_t* metai); | |||
/* | |||
* Get the array of pieces | |||
*/ | |||
int metainfo_pieces(metainfo_t* metai, const sha1sum_t** piece_hash); | |||
/* | |||
* Get the number of pieces, this will return the number | |||
*/ | |||
long int metainfo_piece_count(metainfo_t* metai); | |||
/* | |||
* Get the index'th piece hash, returns -1 if invalid | |||
*/ | |||
int metainfo_piece_index(metainfo_t* metai, int index, \ | |||
const sha1sum_t** piece_hash); | |||
/* | |||
* Get the size of 1 piece | |||
*/ | |||
int metainfo_piece_size(metainfo_t* metai); | |||
/* | |||
* Return 1 if the torrent has multiple files, or 0 if has only 1 | |||
*/ | |||
int metainfo_is_multi_file(metainfo_t* metai); | |||
/* | |||
* Return the number of files, if it's a multi file. This is slow | |||
*/ | |||
long int metainfo_file_count(metainfo_t* metai); | |||
/* | |||
* Get the index'th file information, if it's a multi file | |||
* This is slow and shouldn't be used | |||
*/ | |||
int metainfo_file_index(metainfo_t* metai, int index, fileinfo_t* finfo); | |||
/* | |||
* Create a file iterator. | |||
* This interface should be used to get the file infos. | |||
* This doesn't need to be freed. | |||
*/ | |||
int metainfo_fileiter_create(const metainfo_t* metai, fileiter_t* fileiter); | |||
/* | |||
* Get the next fileinfo in a multi file torrent | |||
* Return -1 if there is no more files. | |||
*/ | |||
int metainfo_file_next(fileiter_t* iter, fileinfo_t* finfo); | |||
/* | |||
* Get the file information, if torrent has only 1 file | |||
*/ | |||
int metainfo_fileinfo(metainfo_t* metai, fileinfo_t* finfo); | |||
/* | |||
* Copy the file path from a fileinto_t struct into the buffer at 'out_str', | |||
* adding separators according to the current platform. | |||
* if 'out_str' is NULL, return the required bytes to store the name, | |||
* otherwise the number of bytes copied, or -1 if error. | |||
* The count does NOT include the null terminator | |||
*/ | |||
int metainfo_fileinfo_path(fileinfo_t* len, char* out_str); | |||
/* | |||
* Return the size of the file in bytes, ofc | |||
*/ | |||
long int metainfo_fileinfo_size(fileinfo_t* finfo); | |||
#endif |
@@ -0,0 +1,39 @@ | |||
#include "opts.h" | |||
#include <unistd.h> | |||
int opt_silent = 0; | |||
int opt_showinfo = 0; | |||
int opt_help = 0; | |||
int opt_no_use_dir = 0; | |||
int opt_pretty_progress = 0; | |||
char* opt_data_path = NULL; | |||
int opts_parse(int argc, char** argv) { | |||
int opt; | |||
while ((opt = getopt(argc, argv, "pnihsv:")) != -1) { | |||
switch (opt) { | |||
case 'i': | |||
opt_showinfo = 1; | |||
break; | |||
case 'h': | |||
opt_help = 1; | |||
break; | |||
case 's': | |||
opt_silent = 1; | |||
break; | |||
case 'n': | |||
opt_no_use_dir = 1; | |||
break; | |||
case 'p': | |||
opt_pretty_progress = 1; | |||
break; | |||
case 'v': | |||
opt_data_path = optarg; | |||
break; | |||
default: | |||
return -1; | |||
} | |||
} | |||
return 0; | |||
} |
@@ -0,0 +1,14 @@ | |||
#ifndef OPTS_H | |||
#define OPTS_H | |||
extern int opt_silent; | |||
extern int opt_showinfo; | |||
extern int opt_help; | |||
extern int opt_no_use_dir; | |||
extern int opt_pretty_progress; | |||
extern char* opt_data_path; | |||
/* Parse the given arguments. Return -1 if error */ | |||
int opts_parse(int argc, char** argv); | |||
#endif |
@@ -0,0 +1,296 @@ | |||
/* | |||
SHA-1 in C | |||
By Steve Reid <steve@edmweb.com> | |||
100% Public Domain | |||
Test Vectors (from FIPS PUB 180-1) | |||
"abc" | |||
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D | |||
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" | |||
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 | |||
A million repetitions of "a" | |||
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F | |||
*/ | |||
/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ | |||
/* #define SHA1HANDSOFF * Copies data before messing with it. */ | |||
#define SHA1HANDSOFF | |||
#include <stdio.h> | |||
#include <string.h> | |||
/* for uint32_t */ | |||
#include <stdint.h> | |||
#include "sha1.h" | |||
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) | |||
/* blk0() and blk() perform the initial expand. */ | |||
/* I got the idea of expanding during the round function from SSLeay */ | |||
#if BYTE_ORDER == LITTLE_ENDIAN | |||
#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ | |||
|(rol(block->l[i],8)&0x00FF00FF)) | |||
#elif BYTE_ORDER == BIG_ENDIAN | |||
#define blk0(i) block->l[i] | |||
#else | |||
#error "Endianness not defined!" | |||
#endif | |||
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ | |||
^block->l[(i+2)&15]^block->l[i&15],1)) | |||
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ | |||
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); | |||
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); | |||
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); | |||
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); | |||
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); | |||
/* Hash a single 512-bit block. This is the core of the algorithm. */ | |||
void SHA1Transform( | |||
uint32_t state[5], | |||
const unsigned char buffer[64] | |||
) | |||
{ | |||
uint32_t a, b, c, d, e; | |||
typedef union | |||
{ | |||
unsigned char c[64]; | |||
uint32_t l[16]; | |||
} CHAR64LONG16; | |||
#ifdef SHA1HANDSOFF | |||
CHAR64LONG16 block[1]; /* use array to appear as a pointer */ | |||
memcpy(block, buffer, 64); | |||
#else | |||
/* The following had better never be used because it causes the | |||
* pointer-to-const buffer to be cast into a pointer to non-const. | |||
* And the result is written through. I threw a "const" in, hoping | |||
* this will cause a diagnostic. | |||
*/ | |||
CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; | |||
#endif | |||
/* Copy context->state[] to working vars */ | |||
a = state[0]; | |||
b = state[1]; | |||
c = state[2]; | |||
d = state[3]; | |||
e = state[4]; | |||
/* 4 rounds of 20 operations each. Loop unrolled. */ | |||
R0(a, b, c, d, e, 0); | |||
R0(e, a, b, c, d, 1); | |||
R0(d, e, a, b, c, 2); | |||
R0(c, d, e, a, b, 3); | |||
R0(b, c, d, e, a, 4); | |||
R0(a, b, c, d, e, 5); | |||
R0(e, a, b, c, d, 6); | |||
R0(d, e, a, b, c, 7); | |||
R0(c, d, e, a, b, 8); | |||
R0(b, c, d, e, a, 9); | |||
R0(a, b, c, d, e, 10); | |||
R0(e, a, b, c, d, 11); | |||
R0(d, e, a, b, c, 12); | |||
R0(c, d, e, a, b, 13); | |||
R0(b, c, d, e, a, 14); | |||
R0(a, b, c, d, e, 15); | |||
R1(e, a, b, c, d, 16); | |||
R1(d, e, a, b, c, 17); | |||
R1(c, d, e, a, b, 18); | |||
R1(b, c, d, e, a, 19); | |||
R2(a, b, c, d, e, 20); | |||
R2(e, a, b, c, d, 21); | |||
R2(d, e, a, b, c, 22); | |||
R2(c, d, e, a, b, 23); | |||
R2(b, c, d, e, a, 24); | |||
R2(a, b, c, d, e, 25); | |||
R2(e, a, b, c, d, 26); | |||
R2(d, e, a, b, c, 27); | |||
R2(c, d, e, a, b, 28); | |||
R2(b, c, d, e, a, 29); | |||
R2(a, b, c, d, e, 30); | |||
R2(e, a, b, c, d, 31); | |||
R2(d, e, a, b, c, 32); | |||
R2(c, d, e, a, b, 33); | |||
R2(b, c, d, e, a, 34); | |||
R2(a, b, c, d, e, 35); | |||
R2(e, a, b, c, d, 36); | |||
R2(d, e, a, b, c, 37); | |||
R2(c, d, e, a, b, 38); | |||
R2(b, c, d, e, a, 39); | |||
R3(a, b, c, d, e, 40); | |||
R3(e, a, b, c, d, 41); | |||
R3(d, e, a, b, c, 42); | |||
R3(c, d, e, a, b, 43); | |||
R3(b, c, d, e, a, 44); | |||
R3(a, b, c, d, e, 45); | |||
R3(e, a, b, c, d, 46); | |||
R3(d, e, a, b, c, 47); | |||
R3(c, d, e, a, b, 48); | |||
R3(b, c, d, e, a, 49); | |||
R3(a, b, c, d, e, 50); | |||
R3(e, a, b, c, d, 51); | |||
R3(d, e, a, b, c, 52); | |||
R3(c, d, e, a, b, 53); | |||
R3(b, c, d, e, a, 54); | |||
R3(a, b, c, d, e, 55); | |||
R3(e, a, b, c, d, 56); | |||
R3(d, e, a, b, c, 57); | |||
R3(c, d, e, a, b, 58); | |||
R3(b, c, d, e, a, 59); | |||
R4(a, b, c, d, e, 60); | |||
R4(e, a, b, c, d, 61); | |||
R4(d, e, a, b, c, 62); | |||
R4(c, d, e, a, b, 63); | |||
R4(b, c, d, e, a, 64); | |||
R4(a, b, c, d, e, 65); | |||
R4(e, a, b, c, d, 66); | |||
R4(d, e, a, b, c, 67); | |||
R4(c, d, e, a, b, 68); | |||
R4(b, c, d, e, a, 69); | |||
R4(a, b, c, d, e, 70); | |||
R4(e, a, b, c, d, 71); | |||
R4(d, e, a, b, c, 72); | |||
R4(c, d, e, a, b, 73); | |||
R4(b, c, d, e, a, 74); | |||
R4(a, b, c, d, e, 75); | |||
R4(e, a, b, c, d, 76); | |||
R4(d, e, a, b, c, 77); | |||
R4(c, d, e, a, b, 78); | |||
R4(b, c, d, e, a, 79); | |||
/* Add the working vars back into context.state[] */ | |||
state[0] += a; | |||
state[1] += b; | |||
state[2] += c; | |||
state[3] += d; | |||
state[4] += e; | |||
/* Wipe variables */ | |||
a = b = c = d = e = 0; | |||
#ifdef SHA1HANDSOFF | |||
memset(block, '\0', sizeof(block)); | |||
#endif | |||
} | |||
/* SHA1Init - Initialize new context */ | |||
void SHA1Init( | |||
SHA1_CTX * context | |||
) | |||
{ | |||
/* SHA1 initialization constants */ | |||
context->state[0] = 0x67452301; | |||
context->state[1] = 0xEFCDAB89; | |||
context->state[2] = 0x98BADCFE; | |||
context->state[3] = 0x10325476; | |||
context->state[4] = 0xC3D2E1F0; | |||
context->count[0] = context->count[1] = 0; | |||
} | |||
/* Run your data through this. */ | |||
void SHA1Update( | |||
SHA1_CTX * context, | |||
const unsigned char *data, | |||
uint32_t len | |||
) | |||
{ | |||
uint32_t i; | |||
uint32_t j; | |||
j = context->count[0]; | |||
if ((context->count[0] += len << 3) < j) | |||
context->count[1]++; | |||
context->count[1] += (len >> 29); | |||
j = (j >> 3) & 63; | |||
if ((j + len) > 63) | |||
{ | |||
memcpy(&context->buffer[j], data, (i = 64 - j)); | |||
SHA1Transform(context->state, context->buffer); | |||
for (; i + 63 < len; i += 64) | |||
{ | |||
SHA1Transform(context->state, &data[i]); | |||
} | |||
j = 0; | |||
} | |||
else | |||
i = 0; | |||
memcpy(&context->buffer[j], &data[i], len - i); | |||
} | |||
/* Add padding and return the message digest. */ | |||
void SHA1Final( | |||
unsigned char digest[20], | |||
SHA1_CTX * context | |||
) | |||
{ | |||
unsigned i; | |||
unsigned char finalcount[8]; | |||
unsigned char c; | |||
#if 0 /* untested "improvement" by DHR */ | |||
/* Convert context->count to a sequence of bytes | |||
* in finalcount. Second element first, but | |||
* big-endian order within element. | |||
* But we do it all backwards. | |||
*/ | |||
unsigned char *fcp = &finalcount[8]; | |||
for (i = 0; i < 2; i++) | |||
{ | |||
uint32_t t = context->count[i]; | |||
int j; | |||
for (j = 0; j < 4; t >>= 8, j++) | |||
*--fcp = (unsigned char) t} | |||
#else | |||
for (i = 0; i < 8; i++) | |||
{ | |||
finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ | |||
} | |||
#endif | |||
c = 0200; | |||
SHA1Update(context, &c, 1); | |||
while ((context->count[0] & 504) != 448) | |||
{ | |||
c = 0000; | |||
SHA1Update(context, &c, 1); | |||
} | |||
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ | |||
for (i = 0; i < 20; i++) | |||
{ | |||
digest[i] = (unsigned char) | |||
((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); | |||
} | |||
/* Wipe variables */ | |||
memset(context, '\0', sizeof(*context)); | |||
memset(&finalcount, '\0', sizeof(finalcount)); | |||
} | |||
void SHA1( | |||
char *hash_out, | |||
const char *str, | |||
int len) | |||
{ | |||
SHA1_CTX ctx; | |||
unsigned int ii; | |||
SHA1Init(&ctx); | |||
for (ii=0; ii<len; ii+=1) | |||
SHA1Update(&ctx, (const unsigned char*)str + ii, 1); | |||
SHA1Final((unsigned char *)hash_out, &ctx); | |||
hash_out[20] = '\0'; | |||
} | |||
@@ -0,0 +1,44 @@ | |||
#ifndef SHA1_H | |||
#define SHA1_H | |||
/* | |||
SHA-1 in C | |||
By Steve Reid <steve@edmweb.com> | |||
100% Public Domain | |||
*/ | |||
#include "stdint.h" | |||
typedef struct | |||
{ | |||
uint32_t state[5]; | |||
uint32_t count[2]; | |||
unsigned char buffer[64]; | |||
} SHA1_CTX; | |||
void SHA1Transform( | |||
uint32_t state[5], | |||
const unsigned char buffer[64] | |||
); | |||
void SHA1Init( | |||
SHA1_CTX * context | |||
); | |||
void SHA1Update( | |||
SHA1_CTX * context, | |||
const unsigned char *data, | |||
uint32_t len | |||
); | |||
void SHA1Final( | |||
unsigned char digest[20], | |||
SHA1_CTX * context | |||
); | |||
void SHA1( | |||
char *hash_out, | |||
const char *str, | |||
int len); | |||
#endif /* SHA1_H */ |
@@ -0,0 +1,97 @@ | |||
#include <string.h> | |||
#include <stdio.h> | |||
#include <time.h> | |||
#include "showinfo.h" | |||
#include "util.h" | |||
void showinfo(metainfo_t* m) { | |||
const char* s; | |||
int slen; | |||
long int lint; | |||
char str_buff[100] = {0}; | |||
if (metainfo_name(m, &s, &slen) != 0) { | |||
s = "[UNKNOWN (>_<)]"; | |||
slen = strlen("[UNKNOWN (>_<)]"); | |||
} | |||
printf("Name: %.*s\n", slen, s); | |||
util_byte2hex((const unsigned char*)metainfo_infohash(m), sizeof(sha1sum_t), 0, str_buff); | |||
printf("Info hash: %s\n", str_buff); | |||
lint = metainfo_piece_count(m); | |||
printf("Piece count: %ld\n", lint); | |||
lint = metainfo_piece_size(m); | |||
if (util_byte2human(lint, 1, 0, str_buff, sizeof(str_buff)) == -1) | |||
strncpy(str_buff, "err", sizeof(str_buff)); | |||
printf("Piece size: %ld (%s)\n", lint, str_buff); | |||
printf("Is multi file: %s\n", metainfo_is_multi_file(m) ? "Yes" : "No"); | |||
if (metainfo_is_multi_file(m)) { | |||
printf("File count is: %ld\n", metainfo_file_count(m)); | |||
} | |||
printf("Is private: %s\n", metainfo_is_private(m) ? "Yes" : "No"); | |||
if (metainfo_announce(m, &s, &slen) != -1) { | |||
printf("Tracker: %.*s\n", slen, s); | |||
} | |||
lint = metainfo_creation_date(m); | |||
if (lint != -1) { | |||
struct tm* time = localtime(&lint); | |||
if (strftime(str_buff, sizeof(str_buff), "%F %T", time) == 0) | |||
strncpy(str_buff, "overflow", sizeof(str_buff)); | |||
printf("Creation date: %s\n", str_buff); | |||
} | |||
if (metainfo_created_by(m, &s, &slen) != -1) { | |||
printf("Created by: %.*s\n", slen, s); | |||
} | |||
if (metainfo_source(m, &s, &slen) != -1) { | |||
printf("Source: %.*s\n", slen, s); | |||
} | |||
printf("Files:\n"); | |||
unsigned long total_size = 0; | |||
fileinfo_t f; | |||
if (metainfo_is_multi_file(m)) { | |||
fileiter_t fi; | |||
if (metainfo_fileiter_create(m, &fi) == 0) { | |||
while (metainfo_file_next(&fi, &f) == 0) { | |||
int pathlen = metainfo_fileinfo_path(&f, NULL); | |||
char pathbuff[pathlen]; | |||
metainfo_fileinfo_path(&f, pathbuff); | |||
if (util_byte2human(f.size, 1, -1, str_buff, sizeof(str_buff)) == -1) { | |||
strncpy(str_buff, "err", sizeof(str_buff)); | |||
} | |||
printf("\t%8s %.*s\n", str_buff, pathlen, pathbuff); | |||
total_size += metainfo_fileinfo_size(&f); | |||
} | |||
} | |||
} else { | |||
metainfo_fileinfo(m, &f); | |||
int pathlen = metainfo_fileinfo_path(&f, NULL); | |||
char pathbuff[pathlen]; | |||
metainfo_fileinfo_path(&f, pathbuff); | |||
if (util_byte2human(f.size, 1, -1, str_buff, sizeof(str_buff)) == -1) { | |||
strncpy(str_buff, "err", sizeof(str_buff)); | |||
} | |||
printf("\t%8s %.*s\n", str_buff, pathlen, pathbuff); | |||
total_size = metainfo_fileinfo_size(&f); | |||
} | |||
if (util_byte2human(total_size, 1, -1, str_buff, sizeof(str_buff)) == -1) | |||
strncpy(str_buff, "err", sizeof(str_buff)); | |||
printf("Total size: %s\n", str_buff); | |||
} |
@@ -0,0 +1,10 @@ | |||
#ifndef SHOWINFO_H | |||
#define SHOWINFO_H | |||
#include "metainfo.h" | |||
/* | |||
* Print the contents of a metainfo file | |||
*/ | |||
void showinfo(metainfo_t* m); | |||
#endif |
@@ -0,0 +1,47 @@ | |||
#include <stdio.h> | |||
#include "util.h" | |||
#define B_IN_KiB 1024ull | |||
#define B_IN_MiB B_IN_KiB * 1024 | |||
#define B_IN_GiB B_IN_MiB * 1024 | |||
#define B_IN_TiB B_IN_GiB * 1024 | |||
#define B_IN_KB 1000ull | |||
#define B_IN_MB B_IN_KB * 1000 | |||
#define B_IN_GB B_IN_MB * 1000 | |||
#define B_IN_TB B_IN_GB * 1000 | |||
int util_byte2human(long int bytes, int binary, int precision, char* out, size_t out_len) { | |||
#define S_CONV(t) ((binary) ? (B_IN_##t##iB) : (B_IN_##t##B)) | |||
#define S_LESS(t) (bytes < S_CONV(t)) | |||
#define S_SUFFIX(t) ((binary) ? (t "iB") : (t "B")) | |||
if (!out) | |||
return -1; | |||
int written; | |||
if (S_LESS(K)) | |||
written = snprintf(out, out_len, "%ld B", bytes); | |||
else if (S_LESS(M)) | |||
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 0 : precision, bytes / (double)(S_CONV(K)), S_SUFFIX("K")); | |||
else if (S_LESS(G)) | |||
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 1 : precision, bytes / (double)(S_CONV(M)), S_SUFFIX("M")); | |||
else if (S_LESS(T)) | |||
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 2 : precision, bytes / (double)(S_CONV(G)), S_SUFFIX("G")); | |||
else | |||
written = snprintf(out, out_len, "%.*f %s", (precision == -1) ? 3 : precision, bytes / (double)(S_CONV(T)), S_SUFFIX("T")); | |||
return (written >= out_len) ? -1 : written; | |||
#undef S_SUFFIX | |||
#undef S_LESS | |||
#undef S_CONV | |||
} | |||
void util_byte2hex(const unsigned char* bytes, int bytes_len, int uppercase, char* out) { | |||
const char* hex = (uppercase) ? "0123456789ABCDEF" : "0123456789abcdef"; | |||
for (int i = 0; i < bytes_len; i++) { | |||
*out++ = hex[bytes[i] >> 4]; | |||
*out++ = hex[bytes[i] & 0xF]; | |||
} | |||
*out = '\0'; | |||
} |
@@ -0,0 +1,19 @@ | |||
#ifndef UTIL_H | |||
#define UTIL_H | |||
/* | |||
* Convert byte to human readable. | |||
* If binary is non-zero, use 1024 as conversation number, or else | |||
* use 1000. | |||
* precision is the precision, or -1 for the default | |||
* Returns the characters written on success, or -1 on error (like out is too small) | |||
*/ | |||
int util_byte2human(long int bytes, int binary, int precision, char* out, size_t out_len); | |||
/* | |||
* Convert raw bytes in 'bytes' to hex format into out | |||
* out has to be at least bytes_len * 2 + 1 large | |||
*/ | |||
void util_byte2hex(const unsigned char* bytes, int bytes_len, int uppercase, char* out); | |||
#endif |
@@ -0,0 +1,478 @@ | |||
#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); | |||
} |
@@ -0,0 +1,14 @@ | |||
#ifndef VERIFY_H | |||
#define VERIFY_H | |||
#include "metainfo.h" | |||
/* Verify torrent files here */ | |||
/* | |||
* Verify files inside a torrent file | |||
* If append folder is 1, and torrent is a multifile one, | |||
* the torrent's name will be appended to data_dir | |||
* Returns 0 if success, -num if error | |||
*/ | |||
int verify(metainfo_t* metai, const char* data_dir, int append_folder); | |||
#endif |
@@ -0,0 +1 @@ | |||
Subproject commit 7e2036b23c37b2d24649d39deda0f743f31c945e |