From c9a231db0e59658be419d926b1dfa17b939ba158 Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 17 Apr 2012 19:24:28 -0700 Subject: [PATCH 01/11] typo in node_http_parser --- src/node_http_parser.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 8d9000d9d70..bc3da82b200 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -191,7 +191,7 @@ struct StringPtr { void Update(const char* str, size_t size) { if (str_ == NULL) str_ = str; - else if (on_heap_ || str_ + size != str) { + else if (on_heap_ || str_ + size_ != str) { // Non-consecutive input, make a copy on the heap. // TODO Use slab allocation, O(n) allocs is bad. char* s = new char[size_ + size]; From 642945cc00cd6e9a4e75fed65089118c3e0e17dd Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 20 Apr 2012 02:57:14 -0400 Subject: [PATCH 02/11] docs: Remove duplicate socket.write() description --- doc/api/net.markdown | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 2b7e0418cb0..140effe0a81 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -282,11 +282,6 @@ buffer. Returns `false` if all or part of the data was queued in user memory. The optional `callback` parameter will be executed when the data is finally written out - this may not be immediately. -### socket.write(data, [encoding], [callback]) - -Write data with the optional encoding. The callback will be made when the -data is flushed to the kernel. - ### socket.end([data], [encoding]) Half-closes the socket. i.e., it sends a FIN packet. It is possible the From 27dfb1d4c0387b9b9100b344d60cb75738fe6b78 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 20 Apr 2012 07:46:36 -0700 Subject: [PATCH 03/11] doc: typo in child_process documentation --- doc/api/child_process.markdown | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index f58a481e681..0c8c433a819 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -313,10 +313,6 @@ leaner than `child_process.exec`. It has the same options. * `setsid` {Boolean} * `encoding` {String} (Default: 'utf8') * `timeout` {Number} (Default: 0) -* `callback` {Function} called with the output when process terminates - * `code` {Integer} Exit code - * `stdout` {Buffer} - * `stderr` {Buffer} * Return: ChildProcess object This is a special case of the `spawn()` functionality for spawning Node From a64acd8baadaa78910bb3cb429602f7f7619a486 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 23 Apr 2012 15:58:32 +0200 Subject: [PATCH 04/11] test: cluster: add worker death event test --- test/simple/test-cluster-worker-death.js | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 test/simple/test-cluster-worker-death.js diff --git a/test/simple/test-cluster-worker-death.js b/test/simple/test-cluster-worker-death.js new file mode 100644 index 00000000000..41dbfc58b34 --- /dev/null +++ b/test/simple/test-cluster-worker-death.js @@ -0,0 +1,45 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// 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. + +var common = require('../common'); +var assert = require('assert'); +var cluster = require('cluster'); + +if (!cluster.isMaster) { + process.exit(42); +} +else { + var seenExit = 0; + var seenDeath = 0; + var worker = cluster.fork(); + worker.on('exit', function(statusCode) { + assert.equal(statusCode, 42); + seenExit++; + }); + cluster.on('death', function(worker_) { + assert.equal(worker_, worker); + seenDeath++; + }); + process.on('exit', function() { + assert.equal(seenExit, 1); + assert.equal(seenDeath, 1); + }); +} From 76de7c0c26e9fa8d40e9e9e0fc5980acf6ed2d2b Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 27 Apr 2012 07:58:38 -0700 Subject: [PATCH 05/11] Add customary 'fork me on github' banner to website --- doc/index.html | 2 ++ doc/pipe.css | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/index.html b/doc/index.html index 98187f7c70e..9f61fd1fff9 100644 --- a/doc/index.html +++ b/doc/index.html @@ -32,6 +32,8 @@ Download Docs

__VERSION__

+ + Fork me on GitHub

Node.js in the Industry

diff --git a/doc/pipe.css b/doc/pipe.css index 5c1ce79b0e9..286c1a6fca8 100644 --- a/doc/pipe.css +++ b/doc/pipe.css @@ -99,10 +99,19 @@ h1 a, h2 a, h3 a, h4 a border-radius: 4px; margin: 0 1px; color: #46483e; + background-color: #9a9f8b; +} + +#intro .forkme { + position: absolute; + top: 0; + right: 0; + border: 0; } #intro .button:hover { text-decoration: none; + background-color: #aab293; } #intro #downloadbutton { @@ -113,14 +122,6 @@ h1 a, h2 a, h3 a, h4 a background-color: #73a53e; } -#intro #docsbutton { - background-color: #9a9f8b; -} - -#intro #docsbutton:hover { - background-color: #aab293; -} - #quotes { text-align: center; width: 100%; From e221cd4a53160c87a1a021916c9a865d53783be2 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Apr 2012 21:28:56 +0200 Subject: [PATCH 06/11] uv: upgrade to aea5db5da1 --- deps/uv/include/uv-private/uv-unix.h | 5 + deps/uv/include/uv-private/uv-win.h | 4 + deps/uv/include/uv.h | 28 +++++ deps/uv/src/unix/error.c | 4 + deps/uv/src/unix/linux.c | 1 + deps/uv/src/unix/pipe.c | 7 +- deps/uv/src/unix/process.c | 20 +++- deps/uv/src/unix/stream.c | 6 +- deps/uv/src/uv-common.c | 26 ++++ deps/uv/src/win/error.c | 2 + deps/uv/src/win/fs.c | 2 +- deps/uv/src/win/process.c | 15 ++- deps/uv/test/test-list.h | 10 +- deps/uv/test/test-spawn.c | 173 +++++++++++++++++++++++++++ 14 files changed, 287 insertions(+), 16 deletions(-) diff --git a/deps/uv/include/uv-private/uv-unix.h b/deps/uv/include/uv-private/uv-unix.h index 21078fe3637..1c33684aaba 100644 --- a/deps/uv/include/uv-private/uv-unix.h +++ b/deps/uv/include/uv-private/uv-unix.h @@ -33,6 +33,7 @@ #include #include #include +#include #include /* Note: May be cast to struct iovec. See writev(2). */ @@ -43,6 +44,10 @@ typedef struct { typedef int uv_file; +/* Platform-specific definitions for uv_spawn support. */ +typedef gid_t uv_gid_t; +typedef uid_t uv_uid_t; + /* Platform-specific definitions for uv_dlopen support. */ typedef void* uv_lib_t; #define UV_DYNAMIC /* empty */ diff --git a/deps/uv/include/uv-private/uv-win.h b/deps/uv/include/uv-private/uv-win.h index e620f8b0848..4d8ac9c689d 100644 --- a/deps/uv/include/uv-private/uv-win.h +++ b/deps/uv/include/uv-private/uv-win.h @@ -137,6 +137,10 @@ typedef struct uv_buf_t { typedef int uv_file; +/* Platform-specific definitions for uv_spawn support. */ +typedef unsigned char uv_uid_t; +typedef unsigned char uv_gid_t; + /* Platform-specific definitions for uv_dlopen support. */ typedef HMODULE uv_lib_t; #define UV_DYNAMIC FAR WINAPI diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index ed6100aebfd..c6f15e84d4b 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -1058,6 +1058,34 @@ struct uv_process_s { UV_EXTERN int uv_spawn(uv_loop_t*, uv_process_t*, uv_process_options_t options); + +/* Temporary fix for node. Do no use. */ +enum uv_process_flags { + UV_PROCESS_SETUID = (1 << 0), + UV_PROCESS_SETGID = (1 << 1), + UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = (1 << 2) +}; + +/* Temporary fix for node. Do not use. */ +typedef struct uv_process_options2_s { + uv_exit_cb exit_cb; /* Called after the process exits. */ + const char* file; /* Path to program to execute. */ + char** args; + char** env; + char* cwd; + unsigned int flags; + uv_pipe_t* stdin_stream; + uv_pipe_t* stdout_stream; + uv_pipe_t* stderr_stream; + uv_uid_t uid; + uv_gid_t gid; +} uv_process_options2_t; + +/* Temporary fix for node. Do not use. */ +UV_EXTERN int uv_spawn2(uv_loop_t*, uv_process_t*, + uv_process_options2_t options); + + /* * Kills the process with the specified signal. The user must still * call uv_close on the process. diff --git a/deps/uv/src/unix/error.c b/deps/uv/src/unix/error.c index d99d4112729..e01d06ac6df 100644 --- a/deps/uv/src/unix/error.c +++ b/deps/uv/src/unix/error.c @@ -68,6 +68,9 @@ uv_err_code uv_translate_sys_error(int sys_errno) { case EBADF: return UV_EBADF; case EPIPE: return UV_EPIPE; case EAGAIN: return UV_EAGAIN; +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: return UV_EAGAIN; +#endif case ECONNRESET: return UV_ECONNRESET; case EFAULT: return UV_EFAULT; case EMFILE: return UV_EMFILE; @@ -91,6 +94,7 @@ uv_err_code uv_translate_sys_error(int sys_errno) { case EBUSY: return UV_EBUSY; case ENOTEMPTY: return UV_ENOTEMPTY; case ENOSPC: return UV_ENOSPC; + case ENOMEM: return UV_ENOMEM; default: return UV_UNKNOWN; } diff --git a/deps/uv/src/unix/linux.c b/deps/uv/src/unix/linux.c index 809e644d0d0..a01d14b74d4 100644 --- a/deps/uv/src/unix/linux.c +++ b/deps/uv/src/unix/linux.c @@ -271,6 +271,7 @@ int uv_fs_event_init(uv_loop_t* loop, | IN_MODIFY | IN_DELETE | IN_DELETE_SELF + | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO; diff --git a/deps/uv/src/unix/pipe.c b/deps/uv/src/unix/pipe.c index f1be9e972bc..1efb664b081 100644 --- a/deps/uv/src/unix/pipe.c +++ b/deps/uv/src/unix/pipe.c @@ -254,16 +254,15 @@ void uv__pipe_accept(EV_P_ ev_io* watcher, int revents) { sockfd = uv__accept(pipe->fd, (struct sockaddr *)&saddr, sizeof saddr); if (sockfd == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - assert(0 && "EAGAIN on uv__accept(pipefd)"); - } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { uv__set_sys_error(pipe->loop, errno); + pipe->connection_cb((uv_stream_t*)pipe, -1); } } else { pipe->accepted_fd = sockfd; pipe->connection_cb((uv_stream_t*)pipe, 0); if (pipe->accepted_fd == sockfd) { - /* The user hasn't yet accepted called uv_accept() */ + /* The user hasn't called uv_accept() yet */ ev_io_stop(pipe->loop->ev, &pipe->read_watcher); } } diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c index 5581d8b8099..89c2c77c761 100644 --- a/deps/uv/src/unix/process.c +++ b/deps/uv/src/unix/process.c @@ -161,8 +161,8 @@ static int uv__process_init_pipe(uv_pipe_t* handle, int fds[2], int flags) { # define SPAWN_WAIT_EXEC 1 #endif -int uv_spawn(uv_loop_t* loop, uv_process_t* process, - uv_process_options_t options) { +int uv_spawn2(uv_loop_t* loop, uv_process_t* process, + uv_process_options2_t options) { /* * Save environ in the case that we get it clobbered * by the child process. @@ -179,6 +179,12 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process, pid_t pid; int flags; + assert(options.file != NULL); + assert(!(options.flags & ~(UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | + UV_PROCESS_SETGID | + UV_PROCESS_SETUID))); + + uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); loop->counters.process_init++; @@ -268,6 +274,16 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process, _exit(127); } + if ((options.flags & UV_PROCESS_SETGID) && setgid(options.gid)) { + perror("setgid()"); + _exit(127); + } + + if ((options.flags & UV_PROCESS_SETUID) && setuid(options.uid)) { + perror("setuid()"); + _exit(127); + } + environ = options.env; execvp(options.file, options.args); diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index eee4199fadb..6f0d1378f6c 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -176,7 +176,7 @@ void uv__server_io(EV_P_ ev_io* watcher, int revents) { fd = uv__accept(stream->fd, (struct sockaddr*)&addr, sizeof addr); if (fd < 0) { - if (errno == EAGAIN) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { /* No problem. */ return; } else if (errno == EMFILE) { @@ -416,7 +416,7 @@ start: } if (n < 0) { - if (errno != EAGAIN) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { /* Error */ req->error = errno; stream->write_queue_size -= uv__write_req_size(req); @@ -562,7 +562,7 @@ static void uv__read(uv_stream_t* stream) { if (nread < 0) { /* Error */ - if (errno == EAGAIN) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { /* Wait for the next one. */ if (stream->flags & UV_READING) { ev_io_start(ev, &stream->read_watcher); diff --git a/deps/uv/src/uv-common.c b/deps/uv/src/uv-common.c index 3143bd2d800..c5172f62723 100644 --- a/deps/uv/src/uv-common.c +++ b/deps/uv/src/uv-common.c @@ -261,3 +261,29 @@ int uv_tcp_connect6(uv_connect_t* req, return uv__tcp_connect6(req, handle, address, cb); } + + +/* Thunk that converts uv_process_options_t into uv_process_options2_t, */ +/* and then calls uv_spawn2. */ +int uv_spawn(uv_loop_t* loop, uv_process_t* process, + uv_process_options_t options) { + uv_process_options2_t options2; + + options2.exit_cb = options.exit_cb; + options2.file = options.file; + options2.args = options.args; + options2.cwd = options.cwd; + options2.env = options.env; + options2.stdin_stream = options.stdin_stream; + options2.stdout_stream = options.stdout_stream; + options2.stderr_stream = options.stderr_stream; + + options2.flags = 0; + if (options.windows_verbatim_arguments) { + options2.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; + } + + /* No need to set gid and uid. */ + + return uv_spawn2(loop, process, options2); +} diff --git a/deps/uv/src/win/error.c b/deps/uv/src/win/error.c index 7bdc3cda39f..e818fc5c4a5 100644 --- a/deps/uv/src/win/error.c +++ b/deps/uv/src/win/error.c @@ -68,6 +68,8 @@ uv_err_code uv_translate_sys_error(int sys_errno) { switch (sys_errno) { case ERROR_SUCCESS: return UV_OK; case ERROR_FILE_NOT_FOUND: return UV_ENOENT; + case ERROR_INVALID_NAME: return UV_ENOENT; + case ERROR_MOD_NOT_FOUND: return UV_ENOENT; case ERROR_PATH_NOT_FOUND: return UV_ENOENT; case ERROR_ACCESS_DENIED: return UV_EPERM; case ERROR_NOACCESS: return UV_EACCES; diff --git a/deps/uv/src/win/fs.c b/deps/uv/src/win/fs.c index daa23ed19d7..9e8a99515be 100644 --- a/deps/uv/src/win/fs.c +++ b/deps/uv/src/win/fs.c @@ -913,7 +913,7 @@ static DWORD WINAPI uv_fs_thread_proc(void* parameter) { fs__fsync(req, (uv_file)req->arg0); break; case UV_FS_FTRUNCATE: - fs__ftruncate(req, (uv_file)req->arg0, (off_t)req->stat.st_atime); + fs__ftruncate(req, (uv_file)req->arg0, req->stat.st_atime); break; case UV_FS_SENDFILE: fs__sendfile(req, diff --git a/deps/uv/src/win/process.c b/deps/uv/src/win/process.c index a23ba0b14fc..71620a00d4b 100644 --- a/deps/uv/src/win/process.c +++ b/deps/uv/src/win/process.c @@ -860,8 +860,8 @@ static int duplicate_std_handle(uv_loop_t* loop, DWORD id, HANDLE* dup) { } -int uv_spawn(uv_loop_t* loop, uv_process_t* process, - uv_process_options_t options) { +int uv_spawn2(uv_loop_t* loop, uv_process_t* process, + uv_process_options2_t options) { int err = 0, keep_child_stdio_open = 0; wchar_t* path = NULL; int size; @@ -872,17 +872,22 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process, STARTUPINFOW startup; PROCESS_INFORMATION info; - if (!options.file) { - uv__set_artificial_error(loop, UV_EINVAL); + if (options.flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) { + uv__set_sys_error(loop, UV_ENOTSUP); return -1; } + assert(options.file != NULL); + assert(!(options.flags & ~(UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | + UV_PROCESS_SETGID | + UV_PROCESS_SETUID))); + uv_process_init(loop, process); process->exit_cb = options.exit_cb; UTF8_TO_UTF16(options.file, application); arguments = options.args ? make_program_args(options.args, - options.windows_verbatim_arguments) : NULL; + options.flags & UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS) : NULL; env = options.env ? make_program_env(options.env) : NULL; if (options.cwd) { diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h index 5aad6331e4c..8c6e9f1124e 100644 --- a/deps/uv/test/test-list.h +++ b/deps/uv/test/test-list.h @@ -110,6 +110,8 @@ TEST_DECLARE (spawn_stdout) TEST_DECLARE (spawn_stdin) TEST_DECLARE (spawn_and_kill) TEST_DECLARE (spawn_and_ping) +TEST_DECLARE (spawn_setuid_fails) +TEST_DECLARE (spawn_setgid_fails) TEST_DECLARE (kill) TEST_DECLARE (fs_file_noent) TEST_DECLARE (fs_file_nametoolong) @@ -133,7 +135,7 @@ TEST_DECLARE (fs_event_watch_file_current_dir) TEST_DECLARE (fs_event_no_callback_on_close) TEST_DECLARE (fs_event_immediate_close) TEST_DECLARE (fs_event_close_with_pending_event) -TEST_DECLARE (fs_event_close_in_callback); +TEST_DECLARE (fs_event_close_in_callback) TEST_DECLARE (fs_readdir_empty_dir) TEST_DECLARE (fs_readdir_file) TEST_DECLARE (fs_open_dir) @@ -147,6 +149,8 @@ TEST_DECLARE (environment_creation) TEST_DECLARE (listen_with_simultaneous_accepts) TEST_DECLARE (listen_no_simultaneous_accepts) TEST_DECLARE (fs_stat_root) +#else +TEST_DECLARE (spawn_setuid_setgid) #endif HELPER_DECLARE (tcp4_echo_server) HELPER_DECLARE (tcp6_echo_server) @@ -289,6 +293,8 @@ TASK_LIST_START TEST_ENTRY (spawn_stdin) TEST_ENTRY (spawn_and_kill) TEST_ENTRY (spawn_and_ping) + TEST_ENTRY (spawn_setuid_fails) + TEST_ENTRY (spawn_setgid_fails) TEST_ENTRY (kill) #ifdef _WIN32 TEST_ENTRY (spawn_detect_pipe_name_collisions_on_windows) @@ -297,6 +303,8 @@ TASK_LIST_START TEST_ENTRY (listen_with_simultaneous_accepts) TEST_ENTRY (listen_no_simultaneous_accepts) TEST_ENTRY (fs_stat_root) +#else + TEST_ENTRY (spawn_setuid_setgid) #endif TEST_ENTRY (fs_file_noent) diff --git a/deps/uv/test/test-spawn.c b/deps/uv/test/test-spawn.c index 68720114b77..69060517465 100644 --- a/deps/uv/test/test-spawn.c +++ b/deps/uv/test/test-spawn.c @@ -30,6 +30,7 @@ static int exit_cb_called; static uv_process_t process; static uv_timer_t timer; static uv_process_options_t options; +static uv_process_options2_t options2; static char exepath[1024]; static size_t exepath_size = 1024; static char* args[3]; @@ -55,6 +56,22 @@ static void exit_cb(uv_process_t* process, int exit_status, int term_signal) { } +static void exit_cb_failure_expected(uv_process_t* process, int exit_status, + int term_signal) { + printf("exit_cb\n"); + exit_cb_called++; + ASSERT(exit_status == 127); + ASSERT(term_signal == 0); + uv_close((uv_handle_t*)process, close_cb); +} + + +static void exit_cb_unexpected(uv_process_t* process, int exit_status, + int term_signal) { + ASSERT(0 && "should not have been called"); +} + + static void kill_cb(uv_process_t* process, int exit_status, int term_signal) { uv_err_t err; @@ -116,6 +133,22 @@ static void init_process_options(char* test, uv_exit_cb exit_cb) { options.file = exepath; options.args = args; options.exit_cb = exit_cb; + options.windows_verbatim_arguments = 0; +} + + +static void init_process_options2(char* test, uv_exit_cb exit_cb) { + /* Note spawn_helper1 defined in test/run-tests.c */ + int r = uv_exepath(exepath, &exepath_size); + ASSERT(r == 0); + exepath[exepath_size] = '\0'; + args[0] = exepath; + args[1] = test; + args[2] = NULL; + options2.file = exepath; + options2.args = args; + options2.exit_cb = exit_cb; + options2.flags = 0; } @@ -466,3 +499,143 @@ TEST_IMPL(environment_creation) { return 0; } #endif + +#ifndef _WIN32 +TEST_IMPL(spawn_setuid_setgid) { + int r; + + /* if not root, then this will fail. */ + uv_uid_t uid = getuid(); + if (uid != 0) { + fprintf(stderr, "spawn_setuid_setgid skipped: not root\n"); + return 0; + } + + init_process_options2("spawn_helper1", exit_cb); + + // become the "nobody" user. + struct passwd* pw; + pw = getpwnam("nobody"); + ASSERT(pw != NULL); + options2.uid = pw->pw_uid; + options2.gid = pw->pw_gid; + options2.flags = UV_PROCESS_SETUID | UV_PROCESS_SETGID; + + r = uv_spawn2(uv_default_loop(), &process, options2); + ASSERT(r == 0); + + r = uv_run(uv_default_loop()); + ASSERT(r == 0); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 1); + + 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"); + ASSERT(pw != NULL); + r = setuid(pw->pw_uid); + ASSERT(r == 0); + } + + init_process_options2("spawn_helper1", exit_cb_failure_expected); + + options2.flags |= UV_PROCESS_SETUID; + options2.uid = (uv_uid_t) -42424242; + + r = uv_spawn2(uv_default_loop(), &process, options2); + ASSERT(r == 0); + + r = uv_run(uv_default_loop()); + ASSERT(r == 0); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 1); + + 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"); + ASSERT(pw != NULL); + r = setuid(pw->pw_uid); + ASSERT(r == 0); + } + + init_process_options2("spawn_helper1", exit_cb_failure_expected); + + options2.flags |= UV_PROCESS_SETGID; + options2.gid = (uv_gid_t) -42424242; + + r = uv_spawn2(uv_default_loop(), &process, options2); + ASSERT(r == 0); + + r = uv_run(uv_default_loop()); + ASSERT(r == 0); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 1); + + return 0; +} +#endif + + +#ifdef _WIN32 +TEST_IMPL(spawn_setuid_fails) { + int r; + + init_process_options2("spawn_helper1", exit_cb_unexpected); + + options2.flags |= UV_PROCESS_SETUID; + options2.uid = (uv_uid_t) -42424242; + + r = uv_spawn2(uv_default_loop(), &process, options2); + ASSERT(r == -1); + + r = uv_run(uv_default_loop()); + ASSERT(r == 0); + + ASSERT(close_cb_called == 0); + + return 0; +} + + +TEST_IMPL(spawn_setgid_fails) { + int r; + + init_process_options2("spawn_helper1", exit_cb_unexpected); + + options2.flags |= UV_PROCESS_SETGID; + options2.gid = (uv_gid_t) -42424242; + + r = uv_spawn2(uv_default_loop(), &process, options2); + ASSERT(r == -1); + + r = uv_run(uv_default_loop()); + ASSERT(r == 0); + + ASSERT(close_cb_called == 0); + + return 0; +} +#endif From 0b75eee364230a6e08f4e7ada0735af1d1a25bc4 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Apr 2012 22:00:44 +0200 Subject: [PATCH 07/11] uv: upgrade to d41cc9118d --- deps/uv/src/win/process.c | 2 +- deps/uv/test/test-spawn.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deps/uv/src/win/process.c b/deps/uv/src/win/process.c index 71620a00d4b..b89ab812498 100644 --- a/deps/uv/src/win/process.c +++ b/deps/uv/src/win/process.c @@ -873,7 +873,7 @@ int uv_spawn2(uv_loop_t* loop, uv_process_t* process, PROCESS_INFORMATION info; if (options.flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) { - uv__set_sys_error(loop, UV_ENOTSUP); + uv__set_artificial_error(loop, UV_ENOTSUP); return -1; } diff --git a/deps/uv/test/test-spawn.c b/deps/uv/test/test-spawn.c index 69060517465..c79602adcca 100644 --- a/deps/uv/test/test-spawn.c +++ b/deps/uv/test/test-spawn.c @@ -610,6 +610,7 @@ TEST_IMPL(spawn_setuid_fails) { r = uv_spawn2(uv_default_loop(), &process, options2); ASSERT(r == -1); + ASSERT(uv_last_error(uv_default_loop()).code == UV_ENOTSUP); r = uv_run(uv_default_loop()); ASSERT(r == 0); @@ -630,6 +631,7 @@ TEST_IMPL(spawn_setgid_fails) { r = uv_spawn2(uv_default_loop(), &process, options2); ASSERT(r == -1); + ASSERT(uv_last_error(uv_default_loop()).code == UV_ENOTSUP); r = uv_run(uv_default_loop()); ASSERT(r == 0); From 51e66ec4102c64704553c9b646f11454496593ee Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Apr 2012 22:06:12 +0200 Subject: [PATCH 08/11] Windows: turn off /Gm Otherwise multicode compile doesn't work. --- common.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index 70606083292..daa64d332aa 100644 --- a/common.gypi +++ b/common.gypi @@ -27,7 +27,7 @@ }], ], 'Optimization': 0, # /Od, no optimization - 'MinimalRebuild': 'true', + 'MinimalRebuild': 'false', 'OmitFramePointers': 'false', 'BasicRuntimeChecks': 3, # /RTC1 }, From 55e4d54927536e6f796de87c0ab266a74b9e1c81 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Fri, 27 Apr 2012 22:13:00 +0200 Subject: [PATCH 09/11] Child process: support the `gid` and `uid` options --- lib/child_process.js | 4 ++- src/process_wrap.cc | 63 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/lib/child_process.js b/lib/child_process.js index d73f65f2cd5..f3a5bdd7dc6 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -346,7 +346,9 @@ var spawn = exports.spawn = function(file, args, options) { windowsVerbatimArguments: !!(options && options.windowsVerbatimArguments), envPairs: envPairs, customFds: options ? options.customFds : null, - stdinStream: options ? options.stdinStream : null + stdinStream: options ? options.stdinStream : null, + uid: options ? options.uid : null, + gid: options ? options.gid : null }); return child; diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 6600a044c86..f9651c4bea0 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -53,6 +53,8 @@ using v8::TryCatch; using v8::Context; using v8::Arguments; using v8::Integer; +using v8::Exception; +using v8::ThrowException; class ProcessWrap : public HandleWrap { @@ -98,7 +100,7 @@ class ProcessWrap : public HandleWrap { Local js_options = args[0]->ToObject(); - uv_process_options_t options; + uv_process_options2_t options; memset(&options, 0, sizeof(uv_process_options_t)); options.exit_cb = OnExit; @@ -106,14 +108,16 @@ class ProcessWrap : public HandleWrap { // TODO is this possible to do without mallocing ? // options.file - Local file_v = js_options->Get(String::New("file")); + Local file_v = js_options->Get(String::NewSymbol("file")); if (!file_v.IsEmpty() && file_v->IsString()) { String::Utf8Value file(file_v->ToString()); options.file = strdup(*file); + } else { + return ThrowException(Exception::TypeError(String::New("Bad argument"))); } // options.args - Local argv_v = js_options->Get(String::New("args")); + Local argv_v = js_options->Get(String::NewSymbol("args")); if (!argv_v.IsEmpty() && argv_v->IsArray()) { Local js_argv = Local::Cast(argv_v); int argc = js_argv->Length(); @@ -127,7 +131,7 @@ class ProcessWrap : public HandleWrap { } // options.cwd - Local cwd_v = js_options->Get(String::New("cwd")); + Local cwd_v = js_options->Get(String::NewSymbol("cwd")); if (!cwd_v.IsEmpty() && cwd_v->IsString()) { String::Utf8Value cwd(cwd_v->ToString()); if (cwd.length() > 0) { @@ -136,7 +140,7 @@ class ProcessWrap : public HandleWrap { } // options.env - Local env_v = js_options->Get(String::New("envPairs")); + Local env_v = js_options->Get(String::NewSymbol("envPairs")); if (!env_v.IsEmpty() && env_v->IsArray()) { Local env = Local::Cast(env_v); int envc = env->Length(); @@ -149,33 +153,66 @@ class ProcessWrap : public HandleWrap { } // options.stdin_stream - Local stdin_stream_v = js_options->Get(String::New("stdinStream")); + Local stdin_stream_v = js_options->Get( + String::NewSymbol("stdinStream")); if (!stdin_stream_v.IsEmpty() && stdin_stream_v->IsObject()) { PipeWrap* stdin_wrap = PipeWrap::Unwrap(stdin_stream_v->ToObject()); options.stdin_stream = stdin_wrap->UVHandle(); } // options.stdout_stream - Local stdout_stream_v = js_options->Get(String::New("stdoutStream")); + Local stdout_stream_v = js_options->Get( + String::NewSymbol("stdoutStream")); if (!stdout_stream_v.IsEmpty() && stdout_stream_v->IsObject()) { PipeWrap* stdout_wrap = PipeWrap::Unwrap(stdout_stream_v->ToObject()); options.stdout_stream = stdout_wrap->UVHandle(); } // options.stderr_stream - Local stderr_stream_v = js_options->Get(String::New("stderrStream")); + Local stderr_stream_v = js_options->Get( + String::NewSymbol("stderrStream")); if (!stderr_stream_v.IsEmpty() && stderr_stream_v->IsObject()) { PipeWrap* stderr_wrap = PipeWrap::Unwrap(stderr_stream_v->ToObject()); options.stderr_stream = stderr_wrap->UVHandle(); } // options.windows_verbatim_arguments -#if defined(_WIN32) - options.windows_verbatim_arguments = js_options-> - Get(String::NewSymbol("windowsVerbatimArguments"))->IsTrue(); -#endif + if (js_options->Get(String::NewSymbol("windowsVerbatimArguments"))-> + IsTrue()) { + options.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; + } - int r = uv_spawn(uv_default_loop(), &wrap->process_, options); + // options.uid + Local uid_v = js_options->Get(String::NewSymbol("uid")); + if (uid_v->IsInt32()) { + int32_t uid = uid_v->Int32Value(); + if (uid & ~((uv_uid_t) ~0)) { + return ThrowException(Exception::RangeError( + String::New("options.uid is out of range"))); + } + options.flags |= UV_PROCESS_SETUID; + options.uid = (uv_uid_t) uid; + } else if (!uid_v->IsUndefined() && !uid_v->IsNull()) { + return ThrowException(Exception::TypeError( + String::New("options.uid should be a number"))); + } + + // options.gid + Local gid_v = js_options->Get(String::NewSymbol("gid")); + if (gid_v->IsInt32()) { + int32_t gid = gid_v->Int32Value(); + if (gid & ~((uv_gid_t) ~0)) { + return ThrowException(Exception::RangeError( + String::New("options.gid is out of range"))); + } + options.flags |= UV_PROCESS_SETGID; + options.gid = (uv_gid_t) gid; + } else if (!gid_v->IsUndefined() && !gid_v->IsNull()) { + return ThrowException(Exception::TypeError( + String::New("options.gid should be a number"))); + } + + int r = uv_spawn2(uv_default_loop(), &wrap->process_, options); if (r) { SetErrno(uv_last_error(uv_default_loop())); From 3546383cf0c9738019ecee88858b9f78c5202428 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Sat, 28 Apr 2012 23:36:47 +0200 Subject: [PATCH 10/11] process_wrap: avoid leaking memory when throwing due to invalid arguments --- src/process_wrap.cc | 60 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/process_wrap.cc b/src/process_wrap.cc index f9651c4bea0..39ebb054fc6 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -105,6 +105,36 @@ class ProcessWrap : public HandleWrap { options.exit_cb = OnExit; + // options.uid + Local uid_v = js_options->Get(String::NewSymbol("uid")); + if (uid_v->IsInt32()) { + int32_t uid = uid_v->Int32Value(); + if (uid & ~((uv_uid_t) ~0)) { + return ThrowException(Exception::RangeError( + String::New("options.uid is out of range"))); + } + options.flags |= UV_PROCESS_SETUID; + options.uid = (uv_uid_t) uid; + } else if (!uid_v->IsUndefined() && !uid_v->IsNull()) { + return ThrowException(Exception::TypeError( + String::New("options.uid should be a number"))); + } + + // options.gid + Local gid_v = js_options->Get(String::NewSymbol("gid")); + if (gid_v->IsInt32()) { + int32_t gid = gid_v->Int32Value(); + if (gid & ~((uv_gid_t) ~0)) { + return ThrowException(Exception::RangeError( + String::New("options.gid is out of range"))); + } + options.flags |= UV_PROCESS_SETGID; + options.gid = (uv_gid_t) gid; + } else if (!gid_v->IsUndefined() && !gid_v->IsNull()) { + return ThrowException(Exception::TypeError( + String::New("options.gid should be a number"))); + } + // TODO is this possible to do without mallocing ? // options.file @@ -182,36 +212,6 @@ class ProcessWrap : public HandleWrap { options.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; } - // options.uid - Local uid_v = js_options->Get(String::NewSymbol("uid")); - if (uid_v->IsInt32()) { - int32_t uid = uid_v->Int32Value(); - if (uid & ~((uv_uid_t) ~0)) { - return ThrowException(Exception::RangeError( - String::New("options.uid is out of range"))); - } - options.flags |= UV_PROCESS_SETUID; - options.uid = (uv_uid_t) uid; - } else if (!uid_v->IsUndefined() && !uid_v->IsNull()) { - return ThrowException(Exception::TypeError( - String::New("options.uid should be a number"))); - } - - // options.gid - Local gid_v = js_options->Get(String::NewSymbol("gid")); - if (gid_v->IsInt32()) { - int32_t gid = gid_v->Int32Value(); - if (gid & ~((uv_gid_t) ~0)) { - return ThrowException(Exception::RangeError( - String::New("options.gid is out of range"))); - } - options.flags |= UV_PROCESS_SETGID; - options.gid = (uv_gid_t) gid; - } else if (!gid_v->IsUndefined() && !gid_v->IsNull()) { - return ThrowException(Exception::TypeError( - String::New("options.gid should be a number"))); - } - int r = uv_spawn2(uv_default_loop(), &wrap->process_, options); if (r) { From db844b152a92bcc02c20e62ea85958c0f3dfc532 Mon Sep 17 00:00:00 2001 From: ssuda Date: Tue, 20 Mar 2012 21:09:49 +0530 Subject: [PATCH 11/11] process: don't use strdup() file and cwd can be directly used from Utf8Value. Conflicts: src/process_wrap.cc --- src/process_wrap.cc | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 39ebb054fc6..4a434377c6e 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -139,9 +139,9 @@ class ProcessWrap : public HandleWrap { // options.file Local file_v = js_options->Get(String::NewSymbol("file")); - if (!file_v.IsEmpty() && file_v->IsString()) { - String::Utf8Value file(file_v->ToString()); - options.file = strdup(*file); + String::Utf8Value file(file_v->IsString() ? file_v : Local()); + if (file.length() > 0) { + options.file = *file; } else { return ThrowException(Exception::TypeError(String::New("Bad argument"))); } @@ -162,12 +162,10 @@ class ProcessWrap : public HandleWrap { // options.cwd Local cwd_v = js_options->Get(String::NewSymbol("cwd")); - if (!cwd_v.IsEmpty() && cwd_v->IsString()) { - String::Utf8Value cwd(cwd_v->ToString()); + String::Utf8Value cwd(cwd_v->IsString() ? cwd_v : Local()); if (cwd.length() > 0) { - options.cwd = strdup(*cwd); + options.cwd = *cwd; } - } // options.env Local env_v = js_options->Get(String::NewSymbol("envPairs")); @@ -228,9 +226,6 @@ class ProcessWrap : public HandleWrap { delete [] options.args; } - free(options.cwd); - free((void*)options.file); - if (options.env) { for (int i = 0; options.env[i]; i++) free(options.env[i]); delete [] options.env;