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:
parent
5b29fe1556
commit
30c7c05cbb
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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)
|
||||
|
|
|
@ -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) || \
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue