commit ba3da1a630b8994227ea7332fc9bdca2fcc21b32 Author: x3 Date: Sun May 2 21:19:33 2021 +0200 #1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcea0e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +/torrent-verify +tags diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9f2911c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "subm/heapless-bencode"] + path = subm/heapless-bencode + url = https://github.com/willemt/heapless-bencode diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c4a27e6 --- /dev/null +++ b/Makefile @@ -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) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..90923f9 --- /dev/null +++ b/src/main.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#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; +} diff --git a/src/metainfo.c b/src/metainfo.c new file mode 100644 index 0000000..5e2f1cd --- /dev/null +++ b/src/metainfo.c @@ -0,0 +1,393 @@ +#include "metainfo.h" +#include +#include +#include +#include + +#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 + diff --git a/src/metainfo.h b/src/metainfo.h new file mode 100644 index 0000000..748d43e --- /dev/null +++ b/src/metainfo.h @@ -0,0 +1,162 @@ +#ifndef METAFILE_H +#define METAFILE_H +#include + +/* 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 diff --git a/src/opts.c b/src/opts.c new file mode 100644 index 0000000..eb88021 --- /dev/null +++ b/src/opts.c @@ -0,0 +1,39 @@ +#include "opts.h" +#include + +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; +} diff --git a/src/opts.h b/src/opts.h new file mode 100644 index 0000000..3d67302 --- /dev/null +++ b/src/opts.h @@ -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 diff --git a/src/sha1.c b/src/sha1.c new file mode 100644 index 0000000..c2bf174 --- /dev/null +++ b/src/sha1.c @@ -0,0 +1,296 @@ +/* +SHA-1 in C +By Steve Reid +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 +#include + +/* for uint32_t */ +#include + +#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 + 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 */ diff --git a/src/showinfo.c b/src/showinfo.c new file mode 100644 index 0000000..6a8fe00 --- /dev/null +++ b/src/showinfo.c @@ -0,0 +1,97 @@ +#include +#include +#include +#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); +} diff --git a/src/showinfo.h b/src/showinfo.h new file mode 100644 index 0000000..a521a8d --- /dev/null +++ b/src/showinfo.h @@ -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 diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..31da055 --- /dev/null +++ b/src/util.c @@ -0,0 +1,47 @@ +#include +#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'; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..361c826 --- /dev/null +++ b/src/util.h @@ -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 diff --git a/src/verify.c b/src/verify.c new file mode 100644 index 0000000..3c3c79b --- /dev/null +++ b/src/verify.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include +#include "verify.h" +#include "sha1.h" +#include "opts.h" + +#ifdef MT +#include +#include +#include + +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 +/* 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); +} diff --git a/src/verify.h b/src/verify.h new file mode 100644 index 0000000..dc83019 --- /dev/null +++ b/src/verify.h @@ -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 diff --git a/subm/heapless-bencode b/subm/heapless-bencode new file mode 160000 index 0000000..7e2036b --- /dev/null +++ b/subm/heapless-bencode @@ -0,0 +1 @@ +Subproject commit 7e2036b23c37b2d24649d39deda0f743f31c945e