@@ -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 |