diff --git a/Makefile b/Makefile index f3c62c24..c3e4d1d6 100644 --- a/Makefile +++ b/Makefile @@ -41,8 +41,7 @@ CMAKE_DEFINES := \ -DBUILDTESTER=${TUV_BUILDTESTER} \ -DBUILD_HOST_HELPER=${TUV_BUILDHOSTHELPER} \ -DCREATE_SHARED_LIB=${TUV_CREATE_SHARED_LIB} \ - -DTUV_FEATURE_SIGNAL=ON \ - -DTUV_FEATURE_PIPE=ON + -DTUV_FEATURE_PROCESS=ON ifneq ($(TUV_BOARD),unknown) CMAKE_DEFINES += -DTARGET_BOARD=${TUV_BOARD} diff --git a/cmake/option/option_unix_common.cmake b/cmake/option/option_unix_common.cmake index 2e1cf272..6c295bd9 100644 --- a/cmake/option/option_unix_common.cmake +++ b/cmake/option/option_unix_common.cmake @@ -79,6 +79,18 @@ set(TEST_UNITFILES "${TEST_ROOT}/test_async.c" ) +# { TUV_CHANGES@20180803: +# Made signal build time configurable } +if(TUV_FEATURE_PROCESS) + set(TUV_FEATURE_PIPE ON) + set(TUV_FEATURE_SIGNAL ON) + set(FLAGS_COMMON "${FLAGS_COMMON}" "-DTUV_FEATURE_PROCESS=1") + set(TEST_UNITFILES "${TEST_UNITFILES}" + "${TEST_ROOT}/test_ipc.c" + "${TEST_ROOT}/test_spawn.c") +endif() + + # { TUV_CHANGES@20180724: # Made pipe build time configurable } if(TUV_FEATURE_PIPE) diff --git a/src/unix/core.c b/src/unix/core.c index 09525692..4cceb9b1 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -145,6 +145,14 @@ void uv_close(uv_handle_t* handle, uv_close_cb close_cb) { uv__timer_close((uv_timer_t*)handle); break; +// { TUV_CHANGES@20180803: +// Made signal build time configurable } +#if TUV_FEATURE_PROCESS + case UV_PROCESS: + uv__process_close((uv_process_t*)handle); + break; +#endif + case UV_POLL: uv__poll_close((uv_poll_t*)handle); break; @@ -209,7 +217,7 @@ static void uv__finish_close(uv_handle_t* handle) { case UV_IDLE: case UV_ASYNC: case UV_TIMER: - // case UV_PROCESS: + case UV_PROCESS: // case UV_FS_EVENT: // case UV_FS_POLL: case UV_POLL: diff --git a/src/unix/linux-core.c b/src/unix/linux-core.c index 4141497c..a9b137bf 100644 --- a/src/unix/linux-core.c +++ b/src/unix/linux-core.c @@ -459,3 +459,26 @@ uint64_t uv__hrtime(uv_clocktype_t type) { return t.tv_sec * (uint64_t) 1e9 + t.tv_nsec; } + +// { TUV_CHANGES@20180803: +// Made signal build time configurable } +#if TUV_FEATURE_PROCESS +int uv_exepath(char* buffer, size_t* size) { + ssize_t n; + + if (buffer == NULL || size == NULL || *size == 0) + return -EINVAL; + + n = *size - 1; + if (n > 0) + n = readlink("/proc/self/exe", buffer, n); + + if (n == -1) + return -errno; + + buffer[n] = '\0'; + *size = n; + + return 0; +} +#endif /* TUV_FEATURE_PROCESS */ diff --git a/src/unix/process.c b/src/unix/process.c index a5280544..35a07eb3 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -50,12 +50,120 @@ #if defined(__APPLE__) && !TARGET_OS_IPHONE # include +# define environ (*_NSGetEnviron()) +#else +extern char **environ; #endif #if defined(__linux__) || defined(__GLIBC__) # include #endif +// { TUV_CHANGES@20180803: +// Made signal build time configurable } +#if TUV_FEATURE_PROCESS +static void uv__chld(uv_signal_t* handle, int signum) { + uv_process_t* process; + uv_loop_t* loop; + int exit_status; + int term_signal; + int status; + pid_t pid; + QUEUE pending; + QUEUE* q; + QUEUE* h; + + assert(signum == SIGCHLD); + + QUEUE_INIT(&pending); + loop = handle->loop; + + h = &loop->process_handles; + q = QUEUE_HEAD(h); + while (q != h) { + process = QUEUE_DATA(q, uv_process_t, queue); + q = QUEUE_NEXT(q); + + do + pid = waitpid(process->pid, &status, WNOHANG); + while (pid == -1 && errno == EINTR); + + if (pid == 0) + continue; + + if (pid == -1) { + if (errno != ECHILD) + abort(); + continue; + } + + process->status = status; + QUEUE_REMOVE(&process->queue); + QUEUE_INSERT_TAIL(&pending, &process->queue); + } + + h = &pending; + q = QUEUE_HEAD(h); + while (q != h) { + process = QUEUE_DATA(q, uv_process_t, queue); + q = QUEUE_NEXT(q); + + QUEUE_REMOVE(&process->queue); + QUEUE_INIT(&process->queue); + uv__handle_stop(process); + + if (process->exit_cb == NULL) + continue; + + exit_status = 0; + if (WIFEXITED(process->status)) + exit_status = WEXITSTATUS(process->status); + + term_signal = 0; + if (WIFSIGNALED(process->status)) + term_signal = WTERMSIG(process->status); + + process->exit_cb(process, exit_status, term_signal); + } + assert(QUEUE_EMPTY(&pending)); +} + + +int uv__make_socketpair(int fds[2], int flags) { +#if defined(__linux__) + static int no_cloexec; + + if (no_cloexec) + goto skip; + + if (socketpair(AF_UNIX, SOCK_STREAM | UV__SOCK_CLOEXEC | flags, 0, fds) == 0) + return 0; + + /* Retry on EINVAL, it means SOCK_CLOEXEC is not supported. + * Anything else is a genuine error. + */ + if (errno != EINVAL) + return -errno; + + no_cloexec = 1; + +skip: +#endif + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) + return -errno; + + uv__cloexec(fds[0], 1); + uv__cloexec(fds[1], 1); + + if (flags & UV__F_NONBLOCK) { + uv__nonblock(fds[0], 1); + uv__nonblock(fds[1], 1); + } + + return 0; +} +#endif /* TUV_FEATURE_PROCESS */ int uv__make_pipe(int fds[2], int flags) { #if defined(__linux__) @@ -88,3 +196,390 @@ int uv__make_pipe(int fds[2], int flags) { return 0; } + + +// { TUV_CHANGES@20180803: +// Made signal build time configurable } +#if TUV_FEATURE_PROCESS +/* + * Used for initializing stdio streams like options.stdin_stream. Returns + * zero on success. See also the cleanup section in uv_spawn(). + */ +static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2]) { + int mask; + int fd; + + mask = UV_IGNORE | UV_CREATE_PIPE | UV_INHERIT_FD | UV_INHERIT_STREAM; + + switch (container->flags & mask) { + case UV_IGNORE: + return 0; + + case UV_CREATE_PIPE: + assert(container->data.stream != NULL); + if (container->data.stream->type != UV_NAMED_PIPE) + return -EINVAL; + else + return uv__make_socketpair(fds, 0); + + case UV_INHERIT_FD: + case UV_INHERIT_STREAM: + if (container->flags & UV_INHERIT_FD) + fd = container->data.fd; + else + fd = uv__stream_fd(container->data.stream); + + if (fd == -1) + return -EINVAL; + + fds[1] = fd; + return 0; + + default: + assert(0 && "Unexpected flags"); + return -EINVAL; + } +} + + +static int uv__process_open_stream(uv_stdio_container_t* container, + int pipefds[2], + int writable) { + int flags; + int err; + + if (!(container->flags & UV_CREATE_PIPE) || pipefds[0] < 0) + return 0; + + err = uv__close(pipefds[1]); + if (err != 0) + abort(); + + pipefds[1] = -1; + uv__nonblock(pipefds[0], 1); + + if (container->data.stream->type == UV_NAMED_PIPE && + ((uv_pipe_t*)container->data.stream)->ipc) + flags = UV_STREAM_READABLE | UV_STREAM_WRITABLE; + else if (writable) + flags = UV_STREAM_WRITABLE; + else + flags = UV_STREAM_READABLE; + + return uv__stream_open(container->data.stream, pipefds[0], flags); +} + + +static void uv__process_close_stream(uv_stdio_container_t* container) { + if (!(container->flags & UV_CREATE_PIPE)) return; + uv__stream_close((uv_stream_t*)container->data.stream); +} + + +static void uv__write_int(int fd, int val) { + ssize_t n; + + do + n = write(fd, &val, sizeof(val)); + while (n == -1 && errno == EINTR); + + if (n == -1 && errno == EPIPE) + return; /* parent process has quit */ + + assert(n == sizeof(val)); +} + + +#if !(defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)) +/* execvp is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED, so must be + * avoided. Since this isn't called on those targets, the function + * doesn't even need to be defined for them. + */ +static void uv__process_child_init(const uv_process_options_t* options, + int stdio_count, + int (*pipes)[2], + int error_fd) { + int close_fd; + int use_fd; + int fd; + + if (options->flags & UV_PROCESS_DETACHED) + setsid(); + + /* First duplicate low numbered fds, since it's not safe to duplicate them, + * they could get replaced. Example: swapping stdout and stderr; without + * this fd 2 (stderr) would be duplicated into fd 1, thus making both + * stdout and stderr go to the same fd, which was not the intention. */ + for (fd = 0; fd < stdio_count; fd++) { + use_fd = pipes[fd][1]; + if (use_fd < 0 || use_fd >= fd) + continue; + pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count); + if (pipes[fd][1] == -1) { + uv__write_int(error_fd, -errno); + _exit(127); + } + } + + for (fd = 0; fd < stdio_count; fd++) { + close_fd = pipes[fd][0]; + use_fd = pipes[fd][1]; + + if (use_fd < 0) { + if (fd >= 3) + continue; + else { + /* redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is + * set + */ + use_fd = open("/dev/null", fd == 0 ? O_RDONLY : O_RDWR); + close_fd = use_fd; + + if (use_fd == -1) { + uv__write_int(error_fd, -errno); + _exit(127); + } + } + } + + if (fd == use_fd) + uv__cloexec(use_fd, 0); + else + fd = dup2(use_fd, fd); + + if (fd == -1) { + uv__write_int(error_fd, -errno); + _exit(127); + } + + if (fd <= 2) + uv__nonblock(fd, 0); + + if (close_fd >= stdio_count) + uv__close(close_fd); + } + + for (fd = 0; fd < stdio_count; fd++) { + use_fd = pipes[fd][1]; + + if (use_fd >= stdio_count) + uv__close(use_fd); + } + + if (options->cwd != NULL && chdir(options->cwd)) { + uv__write_int(error_fd, -errno); + _exit(127); + } + + if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) { + /* When dropping privileges from root, the `setgroups` call will + * remove any extraneous groups. If we don't call this, then + * even though our uid has dropped, we may still have groups + * that enable us to do super-user things. This will fail if we + * aren't root, so don't bother checking the return value, this + * is just done as an optimistic privilege dropping function. + */ + SAVE_ERRNO(setgroups(0, NULL)); + } + + if ((options->flags & UV_PROCESS_SETGID) && setgid(options->gid)) { + uv__write_int(error_fd, -errno); + _exit(127); + } + + if ((options->flags & UV_PROCESS_SETUID) && setuid(options->uid)) { + uv__write_int(error_fd, -errno); + _exit(127); + } + + if (options->env != NULL) { + environ = options->env; + } + + execvp(options->file, options->args); + uv__write_int(error_fd, -errno); + _exit(127); +} +#endif + + +int uv_spawn(uv_loop_t* loop, + uv_process_t* process, + const uv_process_options_t* options) { +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + /* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */ + return -ENOSYS; +#else + int signal_pipe[2] = { -1, -1 }; + int (*pipes)[2]; + int stdio_count; + ssize_t r; + pid_t pid; + int err; + int exec_errorno; + int i; + int status; + + assert(options->file != NULL); + assert(!(options->flags & ~(UV_PROCESS_DETACHED | + UV_PROCESS_SETGID | + UV_PROCESS_SETUID | + UV_PROCESS_WINDOWS_HIDE | + UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); + + uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); + QUEUE_INIT(&process->queue); + + stdio_count = options->stdio_count; + if (stdio_count < 3) + stdio_count = 3; + + err = -ENOMEM; + pipes = uv__malloc(stdio_count * sizeof(*pipes)); + if (pipes == NULL) + goto error; + + for (i = 0; i < stdio_count; i++) { + pipes[i][0] = -1; + pipes[i][1] = -1; + } + + for (i = 0; i < options->stdio_count; i++) { + err = uv__process_init_stdio(options->stdio + i, pipes[i]); + if (err) + goto error; + } + + /* This pipe is used by the parent to wait until + * the child has called `execve()`. We need this + * to avoid the following race condition: + * + * if ((pid = fork()) > 0) { + * kill(pid, SIGTERM); + * } + * else if (pid == 0) { + * execve("/bin/cat", argp, envp); + * } + * + * The parent sends a signal immediately after forking. + * Since the child may not have called `execve()` yet, + * there is no telling what process receives the signal, + * our fork or /bin/cat. + * + * To avoid ambiguity, we create a pipe with both ends + * marked close-on-exec. Then, after the call to `fork()`, + * the parent polls the read end until it EOFs or errors with EPIPE. + */ + err = uv__make_pipe(signal_pipe, 0); + if (err) + goto error; + + uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD); + + /* Acquire write lock to prevent opening new fds in worker threads */ + uv_rwlock_wrlock(&loop->cloexec_lock); + pid = fork(); + + if (pid == -1) { + err = -errno; + uv_rwlock_wrunlock(&loop->cloexec_lock); + uv__close(signal_pipe[0]); + uv__close(signal_pipe[1]); + goto error; + } + + if (pid == 0) { + uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); + abort(); + } + + /* Release lock in parent process */ + uv_rwlock_wrunlock(&loop->cloexec_lock); + uv__close(signal_pipe[1]); + + process->status = 0; + exec_errorno = 0; + do + r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno)); + while (r == -1 && errno == EINTR); + + if (r == 0) + ; /* okay, EOF */ + else if (r == sizeof(exec_errorno)) { + do + err = waitpid(pid, &status, 0); /* okay, read errorno */ + while (err == -1 && errno == EINTR); + assert(err == pid); + } else if (r == -1 && errno == EPIPE) { + do + err = waitpid(pid, &status, 0); /* okay, got EPIPE */ + while (err == -1 && errno == EINTR); + assert(err == pid); + } else + abort(); + + uv__close_nocheckstdio(signal_pipe[0]); + + for (i = 0; i < options->stdio_count; i++) { + err = uv__process_open_stream(options->stdio + i, pipes[i], i == 0); + if (err == 0) + continue; + + while (i--) + uv__process_close_stream(options->stdio + i); + + goto error; + } + + /* Only activate this handle if exec() happened successfully */ + if (exec_errorno == 0) { + QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue); + uv__handle_start(process); + } + + process->pid = pid; + process->exit_cb = options->exit_cb; + + uv__free(pipes); + return exec_errorno; + +error: + if (pipes != NULL) { + for (i = 0; i < stdio_count; i++) { + if (i < options->stdio_count) + if (options->stdio[i].flags & (UV_INHERIT_FD | UV_INHERIT_STREAM)) + continue; + if (pipes[i][0] != -1) + uv__close_nocheckstdio(pipes[i][0]); + if (pipes[i][1] != -1) + uv__close_nocheckstdio(pipes[i][1]); + } + uv__free(pipes); + } + + return err; +#endif +} + + +int uv_process_kill(uv_process_t* process, int signum) { + return uv_kill(process->pid, signum); +} + + +int uv_kill(int pid, int signum) { + if (kill(pid, signum)) + return -errno; + else + return 0; +} + + +void uv__process_close(uv_process_t* handle) { + QUEUE_REMOVE(&handle->queue); + uv__handle_stop(handle); + if (QUEUE_EMPTY(&handle->loop->process_handles)) + uv_signal_stop(&handle->loop->child_watcher); +} + +#endif /* TUV_FEATURE_PROCESS */ diff --git a/test/runner_linux.c b/test/runner_linux.c index 92294406..1b4a42ea 100644 --- a/test/runner_linux.c +++ b/test/runner_linux.c @@ -347,6 +347,14 @@ int main(int argc, char *argv[]) { #else +int ipc_helper(int listen_after_write); +int ipc_helper_tcp_connection(void); +// int ipc_send_recv_helper(void); +int ipc_helper_bind_twice(void); +// int stdio_over_pipes_helper(void); +int spawn_stdin_stdout(void); + +static int maybe_run_test(int argc, char **argv); static pthread_t tid = 0; @@ -387,20 +395,152 @@ int run_test_one(task_entry_t* task) { return run_test_part(task->task_name, task->process_name); } -int main(int argc, char *argv[]) { - int result; - InitDebugSettings(); +int main(int argc, char **argv) { + if (platform_init(argc, argv)) + return 1; - platform_init(argc, argv); + InitDebugSettings(); - if (argc>2) { - return run_test_part(argv[1], argv[2]); + switch (argc) { + case 1: return run_tests(); + case 2: return maybe_run_test(argc, argv); + case 3: return run_test_part(argv[1], argv[2]); + default: + ReleaseDebugSettings(); + + fprintf(stderr, "Too many arguments.\n"); + fflush(stderr); + return 1; } - result = run_tests(); + ReleaseDebugSettings(); - return result; + return 0; +} + +static int maybe_run_test(int argc, char **argv) { +#if TUV_FEATURE_PROCESS + if (strcmp(argv[1], "ipc_helper_listen_before_write") == 0) { + return ipc_helper(0); + } + + if (strcmp(argv[1], "ipc_helper_listen_after_write") == 0) { + return ipc_helper(1); + } + + // if (strcmp(argv[1], "ipc_send_recv_helper") == 0) { + // return ipc_send_recv_helper(); + // } + + if (strcmp(argv[1], "ipc_helper_tcp_connection") == 0) { + return ipc_helper_tcp_connection(); + } + + if (strcmp(argv[1], "ipc_helper_bind_twice") == 0) { + return ipc_helper_bind_twice(); + } + + // if (strcmp(argv[1], "stdio_over_pipes_helper") == 0) { + // return stdio_over_pipes_helper(); + // } + + if (strcmp(argv[1], "spawn_helper1") == 0) { + return 1; + } + + if (strcmp(argv[1], "spawn_helper2") == 0) { + printf("hello world\n"); + return 1; + } + + if (strcmp(argv[1], "spawn_helper3") == 0) { + char buffer[256]; + TUV_ASSERT(buffer == fgets(buffer, sizeof(buffer) - 1, stdin)); + buffer[sizeof(buffer) - 1] = '\0'; + fputs(buffer, stdout); + return 1; + } + + if (strcmp(argv[1], "spawn_helper4") == 0) { + /* Never surrender, never return! */ + while (1) uv_sleep(10000); + } + + if (strcmp(argv[1], "spawn_helper5") == 0) { + const char out[] = "fourth stdio!\n"; +#ifdef _WIN32 + DWORD bytes; + WriteFile((HANDLE) _get_osfhandle(3), out, sizeof(out) - 1, &bytes, NULL); +#else + { + ssize_t r; + + do + r = write(3, out, sizeof(out) - 1); + while (r == -1 && errno == EINTR); + + fsync(3); + } +#endif + return 1; + } + + if (strcmp(argv[1], "spawn_helper6") == 0) { + int r; + + r = fprintf(stdout, "hello world\n"); + TUV_ASSERT(r > 0); + + r = fprintf(stderr, "hello errworld\n"); + TUV_ASSERT(r > 0); + + return 1; + } + + if (strcmp(argv[1], "spawn_helper7") == 0) { + int r; + char *test; + /* Test if the test value from the parent is still set */ + test = getenv("ENV_TEST"); + TUV_ASSERT(test != NULL); + + r = fprintf(stdout, "%s", test); + TUV_ASSERT(r > 0); + + return 1; + } + +#ifndef _WIN32 + if (strcmp(argv[1], "spawn_helper8") == 0) { + int fd; + TUV_ASSERT(sizeof(fd) == read(0, &fd, sizeof(fd))); + TUV_ASSERT(fd > 2); + TUV_ASSERT(-1 == write(fd, "x", 1)); + + return 1; + } +#endif /* !_WIN32 */ + + if (strcmp(argv[1], "spawn_helper9") == 0) { + return spawn_stdin_stdout(); + } + +#ifndef _WIN32 + if (strcmp(argv[1], "spawn_helper_setuid_setgid") == 0) { + uv_uid_t uid = atoi(argv[2]); + uv_gid_t gid = atoi(argv[3]); + + TUV_ASSERT(uid == getuid()); + TUV_ASSERT(gid == getgid()); + + return 1; + } +#endif /* !_WIN32 */ + +#endif /* TUV_FEATURE_PROCESS */ + // return run_test(argv[1], 0, 1); + return 1; } #endif diff --git a/test/runner_list.h b/test/runner_list.h index 8cae82ea..1b2e06e3 100644 --- a/test/runner_list.h +++ b/test/runner_list.h @@ -101,20 +101,8 @@ // shutdown_eof should be last of tcp test, it'll stop "echo_sevrer" -#if defined(__linux__) -#define TEST_LIST_EXT(TE) \ - TE(condvar_1, 5000) \ - TE(condvar_4, 5000) \ - \ - TE(fs_file_nametoolong, 5000) \ - TE(fs_fstat, 5000) \ - TE(fs_utime, 5000) \ - TE(fs_futime, 5000) \ - \ - TE(getaddrinfo_basic, 5000) \ - TE(getaddrinfo_basic_sync, 5000) \ - TE(getaddrinfo_concurrent, 5000) \ - \ +#if defined(__linux__) && defined(TUV_FEATURE_PIPE) +#define TEST_LIST_EXT_PIPE(TE) \ TE(pipe_bind_error_addrinuse, 5000) \ TE(pipe_bind_error_addrnotavail, 5000) \ TE(pipe_bind_error_inval, 5000) \ @@ -130,10 +118,73 @@ TE(pipe_pending_instances, 5000) \ TE(pipe_sendmsg, 5000) \ TE(pipe_server_close, 5000) \ - TE(pipe_set_non_blocking, 5000) \ - \ + TE(pipe_set_non_blocking, 5000) +#else +#define TEST_LIST_EXT_PIPE(TE) +#endif + +#if defined(__linux__) && defined(TUV_FEATURE_SIGNAL) +#define TEST_LIST_EXT_SIGNAL(TE) \ TE(we_get_signal, 5000) \ TE(we_get_signals, 5000) +#else +#define TEST_LIST_EXT_SIGNAL(TE) +#endif + +#if defined(__linux__) && defined(TUV_FEATURE_PROCESS) +#define TEST_LIST_EXT_PROCESS(TE) \ +/*TE(ipc_listen_before_write, 5000)*/ \ +/*TE(ipc_listen_after_write, 5000)*/ \ +/*TE(ipc_tcp_connection, 5000)*/ \ +/*TE(spawn_fails_check_for_waitpid_cleanup, 5000) */ \ +/*TE(spawn_exit_code, 5000) */ \ + TE(spawn_stdout, 5000) \ +/*TE(spawn_stdout_to_file, 5000) */ \ +/*TE(spawn_stdout_and_stderr_to_file, 5000) */ \ +/*TE(spawn_stdout_and_stderr_to_file2, 5000) */ \ +/*TE(spawn_stdout_and_stderr_to_file_swap, 5000) */ \ +/*TE(spawn_stdin, 5000) */ \ +/*TE(spawn_stdio_greater_than_3, 5000) */ \ +/*TE(spawn_ignored_stdio, 5000) */ \ +/*TE(spawn_and_kill, 5000) */ \ +/*TE(spawn_preserve_env, 5000) */ \ +/*TE(spawn_detached, 5000) */ \ +/*TE(spawn_and_kill_with_std, 5000) */ \ +/*TE(spawn_and_ping, 5000) */ \ +/*TE(spawn_same_stdout_stderr, 5000) */ \ +/*TE(spawn_closed_process_io, 5000) */ \ +/*TE(spawn_fails, 5000) */ \ +/*TE(kill, 5000) */ \ +/*TE(spawn_setuid_fails, 5000) */ \ +/*TE(spawn_setgid_fails, 5000) */ \ +/*TE(spawn_setuid_fails, 5000) */ \ +/*TE(spawn_setgid_fails, 5000) */ \ +/*TE(spawn_auto_unref, 5000) */ \ +/*TE(spawn_fs_open, 5000) */ \ +/*TE(closed_fd_events, 5000) */ \ +/*TE(spawn_reads_child_path, 5000) */ \ +/*TE(spawn_inherit_streams, 5000) */ +#else +#define TEST_LIST_EXT_PROCESS(TE) +#endif + +#if defined(__linux__) +#define TEST_LIST_EXT(TE) \ + TE(condvar_1, 5000) \ + TE(condvar_4, 5000) \ + \ + TE(fs_file_nametoolong, 5000) \ + TE(fs_fstat, 5000) \ + TE(fs_utime, 5000) \ + TE(fs_futime, 5000) \ + \ + TE(getaddrinfo_basic, 5000) \ + TE(getaddrinfo_basic_sync, 5000) \ + TE(getaddrinfo_concurrent, 5000) \ + \ + TEST_LIST_EXT_PIPE(TE) \ + TEST_LIST_EXT_PROCESS(TE) \ + TEST_LIST_EXT_SIGNAL(TE) #elif defined(__TUV_RAW__) #define TEST_LIST_EXT(TE) \ diff --git a/test/test_ipc.c b/test/test_ipc.c new file mode 100644 index 00000000..4972f2b7 --- /dev/null +++ b/test/test_ipc.c @@ -0,0 +1,773 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "runner.h" + +#include +#include + +static uv_pipe_t channel; +static uv_tcp_t tcp_server; +static uv_tcp_t tcp_server2; +static uv_tcp_t tcp_connection; + +static int exit_cb_called; +static int read_cb_called; +static int tcp_write_cb_called; +static int tcp_read_cb_called; +static int on_pipe_read_called; +static int local_conn_accepted; +static int remote_conn_accepted; +static int tcp_server_listening; +static uv_write_t write_req; +static uv_write_t conn_notify_req; +static int close_cb_called; +static int connection_accepted; +static int tcp_conn_read_cb_called; +static int tcp_conn_write_cb_called; + +typedef struct { + uv_connect_t conn_req; + uv_write_t tcp_write_req; + uv_tcp_t conn; +} tcp_conn; + +#define CONN_COUNT 100 +#define BACKLOG 128 + + +static void close_server_conn_cb(uv_handle_t* handle) { + free(handle); +} + + +static void on_connection(uv_stream_t* server, int status) { + uv_tcp_t* conn; + int r; + + if (!local_conn_accepted) { + /* Accept the connection and close it. Also and close the server. */ + TUV_ASSERT(status == 0); + TUV_ASSERT((uv_stream_t*)&tcp_server == server); + + conn = malloc(sizeof(*conn)); + TUV_ASSERT(conn); + r = uv_tcp_init(server->loop, conn); + TUV_ASSERT(r == 0); + + r = uv_accept(server, (uv_stream_t*)conn); + TUV_ASSERT(r == 0); + + uv_close((uv_handle_t*)conn, close_server_conn_cb); + uv_close((uv_handle_t*)server, NULL); + local_conn_accepted = 1; + } +} + + +static void exit_cb(uv_process_t* process, + int64_t exit_status, + int term_signal) { + printf("exit_cb\n"); + exit_cb_called++; + TUV_ASSERT(exit_status == 0); + uv_close((uv_handle_t*)process, NULL); +} + + +static void on_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + buf->base = malloc(suggested_size); + buf->len = suggested_size; +} + + +static void close_client_conn_cb(uv_handle_t* handle) { + tcp_conn* p = (tcp_conn*)handle->data; + free(p); +} + + +static void connect_cb(uv_connect_t* req, int status) { + uv_close((uv_handle_t*)req->handle, close_client_conn_cb); +} + + +static void make_many_connections(void) { + tcp_conn* conn; + struct sockaddr_in addr; + int r, i; + + for (i = 0; i < CONN_COUNT; i++) { + conn = malloc(sizeof(*conn)); + TUV_ASSERT(conn); + + r = uv_tcp_init(uv_default_loop(), &conn->conn); + TUV_ASSERT(r == 0); + + TUV_ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + r = uv_tcp_connect(&conn->conn_req, + (uv_tcp_t*) &conn->conn, + (const struct sockaddr*) &addr, + connect_cb); + TUV_ASSERT(r == 0); + + conn->conn.data = conn; + } +} + + +static void on_read(uv_stream_t* handle, + ssize_t nread, + const uv_buf_t* buf) { + int r; + uv_pipe_t* pipe; + uv_handle_type pending; + uv_buf_t outbuf; + + pipe = (uv_pipe_t*) handle; + + if (nread == 0) { + /* Everything OK, but nothing read. */ + free(buf->base); + return; + } + + if (nread < 0) { + if (nread == UV_EOF) { + free(buf->base); + return; + } + + printf("error recving on channel: %s\n", uv_strerror(nread)); + abort(); + } + + fprintf(stderr, "got %d bytes\n", (int)nread); + + pending = uv_pipe_pending_type(pipe); + if (!tcp_server_listening) { + TUV_ASSERT(1 == uv_pipe_pending_count(pipe)); + TUV_ASSERT(nread > 0 && buf->base && pending != UV_UNKNOWN_HANDLE); + read_cb_called++; + + /* Accept the pending TCP server, and start listening on it. */ + TUV_ASSERT(pending == UV_TCP); + r = uv_tcp_init(uv_default_loop(), &tcp_server); + TUV_ASSERT(r == 0); + + r = uv_accept((uv_stream_t*)pipe, (uv_stream_t*)&tcp_server); + TUV_ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, on_connection); + TUV_ASSERT(r == 0); + + tcp_server_listening = 1; + + /* Make sure that the expected data is correctly multiplexed. */ + TUV_ASSERT(memcmp("hello\n", buf->base, nread) == 0); + + outbuf = uv_buf_init("world\n", 6); + r = uv_write(&write_req, (uv_stream_t*)pipe, &outbuf, 1, NULL); + TUV_ASSERT(r == 0); + + /* Create a bunch of connections to get both servers to accept. */ + make_many_connections(); + } else if (memcmp("accepted_connection\n", buf->base, nread) == 0) { + /* Remote server has accepted a connection. Close the channel. */ + TUV_ASSERT(0 == uv_pipe_pending_count(pipe)); + TUV_ASSERT(pending == UV_UNKNOWN_HANDLE); + remote_conn_accepted = 1; + uv_close((uv_handle_t*)&channel, NULL); + } + + free(buf->base); +} + +#ifdef _WIN32 +static void on_read_listen_after_bound_twice(uv_stream_t* handle, + ssize_t nread, + const uv_buf_t* buf) { + int r; + uv_pipe_t* pipe; + uv_handle_type pending; + + pipe = (uv_pipe_t*) handle; + + if (nread == 0) { + /* Everything OK, but nothing read. */ + free(buf->base); + return; + } + + if (nread < 0) { + if (nread == UV_EOF) { + free(buf->base); + return; + } + + printf("error recving on channel: %s\n", uv_strerror(nread)); + abort(); + } + + fprintf(stderr, "got %d bytes\n", (int)nread); + + TUV_ASSERT(uv_pipe_pending_count(pipe) > 0); + pending = uv_pipe_pending_type(pipe); + TUV_ASSERT(nread > 0 && buf->base && pending != UV_UNKNOWN_HANDLE); + read_cb_called++; + + if (read_cb_called == 1) { + /* Accept the first TCP server, and start listening on it. */ + TUV_ASSERT(pending == UV_TCP); + r = uv_tcp_init(uv_default_loop(), &tcp_server); + TUV_ASSERT(r == 0); + + r = uv_accept((uv_stream_t*)pipe, (uv_stream_t*)&tcp_server); + TUV_ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, on_connection); + TUV_ASSERT(r == 0); + } else if (read_cb_called == 2) { + /* Accept the second TCP server, and start listening on it. */ + TUV_ASSERT(pending == UV_TCP); + r = uv_tcp_init(uv_default_loop(), &tcp_server2); + TUV_ASSERT(r == 0); + + r = uv_accept((uv_stream_t*)pipe, (uv_stream_t*)&tcp_server2); + TUV_ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&tcp_server2, BACKLOG, on_connection); + TUV_ASSERT(r == UV_EADDRINUSE); + + uv_close((uv_handle_t*)&tcp_server, NULL); + uv_close((uv_handle_t*)&tcp_server2, NULL); + TUV_ASSERT(0 == uv_pipe_pending_count(pipe)); + uv_close((uv_handle_t*)&channel, NULL); + } + + free(buf->base); +} +#endif + +void spawn_helper(uv_pipe_t* channel, + uv_process_t* process, + const char* helper) { + uv_process_options_t options; + size_t exepath_size; + char exepath[1024]; + char* args[3]; + int r; + uv_stdio_container_t stdio[1]; + + r = uv_pipe_init(uv_default_loop(), channel, 1); + TUV_ASSERT(r == 0); + TUV_ASSERT(channel->ipc); + + exepath_size = sizeof(exepath); + r = uv_exepath(exepath, &exepath_size); + TUV_ASSERT(r == 0); + + exepath[exepath_size] = '\0'; + args[0] = exepath; + args[1] = (char*)helper; + args[2] = NULL; + + memset(&options, 0, sizeof(options)); + options.file = exepath; + options.args = args; + options.exit_cb = exit_cb; + + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | + UV_READABLE_PIPE | UV_WRITABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)channel; + options.stdio_count = 1; + + r = uv_spawn(uv_default_loop(), process, &options); + TUV_ASSERT(r == 0); +} + + +static void on_tcp_write(uv_write_t* req, int status) { + TUV_ASSERT(status == 0); + TUV_ASSERT(req->handle == (uv_stream_t*)&tcp_connection); + tcp_write_cb_called++; +} + + +static void on_read_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + buf->base = malloc(suggested_size); + buf->len = suggested_size; +} + + +static void on_tcp_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { + TUV_ASSERT(nread > 0); + TUV_ASSERT(memcmp("hello again\n", buf->base, nread) == 0); + TUV_ASSERT(tcp == (uv_stream_t*)&tcp_connection); + free(buf->base); + + tcp_read_cb_called++; + + uv_close((uv_handle_t*)tcp, NULL); + uv_close((uv_handle_t*)&channel, NULL); +} + + +static void on_read_connection(uv_stream_t* handle, + ssize_t nread, + const uv_buf_t* buf) { + int r; + uv_buf_t outbuf; + uv_pipe_t* pipe; + uv_handle_type pending; + + pipe = (uv_pipe_t*) handle; + if (nread == 0) { + /* Everything OK, but nothing read. */ + free(buf->base); + return; + } + + if (nread < 0) { + if (nread == UV_EOF) { + free(buf->base); + return; + } + + printf("error recving on channel: %s\n", uv_strerror(nread)); + abort(); + } + + fprintf(stderr, "got %d bytes\n", (int)nread); + + TUV_ASSERT(1 == uv_pipe_pending_count(pipe)); + pending = uv_pipe_pending_type(pipe); + + TUV_ASSERT(nread > 0 && buf->base && pending != UV_UNKNOWN_HANDLE); + read_cb_called++; + + /* Accept the pending TCP connection */ + TUV_ASSERT(pending == UV_TCP); + r = uv_tcp_init(uv_default_loop(), &tcp_connection); + TUV_ASSERT(r == 0); + + r = uv_accept(handle, (uv_stream_t*)&tcp_connection); + TUV_ASSERT(r == 0); + + /* Make sure that the expected data is correctly multiplexed. */ + TUV_ASSERT(memcmp("hello\n", buf->base, nread) == 0); + + /* Write/read to/from the connection */ + outbuf = uv_buf_init("world\n", 6); + r = uv_write(&write_req, (uv_stream_t*)&tcp_connection, &outbuf, 1, + on_tcp_write); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*)&tcp_connection, on_read_alloc, on_tcp_read); + TUV_ASSERT(r == 0); + + free(buf->base); +} + + +static int run_ipc_test(const char* helper, uv_read_cb read_cb) { + uv_process_t process; + int r; + + spawn_helper(&channel, &process, helper); + uv_read_start((uv_stream_t*)&channel, on_alloc, read_cb); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + return 0; +} + + +TEST_IMPL(ipc_listen_before_write) { + int r = run_ipc_test("ipc_helper_listen_before_write", on_read); + TUV_ASSERT(local_conn_accepted == 1); + TUV_ASSERT(remote_conn_accepted == 1); + TUV_ASSERT(read_cb_called == 1); + TUV_ASSERT(exit_cb_called == 1); + return r; +} + + +TEST_IMPL(ipc_listen_after_write) { + int r = run_ipc_test("ipc_helper_listen_after_write", on_read); + TUV_ASSERT(local_conn_accepted == 1); + TUV_ASSERT(remote_conn_accepted == 1); + TUV_ASSERT(read_cb_called == 1); + TUV_ASSERT(exit_cb_called == 1); + return r; +} + + +TEST_IMPL(ipc_tcp_connection) { + int r = run_ipc_test("ipc_helper_tcp_connection", on_read_connection); + TUV_ASSERT(read_cb_called == 1); + TUV_ASSERT(tcp_write_cb_called == 1); + TUV_ASSERT(tcp_read_cb_called == 1); + TUV_ASSERT(exit_cb_called == 1); + return r; +} + + +#ifdef _WIN32 +TEST_IMPL(listen_with_simultaneous_accepts) { + uv_tcp_t server; + int r; + struct sockaddr_in addr; + + TUV_ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + r = uv_tcp_init(uv_default_loop(), &server); + TUV_ASSERT(r == 0); + + r = uv_tcp_bind(&server, (const struct sockaddr*) &addr, 0); + TUV_ASSERT(r == 0); + + r = uv_tcp_simultaneous_accepts(&server, 1); + TUV_ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&server, SOMAXCONN, NULL); + TUV_ASSERT(r == 0); + TUV_ASSERT(server.reqs_pending == 32); + + return 0; +} + + +TEST_IMPL(listen_no_simultaneous_accepts) { + uv_tcp_t server; + int r; + struct sockaddr_in addr; + + TUV_ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + r = uv_tcp_init(uv_default_loop(), &server); + TUV_ASSERT(r == 0); + + r = uv_tcp_bind(&server, (const struct sockaddr*) &addr, 0); + TUV_ASSERT(r == 0); + + r = uv_tcp_simultaneous_accepts(&server, 0); + TUV_ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&server, SOMAXCONN, NULL); + TUV_ASSERT(r == 0); + TUV_ASSERT(server.reqs_pending == 1); + + return 0; +} + +TEST_IMPL(ipc_listen_after_bind_twice) { + int r = run_ipc_test("ipc_helper_bind_twice", on_read_listen_after_bound_twice); + TUV_ASSERT(read_cb_called == 2); + TUV_ASSERT(exit_cb_called == 1); + return r; +} +#endif + + +/* Everything here runs in a child process. */ + +static tcp_conn conn; + + +static void close_cb(uv_handle_t* handle) { + close_cb_called++; +} + + +static void conn_notify_write_cb(uv_write_t* req, int status) { + uv_close((uv_handle_t*)&tcp_server, close_cb); + uv_close((uv_handle_t*)&channel, close_cb); +} + + +static void tcp_connection_write_cb(uv_write_t* req, int status) { + TUV_ASSERT((uv_handle_t*)&conn.conn == (uv_handle_t*)req->handle); + uv_close((uv_handle_t*)req->handle, close_cb); + uv_close((uv_handle_t*)&channel, close_cb); + uv_close((uv_handle_t*)&tcp_server, close_cb); + tcp_conn_write_cb_called++; +} + + +static void on_tcp_child_process_read(uv_stream_t* tcp, + ssize_t nread, + const uv_buf_t* buf) { + uv_buf_t outbuf; + int r; + + if (nread < 0) { + if (nread == UV_EOF) { + free(buf->base); + return; + } + + printf("error recving on tcp connection: %s\n", uv_strerror(nread)); + abort(); + } + + TUV_ASSERT(nread > 0); + TUV_ASSERT(memcmp("world\n", buf->base, nread) == 0); + on_pipe_read_called++; + free(buf->base); + + /* Write to the socket */ + outbuf = uv_buf_init("hello again\n", 12); + r = uv_write(&conn.tcp_write_req, tcp, &outbuf, 1, tcp_connection_write_cb); + TUV_ASSERT(r == 0); + + tcp_conn_read_cb_called++; +} + + +static void connect_child_process_cb(uv_connect_t* req, int status) { + int r; + + TUV_ASSERT(status == 0); + r = uv_read_start(req->handle, on_read_alloc, on_tcp_child_process_read); + TUV_ASSERT(r == 0); +} + + +static void ipc_on_connection(uv_stream_t* server, int status) { + int r; + uv_buf_t buf; + + if (!connection_accepted) { + /* + * Accept the connection and close it. Also let the other + * side know. + */ + TUV_ASSERT(status == 0); + TUV_ASSERT((uv_stream_t*)&tcp_server == server); + + r = uv_tcp_init(server->loop, &conn.conn); + TUV_ASSERT(r == 0); + + r = uv_accept(server, (uv_stream_t*)&conn.conn); + TUV_ASSERT(r == 0); + + uv_close((uv_handle_t*)&conn.conn, close_cb); + + buf = uv_buf_init("accepted_connection\n", 20); + r = uv_write2(&conn_notify_req, (uv_stream_t*)&channel, &buf, 1, + NULL, conn_notify_write_cb); + TUV_ASSERT(r == 0); + + connection_accepted = 1; + } +} + + +static void ipc_on_connection_tcp_conn(uv_stream_t* server, int status) { + int r; + uv_buf_t buf; + uv_tcp_t* conn; + + TUV_ASSERT(status == 0); + TUV_ASSERT((uv_stream_t*)&tcp_server == server); + + conn = malloc(sizeof(*conn)); + TUV_ASSERT(conn); + + r = uv_tcp_init(server->loop, conn); + TUV_ASSERT(r == 0); + + r = uv_accept(server, (uv_stream_t*)conn); + TUV_ASSERT(r == 0); + + /* Send the accepted connection to the other process */ + buf = uv_buf_init("hello\n", 6); + r = uv_write2(&conn_notify_req, (uv_stream_t*)&channel, &buf, 1, + (uv_stream_t*)conn, NULL); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) conn, + on_read_alloc, + on_tcp_child_process_read); + TUV_ASSERT(r == 0); + + uv_close((uv_handle_t*)conn, close_cb); +} + + +int ipc_helper(int listen_after_write) { + /* + * This is launched from test-ipc.c. stdin is a duplex channel that we + * over which a handle will be transmitted. + */ + struct sockaddr_in addr; + uv_write_t write_req; + int r; + uv_buf_t buf; + + TUV_ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + r = uv_pipe_init(uv_default_loop(), &channel, 1); + TUV_ASSERT(r == 0); + + uv_pipe_open(&channel, 0); + + TUV_ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); + TUV_ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); + TUV_ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + + r = uv_tcp_init(uv_default_loop(), &tcp_server); + TUV_ASSERT(r == 0); + + r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); + TUV_ASSERT(r == 0); + + if (!listen_after_write) { + r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, ipc_on_connection); + TUV_ASSERT(r == 0); + } + + buf = uv_buf_init("hello\n", 6); + r = uv_write2(&write_req, (uv_stream_t*)&channel, &buf, 1, + (uv_stream_t*)&tcp_server, NULL); + TUV_ASSERT(r == 0); + + if (listen_after_write) { + r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, ipc_on_connection); + TUV_ASSERT(r == 0); + } + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(connection_accepted == 1); + TUV_ASSERT(close_cb_called == 3); + + return 0; +} + + +int ipc_helper_tcp_connection(void) { + /* + * This is launched from test-ipc.c. stdin is a duplex channel + * over which a handle will be transmitted. + */ + + int r; + struct sockaddr_in addr; + + r = uv_pipe_init(uv_default_loop(), &channel, 1); + TUV_ASSERT(r == 0); + + uv_pipe_open(&channel, 0); + + TUV_ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); + TUV_ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); + TUV_ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + + r = uv_tcp_init(uv_default_loop(), &tcp_server); + TUV_ASSERT(r == 0); + + TUV_ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); + TUV_ASSERT(r == 0); + + r = uv_listen((uv_stream_t*)&tcp_server, BACKLOG, ipc_on_connection_tcp_conn); + TUV_ASSERT(r == 0); + + /* Make a connection to the server */ + r = uv_tcp_init(uv_default_loop(), &conn.conn); + TUV_ASSERT(r == 0); + + TUV_ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &addr)); + + r = uv_tcp_connect(&conn.conn_req, + (uv_tcp_t*) &conn.conn, + (const struct sockaddr*) &addr, + connect_child_process_cb); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(tcp_conn_read_cb_called == 1); + TUV_ASSERT(tcp_conn_write_cb_called == 1); + TUV_ASSERT(close_cb_called == 4); + + return 0; +} + +int ipc_helper_bind_twice(void) { + /* + * This is launched from test-ipc.c. stdin is a duplex channel + * over which two handles will be transmitted. + */ + struct sockaddr_in addr; + uv_write_t write_req; + uv_write_t write_req2; + int r; + uv_buf_t buf; + + TUV_ASSERT(0 == uv_ip4_addr("0.0.0.0", TEST_PORT, &addr)); + + r = uv_pipe_init(uv_default_loop(), &channel, 1); + TUV_ASSERT(r == 0); + + uv_pipe_open(&channel, 0); + + TUV_ASSERT(1 == uv_is_readable((uv_stream_t*) &channel)); + TUV_ASSERT(1 == uv_is_writable((uv_stream_t*) &channel)); + TUV_ASSERT(0 == uv_is_closing((uv_handle_t*) &channel)); + + buf = uv_buf_init("hello\n", 6); + + r = uv_tcp_init(uv_default_loop(), &tcp_server); + TUV_ASSERT(r == 0); + r = uv_tcp_init(uv_default_loop(), &tcp_server2); + TUV_ASSERT(r == 0); + + r = uv_tcp_bind(&tcp_server, (const struct sockaddr*) &addr, 0); + TUV_ASSERT(r == 0); + r = uv_tcp_bind(&tcp_server2, (const struct sockaddr*) &addr, 0); + TUV_ASSERT(r == 0); + + r = uv_write2(&write_req, (uv_stream_t*)&channel, &buf, 1, + (uv_stream_t*)&tcp_server, NULL); + TUV_ASSERT(r == 0); + r = uv_write2(&write_req2, (uv_stream_t*)&channel, &buf, 1, + (uv_stream_t*)&tcp_server2, NULL); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + return 0; +} diff --git a/test/test_spawn.c b/test/test_spawn.c new file mode 100644 index 00000000..1c48122e --- /dev/null +++ b/test/test_spawn.c @@ -0,0 +1,1638 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "runner.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# if defined(__MINGW32__) +# include +# endif +# include +# include +#else +# include +# include +#endif + + +static int close_cb_called; +static int exit_cb_called; +static uv_process_t process; +static uv_timer_t timer; +static uv_process_options_t options; +static char exepath[1024]; +static size_t exepath_size = 1024; +static char* args[5]; +static int no_term_signal; +static int timer_counter; + +#define OUTPUT_SIZE 1024 +static char output[OUTPUT_SIZE]; +static int output_used; + + +static void close_cb(uv_handle_t* handle) { + printf("close_cb\n"); + close_cb_called++; +} + +static void exit_cb(uv_process_t* process, + int64_t exit_status, + int term_signal) { + printf("exit_cb\n"); + exit_cb_called++; + TUV_ASSERT(exit_status == 1); + TUV_ASSERT(term_signal == 0); + uv_close((uv_handle_t*)process, close_cb); +} + + +static void fail_cb(uv_process_t* process, + int64_t exit_status, + int term_signal) { + TUV_ASSERT(0 && "fail_cb called"); +} + + +static void kill_cb(uv_process_t* process, + int64_t exit_status, + int term_signal) { + int err; + + printf("exit_cb\n"); + exit_cb_called++; +#ifdef _WIN32 + TUV_ASSERT(exit_status == 1); +#else + TUV_ASSERT(exit_status == 0); +#endif + TUV_ASSERT(no_term_signal || term_signal == 15); + uv_close((uv_handle_t*)process, close_cb); + + /* + * Sending signum == 0 should check if the + * child process is still alive, not kill it. + * This process should be dead. + */ + err = uv_kill(process->pid, 0); + TUV_ASSERT(err == UV_ESRCH); +} + +static void detach_failure_cb(uv_process_t* process, + int64_t exit_status, + int term_signal) { + printf("detach_cb\n"); + exit_cb_called++; +} + +static void on_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + buf->base = output + output_used; + buf->len = OUTPUT_SIZE - output_used; +} + + +static void on_read(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { + if (nread > 0) { + output_used += nread; + } else if (nread < 0) { + TUV_ASSERT(nread == UV_EOF); + uv_close((uv_handle_t*)tcp, close_cb); + } +} + + +static void on_read_once(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) { + uv_read_stop(tcp); + on_read(tcp, nread, buf); +} + + +static void write_cb(uv_write_t* req, int status) { + TUV_ASSERT(status == 0); + uv_close((uv_handle_t*)req->handle, close_cb); +} + + +static void init_process_options(char* test, uv_exit_cb exit_cb) { + /* Note spawn_helper1 defined in test/run-tests.c */ + int r = uv_exepath(exepath, &exepath_size); + TUV_ASSERT(r == 0); + exepath[exepath_size] = '\0'; + args[0] = exepath; + args[1] = test; + args[2] = NULL; + args[3] = NULL; + args[4] = NULL; + options.file = exepath; + options.args = args; + options.exit_cb = exit_cb; + options.flags = 0; +} + + +static void timer_cb(uv_timer_t* handle) { + uv_process_kill(&process, /* SIGTERM */ 15); + uv_close((uv_handle_t*)handle, close_cb); +} + + +static void timer_counter_cb(uv_timer_t* handle) { + ++timer_counter; +} + + +TEST_IMPL(spawn_fails) { + int r; + + init_process_options("", fail_cb); + options.file = options.args[0] = "program-that-had-better-not-exist"; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_ENOENT || r == UV_EACCES); + TUV_ASSERT(0 == uv_is_active((uv_handle_t*) &process)); + uv_close((uv_handle_t*) &process, NULL); + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + return 0; +} + + +#ifndef _WIN32 +TEST_IMPL(spawn_fails_check_for_waitpid_cleanup) { + int r; + int status; + int err; + + init_process_options("", fail_cb); + options.file = options.args[0] = "program-that-had-better-not-exist"; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_ENOENT || r == UV_EACCES); + TUV_ASSERT(0 == uv_is_active((uv_handle_t*) &process)); + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + /* verify the child is successfully cleaned up within libuv */ + do + err = waitpid(process.pid, &status, 0); + while (err == -1 && errno == EINTR); + + TUV_ASSERT(err == -1); + TUV_ASSERT(errno == ECHILD); + + uv_close((uv_handle_t*) &process, NULL); + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + return 0; +} +#endif + + +TEST_IMPL(spawn_exit_code) { + int r; + + init_process_options("spawn_helper1", exit_cb); + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + return 0; +} + + +TEST_IMPL(spawn_stdout) { + int r; + uv_pipe_t out; + uv_stdio_container_t stdio[2]; + + init_process_options("spawn_helper2", exit_cb); + + uv_pipe_init(uv_default_loop(), &out, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */ + printf("output is: %s", output); + TUV_ASSERT(strcmp("hello world\n", output) == 0); + + return 0; +} + + +TEST_IMPL(spawn_stdout_to_file) { + int r; + uv_file file; + uv_fs_t fs_req; + uv_stdio_container_t stdio[2]; + uv_buf_t buf; + + /* Setup. */ + unlink("stdout_file"); + + init_process_options("spawn_helper2", exit_cb); + + r = uv_fs_open(NULL, &fs_req, "stdout_file", O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR, NULL); + TUV_ASSERT(r != -1); + uv_fs_req_cleanup(&fs_req); + + file = r; + + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_INHERIT_FD; + options.stdio[1].data.fd = file; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + buf = uv_buf_init(output, sizeof(output)); + r = uv_fs_read(NULL, &fs_req, file, &buf, 1, 0, NULL); + TUV_ASSERT(r == 12); + uv_fs_req_cleanup(&fs_req); + + r = uv_fs_close(NULL, &fs_req, file, NULL); + TUV_ASSERT(r == 0); + uv_fs_req_cleanup(&fs_req); + + printf("output is: %s", output); + TUV_ASSERT(strcmp("hello world\n", output) == 0); + + /* Cleanup. */ + unlink("stdout_file"); + + return 0; +} + + +TEST_IMPL(spawn_stdout_and_stderr_to_file) { + int r; + uv_file file; + uv_fs_t fs_req; + uv_stdio_container_t stdio[3]; + uv_buf_t buf; + + /* Setup. */ + unlink("stdout_file"); + + init_process_options("spawn_helper6", exit_cb); + + r = uv_fs_open(NULL, &fs_req, "stdout_file", O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR, NULL); + TUV_ASSERT(r != -1); + uv_fs_req_cleanup(&fs_req); + + file = r; + + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_INHERIT_FD; + options.stdio[1].data.fd = file; + options.stdio[2].flags = UV_INHERIT_FD; + options.stdio[2].data.fd = file; + options.stdio_count = 3; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + buf = uv_buf_init(output, sizeof(output)); + r = uv_fs_read(NULL, &fs_req, file, &buf, 1, 0, NULL); + TUV_ASSERT(r == 27); + uv_fs_req_cleanup(&fs_req); + + r = uv_fs_close(NULL, &fs_req, file, NULL); + TUV_ASSERT(r == 0); + uv_fs_req_cleanup(&fs_req); + + printf("output is: %s", output); + TUV_ASSERT(strcmp("hello world\nhello errworld\n", output) == 0); + + /* Cleanup. */ + unlink("stdout_file"); + + return 0; +} + + +#ifndef _WIN32 +TEST_IMPL(spawn_stdout_and_stderr_to_file2) { + int r; + uv_file file; + uv_fs_t fs_req; + uv_stdio_container_t stdio[3]; + uv_buf_t buf; + + /* Setup. */ + unlink("stdout_file"); + + init_process_options("spawn_helper6", exit_cb); + + /* Replace stderr with our file */ + r = uv_fs_open(NULL, + &fs_req, + "stdout_file", + O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR, + NULL); + TUV_ASSERT(r != -1); + uv_fs_req_cleanup(&fs_req); + file = dup2(r, STDERR_FILENO); + TUV_ASSERT(file != -1); + + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_INHERIT_FD; + options.stdio[1].data.fd = file; + options.stdio[2].flags = UV_INHERIT_FD; + options.stdio[2].data.fd = file; + options.stdio_count = 3; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + buf = uv_buf_init(output, sizeof(output)); + r = uv_fs_read(NULL, &fs_req, file, &buf, 1, 0, NULL); + TUV_ASSERT(r == 27); + uv_fs_req_cleanup(&fs_req); + + r = uv_fs_close(NULL, &fs_req, file, NULL); + TUV_ASSERT(r == 0); + uv_fs_req_cleanup(&fs_req); + + printf("output is: %s", output); + TUV_ASSERT(strcmp("hello world\nhello errworld\n", output) == 0); + + /* Cleanup. */ + unlink("stdout_file"); + + return 0; +} +#endif + + +#ifndef _WIN32 +TEST_IMPL(spawn_stdout_and_stderr_to_file_swap) { + int r; + uv_file stdout_file; + uv_file stderr_file; + uv_fs_t fs_req; + uv_stdio_container_t stdio[3]; + uv_buf_t buf; + + /* Setup. */ + unlink("stdout_file"); + unlink("stderr_file"); + + init_process_options("spawn_helper6", exit_cb); + + /* open 'stdout_file' and replace STDOUT_FILENO with it */ + r = uv_fs_open(NULL, + &fs_req, + "stdout_file", + O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR, + NULL); + TUV_ASSERT(r != -1); + uv_fs_req_cleanup(&fs_req); + stdout_file = dup2(r, STDOUT_FILENO); + TUV_ASSERT(stdout_file != -1); + + /* open 'stderr_file' and replace STDERR_FILENO with it */ + r = uv_fs_open(NULL, &fs_req, "stderr_file", O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR, NULL); + TUV_ASSERT(r != -1); + uv_fs_req_cleanup(&fs_req); + stderr_file = dup2(r, STDERR_FILENO); + TUV_ASSERT(stderr_file != -1); + + /* now we're going to swap them: the child process' stdout will be our + * stderr_file and vice versa */ + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_INHERIT_FD; + options.stdio[1].data.fd = stderr_file; + options.stdio[2].flags = UV_INHERIT_FD; + options.stdio[2].data.fd = stdout_file; + options.stdio_count = 3; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + buf = uv_buf_init(output, sizeof(output)); + + /* check the content of stdout_file */ + r = uv_fs_read(NULL, &fs_req, stdout_file, &buf, 1, 0, NULL); + TUV_ASSERT(r >= 15); + uv_fs_req_cleanup(&fs_req); + + r = uv_fs_close(NULL, &fs_req, stdout_file, NULL); + TUV_ASSERT(r == 0); + uv_fs_req_cleanup(&fs_req); + + printf("output is: %s", output); + TUV_ASSERT(strncmp("hello errworld\n", output, 15) == 0); + + /* check the content of stderr_file */ + r = uv_fs_read(NULL, &fs_req, stderr_file, &buf, 1, 0, NULL); + TUV_ASSERT(r >= 12); + uv_fs_req_cleanup(&fs_req); + + r = uv_fs_close(NULL, &fs_req, stderr_file, NULL); + TUV_ASSERT(r == 0); + uv_fs_req_cleanup(&fs_req); + + printf("output is: %s", output); + TUV_ASSERT(strncmp("hello world\n", output, 12) == 0); + + /* Cleanup. */ + unlink("stdout_file"); + unlink("stderr_file"); + + return 0; +} +#endif + + +TEST_IMPL(spawn_stdin) { + int r; + uv_pipe_t out; + uv_pipe_t in; + uv_write_t write_req; + uv_buf_t buf; + uv_stdio_container_t stdio[2]; + char buffer[] = "hello-from-spawn_stdin"; + + init_process_options("spawn_helper3", exit_cb); + + uv_pipe_init(uv_default_loop(), &out, 0); + uv_pipe_init(uv_default_loop(), &in, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + buf.base = buffer; + buf.len = sizeof(buffer); + r = uv_write(&write_req, (uv_stream_t*)&in, &buf, 1, write_cb); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 3); /* Once for process twice for the pipe. */ + TUV_ASSERT(strcmp(buffer, output) == 0); + + return 0; +} + + +TEST_IMPL(spawn_stdio_greater_than_3) { + int r; + uv_pipe_t pipe; + uv_stdio_container_t stdio[4]; + + init_process_options("spawn_helper5", exit_cb); + + uv_pipe_init(uv_default_loop(), &pipe, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_IGNORE; + options.stdio[2].flags = UV_IGNORE; + options.stdio[3].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[3].data.stream = (uv_stream_t*)&pipe; + options.stdio_count = 4; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &pipe, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */ + printf("output from stdio[3] is: %s", output); + TUV_ASSERT(strcmp("fourth stdio!\n", output) == 0); + + return 0; +} + + +TEST_IMPL(spawn_ignored_stdio) { + int r; + + init_process_options("spawn_helper6", exit_cb); + + options.stdio = NULL; + options.stdio_count = 0; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + return 0; +} + + +TEST_IMPL(spawn_and_kill) { + int r; + + init_process_options("spawn_helper4", kill_cb); + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_timer_init(uv_default_loop(), &timer); + TUV_ASSERT(r == 0); + + r = uv_timer_start(&timer, timer_cb, 500, 0); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); /* Once for process and once for timer. */ + + return 0; +} + + +TEST_IMPL(spawn_preserve_env) { + int r; + uv_pipe_t out; + uv_stdio_container_t stdio[2]; + + init_process_options("spawn_helper7", exit_cb); + + uv_pipe_init(uv_default_loop(), &out, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*) &out; + options.stdio_count = 2; + + r = putenv("ENV_TEST=testval"); + TUV_ASSERT(r == 0); + + /* Explicitly set options.env to NULL to test for env clobbering. */ + options.env = NULL; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); + + printf("output is: %s", output); + TUV_ASSERT(strcmp("testval", output) == 0); + + return 0; +} + + +TEST_IMPL(spawn_detached) { + int r; + + init_process_options("spawn_helper4", detach_failure_cb); + + options.flags |= UV_PROCESS_DETACHED; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + uv_unref((uv_handle_t*)&process); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 0); + + r = uv_kill(process.pid, 0); + TUV_ASSERT(r == 0); + + r = uv_kill(process.pid, 15); + TUV_ASSERT(r == 0); + + return 0; +} + +TEST_IMPL(spawn_and_kill_with_std) { + int r; + uv_pipe_t in, out, err; + uv_write_t write; + char message[] = "Nancy's joining me because the message this evening is " + "not my message but ours."; + uv_buf_t buf; + uv_stdio_container_t stdio[3]; + + init_process_options("spawn_helper4", kill_cb); + + r = uv_pipe_init(uv_default_loop(), &in, 0); + TUV_ASSERT(r == 0); + + r = uv_pipe_init(uv_default_loop(), &out, 0); + TUV_ASSERT(r == 0); + + r = uv_pipe_init(uv_default_loop(), &err, 0); + TUV_ASSERT(r == 0); + + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[2].data.stream = (uv_stream_t*)&err; + options.stdio_count = 3; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + buf = uv_buf_init(message, sizeof message); + r = uv_write(&write, (uv_stream_t*) &in, &buf, 1, write_cb); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &err, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_timer_init(uv_default_loop(), &timer); + TUV_ASSERT(r == 0); + + r = uv_timer_start(&timer, timer_cb, 500, 0); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 5); /* process x 1, timer x 1, stdio x 3. */ + + return 0; +} + + +TEST_IMPL(spawn_and_ping) { + uv_write_t write_req; + uv_pipe_t in, out; + uv_buf_t buf; + uv_stdio_container_t stdio[2]; + int r; + + init_process_options("spawn_helper3", exit_cb); + buf = uv_buf_init("TEST", 4); + + uv_pipe_init(uv_default_loop(), &out, 0); + uv_pipe_init(uv_default_loop(), &in, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + /* Sending signum == 0 should check if the + * child process is still alive, not kill it. + */ + r = uv_process_kill(&process, 0); + TUV_ASSERT(r == 0); + + r = uv_write(&write_req, (uv_stream_t*)&in, &buf, 1, write_cb); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*)&out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(strcmp(output, "TEST") == 0); + + return 0; +} + + +TEST_IMPL(spawn_same_stdout_stderr) { + uv_write_t write_req; + uv_pipe_t in, out; + uv_buf_t buf; + uv_stdio_container_t stdio[3]; + int r; + + init_process_options("spawn_helper3", exit_cb); + buf = uv_buf_init("TEST", 4); + + uv_pipe_init(uv_default_loop(), &out, 0); + uv_pipe_init(uv_default_loop(), &in, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + /* Sending signum == 0 should check if the + * child process is still alive, not kill it. + */ + r = uv_process_kill(&process, 0); + TUV_ASSERT(r == 0); + + r = uv_write(&write_req, (uv_stream_t*)&in, &buf, 1, write_cb); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*)&out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(strcmp(output, "TEST") == 0); + + return 0; +} + + +TEST_IMPL(spawn_closed_process_io) { + uv_pipe_t in; + uv_write_t write_req; + uv_buf_t buf; + uv_stdio_container_t stdio[2]; + static char buffer[] = "hello-from-spawn_stdin\n"; + + init_process_options("spawn_helper3", exit_cb); + + uv_pipe_init(uv_default_loop(), &in, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*) ∈ + options.stdio_count = 1; + + close(0); /* Close process stdin. */ + + TUV_ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + + buf = uv_buf_init(buffer, sizeof(buffer)); + TUV_ASSERT(0 == uv_write(&write_req, (uv_stream_t*) &in, &buf, 1, write_cb)); + + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); /* process, child stdin */ + + return 0; +} + + +TEST_IMPL(kill) { + int r; + +#ifdef _WIN32 + no_term_signal = 1; +#endif + + init_process_options("spawn_helper4", kill_cb); + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + /* Sending signum == 0 should check if the + * child process is still alive, not kill it. + */ + r = uv_kill(process.pid, 0); + TUV_ASSERT(r == 0); + + /* Kill the process. */ + r = uv_kill(process.pid, /* SIGTERM */ 15); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + return 0; +} + + +#ifdef _WIN32 +TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { + int r; + uv_pipe_t out; + char name[64]; + HANDLE pipe_handle; + uv_stdio_container_t stdio[2]; + + init_process_options("spawn_helper2", exit_cb); + + uv_pipe_init(uv_default_loop(), &out, 0); + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; + + /* Create a pipe that'll cause a collision. */ + snprintf(name, + sizeof(name), + "\\\\.\\pipe\\uv\\%p-%d", + &out, + GetCurrentProcessId()); + pipe_handle = CreateNamedPipeA(name, + PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 10, + 65536, + 65536, + 0, + NULL); + TUV_ASSERT(pipe_handle != INVALID_HANDLE_VALUE); + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*) &out, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */ + printf("output is: %s", output); + TUV_ASSERT(strcmp("hello world\n", output) == 0); + + return 0; +} + + +#if !defined(USING_UV_SHARED) +int make_program_args(char** args, int verbatim_arguments, WCHAR** dst_ptr); +WCHAR* quote_cmd_arg(const WCHAR *source, WCHAR *target); + +TEST_IMPL(argument_escaping) { + const WCHAR* test_str[] = { + L"", + L"HelloWorld", + L"Hello World", + L"Hello\"World", + L"Hello World\\", + L"Hello\\\"World", + L"Hello\\World", + L"Hello\\\\World", + L"Hello World\\", + L"c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\"" + }; + const int count = sizeof(test_str) / sizeof(*test_str); + WCHAR** test_output; + WCHAR* command_line; + WCHAR** cracked; + size_t total_size = 0; + int i; + int num_args; + int result; + + char* verbatim[] = { + "cmd.exe", + "/c", + "c:\\path\\to\\node.exe --eval \"require('c:\\\\path\\\\to\\\\test.js')\"", + NULL + }; + WCHAR* verbatim_output; + WCHAR* non_verbatim_output; + + test_output = calloc(count, sizeof(WCHAR*)); + TUV_ASSERT(test_output != NULL); + for (i = 0; i < count; ++i) { + test_output[i] = calloc(2 * (wcslen(test_str[i]) + 2), sizeof(WCHAR)); + quote_cmd_arg(test_str[i], test_output[i]); + wprintf(L"input : %s\n", test_str[i]); + wprintf(L"output: %s\n", test_output[i]); + total_size += wcslen(test_output[i]) + 1; + } + command_line = calloc(total_size + 1, sizeof(WCHAR)); + TUV_ASSERT(command_line != NULL); + for (i = 0; i < count; ++i) { + wcscat(command_line, test_output[i]); + wcscat(command_line, L" "); + } + command_line[total_size - 1] = L'\0'; + + wprintf(L"command_line: %s\n", command_line); + + cracked = CommandLineToArgvW(command_line, &num_args); + for (i = 0; i < num_args; ++i) { + wprintf(L"%d: %s\t%s\n", i, test_str[i], cracked[i]); + TUV_ASSERT(wcscmp(test_str[i], cracked[i]) == 0); + } + + LocalFree(cracked); + for (i = 0; i < count; ++i) { + free(test_output[i]); + } + + result = make_program_args(verbatim, 1, &verbatim_output); + TUV_ASSERT(result == 0); + result = make_program_args(verbatim, 0, &non_verbatim_output); + TUV_ASSERT(result == 0); + + wprintf(L" verbatim_output: %s\n", verbatim_output); + wprintf(L"non_verbatim_output: %s\n", non_verbatim_output); + + TUV_ASSERT(wcscmp(verbatim_output, + L"cmd.exe /c c:\\path\\to\\node.exe --eval " + L"\"require('c:\\\\path\\\\to\\\\test.js')\"") == 0); + TUV_ASSERT(wcscmp(non_verbatim_output, + L"cmd.exe /c \"c:\\path\\to\\node.exe --eval " + L"\\\"require('c:\\\\path\\\\to\\\\test.js')\\\"\"") == 0); + + free(verbatim_output); + free(non_verbatim_output); + + return 0; +} + +int make_program_env(char** env_block, WCHAR** dst_ptr); + +TEST_IMPL(environment_creation) { + int i; + char* environment[] = { + "FOO=BAR", + "SYSTEM=ROOT", /* substring of a supplied var name */ + "SYSTEMROOTED=OMG", /* supplied var name is a substring */ + "TEMP=C:\\Temp", + "INVALID", + "BAZ=QUX", + "B_Z=QUX", + "B\xe2\x82\xacZ=QUX", + "B\xf0\x90\x80\x82Z=QUX", + "B\xef\xbd\xa1Z=QUX", + "B\xf0\xa3\x91\x96Z=QUX", + "BAZ", /* repeat, invalid variable */ + NULL + }; + WCHAR* wenvironment[] = { + L"BAZ=QUX", + L"B_Z=QUX", + L"B\x20acZ=QUX", + L"B\xd800\xdc02Z=QUX", + L"B\xd84d\xdc56Z=QUX", + L"B\xff61Z=QUX", + L"FOO=BAR", + L"SYSTEM=ROOT", /* substring of a supplied var name */ + L"SYSTEMROOTED=OMG", /* supplied var name is a substring */ + L"TEMP=C:\\Temp", + }; + WCHAR* from_env[] = { + /* list should be kept in sync with list + * in process.c, minus variables in wenvironment */ + L"HOMEDRIVE", + L"HOMEPATH", + L"LOGONSERVER", + L"PATH", + L"USERDOMAIN", + L"USERNAME", + L"USERPROFILE", + L"SYSTEMDRIVE", + L"SYSTEMROOT", + L"WINDIR", + /* test for behavior in the absence of a + * required-environment variable: */ + L"ZTHIS_ENV_VARIABLE_DOES_NOT_EXIST", + }; + int found_in_loc_env[ARRAY_SIZE(wenvironment)] = {0}; + int found_in_usr_env[ARRAY_SIZE(from_env)] = {0}; + WCHAR *expected[ARRAY_SIZE(from_env)]; + int result; + WCHAR* str; + WCHAR* prev; + WCHAR* env; + + for (i = 0; i < ARRAY_SIZE(from_env); i++) { + /* copy expected additions to environment locally */ + size_t len = GetEnvironmentVariableW(from_env[i], NULL, 0); + if (len == 0) { + found_in_usr_env[i] = 1; + str = malloc(1 * sizeof(WCHAR)); + *str = 0; + expected[i] = str; + } else { + size_t name_len = wcslen(from_env[i]); + str = malloc((name_len+1+len) * sizeof(WCHAR)); + wmemcpy(str, from_env[i], name_len); + expected[i] = str; + str += name_len; + *str++ = L'='; + GetEnvironmentVariableW(from_env[i], str, len); + } + } + + result = make_program_env(environment, &env); + TUV_ASSERT(result == 0); + + for (str = env, prev = NULL; *str; prev = str, str += wcslen(str) + 1) { + int found = 0; +#if 0 + _cputws(str); + putchar('\n'); +#endif + for (i = 0; i < ARRAY_SIZE(wenvironment) && !found; i++) { + if (!wcscmp(str, wenvironment[i])) { + TUV_ASSERT(!found_in_loc_env[i]); + found_in_loc_env[i] = 1; + found = 1; + } + } + for (i = 0; i < ARRAY_SIZE(expected) && !found; i++) { + if (!wcscmp(str, expected[i])) { + TUV_ASSERT(!found_in_usr_env[i]); + found_in_usr_env[i] = 1; + found = 1; + } + } + if (prev) { /* verify sort order -- requires Vista */ +#if _WIN32_WINNT >= 0x0600 && \ + (!defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR)) + TUV_ASSERT(CompareStringOrdinal(prev, -1, str, -1, TRUE) == 1); +#endif + } + TUV_ASSERT(found); /* verify that we expected this variable */ + } + + /* verify that we found all expected variables */ + for (i = 0; i < ARRAY_SIZE(wenvironment); i++) { + TUV_ASSERT(found_in_loc_env[i]); + } + for (i = 0; i < ARRAY_SIZE(expected); i++) { + TUV_ASSERT(found_in_usr_env[i]); + } + + return 0; +} +#endif + +/* Regression test for issue #909 */ +TEST_IMPL(spawn_with_an_odd_path) { + int r; + + char newpath[2048]; + char *path = getenv("PATH"); + TUV_ASSERT(path != NULL); + snprintf(newpath, 2048, ";.;%s", path); + SetEnvironmentVariable("PATH", newpath); + + init_process_options("", exit_cb); + options.file = options.args[0] = "program-that-had-better-not-exist"; + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_ENOENT || r == UV_EACCES); + TUV_ASSERT(0 == uv_is_active((uv_handle_t*) &process)); + uv_close((uv_handle_t*) &process, NULL); + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + + return 0; +} +#endif + +#ifndef _WIN32 +TEST_IMPL(spawn_setuid_fails) { + int r; + + /* if root, become nobody. */ + uv_uid_t uid = getuid(); + if (uid == 0) { + struct passwd* pw; + pw = getpwnam("nobody"); + TUV_ASSERT(pw != NULL); + TUV_ASSERT(0 == setgid(pw->pw_gid)); + TUV_ASSERT(0 == setuid(pw->pw_uid)); + } + + init_process_options("spawn_helper1", fail_cb); + + options.flags |= UV_PROCESS_SETUID; + options.uid = 0; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_EPERM); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(close_cb_called == 0); + + return 0; +} + + +TEST_IMPL(spawn_setgid_fails) { + int r; + + /* if root, become nobody. */ + uv_uid_t uid = getuid(); + if (uid == 0) { + struct passwd* pw; + pw = getpwnam("nobody"); + TUV_ASSERT(pw != NULL); + TUV_ASSERT(0 == setgid(pw->pw_gid)); + TUV_ASSERT(0 == setuid(pw->pw_uid)); + } + + init_process_options("spawn_helper1", fail_cb); + + options.flags |= UV_PROCESS_SETGID; + options.gid = 0; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_EPERM); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(close_cb_called == 0); + + return 0; +} +#endif + + +#ifdef _WIN32 + +static void exit_cb_unexpected(uv_process_t* process, + int64_t exit_status, + int term_signal) { + TUV_ASSERT(0 && "should not have been called"); +} + + +TEST_IMPL(spawn_setuid_fails) { + int r; + + init_process_options("spawn_helper1", exit_cb_unexpected); + + options.flags |= UV_PROCESS_SETUID; + options.uid = (uv_uid_t) -42424242; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_ENOTSUP); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(close_cb_called == 0); + + return 0; +} + + +TEST_IMPL(spawn_setgid_fails) { + int r; + + init_process_options("spawn_helper1", exit_cb_unexpected); + + options.flags |= UV_PROCESS_SETGID; + options.gid = (uv_gid_t) -42424242; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == UV_ENOTSUP); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(close_cb_called == 0); + + return 0; +} +#endif + + +TEST_IMPL(spawn_auto_unref) { + init_process_options("spawn_helper1", NULL); + TUV_ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + TUV_ASSERT(0 == uv_is_closing((uv_handle_t*) &process)); + uv_close((uv_handle_t*) &process, NULL); + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + TUV_ASSERT(1 == uv_is_closing((uv_handle_t*) &process)); + return 0; +} + + +#ifndef _WIN32 +TEST_IMPL(spawn_fs_open) { + int fd; + uv_fs_t fs_req; + uv_pipe_t in; + uv_write_t write_req; + uv_buf_t buf; + uv_stdio_container_t stdio[1]; + + fd = uv_fs_open(NULL, &fs_req, "/dev/null", O_RDWR, 0, NULL); + TUV_ASSERT(fd >= 0); + uv_fs_req_cleanup(&fs_req); + + init_process_options("spawn_helper8", exit_cb); + + TUV_ASSERT(0 == uv_pipe_init(uv_default_loop(), &in, 0)); + + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*) ∈ + options.stdio_count = 1; + + TUV_ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + + buf = uv_buf_init((char*) &fd, sizeof(fd)); + TUV_ASSERT(0 == uv_write(&write_req, (uv_stream_t*) &in, &buf, 1, write_cb)); + + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + TUV_ASSERT(0 == uv_fs_close(NULL, &fs_req, fd, NULL)); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 2); /* One for `in`, one for process */ + + return 0; +} +#endif /* !_WIN32 */ + + +#ifndef _WIN32 +TEST_IMPL(closed_fd_events) { + uv_stdio_container_t stdio[3]; + uv_pipe_t pipe_handle; + int fd[2]; + + /* create a pipe and share it with a child process */ + TUV_ASSERT(0 == pipe(fd)); + + /* spawn_helper4 blocks indefinitely. */ + init_process_options("spawn_helper4", exit_cb); + options.stdio_count = 3; + options.stdio = stdio; + options.stdio[0].flags = UV_INHERIT_FD; + options.stdio[0].data.fd = fd[0]; + options.stdio[1].flags = UV_IGNORE; + options.stdio[2].flags = UV_IGNORE; + + TUV_ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + uv_unref((uv_handle_t*) &process); + + /* read from the pipe with uv */ + TUV_ASSERT(0 == uv_pipe_init(uv_default_loop(), &pipe_handle, 0)); + TUV_ASSERT(0 == uv_pipe_open(&pipe_handle, fd[0])); + fd[0] = -1; + + TUV_ASSERT(0 == uv_read_start((uv_stream_t*) &pipe_handle, on_alloc, on_read_once)); + + TUV_ASSERT(1 == write(fd[1], "", 1)); + + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_ONCE)); + + /* should have received just one byte */ + TUV_ASSERT(output_used == 1); + + /* close the pipe and see if we still get events */ + uv_close((uv_handle_t*) &pipe_handle, close_cb); + + TUV_ASSERT(1 == write(fd[1], "", 1)); + + TUV_ASSERT(0 == uv_timer_init(uv_default_loop(), &timer)); + TUV_ASSERT(0 == uv_timer_start(&timer, timer_counter_cb, 10, 0)); + + /* see if any spurious events interrupt the timer */ + if (1 == uv_run(uv_default_loop(), UV_RUN_ONCE)) + /* have to run again to really trigger the timer */ + TUV_ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_ONCE)); + + TUV_ASSERT(timer_counter == 1); + + /* cleanup */ + TUV_ASSERT(0 == uv_process_kill(&process, /* SIGTERM */ 15)); + TUV_ASSERT(0 == close(fd[1])); + + return 0; +} +#endif /* !_WIN32 */ + +TEST_IMPL(spawn_reads_child_path) { + int r; + int len; + char file[64]; + char path[1024]; + char* env[3]; + + /* Need to carry over the dynamic linker path when the test runner is + * linked against libuv.so, see https://github.com/libuv/libuv/issues/85. + */ +#if defined(__APPLE__) + static const char dyld_path_var[] = "DYLD_LIBRARY_PATH"; +#elif defined __MVS__ + static const char dyld_path_var[] = "LIBPATH"; +#else + static const char dyld_path_var[] = "LD_LIBRARY_PATH"; +#endif + + /* Set up the process, but make sure that the file to run is relative and */ + /* requires a lookup into PATH */ + init_process_options("spawn_helper1", exit_cb); + + /* Set up the PATH env variable */ + for (len = strlen(exepath); + exepath[len - 1] != '/' && exepath[len - 1] != '\\'; + len--); + strcpy(file, exepath + len); + exepath[len] = 0; + strcpy(path, "PATH="); + strcpy(path + 5, exepath); + + env[0] = path; + env[1] = getenv(dyld_path_var); + env[2] = NULL; + + if (env[1] != NULL) { + static char buf[1024 + sizeof(dyld_path_var)]; + snprintf(buf, sizeof(buf), "%s=%s", dyld_path_var, env[1]); + env[1] = buf; + } + + options.file = file; + options.args[0] = file; + options.env = env; + + r = uv_spawn(uv_default_loop(), &process, &options); + TUV_ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 1); + + return 0; +} + +#ifndef _WIN32 +static int mpipe(int *fds) { + if (pipe(fds) == -1) + return -1; + if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 || + fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) { + close(fds[0]); + close(fds[1]); + return -1; + } + return 0; +} +#else +static int mpipe(int *fds) { + SECURITY_ATTRIBUTES attr; + HANDLE readh, writeh; + attr.nLength = sizeof(attr); + attr.lpSecurityDescriptor = NULL; + attr.bInheritHandle = FALSE; + if (!CreatePipe(&readh, &writeh, &attr, 0)) + return -1; + fds[0] = _open_osfhandle((intptr_t)readh, 0); + fds[1] = _open_osfhandle((intptr_t)writeh, 0); + if (fds[0] == -1 || fds[1] == -1) { + CloseHandle(readh); + CloseHandle(writeh); + return -1; + } + return 0; +} +#endif /* !_WIN32 */ + +TEST_IMPL(spawn_inherit_streams) { + uv_process_t child_req; + uv_stdio_container_t child_stdio[2]; + int fds_stdin[2]; + int fds_stdout[2]; + uv_pipe_t pipe_stdin_child; + uv_pipe_t pipe_stdout_child; + uv_pipe_t pipe_stdin_parent; + uv_pipe_t pipe_stdout_parent; + unsigned char ubuf[OUTPUT_SIZE - 1]; + uv_buf_t buf; + unsigned int i; + int r; + uv_write_t write_req; + uv_loop_t* loop; + + init_process_options("spawn_helper9", exit_cb); + + loop = uv_default_loop(); + TUV_ASSERT(uv_pipe_init(loop, &pipe_stdin_child, 0) == 0); + TUV_ASSERT(uv_pipe_init(loop, &pipe_stdout_child, 0) == 0); + TUV_ASSERT(uv_pipe_init(loop, &pipe_stdin_parent, 0) == 0); + TUV_ASSERT(uv_pipe_init(loop, &pipe_stdout_parent, 0) == 0); + + TUV_ASSERT(mpipe(fds_stdin) != -1); + TUV_ASSERT(mpipe(fds_stdout) != -1); + + TUV_ASSERT(uv_pipe_open(&pipe_stdin_child, fds_stdin[0]) == 0); + TUV_ASSERT(uv_pipe_open(&pipe_stdout_child, fds_stdout[1]) == 0); + TUV_ASSERT(uv_pipe_open(&pipe_stdin_parent, fds_stdin[1]) == 0); + TUV_ASSERT(uv_pipe_open(&pipe_stdout_parent, fds_stdout[0]) == 0); + + child_stdio[0].flags = UV_INHERIT_STREAM; + child_stdio[0].data.stream = (uv_stream_t *)&pipe_stdin_child; + + child_stdio[1].flags = UV_INHERIT_STREAM; + child_stdio[1].data.stream = (uv_stream_t *)&pipe_stdout_child; + + options.stdio = child_stdio; + options.stdio_count = 2; + + TUV_ASSERT(uv_spawn(loop, &child_req, &options) == 0); + + uv_close((uv_handle_t*)&pipe_stdin_child, NULL); + uv_close((uv_handle_t*)&pipe_stdout_child, NULL); + + buf = uv_buf_init((char*)ubuf, sizeof ubuf); + for (i = 0; i < sizeof ubuf; ++i) + ubuf[i] = i & 255u; + memset(output, 0, sizeof ubuf); + + r = uv_write(&write_req, + (uv_stream_t*)&pipe_stdin_parent, + &buf, + 1, + write_cb); + TUV_ASSERT(r == 0); + + r = uv_read_start((uv_stream_t*)&pipe_stdout_parent, on_alloc, on_read); + TUV_ASSERT(r == 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + TUV_ASSERT(r == 0); + + TUV_ASSERT(exit_cb_called == 1); + TUV_ASSERT(close_cb_called == 3); + + r = memcmp(ubuf, output, sizeof ubuf); + TUV_ASSERT(r == 0); + + return 0; +} + +/* Helper for child process of spawn_inherit_streams */ +#ifndef _WIN32 +int spawn_stdin_stdout(void) { + char buf[1024]; + char* pbuf; + for (;;) { + ssize_t r, w, c; + do { + r = read(0, buf, sizeof buf); + } while (r == -1 && errno == EINTR); + if (r == 0) { + return 1; + } + TUV_ASSERT(r > 0); + c = r; + pbuf = buf; + while (c) { + do { + w = write(1, pbuf, (size_t)c); + } while (w == -1 && errno == EINTR); + TUV_ASSERT(w >= 0); + pbuf = pbuf + w; + c = c - w; + } + } + return 2; +} +#else +int spawn_stdin_stdout(void) { + char buf[1024]; + char* pbuf; + HANDLE h_stdin = GetStdHandle(STD_INPUT_HANDLE); + HANDLE h_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + TUV_ASSERT(h_stdin != INVALID_HANDLE_VALUE); + TUV_ASSERT(h_stdout != INVALID_HANDLE_VALUE); + for (;;) { + DWORD n_read; + DWORD n_written; + DWORD to_write; + if (!ReadFile(h_stdin, buf, sizeof buf, &n_read, NULL)) { + TUV_ASSERT(GetLastError() == ERROR_BROKEN_PIPE); + return 1; + } + to_write = n_read; + pbuf = buf; + while (to_write) { + TUV_ASSERT(WriteFile(h_stdout, pbuf, to_write, &n_written, NULL)); + to_write -= n_written; + pbuf += n_written; + } + } + return 2; +} +#endif /* !_WIN32 */