mirror of
https://github.com/transmission/transmission
synced 2024-12-29 19:16:23 +00:00
97a0fed734
* MISRA C:2004, 12.4 - The right-hand operand of a logical && or || operator shall not contain side effects. * MISRA C++:2008, 5-14-1 - The right hand operand of a logical && or || operator shall not contain side effects. * MISRA C:2012, 13.5 - The right hand operand of a logical && or || operator shall not contain persistent side effects * CERT, EXP02-C. - Be aware of the short-circuit behavior of the logical AND and OR operators
589 lines
14 KiB
C
589 lines
14 KiB
C
/*
|
|
* This file Copyright (C) 2013-2017 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h> /* mkstemp() */
|
|
#include <string.h> /* strcmp() */
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h> /* sync() */
|
|
#endif
|
|
|
|
#include "transmission.h"
|
|
#include "crypto-utils.h"
|
|
#include "error.h"
|
|
#include "file.h"
|
|
#include "platform.h" /* TR_PATH_DELIMETER */
|
|
#include "torrent.h"
|
|
#include "tr-assert.h"
|
|
#include "trevent.h"
|
|
#include "variant.h"
|
|
#include "libtransmission-test.h"
|
|
|
|
bool verbose = false;
|
|
|
|
int current_test = 0;
|
|
|
|
bool should_print(bool pass)
|
|
{
|
|
if (!pass)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (verbose)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#ifdef VERBOSE
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool libtest_check(char const* file, int line, bool pass, bool condition, char const* condition_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
fprintf(stderr, "%s %s:%d: %s (%s)\n", pass ? "PASS" : "FAIL", file, line, condition_str, condition ? "true" : "false");
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
bool libtest_check_bool(char const* file, int line, bool pass, bool lhs, bool rhs, char const* lhs_str, char const* op_str,
|
|
char const* rhs_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
fprintf(stderr, "%s %s:%d: %s %s %s (%s %s %s)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str,
|
|
lhs ? "true" : "false", op_str, rhs ? "true" : "false");
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
bool libtest_check_str(char const* file, int line, bool pass, char const* lhs, char const* rhs, char const* lhs_str,
|
|
char const* op_str, char const* rhs_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
char const* const lhs_quote = lhs != NULL ? "\"" : "";
|
|
char const* const rhs_quote = rhs != NULL ? "\"" : "";
|
|
|
|
fprintf(stderr, "%s %s:%d: %s %s %s (%s%s%s %s %s%s%s)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str,
|
|
lhs_quote, lhs != NULL ? lhs : "NULL", lhs_quote, op_str, rhs_quote, rhs != NULL ? rhs : "NULL", rhs_quote);
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
static void print_mem(FILE* stream, void const* data, size_t size)
|
|
{
|
|
if (data == NULL)
|
|
{
|
|
fprintf(stream, "NULL");
|
|
return;
|
|
}
|
|
|
|
if (size == 0)
|
|
{
|
|
fprintf(stream, "(no bytes)");
|
|
return;
|
|
}
|
|
|
|
uint8_t const* byte_data = data;
|
|
|
|
fprintf(stream, "x'");
|
|
|
|
for (size_t i = 0; i < size; ++i)
|
|
{
|
|
fprintf(stream, "%02x", (unsigned int)byte_data[i]);
|
|
}
|
|
|
|
fprintf(stream, "'");
|
|
}
|
|
|
|
bool libtest_check_mem(char const* file, int line, bool pass, void const* lhs, void const* rhs, size_t size,
|
|
char const* lhs_str, char const* op_str, char const* rhs_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
fprintf(stderr, "%s %s:%d: %s %s %s (", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str);
|
|
print_mem(stderr, lhs, size);
|
|
fprintf(stderr, " %s ", op_str);
|
|
print_mem(stderr, rhs, size);
|
|
fprintf(stderr, ")\n");
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
bool libtest_check_int(char const* file, int line, bool pass, intmax_t lhs, intmax_t rhs, char const* lhs_str,
|
|
char const* op_str, char const* rhs_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
fprintf(stderr, "%s %s:%d: %s %s %s (%jd %s %jd)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs,
|
|
op_str, rhs);
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
bool libtest_check_uint(char const* file, int line, bool pass, uintmax_t lhs, uintmax_t rhs, char const* lhs_str,
|
|
char const* op_str, char const* rhs_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
fprintf(stderr, "%s %s:%d: %s %s %s (%ju %s %ju)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs,
|
|
op_str, rhs);
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
bool libtest_check_ptr(char const* file, int line, bool pass, void const* lhs, void const* rhs, char const* lhs_str,
|
|
char const* op_str, char const* rhs_str)
|
|
{
|
|
if (should_print(pass))
|
|
{
|
|
fprintf(stderr, "%s %s:%d: %s %s %s (%p %s %p)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs,
|
|
op_str, rhs);
|
|
}
|
|
|
|
return pass;
|
|
}
|
|
|
|
int runTests(testFunc const* const tests, int numTests)
|
|
{
|
|
int ret = 0;
|
|
|
|
(void)current_test; /* Use test even if we don't have any tests to run */
|
|
|
|
for (int i = 0; i < numTests; i++)
|
|
{
|
|
if ((*tests[i])() != 0)
|
|
{
|
|
++ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static char* tr_getcwd(void)
|
|
{
|
|
char* result;
|
|
tr_error* error = NULL;
|
|
|
|
result = tr_sys_dir_get_current(&error);
|
|
|
|
if (result == NULL)
|
|
{
|
|
fprintf(stderr, "getcwd error: \"%s\"", error->message);
|
|
tr_error_free(error);
|
|
result = tr_strdup("");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
char* libtest_sandbox_create(void)
|
|
{
|
|
char* path = tr_getcwd();
|
|
char* sandbox = tr_buildPath(path, "sandbox-XXXXXX", NULL);
|
|
tr_free(path);
|
|
tr_sys_dir_create_temp(sandbox, NULL);
|
|
return tr_sys_path_native_separators(sandbox);
|
|
}
|
|
|
|
static void rm_rf(char const* killme)
|
|
{
|
|
tr_sys_path_info info;
|
|
|
|
if (!tr_sys_path_get_info(killme, 0, &info, NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
tr_sys_dir_t odir = info.type == TR_SYS_PATH_IS_DIRECTORY ? tr_sys_dir_open(killme, NULL) : TR_BAD_SYS_DIR;
|
|
|
|
if (odir != TR_BAD_SYS_DIR)
|
|
{
|
|
char const* name;
|
|
|
|
while ((name = tr_sys_dir_read_name(odir, NULL)) != NULL)
|
|
{
|
|
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
|
|
{
|
|
char* tmp = tr_buildPath(killme, name, NULL);
|
|
rm_rf(tmp);
|
|
tr_free(tmp);
|
|
}
|
|
}
|
|
|
|
tr_sys_dir_close(odir, NULL);
|
|
}
|
|
|
|
if (verbose)
|
|
{
|
|
fprintf(stderr, "cleanup: removing %s\n", killme);
|
|
}
|
|
|
|
tr_sys_path_remove(killme, NULL);
|
|
}
|
|
|
|
void libtest_sandbox_destroy(char const* sandbox)
|
|
{
|
|
rm_rf(sandbox);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
#define MEM_K 1024
|
|
#define MEM_K_STR "KiB"
|
|
#define MEM_M_STR "MiB"
|
|
#define MEM_G_STR "GiB"
|
|
#define MEM_T_STR "TiB"
|
|
|
|
#define DISK_K 1000
|
|
#define DISK_K_STR "kB"
|
|
#define DISK_M_STR "MB"
|
|
#define DISK_G_STR "GB"
|
|
#define DISK_T_STR "TB"
|
|
|
|
#define SPEED_K 1000
|
|
#define SPEED_K_STR "kB/s"
|
|
#define SPEED_M_STR "MB/s"
|
|
#define SPEED_G_STR "GB/s"
|
|
#define SPEED_T_STR "TB/s"
|
|
|
|
tr_session* libttest_session_init(tr_variant* settings)
|
|
{
|
|
size_t len;
|
|
char const* str;
|
|
char* sandbox;
|
|
char* path;
|
|
tr_quark q;
|
|
static bool formatters_inited = false;
|
|
tr_session* session;
|
|
tr_variant local_settings;
|
|
|
|
tr_variantInitDict(&local_settings, 10);
|
|
|
|
if (settings == NULL)
|
|
{
|
|
settings = &local_settings;
|
|
}
|
|
|
|
sandbox = libtest_sandbox_create();
|
|
|
|
if (!formatters_inited)
|
|
{
|
|
formatters_inited = true;
|
|
tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
|
|
tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
|
|
tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
|
|
}
|
|
|
|
/* download dir */
|
|
q = TR_KEY_download_dir;
|
|
|
|
if (tr_variantDictFindStr(settings, q, &str, &len))
|
|
{
|
|
path = tr_strdup_printf("%s/%*.*s", sandbox, (int)len, (int)len, str);
|
|
}
|
|
else
|
|
{
|
|
path = tr_buildPath(sandbox, "Downloads", NULL);
|
|
}
|
|
|
|
tr_sys_dir_create(path, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL);
|
|
tr_variantDictAddStr(settings, q, path);
|
|
tr_free(path);
|
|
|
|
/* incomplete dir */
|
|
q = TR_KEY_incomplete_dir;
|
|
|
|
if (tr_variantDictFindStr(settings, q, &str, &len))
|
|
{
|
|
path = tr_strdup_printf("%s/%*.*s", sandbox, (int)len, (int)len, str);
|
|
}
|
|
else
|
|
{
|
|
path = tr_buildPath(sandbox, "Incomplete", NULL);
|
|
}
|
|
|
|
tr_variantDictAddStr(settings, q, path);
|
|
tr_free(path);
|
|
|
|
path = tr_buildPath(sandbox, "blocklists", NULL);
|
|
tr_sys_dir_create(path, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL);
|
|
tr_free(path);
|
|
|
|
q = TR_KEY_port_forwarding_enabled;
|
|
|
|
if (tr_variantDictFind(settings, q) == NULL)
|
|
{
|
|
tr_variantDictAddBool(settings, q, false);
|
|
}
|
|
|
|
q = TR_KEY_dht_enabled;
|
|
|
|
if (tr_variantDictFind(settings, q) == NULL)
|
|
{
|
|
tr_variantDictAddBool(settings, q, false);
|
|
}
|
|
|
|
q = TR_KEY_message_level;
|
|
|
|
if (tr_variantDictFind(settings, q) == NULL)
|
|
{
|
|
tr_variantDictAddInt(settings, q, verbose ? TR_LOG_DEBUG : TR_LOG_ERROR);
|
|
}
|
|
|
|
session = tr_sessionInit(sandbox, !verbose, settings);
|
|
|
|
tr_free(sandbox);
|
|
tr_variantFree(&local_settings);
|
|
return session;
|
|
}
|
|
|
|
void libttest_session_close(tr_session* session)
|
|
{
|
|
char* sandbox;
|
|
|
|
sandbox = tr_strdup(tr_sessionGetConfigDir(session));
|
|
tr_sessionClose(session);
|
|
tr_logFreeQueue(tr_logGetQueue());
|
|
session = NULL;
|
|
|
|
libtest_sandbox_destroy(sandbox);
|
|
tr_free(sandbox);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
tr_torrent* libttest_zero_torrent_init(tr_session* session)
|
|
{
|
|
int err;
|
|
size_t metainfo_len;
|
|
char* metainfo;
|
|
char const* metainfo_base64;
|
|
tr_torrent* tor;
|
|
tr_ctor* ctor;
|
|
|
|
/*
|
|
1048576 files-filled-with-zeroes/1048576
|
|
4096 files-filled-with-zeroes/4096
|
|
512 files-filled-with-zeroes/512
|
|
*/
|
|
metainfo_base64 =
|
|
"ZDg6YW5ub3VuY2UzMTpodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Fubm91bmNlMTA6Y3JlYXRlZCBi"
|
|
"eTI1OlRyYW5zbWlzc2lvbi8yLjYxICgxMzQwNykxMzpjcmVhdGlvbiBkYXRlaTEzNTg3MDQwNzVl"
|
|
"ODplbmNvZGluZzU6VVRGLTg0OmluZm9kNTpmaWxlc2xkNjpsZW5ndGhpMTA0ODU3NmU0OnBhdGhs"
|
|
"NzoxMDQ4NTc2ZWVkNjpsZW5ndGhpNDA5NmU0OnBhdGhsNDo0MDk2ZWVkNjpsZW5ndGhpNTEyZTQ6"
|
|
"cGF0aGwzOjUxMmVlZTQ6bmFtZTI0OmZpbGVzLWZpbGxlZC13aXRoLXplcm9lczEyOnBpZWNlIGxl"
|
|
"bmd0aGkzMjc2OGU2OnBpZWNlczY2MDpRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj"
|
|
"/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv17"
|
|
"26aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGEx"
|
|
"Uv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJ"
|
|
"tGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GI"
|
|
"QxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZC"
|
|
"S1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8K"
|
|
"T9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9um"
|
|
"o/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9"
|
|
"e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRh"
|
|
"MVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMY"
|
|
"SbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLOlf5A+Tz30nMBVuNM2hpV3wg/103"
|
|
"OnByaXZhdGVpMGVlZQ==";
|
|
|
|
/* create the torrent ctor */
|
|
metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len);
|
|
TR_ASSERT(metainfo != NULL);
|
|
TR_ASSERT(metainfo_len > 0);
|
|
TR_ASSERT(session != NULL);
|
|
ctor = tr_ctorNew(session);
|
|
tr_ctorSetMetainfo(ctor, (uint8_t*)metainfo, metainfo_len);
|
|
tr_ctorSetPaused(ctor, TR_FORCE, true);
|
|
|
|
/* create the torrent */
|
|
err = 0;
|
|
tor = tr_torrentNew(ctor, &err, NULL);
|
|
TR_ASSERT(err == 0);
|
|
|
|
/* cleanup */
|
|
tr_free(metainfo);
|
|
tr_ctorFree(ctor);
|
|
return tor;
|
|
}
|
|
|
|
void libttest_zero_torrent_populate(tr_torrent* tor, bool complete)
|
|
{
|
|
for (tr_file_index_t i = 0; i < tor->info.fileCount; ++i)
|
|
{
|
|
int err;
|
|
tr_sys_file_t fd;
|
|
char* path;
|
|
char* dirname;
|
|
tr_file const* file = &tor->info.files[i];
|
|
|
|
if (!complete && i == 0)
|
|
{
|
|
path = tr_strdup_printf("%s%c%s.part", tor->currentDir, TR_PATH_DELIMITER, file->name);
|
|
}
|
|
else
|
|
{
|
|
path = tr_strdup_printf("%s%c%s", tor->currentDir, TR_PATH_DELIMITER, file->name);
|
|
}
|
|
|
|
dirname = tr_sys_path_dirname(path, NULL);
|
|
tr_sys_dir_create(dirname, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL);
|
|
fd = tr_sys_file_open(path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, 0600, NULL);
|
|
|
|
for (uint64_t j = 0; j < file->length; ++j)
|
|
{
|
|
tr_sys_file_write(fd, (!complete && i == 0 && j < tor->info.pieceSize) ? "\1" : "\0", 1, NULL, NULL);
|
|
}
|
|
|
|
tr_sys_file_close(fd, NULL);
|
|
|
|
tr_free(dirname);
|
|
tr_free(path);
|
|
|
|
path = tr_torrentFindFile(tor, i);
|
|
TR_ASSERT(path != NULL);
|
|
err = errno;
|
|
TR_ASSERT(tr_sys_path_exists(path, NULL));
|
|
errno = err;
|
|
tr_free(path);
|
|
}
|
|
|
|
libttest_sync();
|
|
libttest_blockingTorrentVerify(tor);
|
|
|
|
if (complete)
|
|
{
|
|
TR_ASSERT(tr_torrentStat(tor)->leftUntilDone == 0);
|
|
}
|
|
else
|
|
{
|
|
TR_ASSERT(tr_torrentStat(tor)->leftUntilDone == tor->info.pieceSize);
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
static void onVerifyDone(tr_torrent* tor UNUSED, bool aborted UNUSED, void* done)
|
|
{
|
|
*(bool*)done = true;
|
|
}
|
|
|
|
void libttest_blockingTorrentVerify(tr_torrent* tor)
|
|
{
|
|
TR_ASSERT(tor->session != NULL);
|
|
TR_ASSERT(!tr_amInEventThread(tor->session));
|
|
|
|
bool done = false;
|
|
|
|
tr_torrentVerify(tor, onVerifyDone, &done);
|
|
|
|
while (!done)
|
|
{
|
|
tr_wait_msec(10);
|
|
}
|
|
}
|
|
|
|
static void build_parent_dir(char const* path)
|
|
{
|
|
char* dir;
|
|
tr_error* error = NULL;
|
|
int const tmperr = errno;
|
|
|
|
dir = tr_sys_path_dirname(path, NULL);
|
|
tr_sys_dir_create(dir, TR_SYS_DIR_CREATE_PARENTS, 0700, &error);
|
|
TR_ASSERT(error == NULL);
|
|
tr_free(dir);
|
|
|
|
errno = tmperr;
|
|
}
|
|
|
|
void libtest_create_file_with_contents(char const* path, void const* payload, size_t n)
|
|
{
|
|
tr_sys_file_t fd;
|
|
int const tmperr = errno;
|
|
|
|
build_parent_dir(path);
|
|
|
|
fd = tr_sys_file_open(path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, 0600, NULL);
|
|
tr_sys_file_write(fd, payload, n, NULL, NULL);
|
|
tr_sys_file_close(fd, NULL);
|
|
|
|
libttest_sync();
|
|
|
|
errno = tmperr;
|
|
}
|
|
|
|
void libtest_create_file_with_string_contents(char const* path, char const* str)
|
|
{
|
|
libtest_create_file_with_contents(path, str, strlen(str));
|
|
}
|
|
|
|
void libtest_create_tmpfile_with_contents(char* tmpl, void const* payload, size_t n)
|
|
{
|
|
tr_sys_file_t fd;
|
|
int const tmperr = errno;
|
|
uint64_t n_left = n;
|
|
tr_error* error = NULL;
|
|
|
|
build_parent_dir(tmpl);
|
|
|
|
fd = tr_sys_file_open_temp(tmpl, NULL);
|
|
|
|
while (n_left > 0)
|
|
{
|
|
uint64_t n;
|
|
|
|
if (!tr_sys_file_write(fd, payload, n_left, &n, &error))
|
|
{
|
|
fprintf(stderr, "Error writing '%s': %s\n", tmpl, error->message);
|
|
tr_error_free(error);
|
|
break;
|
|
}
|
|
|
|
n_left -= n;
|
|
}
|
|
|
|
tr_sys_file_close(fd, NULL);
|
|
|
|
libttest_sync();
|
|
|
|
errno = tmperr;
|
|
}
|
|
|
|
void libttest_sync(void)
|
|
{
|
|
#ifndef _WIN32
|
|
sync();
|
|
#endif
|
|
}
|