]> granicus.if.org Git - transmission/commitdiff
Refactor completion scripts execution
authorMike Gelfand <mikedld@mikedld.com>
Mon, 27 Nov 2017 22:22:44 +0000 (01:22 +0300)
committerMike Gelfand <mikedld@mikedld.com>
Tue, 28 Nov 2017 07:03:08 +0000 (10:03 +0300)
There're still a few issues here and there, but overall I believe it's now
better than it was before.

17 files changed:
Transmission.xcodeproj/project.pbxproj
libtransmission/CMakeLists.txt
libtransmission/Makefile.am
libtransmission/file-posix.c
libtransmission/file-test.c
libtransmission/file-win32.c
libtransmission/file.h
libtransmission/libtransmission-test.c
libtransmission/subprocess-posix.c [new file with mode: 0644]
libtransmission/subprocess-test.c [new file with mode: 0644]
libtransmission/subprocess-test.cmd [new file with mode: 0644]
libtransmission/subprocess-win32.c [new file with mode: 0644]
libtransmission/subprocess.h [new file with mode: 0644]
libtransmission/torrent.c
libtransmission/tr-macros.h
libtransmission/utils.c
libtransmission/utils.h

index f8d5db67804d6c4066d307430b8d3807b3eef6c1..b0f4fa65fbc8bb9e47638d6d037d517956634e80 100644 (file)
                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 */,
index 99854b8ffc7de528f50176b8fa83daaa80f67991..96d73ad90320992d40c76a89e6701e51a0e1f80e 100644 (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)
index dfb66c7a903ac58f48a762ec54de95b16e045526..0885fab0d2e07606e94aee2ab1025006cde2947b 100644 (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}
index b48228935236c2a2af59b6608fd72436c454c372..8bd434f1635fe2a222d30aca5172444f9ff4dbd1 100644 (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;
index 79a0064a321fc2ac0e534c91bb983512086e5eae..25aafed30419b938fe02b0f8a7ec05c2a6016a25 100644 (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,
index 448fa3f40ac539a872ae8792a11601e8b90fcd00..30acebd46a7494b2d28a05d77b0978634948d1cd 100644 (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;
index a878c8e7cdbafc34884dac486cd2d8b32886d099..d723d65e89c8a7087705935dcbf4d1b7c90fc99f 100644 (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 */
 
 /**
index dbca07aad5efafb3fac4cd1fca06da7bfaf701ba..bc4888cd430e66587b86631d8f5de8938cef7050 100644 (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)
diff --git a/libtransmission/subprocess-posix.c b/libtransmission/subprocess-posix.c
new file mode 100644 (file)
index 0000000..840dbd0
--- /dev/null
@@ -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);
+}
diff --git a/libtransmission/subprocess-test.c b/libtransmission/subprocess-test.c
new file mode 100644 (file)
index 0000000..231cda6
--- /dev/null
@@ -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;
+}
diff --git a/libtransmission/subprocess-test.cmd b/libtransmission/subprocess-test.cmd
new file mode 100644 (file)
index 0000000..4d08d06
--- /dev/null
@@ -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
diff --git a/libtransmission/subprocess-win32.c b/libtransmission/subprocess-win32.c
new file mode 100644 (file)
index 0000000..67653a6
--- /dev/null
@@ -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;
+}
diff --git a/libtransmission/subprocess.h b/libtransmission/subprocess.h
new file mode 100644 (file)
index 0000000..79ac7b4
--- /dev/null
@@ -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);
index ee0631a2fd8bac002a4863f73be80f0ee675bee7..b0f43f7731e2b8ffe2bb50f746d7e0c5a5a69ed5 100644 (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();
+
+    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)
index 44cc0dc075d7cfd06cfcb84901667ab6daaacef1..2e3f740c0d80ef517a487e6af53ca978c04c9964 100644 (file)
 #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) || \
index bbbda8c1c31e78677f95510e6b3005fccbc6272a..ae6e606659e851738eec0bba7ce8d18926f8e0fc 100644 (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);
index 628684264621bf279feffe3d9de84db763cbea9a..2b0f98b6c32c7675193142dc3a9314dfb5c68e51 100644 (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