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 */; };
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>"; };
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 */,
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 */,
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 */,
rpc-server.c
session.c
session-id.c
+ subprocess-posix.c
+ subprocess-win32.c
stats.c
torrent.c
torrent-ctor.c
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
resume.h
rpc-server.h
session.h
+ subprocess.h
stats.h
torrent.h
torrent-magnet.h
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}")
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)
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
session.h \
session-id.h \
stats.h \
+ subprocess.h \
torrent.h \
torrent-magnet.h \
tr-getopt.h \
rename-test \
rpc-test \
session-test \
+ subprocess-test \
tr-getopt-test \
utils-test \
variant-test \
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}
/*
- * 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.
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;
/*
- * 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.
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__);
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,
/*
- * 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.
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;
/*
- * 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.
*/
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 */
/**
/*
- * 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.
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)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+@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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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);
/*
- * 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.
#include "ptrarray.h"
#include "resume.h"
#include "session.h"
+#include "subprocess.h"
#include "torrent.h"
#include "torrent-magnet.h"
#include "tr-assert.h"
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();
+
+ tr_strlcpy(buffer, ctime(&now), buffer_len);
- do
+ 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* 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_logAddTorInfo(tor, "Calling script \"%s\"", script);
+ char time_str[32];
+ get_local_time_str(time_str, TR_N_ELEMENTS(time_str));
-#ifdef TR_ENABLE_ASSERTS
-
- /* 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);
- }
-
-#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);
- }
+ char* const torrent_dir = tr_sys_path_native_separators(tr_strdup(tor->currentDir));
- 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));
- }
+ char* const cmd[] =
+ {
+ tr_strdup(script),
+ NULL
+ };
- _exit(0);
- }
+ 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
+ };
-#endif /* _WIN32 */
+ tr_logAddTorInfo(tor, "Calling script \"%s\"", script);
- for (size_t i = 0; cmd[i] != NULL; ++i)
- {
- tr_free(cmd[i]);
- }
+ tr_error* error = NULL;
- 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)
#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) || \
/*
- * 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.
}
}
+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);
/*
- * 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.
/** @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