Refactor completion scripts execution

There're still a few issues here and there, but overall I believe it's now
better than it was before.
This commit is contained in:
Mike Gelfand 2017-11-28 01:22:44 +03:00
parent 5b29fe1556
commit 30c7c05cbb
17 changed files with 1214 additions and 140 deletions

View File

@ -341,6 +341,8 @@
C1077A51183EB29600634C22 /* file.h in Headers */ = {isa = PBXBuildFile; fileRef = C1077A4D183EB29600634C22 /* file.h */; };
C10C644D1D9AF328003C1B4C /* session-id.c in Sources */ = {isa = PBXBuildFile; fileRef = C10C644B1D9AF328003C1B4C /* session-id.c */; };
C10C644E1D9AF328003C1B4C /* session-id.h in Headers */ = {isa = PBXBuildFile; fileRef = C10C644C1D9AF328003C1B4C /* session-id.h */; };
C11DEA161FCD31C0009E22B9 /* subprocess-posix.c in Sources */ = {isa = PBXBuildFile; fileRef = C11DEA141FCD31C0009E22B9 /* subprocess-posix.c */; };
C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = C11DEA151FCD31C0009E22B9 /* subprocess.h */; };
C12F19791E1AE3C30005E93F /* upnperrors.c in Sources */ = {isa = PBXBuildFile; fileRef = C12F19771E1AE3C30005E93F /* upnperrors.c */; };
C12F197B1E1AE4460005E93F /* upnperrors.h in Headers */ = {isa = PBXBuildFile; fileRef = C12F197A1E1AE4460005E93F /* upnperrors.h */; };
C1305EBE186A13B100F03351 /* file.c in Sources */ = {isa = PBXBuildFile; fileRef = C1305EB8186A134000F03351 /* file.c */; };
@ -987,6 +989,8 @@
C1077A4D183EB29600634C22 /* file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = file.h; path = libtransmission/file.h; sourceTree = "<group>"; };
C10C644B1D9AF328003C1B4C /* session-id.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "session-id.c"; path = "libtransmission/session-id.c"; sourceTree = "<group>"; };
C10C644C1D9AF328003C1B4C /* session-id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "session-id.h"; path = "libtransmission/session-id.h"; sourceTree = "<group>"; };
C11DEA141FCD31C0009E22B9 /* subprocess-posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "subprocess-posix.c"; path = "libtransmission/subprocess-posix.c"; sourceTree = "<group>"; };
C11DEA151FCD31C0009E22B9 /* subprocess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = subprocess.h; path = libtransmission/subprocess.h; sourceTree = "<group>"; };
C12F19771E1AE3C30005E93F /* upnperrors.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = upnperrors.c; path = "third-party/miniupnpc/upnperrors.c"; sourceTree = "<group>"; };
C12F197A1E1AE4460005E93F /* upnperrors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = upnperrors.h; path = "third-party/miniupnpc/upnperrors.h"; sourceTree = "<group>"; };
C1305EB8186A134000F03351 /* file.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = file.c; path = libtransmission/file.c; sourceTree = "<group>"; };
@ -1357,6 +1361,8 @@
C1077A4C183EB29600634C22 /* file-posix.c */,
C1305EB8186A134000F03351 /* file.c */,
C1077A4D183EB29600634C22 /* file.h */,
C11DEA141FCD31C0009E22B9 /* subprocess-posix.c */,
C11DEA151FCD31C0009E22B9 /* subprocess.h */,
4D80185710BBC0B0008A4AF2 /* magnet.c */,
4D80185810BBC0B0008A4AF2 /* magnet.h */,
4D8017E810BBC073008A4AF2 /* torrent-magnet.c */,
@ -1810,6 +1816,7 @@
4D36BA780CA2F00800A63CA5 /* peer-mgr.h in Headers */,
4D36BA7A0CA2F00800A63CA5 /* peer-msgs.h in Headers */,
4D36BA7B0CA2F00800A63CA5 /* ptrarray.h in Headers */,
C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */,
A25D2CBE0CF4C73E0096A262 /* stats.h in Headers */,
C1033E0A1A3279B800EF44D8 /* crypto-utils.h in Headers */,
A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */,
@ -2358,6 +2365,7 @@
D4AF3B2F0C41F7A500D46B6B /* list.c in Sources */,
4394AC670C74FB6000F367E8 /* ptrarray.c in Sources */,
A24621420C769D0900088E81 /* trevent.c in Sources */,
C11DEA161FCD31C0009E22B9 /* subprocess-posix.c in Sources */,
4D36BA6F0CA2F00800A63CA5 /* crypto.c in Sources */,
4D36BA720CA2F00800A63CA5 /* handshake.c in Sources */,
4D36BA740CA2F00800A63CA5 /* peer-io.c in Sources */,

View File

@ -47,6 +47,8 @@ set(${PROJECT_NAME}_SOURCES
rpc-server.c
session.c
session-id.c
subprocess-posix.c
subprocess-win32.c
stats.c
torrent.c
torrent-ctor.c
@ -94,9 +96,9 @@ else()
endif()
if(WIN32)
set_source_files_properties(file-posix.c PROPERTIES HEADER_FILE_ONLY ON)
set_source_files_properties(file-posix.c subprocess-posix.c PROPERTIES HEADER_FILE_ONLY ON)
else()
set_source_files_properties(file-win32.c watchdir-win32.c PROPERTIES HEADER_FILE_ONLY ON)
set_source_files_properties(file-win32.c subprocess-win32.c watchdir-win32.c PROPERTIES HEADER_FILE_ONLY ON)
endif()
set(${PROJECT_NAME}_PUBLIC_HEADERS
@ -152,6 +154,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
resume.h
rpc-server.h
session.h
subprocess.h
stats.h
torrent.h
torrent-magnet.h
@ -275,11 +278,12 @@ if(ENABLE_TESTS)
set_property(TARGET ${TR_NAME}-test PROPERTY FOLDER "UnitTests")
set(crypto-test_ADD_SOURCES crypto-test-ref.h)
set(subprocess-test_ADD_SOURCES subprocess-test.cmd)
set(watchdir@generic-test_DEFINITIONS WATCHDIR_TEST_FORCE_GENERIC)
foreach(T bitfield blocklist clients crypto error file history json magnet makemeta metainfo move peer-msgs quark rename rpc
session tr-getopt utils variant watchdir watchdir@generic)
session subprocess tr-getopt utils variant watchdir watchdir@generic)
set(TP ${TR_NAME}-test-${T})
if(T MATCHES "^([^@]+)@.+$")
string(REPLACE "@" "_" TP "${TP}")
@ -295,6 +299,12 @@ if(ENABLE_TESTS)
add_test(NAME ${T} COMMAND ${TP})
set_property(TARGET ${TP} PROPERTY FOLDER "UnitTests")
endforeach()
if(WIN32)
add_custom_command(TARGET ${TR_NAME}-test-subprocess PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/subprocess-test.cmd
$<TARGET_FILE_DIR:${TR_NAME}-test-subprocess>/${TR_NAME}-test-subprocess.cmd)
endif()
endif()
if(INSTALL_LIB)

View File

@ -91,9 +91,9 @@ AM_CPPFLAGS += -DWITH_KQUEUE
endif
if WIN32
libtransmission_a_SOURCES += file-win32.c watchdir-win32.c
libtransmission_a_SOURCES += file-win32.c subprocess-win32.c watchdir-win32.c
else
libtransmission_a_SOURCES += file-posix.c
libtransmission_a_SOURCES += file-posix.c subprocess-posix.c
endif
if CRYPTO_USE_OPENSSL
@ -157,6 +157,7 @@ noinst_HEADERS = \
session.h \
session-id.h \
stats.h \
subprocess.h \
torrent.h \
torrent-magnet.h \
tr-getopt.h \
@ -197,6 +198,7 @@ TESTS = \
rename-test \
rpc-test \
session-test \
subprocess-test \
tr-getopt-test \
utils-test \
variant-test \
@ -286,6 +288,10 @@ session_test_SOURCES = session-test.c $(TEST_SOURCES)
session_test_LDADD = ${apps_ldadd}
session_test_LDFLAGS = ${apps_ldflags}
subprocess_test_SOURCES = subprocess-test.c $(TEST_SOURCES)
subprocess_test_LDADD = ${apps_ldadd}
subprocess_test_LDFLAGS = ${apps_ldflags}
tr_getopt_test_SOURCES = tr-getopt-test.c $(TEST_SOURCES)
tr_getopt_test_LDADD = ${apps_ldadd}
tr_getopt_test_LDFLAGS = ${apps_ldflags}

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2013-2014 Mnemosyne LLC
* 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.
@ -444,6 +444,11 @@ bool tr_sys_path_remove(char const* path, tr_error** error)
return ret;
}
char* tr_sys_path_native_separators(char* path)
{
return path;
}
tr_sys_file_t tr_sys_file_get_std(tr_std_sys_file_t std_file, tr_error** error)
{
tr_sys_file_t ret = TR_BAD_SYS_FILE;

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2013-2014 Mnemosyne LLC
* 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.
@ -959,6 +959,42 @@ static int test_path_remove(void)
return 0;
}
static int test_path_native_separators(void)
{
check_str(tr_sys_path_native_separators(NULL), == , NULL);
char path1[] = "";
char path2[] = "a";
char path3[] = "/";
char path4[] = "/a/b/c";
char path5[] = "C:\\a/b\\c";
struct
{
char* input;
char const* output;
}
test_data[] =
{
{ path1, "" },
{ path2, TR_IF_WIN32("a", "a") },
{ path3, TR_IF_WIN32("\\", "/") },
{ path4, TR_IF_WIN32("\\a\\b\\c", "/a/b/c") },
{ path5, TR_IF_WIN32("C:\\a\\b\\c", "C:\\a/b\\c") },
};
for (size_t i = 0; i < TR_N_ELEMENTS(test_data); ++i)
{
char* const output = tr_sys_path_native_separators(test_data[i].input);
check_str(output, ==, test_data[i].output);
check_str(test_data[i].input, ==, test_data[i].output);
check_ptr(output, ==, test_data[i].input);
}
return 0;
}
static int test_file_open(void)
{
char* const test_dir = create_test_dir(__FUNCTION__);
@ -1565,6 +1601,7 @@ int main(void)
test_path_basename_dirname,
test_path_rename,
test_path_remove,
test_path_native_separators,
test_file_open,
test_file_read_write_seek,
test_file_truncate,

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2013-2014 Mnemosyne LLC
* 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.
@ -742,6 +742,21 @@ bool tr_sys_path_remove(char const* path, tr_error** error)
return ret;
}
char* tr_sys_path_native_separators(char* path)
{
if (path == NULL)
{
return NULL;
}
for (char* slash = strchr(path, '/'); slash != NULL; slash = strchr(slash, '/'))
{
*slash = '\\';
}
return path;
}
tr_sys_file_t tr_sys_file_get_std(tr_std_sys_file_t std_file, tr_error** error)
{
tr_sys_file_t ret = TR_BAD_SYS_FILE;

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2013-2014 Mnemosyne LLC
* 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.
@ -266,6 +266,15 @@ bool tr_sys_path_rename(char const* src_path, char const* dst_path, struct tr_er
*/
bool tr_sys_path_remove(char const* path, struct tr_error** error);
/**
* @brief Transform path separators to native ones, in-place.
*
* @param[in,out] path Path to transform.
*
* @return Same path but with native (and uniform) separators.
*/
char* tr_sys_path_native_separators(char* path);
/* File-related wrappers */
/**

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2013-2014 Mnemosyne LLC
* 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.
@ -208,7 +208,7 @@ char* libtest_sandbox_create(void)
char* sandbox = tr_buildPath(path, "sandbox-XXXXXX", NULL);
tr_free(path);
tr_sys_dir_create_temp(sandbox, NULL);
return sandbox;
return tr_sys_path_native_separators(sandbox);
}
static void rm_rf(char const* killme)

View File

@ -0,0 +1,173 @@
/*
* This file Copyright (C) 2010-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 <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "transmission.h"
#include "error.h"
#include "subprocess.h"
#include "tr-assert.h"
#include "tr-macros.h"
#include "utils.h"
static void handle_sigchld(int i UNUSED)
{
int rc;
do
{
/* FIXME: Only check for our own PIDs */
rc = waitpid(-1, NULL, WNOHANG);
}
while (rc > 0 || (rc == -1 && errno == EINTR));
/* FIXME: Call old handler, if any */
}
static void set_system_error(tr_error** error, int code, char const* what)
{
if (error == NULL)
{
return;
}
if (what == NULL)
{
tr_error_set_literal(error, code, tr_strerror(code));
}
else
{
tr_error_set(error, code, "%s failed: %s", what, tr_strerror(code));
}
}
static bool tr_spawn_async_in_child(char* const* cmd, char* const* env, char const* work_dir, int pipe_fd)
{
if (env != NULL)
{
for (size_t i = 0; env[i] != NULL; ++i)
{
if (putenv(env[i]) != 0)
{
goto fail;
}
}
}
if (work_dir != NULL && chdir(work_dir) == -1)
{
goto fail;
}
if (execvp(cmd[0], cmd) == -1)
{
goto fail;
}
return true;
fail:
write(pipe_fd, &errno, sizeof(errno));
return false;
}
static bool tr_spawn_async_in_parent(int pipe_fd, tr_error** error)
{
int child_errno;
ssize_t count;
TR_STATIC_ASSERT(sizeof(child_errno) == sizeof(errno), "");
do
{
count = read(pipe_fd, &child_errno, sizeof(child_errno));
}
while (count == -1 && errno == EINTR);
close(pipe_fd);
if (count == -1)
{
/* Read failed (what to do?) */
}
else if (count == 0)
{
/* Child successfully exec-ed */
}
else
{
TR_ASSERT((size_t)count == sizeof(child_errno));
set_system_error(error, child_errno, "Child process setup");
return false;
}
return true;
}
bool tr_spawn_async(char* const* cmd, char* const* env, char const* work_dir, tr_error** error)
{
static bool sigchld_handler_set = false;
if (!sigchld_handler_set)
{
/* FIXME: "The effects of signal() in a multithreaded process are unspecified." (c) man 2 signal */
if (signal(SIGCHLD, &handle_sigchld) == SIG_ERR)
{
set_system_error(error, errno, "Call to signal()");
return false;
}
sigchld_handler_set = true;
}
int pipe_fds[2];
if (pipe(pipe_fds) == -1)
{
set_system_error(error, errno, "Call to pipe()");
return false;
}
if (fcntl(pipe_fds[1], F_SETFD, fcntl(pipe_fds[1], F_GETFD) | FD_CLOEXEC))
{
set_system_error(error, errno, "Call to fcntl()");
close(pipe_fds[0]);
close(pipe_fds[1]);
return false;
}
int const child_pid = fork();
if (child_pid == -1)
{
set_system_error(error, errno, "Call to fork()");
return false;
}
if (child_pid == 0)
{
close(pipe_fds[0]);
if (!tr_spawn_async_in_child(cmd, env, work_dir, pipe_fds[1]))
{
_exit(0);
}
}
close(pipe_fds[1]);
return tr_spawn_async_in_parent(pipe_fds[0], error);
}

View File

@ -0,0 +1,392 @@
/*
* This file Copyright (C) 2017 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <stdlib.h>
#include "transmission.h"
#include "error.h"
#include "file.h"
#include "subprocess.h"
#include "utils.h"
#include "libtransmission-test.h"
static char arg_dump_args[] = "--dump-args";
static char arg_dump_env[] = "--dump-env";
static char arg_dump_cwd[] = "--dump-cwd";
static char* self_path = NULL;
static int test_spawn_async_missing_exe(void)
{
char missing_exe_path[] = TR_IF_WIN32("C:\\", "/") "tr-missing-test-exe" TR_IF_WIN32(".exe", "");
char* const args[] =
{
missing_exe_path,
NULL
};
tr_error* error = NULL;
bool const ret = tr_spawn_async(args, NULL, NULL, &error);
check_bool(ret, == , false);
check_ptr(error, != , NULL);
check_int(error->code, != , 0);
check_str(error->message, != , NULL);
return 0;
}
static int test_spawn_async_args(void)
{
char* const test_dir = libtest_sandbox_create();
char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL));
bool const allow_batch_metachars = TR_IF_WIN32(false, true) || !tr_str_has_suffix(self_path, ".cmd");
char test_arg_1[] = "arg1 ";
char test_arg_2[] = " arg2";
char test_arg_3[] = "";
char test_arg_4[] = "\"arg3'^! $PATH %PATH% \\";
char* const args[] =
{
self_path,
result_path,
arg_dump_args,
test_arg_1,
test_arg_2,
test_arg_3,
allow_batch_metachars ? test_arg_4 : NULL,
NULL
};
tr_error* error = NULL;
bool const ret = tr_spawn_async(args, NULL, NULL, &error);
check_bool(ret, ==, true);
check_ptr(error, ==, NULL);
while (!tr_sys_path_exists(result_path, NULL))
{
tr_wait_msec(10);
}
tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL);
check_int(fd, !=, TR_BAD_SYS_FILE);
char buffer[1024];
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_arg_1);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_arg_2);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, == , test_arg_3);
if (allow_batch_metachars)
{
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, == , test_arg_4);
}
check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
tr_sys_file_close(fd, NULL);
tr_free(result_path);
libtest_sandbox_destroy(test_dir);
tr_free(test_dir);
return 0;
}
static int test_spawn_async_env(void)
{
char* const test_dir = libtest_sandbox_create();
char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL));
char test_env_key_1[] = "VAR1";
char test_env_key_2[] = "_VAR_2_";
char test_env_key_3[] = "vAr#";
char test_env_key_4[] = "FOO";
char test_env_key_5[] = "ZOO";
char test_env_key_6[] = "TR_MISSING_TEST_ENV_KEY";
char test_env_value_1[] = "value1 ";
char test_env_value_2[] = " value2";
char test_env_value_3[] = " \"value3'^! $PATH %PATH% ";
char test_env_value_4[] = "bar";
char test_env_value_5[] = "jar";
char* const args[] =
{
self_path,
result_path,
arg_dump_env,
test_env_key_1,
test_env_key_2,
test_env_key_3,
test_env_key_4,
test_env_key_5,
test_env_key_6,
NULL
};
char* const env[] =
{
tr_strdup_printf("%s=%s", test_env_key_1, test_env_value_1),
tr_strdup_printf("%s=%s", test_env_key_2, test_env_value_2),
tr_strdup_printf("%s=%s", test_env_key_3, test_env_value_3),
tr_strdup_printf("%s=%s", test_env_key_5, test_env_value_5),
NULL
};
/* Inherited */
char foo_env_value[] = "FOO=bar";
putenv(foo_env_value);
/* Overridden */
char zoo_env_value[] = "ZOO=tar";
putenv(zoo_env_value);
tr_error* error = NULL;
bool const ret = tr_spawn_async(args, env, NULL, &error);
check_bool(ret, ==, true);
check_ptr(error, ==, NULL);
while (!tr_sys_path_exists(result_path, NULL))
{
tr_wait_msec(10);
}
tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL);
check_int(fd, !=, TR_BAD_SYS_FILE);
char buffer[1024];
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_env_value_1);
tr_free(env[0]);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_env_value_2);
tr_free(env[1]);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_env_value_3);
tr_free(env[2]);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_env_value_4);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, test_env_value_5);
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(buffer, ==, "<null>");
check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
tr_sys_file_close(fd, NULL);
tr_free(result_path);
libtest_sandbox_destroy(test_dir);
tr_free(test_dir);
return 0;
}
static int test_spawn_async_cwd_explicit(void)
{
char* const test_dir = libtest_sandbox_create();
char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL));
char* const args[] =
{
self_path,
result_path,
arg_dump_cwd,
NULL
};
tr_error* error = NULL;
bool const ret = tr_spawn_async(args, NULL, test_dir, &error);
check_bool(ret, ==, true);
check_ptr(error, ==, NULL);
while (!tr_sys_path_exists(result_path, NULL))
{
tr_wait_msec(10);
}
tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL);
check_int(fd, !=, TR_BAD_SYS_FILE);
char buffer[1024];
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(tr_sys_path_native_separators(buffer), ==, tr_sys_path_native_separators(test_dir));
check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
tr_sys_file_close(fd, NULL);
tr_free(result_path);
libtest_sandbox_destroy(test_dir);
tr_free(test_dir);
return 0;
}
static int test_spawn_async_cwd_inherit(void)
{
char* const test_dir = libtest_sandbox_create();
char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL));
char* const expected_cwd = tr_sys_dir_get_current(NULL);
char* const args[] =
{
self_path,
result_path,
arg_dump_cwd,
NULL
};
tr_error* error = NULL;
bool const ret = tr_spawn_async(args, NULL, NULL, &error);
check_bool(ret, ==, true);
check_ptr(error, ==, NULL);
while (!tr_sys_path_exists(result_path, NULL))
{
tr_wait_msec(10);
}
tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL);
check_int(fd, !=, TR_BAD_SYS_FILE);
char buffer[1024];
check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
check_str(tr_sys_path_native_separators(buffer), ==, tr_sys_path_native_separators(expected_cwd));
check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL));
tr_sys_file_close(fd, NULL);
tr_free(expected_cwd);
tr_free(result_path);
libtest_sandbox_destroy(test_dir);
tr_free(test_dir);
return 0;
}
static int test_spawn_async_cwd_missing(void)
{
char* const test_dir = libtest_sandbox_create();
char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL));
char* const args[] =
{
self_path,
result_path,
arg_dump_cwd,
NULL
};
tr_error* error = NULL;
bool const ret = tr_spawn_async(args, NULL, TR_IF_WIN32("C:\\", "/") "tr-missing-test-work-dir", &error);
check_bool(ret, ==, false);
check_ptr(error, !=, NULL);
check_int(error->code, !=, 0);
check_str(error->message, !=, NULL);
tr_free(result_path);
libtest_sandbox_destroy(test_dir);
tr_free(test_dir);
return 0;
}
int main(int argc, char** argv)
{
self_path = tr_sys_path_resolve(argv[0], NULL);
if (argc >= 3)
{
char* const result_path = argv[1];
char* const test_action = argv[2];
char* const tmp_result_path = tr_strdup_printf("%s.tmp", result_path);
tr_sys_file_t const fd = tr_sys_file_open(tmp_result_path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE |
TR_SYS_FILE_TRUNCATE, 0644, NULL);
if (fd == TR_BAD_SYS_FILE)
{
return 1;
}
if (strcmp(test_action, arg_dump_args) == 0)
{
for (int i = 3; i < argc; ++i)
{
tr_sys_file_write_line(fd, argv[i], NULL);
}
}
else if (strcmp(test_action, arg_dump_env) == 0)
{
for (int i = 3; i < argc; ++i)
{
tr_sys_file_write_line(fd, tr_env_get_string(argv[i], "<null>"), NULL);
}
}
else if (strcmp(test_action, arg_dump_cwd) == 0)
{
char* const value = tr_sys_dir_get_current(NULL);
tr_sys_file_write_line(fd, value != NULL ? value : "<null>", NULL);
tr_free(value);
}
else
{
tr_sys_file_close(fd, NULL);
tr_sys_path_remove(tmp_result_path, NULL);
return 1;
}
tr_sys_file_close(fd, NULL);
tr_sys_path_rename(tmp_result_path, result_path, NULL);
return 0;
}
testFunc const tests[] =
{
test_spawn_async_missing_exe,
test_spawn_async_args,
test_spawn_async_env,
test_spawn_async_cwd_explicit,
test_spawn_async_cwd_inherit,
test_spawn_async_cwd_missing
};
int ret = runTests(tests, NUM_TESTS(tests));
#ifdef _WIN32
strcpy(self_path + strlen(self_path) - 4, ".cmd");
int ret2 = runTests(tests, NUM_TESTS(tests));
if (ret == 0)
{
ret = ret2;
}
#endif
tr_free(self_path);
return ret;
}

View File

@ -0,0 +1,48 @@
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set __argc=0
for %%i in (%*) do (
set /a __argc+=1
set "__argv[!__argc!]=%%~i"
)
set "result_path=!__argv[1]!"
set "test_action=!__argv[2]!"
set "temp_result_path=%result_path%.tmp"
>"%temp_result_path%" <nul set /p=
if "%test_action%" == "--dump-args" goto dump_args
if "%test_action%" == "--dump-env" goto dump_env
if "%test_action%" == "--dump-cwd" goto dump_cwd
exit /b 1
:dump_args
for /l %%i in (3,1,%__argc%) do (
>>"%temp_result_path%" echo.!__argv[%%i]!
)
goto finish
:dump_env
for /l %%i in (3,1,%__argc%) do (
>>"%temp_result_path%" call :dump_env_var "!__argv[%%i]!"
)
goto finish
:dump_env_var
if defined %~1 (
echo.!%~1!
) else (
echo.^<null^>
)
exit /b 0
:dump_cwd
>>"%temp_result_path%" echo.%CD%
goto finish
:finish
>nul move /y "%temp_result_path%" "%result_path%"
exit /b 0

View File

@ -0,0 +1,422 @@
/*
* This file Copyright (C) 2011-2017 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <windows.h>
#include "transmission.h"
#include "error.h"
#include "subprocess.h"
#include "tr-assert.h"
#include "utils.h"
enum tr_app_type
{
TR_APP_TYPE_EXE,
TR_APP_TYPE_BATCH
};
static void set_system_error(tr_error** error, DWORD code, char const* what)
{
if (error == NULL)
{
return;
}
char* message = tr_win32_format_message(code);
if (message == NULL)
{
message = tr_strdup_printf("Unknown error: 0x%08lx", code);
}
if (what == NULL)
{
tr_error_set_literal(error, code, message);
}
else
{
tr_error_set(error, code, "%s failed: %s", what, message);
}
tr_free(message);
}
static void append_to_env_block(wchar_t** env_block, size_t* env_block_len, wchar_t const* part, size_t part_len)
{
*env_block = tr_renew(wchar_t, *env_block, *env_block_len + part_len + 1);
wmemcpy(*env_block + *env_block_len, part, part_len);
*env_block_len += part_len;
}
static bool parse_env_block_part(wchar_t const* part, size_t* full_len, size_t* name_len)
{
TR_ASSERT(part != NULL);
wchar_t* const equals_pos = wcschr(part, L'=');
if (equals_pos == NULL)
{
/* Invalid part */
return false;
}
ptrdiff_t const my_name_len = equals_pos - part;
if (my_name_len > SIZE_MAX)
{
/* Invalid part */
return false;
}
if (full_len != NULL)
{
/* Includes terminating '\0' */
*full_len = wcslen(part) + 1;
}
if (name_len != NULL)
{
*name_len = (size_t)my_name_len;
}
return true;
}
static int compare_wide_strings_ci(wchar_t const* lhs, size_t lhs_len, wchar_t const* rhs, size_t rhs_len)
{
int diff = wcsnicmp(lhs, rhs, MIN(lhs_len, rhs_len));
if (diff == 0)
{
diff = lhs_len < rhs_len ? -1 : (lhs_len > rhs_len ? 1 : 0);
}
return diff;
}
static int compare_env_part_names(wchar_t const** lhs, wchar_t const** rhs)
{
int ret = 0;
size_t lhs_part_len;
size_t lhs_name_len;
if (parse_env_block_part(*lhs, &lhs_part_len, &lhs_name_len))
{
size_t rhs_part_len;
size_t rhs_name_len;
if (parse_env_block_part(*rhs, &rhs_part_len, &rhs_name_len))
{
ret = compare_wide_strings_ci(*lhs, lhs_name_len, *rhs, rhs_name_len);
}
}
return ret;
}
static wchar_t** to_wide_env(char const* const* env)
{
if (env == NULL || env[0] == NULL)
{
return NULL;
}
size_t part_count = 0;
while (env[part_count] != NULL)
{
++part_count;
}
wchar_t** wide_env = tr_new(wchar_t*, part_count + 1);
for (size_t i = 0; i < part_count; ++i)
{
wide_env[i] = tr_win32_utf8_to_native(env[i], -1);
}
wide_env[part_count] = NULL;
/* "The sort is case-insensitive, Unicode order, without regard to locale" (c) MSDN */
qsort(wide_env, part_count, sizeof(wchar_t*), &compare_env_part_names);
return wide_env;
}
static bool create_env_block(char const* const* env, wchar_t** env_block, tr_error** error)
{
wchar_t** wide_env = to_wide_env(env);
if (wide_env == NULL)
{
*env_block = NULL;
return true;
}
wchar_t* const old_env_block = GetEnvironmentStringsW();
if (old_env_block == NULL)
{
set_system_error(error, GetLastError(), "Call to GetEnvironmentStrings()");
return false;
}
*env_block = NULL;
wchar_t const* old_part = old_env_block;
size_t env_block_len = 0;
for (size_t i = 0; wide_env[i] != NULL; ++i)
{
wchar_t const* const part = wide_env[i];
size_t part_len;
size_t name_len;
if (!parse_env_block_part(part, &part_len, &name_len))
{
continue;
}
while (*old_part != L'\0')
{
size_t old_part_len;
size_t old_name_len;
if (!parse_env_block_part(old_part, &old_part_len, &old_name_len))
{
continue;
}
int const name_diff = compare_wide_strings_ci(old_part, old_name_len, part, name_len);
if (name_diff < 0)
{
append_to_env_block(env_block, &env_block_len, old_part, old_part_len);
}
if (name_diff <= 0)
{
old_part += old_part_len;
}
if (name_diff >= 0)
{
break;
}
}
append_to_env_block(env_block, &env_block_len, part, part_len);
}
while (*old_part != L'\0')
{
size_t old_part_len;
if (!parse_env_block_part(old_part, &old_part_len, NULL))
{
continue;
}
append_to_env_block(env_block, &env_block_len, old_part, old_part_len);
old_part += old_part_len;
}
(*env_block)[env_block_len] = '\0';
FreeEnvironmentStringsW(old_env_block);
tr_free_ptrv((void* const*)wide_env);
tr_free(wide_env);
return true;
}
static void append_argument(char** arguments, char const* argument)
{
size_t arguments_len = *arguments != NULL ? strlen(*arguments) : 0u;
size_t const argument_len = strlen(argument);
if (arguments_len > 0)
{
(*arguments)[arguments_len++] = ' ';
}
if (argument[0] != '\0' && strpbrk(argument, " \t\n\v\"") == NULL)
{
*arguments = tr_renew(char, *arguments, arguments_len + argument_len + 2);
strcpy(*arguments + arguments_len, argument);
return;
}
*arguments = tr_renew(char, *arguments, arguments_len + argument_len * 2 + 4);
char* dst = *arguments + arguments_len;
*(dst++) = '"';
for (char const* src = argument; *src != '\0';)
{
size_t backslash_count = 0;
while (*src == '\\')
{
++backslash_count;
++src;
}
switch (*src)
{
case '\0':
backslash_count = backslash_count * 2;
break;
case '"':
backslash_count = backslash_count * 2 + 1;
break;
}
if (backslash_count != 0)
{
memset(dst, '\\', backslash_count);
dst += backslash_count;
}
if (*src != '\0')
{
*(dst++) = *(src++);
}
}
*(dst++) = '"';
*(dst++) = '\0';
}
static bool contains_batch_metachars(char const* text)
{
/* First part - chars explicitly documented by `cmd.exe /?` as "special" */
return strpbrk(text, "&<>()@^|" "%!^\"") != NULL;
}
static enum tr_app_type get_app_type(char const* app)
{
if (tr_str_has_suffix(app, ".cmd") || tr_str_has_suffix(app, ".bat"))
{
return TR_APP_TYPE_BATCH;
}
/* TODO: Support other types? */
return TR_APP_TYPE_EXE;
}
static void append_app_launcher_arguments(enum tr_app_type app_type, char** args)
{
switch (app_type)
{
case TR_APP_TYPE_EXE:
break;
case TR_APP_TYPE_BATCH:
append_argument(args, "cmd.exe");
append_argument(args, "/d");
append_argument(args, "/e:off");
append_argument(args, "/v:off");
append_argument(args, "/s");
append_argument(args, "/c");
break;
default:
TR_ASSERT_MSG(false, "unsupported application type %d", (int)app_type);
break;
}
}
static bool construct_cmd_line(char const* const* cmd, wchar_t** cmd_line)
{
enum tr_app_type const app_type = get_app_type(cmd[0]);
char* args = NULL;
size_t arg_count = 0;
bool ret = false;
append_app_launcher_arguments(app_type, &args);
for (size_t i = 0; cmd[i] != NULL; ++i)
{
if (app_type == TR_APP_TYPE_BATCH && i > 0 && contains_batch_metachars(cmd[i]))
{
/* FIXME: My attempts to escape them one or another way didn't lead to anything good so far */
goto cleanup;
}
append_argument(&args, cmd[i]);
++arg_count;
}
*cmd_line = args != NULL ? tr_win32_utf8_to_native(args, -1) : NULL;
ret = true;
cleanup:
tr_free(args);
return ret;
}
bool tr_spawn_async(char* const* cmd, char* const* env, char const* work_dir, tr_error** error)
{
wchar_t* env_block = NULL;
if (!create_env_block(env, &env_block, error))
{
return false;
}
wchar_t* cmd_line;
if (!construct_cmd_line(cmd, &cmd_line))
{
set_system_error(error, ERROR_INVALID_PARAMETER, "Constructing command line");
return false;
}
wchar_t* current_dir = work_dir != NULL ? tr_win32_utf8_to_native(work_dir, -1) : NULL;
STARTUPINFOW si =
{
.cb = sizeof(si),
.dwFlags = STARTF_USESHOWWINDOW,
.wShowWindow = SW_HIDE
};
PROCESS_INFORMATION pi;
bool const ret = CreateProcessW(NULL, cmd_line, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT |
CREATE_NO_WINDOW | CREATE_DEFAULT_ERROR_MODE, env_block, current_dir, &si, &pi);
if (ret)
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else
{
set_system_error(error, GetLastError(), "Call to CreateProcess()");
}
tr_free(current_dir);
tr_free(cmd_line);
tr_free(env_block);
return ret;
}

View File

@ -0,0 +1,11 @@
/*
* This file Copyright (C) 2017 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#pragma once
bool tr_spawn_async(char* const* cmd, char* const* env, char const* work_dir, struct tr_error** error);

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2009-2014 Mnemosyne LLC
* This file Copyright (C) 2009-2017 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
@ -43,6 +43,7 @@
#include "ptrarray.h"
#include "resume.h"
#include "session.h"
#include "subprocess.h"
#include "torrent.h"
#include "torrent-magnet.h"
#include "tr-assert.h"
@ -2213,149 +2214,63 @@ void tr_torrentClearIdleLimitHitCallback(tr_torrent* torrent)
tr_torrentSetIdleLimitHitCallback(torrent, NULL, NULL);
}
#ifndef _WIN32
static void onSigCHLD(int i UNUSED)
static void get_local_time_str(char* const buffer, size_t const buffer_len)
{
int rc;
time_t const now = tr_time();
do
tr_strlcpy(buffer, ctime(&now), buffer_len);
char* newline_pos = strchr(buffer, '\n');
/* ctime() includes '\n', but it's better to be safe */
if (newline_pos != NULL)
{
rc = waitpid(-1, NULL, WNOHANG);
*newline_pos = '\0';
}
while (rc > 0 || (rc == -1 && errno == EINTR));
}
#endif
static void torrentCallScript(tr_torrent const* tor, char const* script)
{
char timeStr[128];
char* newlinePos;
time_t const now = tr_time();
tr_strlcpy(timeStr, ctime(&now), sizeof(timeStr));
/* ctime () includes '\n', but it's better to be safe */
newlinePos = strchr(timeStr, '\n');
if (newlinePos != NULL)
if (script == NULL || *script == '\0')
{
*newlinePos = '\0';
return;
}
if (script != NULL && *script != '\0')
char time_str[32];
get_local_time_str(time_str, TR_N_ELEMENTS(time_str));
char* const torrent_dir = tr_sys_path_native_separators(tr_strdup(tor->currentDir));
char* const cmd[] =
{
char* cmd[] =
{
tr_strdup(script), NULL
};
char* env[] =
{
tr_strdup_printf("TR_APP_VERSION=%s", SHORT_VERSION_STRING),
tr_strdup_printf("TR_TIME_LOCALTIME=%s", timeStr),
tr_strdup_printf("TR_TORRENT_DIR=%s", tor->currentDir),
tr_strdup_printf("TR_TORRENT_HASH=%s", tor->info.hashString),
tr_strdup_printf("TR_TORRENT_ID=%d", tr_torrentId(tor)),
tr_strdup_printf("TR_TORRENT_NAME=%s", tr_torrentName(tor)),
NULL
};
tr_strdup(script),
NULL
};
tr_logAddTorInfo(tor, "Calling script \"%s\"", script);
char* const env[] =
{
tr_strdup_printf("TR_APP_VERSION=%s", SHORT_VERSION_STRING),
tr_strdup_printf("TR_TIME_LOCALTIME=%s", time_str),
tr_strdup_printf("TR_TORRENT_DIR=%s", torrent_dir),
tr_strdup_printf("TR_TORRENT_HASH=%s", tor->info.hashString),
tr_strdup_printf("TR_TORRENT_ID=%d", tr_torrentId(tor)),
tr_strdup_printf("TR_TORRENT_NAME=%s", tr_torrentName(tor)),
NULL
};
#ifdef TR_ENABLE_ASSERTS
tr_logAddTorInfo(tor, "Calling script \"%s\"", script);
/* Win32 environment block strings should be sorted alphabetically */
for (size_t i = 1; env[i] != NULL; ++i)
{
TR_ASSERT(strcmp(env[i - 1], env[i]) < 0);
}
tr_error* error = NULL;
#endif
#ifdef _WIN32
wchar_t* wide_script = tr_win32_utf8_to_native(script, -1);
size_t env_block_size = 0;
char* env_block = NULL;
for (size_t i = 0; env[i] != NULL; ++i)
{
size_t const len = strlen(env[i]) + 1;
env_block = tr_renew(char, env_block, env_block_size + len + 1);
memcpy(env_block + env_block_size, env[i], len + 1);
env_block_size += len;
}
wchar_t* wide_env_block = NULL;
if (env_block != NULL)
{
env_block[env_block_size] = '\0';
wide_env_block = tr_win32_utf8_to_native(env_block, env_block_size + 1);
tr_free(env_block);
}
STARTUPINFOW si = { 0, };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi;
if (CreateProcessW(wide_script, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT |
CREATE_NO_WINDOW | CREATE_DEFAULT_ERROR_MODE | DETACHED_PROCESS, wide_env_block, L"\\", &si, &pi))
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
else
{
char* const message = tr_win32_format_message(GetLastError());
tr_logAddTorErr(tor, "error executing script \"%s\": %s", script, message);
tr_free(message);
}
tr_free(wide_env_block);
tr_free(wide_script);
#else /* _WIN32 */
signal(SIGCHLD, onSigCHLD);
if (fork() == 0)
{
for (size_t i = 0; env[i] != NULL; ++i)
{
putenv(env[i]);
}
if (chdir("/") == -1)
{
/* ignore (nice to have but not that critical) */
}
if (execvp(script, cmd) == -1)
{
tr_logAddTorErr(tor, "error executing script \"%s\": %s", script, tr_strerror(errno));
}
_exit(0);
}
#endif /* _WIN32 */
for (size_t i = 0; cmd[i] != NULL; ++i)
{
tr_free(cmd[i]);
}
for (size_t i = 0; env[i] != NULL; ++i)
{
tr_free(env[i]);
}
if (!tr_spawn_async(cmd, env, TR_IF_WIN32("\\", "/"), &error))
{
tr_logAddTorErr(tor, "Error executing script \"%s\" (%d): %s", script, error->code, error->message);
tr_error_free(error);
}
tr_free_ptrv((void* const*)env);
tr_free_ptrv((void* const*)cmd);
tr_free(torrent_dir);
}
void tr_torrentRecheckCompleteness(tr_torrent* tor)

View File

@ -28,6 +28,12 @@
#define __has_builtin(x) 0
#endif
#ifdef _WIN32
#define TR_IF_WIN32(ThenValue, ElseValue) ThenValue
#else
#define TR_IF_WIN32(ThenValue, ElseValue) ElseValue
#endif
#ifdef __GNUC__
#define TR_GNUC_CHECK_VERSION(major, minor) \
(__GNUC__ > (major) || \

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2009-2014 Mnemosyne LLC
* This file Copyright (C) 2009-2017 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
@ -152,6 +152,20 @@ void tr_free(void* p)
}
}
void tr_free_ptrv(void* const* p)
{
if (p == NULL)
{
return;
}
while (*p != NULL)
{
tr_free(*p);
++p;
}
}
void* tr_memdup(void const* src, size_t byteCount)
{
return memcpy(tr_malloc(byteCount), src, byteCount);

View File

@ -1,5 +1,5 @@
/*
* This file Copyright (C) 2009-2014 Mnemosyne LLC
* This file Copyright (C) 2009-2017 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
@ -154,6 +154,9 @@ void* tr_realloc(void* p, size_t size);
/** @brief Portability wrapper around free() in which `NULL' is a safe argument */
void tr_free(void* p);
/** @brief Free pointers in a NULL-terminated array (the array itself is not freed) */
void tr_free_ptrv(void* const* p);
/**
* @brief make a newly-allocated copy of a chunk of memory
* @param src the memory to copy