1.
This commit is contained in:
commit
bfef7d9084
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.o
|
||||
/torrent-verify
|
||||
tags
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "subm/heapless-bencode"]
|
||||
path = subm/heapless-bencode
|
||||
url = https://github.com/willemt/heapless-bencode
|
24
Makefile
Normal file
24
Makefile
Normal file
@ -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)
|
95
src/main.c
Normal file
95
src/main.c
Normal file
@ -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;
|
||||
}
|
393
src/metainfo.c
Normal file
393
src/metainfo.c
Normal file
@ -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
|
||||
|
162
src/metainfo.h
Normal file
162
src/metainfo.h
Normal file
@ -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
|
39
src/opts.c
Normal file
39
src/opts.c
Normal file
@ -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;
|
||||
}
|
14
src/opts.h
Normal file
14
src/opts.h
Normal file
@ -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
|
296
src/sha1.c
Normal file
296
src/sha1.c
Normal file
@ -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';
|
||||
}
|
||||
|
44
src/sha1.h
Normal file
44
src/sha1.h
Normal file
@ -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 */
|
97
src/showinfo.c
Normal file
97
src/showinfo.c
Normal file
@ -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);
|
||||
}
|
10
src/showinfo.h
Normal file
10
src/showinfo.h
Normal file
@ -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
|
47
src/util.c
Normal file
47
src/util.c
Normal file
@ -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';
|
||||
}
|
19
src/util.h
Normal file
19
src/util.h
Normal file
@ -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
|
478
src/verify.c
Normal file
478
src/verify.c
Normal file
@ -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);
|
||||
}
|
14
src/verify.h
Normal file
14
src/verify.h
Normal file
@ -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
|
1
subm/heapless-bencode
Submodule
1
subm/heapless-bencode
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7e2036b23c37b2d24649d39deda0f743f31c945e
|
Loading…
Reference in New Issue
Block a user