/src/node/src/process_wrap.cc
Line | Count | Source |
1 | | // Copyright Joyent, Inc. and other Node contributors. |
2 | | // |
3 | | // Permission is hereby granted, free of charge, to any person obtaining a |
4 | | // copy of this software and associated documentation files (the |
5 | | // "Software"), to deal in the Software without restriction, including |
6 | | // without limitation the rights to use, copy, modify, merge, publish, |
7 | | // distribute, sublicense, and/or sell copies of the Software, and to permit |
8 | | // persons to whom the Software is furnished to do so, subject to the |
9 | | // following conditions: |
10 | | // |
11 | | // The above copyright notice and this permission notice shall be included |
12 | | // in all copies or substantial portions of the Software. |
13 | | // |
14 | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
15 | | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
16 | | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
17 | | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
18 | | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
19 | | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
20 | | // USE OR OTHER DEALINGS IN THE SOFTWARE. |
21 | | |
22 | | #include "env-inl.h" |
23 | | #include "node_errors.h" |
24 | | #include "node_external_reference.h" |
25 | | #include "permission/permission.h" |
26 | | #include "stream_base-inl.h" |
27 | | #include "stream_wrap.h" |
28 | | #include "util-inl.h" |
29 | | |
30 | | #include <climits> |
31 | | #include <cstdlib> |
32 | | #include <cstring> |
33 | | |
34 | | namespace node { |
35 | | |
36 | | using v8::Array; |
37 | | using v8::Context; |
38 | | using v8::FunctionCallbackInfo; |
39 | | using v8::FunctionTemplate; |
40 | | using v8::HandleScope; |
41 | | using v8::Int32; |
42 | | using v8::Integer; |
43 | | using v8::Isolate; |
44 | | using v8::Just; |
45 | | using v8::JustVoid; |
46 | | using v8::Local; |
47 | | using v8::Maybe; |
48 | | using v8::Nothing; |
49 | | using v8::Number; |
50 | | using v8::Object; |
51 | | using v8::String; |
52 | | using v8::Value; |
53 | | |
54 | | namespace { |
55 | | |
56 | | class ProcessWrap : public HandleWrap { |
57 | | public: |
58 | | static void Initialize(Local<Object> target, |
59 | | Local<Value> unused, |
60 | | Local<Context> context, |
61 | 0 | void* priv) { |
62 | 0 | Environment* env = Environment::GetCurrent(context); |
63 | 0 | Isolate* isolate = env->isolate(); |
64 | 0 | Local<FunctionTemplate> constructor = NewFunctionTemplate(isolate, New); |
65 | 0 | constructor->InstanceTemplate()->SetInternalFieldCount( |
66 | 0 | ProcessWrap::kInternalFieldCount); |
67 | |
|
68 | 0 | constructor->Inherit(HandleWrap::GetConstructorTemplate(env)); |
69 | |
|
70 | 0 | SetProtoMethod(isolate, constructor, "spawn", Spawn); |
71 | 0 | SetProtoMethod(isolate, constructor, "kill", Kill); |
72 | |
|
73 | 0 | SetConstructorFunction(context, target, "Process", constructor); |
74 | 0 | } |
75 | | |
76 | 0 | static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
77 | 0 | registry->Register(New); |
78 | 0 | registry->Register(Spawn); |
79 | 0 | registry->Register(Kill); |
80 | 0 | } |
81 | | |
82 | | SET_NO_MEMORY_INFO() |
83 | | SET_MEMORY_INFO_NAME(ProcessWrap) |
84 | | SET_SELF_SIZE(ProcessWrap) |
85 | | |
86 | | private: |
87 | 0 | static void New(const FunctionCallbackInfo<Value>& args) { |
88 | | // This constructor should not be exposed to public javascript. |
89 | | // Therefore we assert that we are not trying to call this as a |
90 | | // normal function. |
91 | 0 | CHECK(args.IsConstructCall()); |
92 | 0 | Environment* env = Environment::GetCurrent(args); |
93 | 0 | new ProcessWrap(env, args.This()); |
94 | 0 | } |
95 | | |
96 | | ProcessWrap(Environment* env, Local<Object> object) |
97 | 0 | : HandleWrap(env, |
98 | 0 | object, |
99 | 0 | reinterpret_cast<uv_handle_t*>(&process_), |
100 | 0 | AsyncWrap::PROVIDER_PROCESSWRAP) { |
101 | 0 | MarkAsUninitialized(); |
102 | 0 | } |
103 | | |
104 | | static Maybe<uv_stream_t*> StreamForWrap(Environment* env, |
105 | 0 | Local<Object> stdio) { |
106 | 0 | Local<String> handle_key = env->handle_string(); |
107 | | // This property has always been set by JS land if we are in this code path. |
108 | 0 | Local<Value> val; |
109 | 0 | if (!stdio->Get(env->context(), handle_key).ToLocal(&val)) { |
110 | 0 | return Nothing<uv_stream_t*>(); |
111 | 0 | } |
112 | 0 | Local<Object> handle = val.As<Object>(); |
113 | |
|
114 | 0 | uv_stream_t* stream = LibuvStreamWrap::From(env, handle)->stream(); |
115 | 0 | CHECK_NOT_NULL(stream); |
116 | 0 | return Just(stream); |
117 | 0 | } |
118 | | |
119 | | static Maybe<void> ParseStdioOptions( |
120 | | Environment* env, |
121 | | Local<Object> js_options, |
122 | 0 | std::vector<uv_stdio_container_t>* options_stdio) { |
123 | 0 | Local<Context> context = env->context(); |
124 | 0 | Local<String> stdio_key = env->stdio_string(); |
125 | 0 | Local<Value> stdios_val; |
126 | 0 | if (!js_options->Get(context, stdio_key).ToLocal(&stdios_val)) { |
127 | 0 | return Nothing<void>(); |
128 | 0 | } |
129 | 0 | if (!stdios_val->IsArray()) { |
130 | 0 | THROW_ERR_INVALID_ARG_TYPE(env, "options.stdio must be an array"); |
131 | 0 | return Nothing<void>(); |
132 | 0 | } |
133 | 0 | Local<Array> stdios = stdios_val.As<Array>(); |
134 | |
|
135 | 0 | uint32_t len = stdios->Length(); |
136 | 0 | options_stdio->resize(len); |
137 | |
|
138 | 0 | for (uint32_t i = 0; i < len; i++) { |
139 | 0 | Local<Value> val; |
140 | 0 | if (!stdios->Get(context, i).ToLocal(&val)) { |
141 | 0 | return Nothing<void>(); |
142 | 0 | } |
143 | 0 | Local<Object> stdio = val.As<Object>(); |
144 | 0 | Local<Value> type; |
145 | 0 | if (!stdio->Get(context, env->type_string()).ToLocal(&type)) { |
146 | 0 | return Nothing<void>(); |
147 | 0 | } |
148 | | |
149 | 0 | if (type->StrictEquals(env->ignore_string())) { |
150 | 0 | (*options_stdio)[i].flags = UV_IGNORE; |
151 | 0 | } else if (type->StrictEquals(env->pipe_string())) { |
152 | 0 | (*options_stdio)[i].flags = static_cast<uv_stdio_flags>( |
153 | 0 | UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE); |
154 | 0 | if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) { |
155 | 0 | return Nothing<void>(); |
156 | 0 | } |
157 | 0 | } else if (type->StrictEquals(env->overlapped_string())) { |
158 | 0 | (*options_stdio)[i].flags = |
159 | 0 | static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_READABLE_PIPE | |
160 | 0 | UV_WRITABLE_PIPE | UV_OVERLAPPED_PIPE); |
161 | 0 | if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) { |
162 | 0 | return Nothing<void>(); |
163 | 0 | } |
164 | 0 | } else if (type->StrictEquals(env->wrap_string())) { |
165 | 0 | (*options_stdio)[i].flags = UV_INHERIT_STREAM; |
166 | 0 | if (!StreamForWrap(env, stdio).To(&(*options_stdio)[i].data.stream)) { |
167 | 0 | return Nothing<void>(); |
168 | 0 | } |
169 | 0 | } else { |
170 | 0 | Local<String> fd_key = env->fd_string(); |
171 | 0 | Local<Value> fd_value; |
172 | 0 | if (!stdio->Get(context, fd_key).ToLocal(&fd_value)) { |
173 | 0 | return Nothing<void>(); |
174 | 0 | } |
175 | 0 | CHECK(fd_value->IsNumber()); |
176 | 0 | int fd = FromV8Value<int>(fd_value); |
177 | 0 | (*options_stdio)[i].flags = UV_INHERIT_FD; |
178 | 0 | (*options_stdio)[i].data.fd = fd; |
179 | 0 | } |
180 | 0 | } |
181 | 0 | return JustVoid(); |
182 | 0 | } |
183 | | |
184 | 0 | static void Spawn(const FunctionCallbackInfo<Value>& args) { |
185 | 0 | Environment* env = Environment::GetCurrent(args); |
186 | 0 | Local<Context> context = env->context(); |
187 | 0 | ProcessWrap* wrap; |
188 | 0 | ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); |
189 | 0 | int err = 0; |
190 | |
|
191 | 0 | if (!args[0]->IsObject()) { |
192 | 0 | return THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object"); |
193 | 0 | } |
194 | | |
195 | 0 | Local<Object> js_options = args[0].As<Object>(); |
196 | |
|
197 | 0 | uv_process_options_t options; |
198 | 0 | memset(&options, 0, sizeof(uv_process_options_t)); |
199 | |
|
200 | 0 | options.exit_cb = OnExit; |
201 | | |
202 | | // options.file |
203 | 0 | Local<Value> file_v; |
204 | 0 | if (!js_options->Get(context, env->file_string()).ToLocal(&file_v)) { |
205 | 0 | return; |
206 | 0 | } |
207 | 0 | CHECK(file_v->IsString()); |
208 | 0 | node::Utf8Value file(env->isolate(), file_v); |
209 | 0 | options.file = *file; |
210 | |
|
211 | 0 | THROW_IF_INSUFFICIENT_PERMISSIONS( |
212 | 0 | env, permission::PermissionScope::kChildProcess, file.ToStringView()); |
213 | | |
214 | | // options.uid |
215 | 0 | Local<Value> uid_v; |
216 | 0 | if (!js_options->Get(context, env->uid_string()).ToLocal(&uid_v)) { |
217 | 0 | return; |
218 | 0 | } |
219 | 0 | if (!uid_v->IsUndefined() && !uid_v->IsNull()) { |
220 | 0 | CHECK(uid_v->IsInt32()); |
221 | 0 | const int32_t uid = uid_v.As<Int32>()->Value(); |
222 | 0 | options.flags |= UV_PROCESS_SETUID; |
223 | 0 | options.uid = static_cast<uv_uid_t>(uid); |
224 | 0 | } |
225 | | |
226 | | // options.gid |
227 | 0 | Local<Value> gid_v; |
228 | 0 | if (!js_options->Get(context, env->gid_string()).ToLocal(&gid_v)) { |
229 | 0 | return; |
230 | 0 | } |
231 | 0 | if (!gid_v->IsUndefined() && !gid_v->IsNull()) { |
232 | 0 | CHECK(gid_v->IsInt32()); |
233 | 0 | const int32_t gid = gid_v.As<Int32>()->Value(); |
234 | 0 | options.flags |= UV_PROCESS_SETGID; |
235 | 0 | options.gid = static_cast<uv_gid_t>(gid); |
236 | 0 | } |
237 | | |
238 | | // Undocumented feature of Win32 CreateProcess API allows spawning |
239 | | // batch files directly but is potentially insecure because arguments |
240 | | // are not escaped (and sometimes cannot be unambiguously escaped), |
241 | | // hence why they are rejected here. |
242 | | #ifdef _WIN32 |
243 | | if (IsWindowsBatchFile(options.file)) |
244 | | err = UV_EINVAL; |
245 | | #endif |
246 | | |
247 | | // options.args |
248 | 0 | Local<Value> argv_v; |
249 | 0 | if (!js_options->Get(context, env->args_string()).ToLocal(&argv_v)) { |
250 | 0 | return; |
251 | 0 | } |
252 | 0 | std::vector<char*> options_args; |
253 | 0 | std::vector<std::string> args_vals; |
254 | 0 | if (argv_v->IsArray()) { |
255 | 0 | Local<Array> js_argv = argv_v.As<Array>(); |
256 | 0 | int argc = js_argv->Length(); |
257 | 0 | CHECK_LT(argc, INT_MAX); // Check for overflow. |
258 | 0 | args_vals.reserve(argc); |
259 | 0 | for (int i = 0; i < argc; i++) { |
260 | 0 | Local<Value> val; |
261 | 0 | if (!js_argv->Get(context, i).ToLocal(&val)) { |
262 | 0 | return; |
263 | 0 | } |
264 | 0 | node::Utf8Value arg(env->isolate(), val); |
265 | 0 | args_vals.emplace_back(arg.ToString()); |
266 | 0 | } |
267 | 0 | options_args.resize(args_vals.size() + 1); |
268 | 0 | for (size_t i = 0; i < args_vals.size(); i++) { |
269 | 0 | options_args[i] = const_cast<char*>(args_vals[i].c_str()); |
270 | 0 | CHECK_NOT_NULL(options_args[i]); |
271 | 0 | } |
272 | 0 | options_args.back() = nullptr; |
273 | 0 | options.args = options_args.data(); |
274 | 0 | } |
275 | | |
276 | | // options.cwd |
277 | 0 | Local<Value> cwd_v; |
278 | 0 | if (!js_options->Get(context, env->cwd_string()).ToLocal(&cwd_v)) { |
279 | 0 | return; |
280 | 0 | } |
281 | 0 | node::Utf8Value cwd(env->isolate(), |
282 | 0 | cwd_v->IsString() ? cwd_v : Local<Value>()); |
283 | 0 | if (cwd.length() > 0) { |
284 | 0 | options.cwd = *cwd; |
285 | 0 | } |
286 | | |
287 | | // options.env |
288 | 0 | Local<Value> env_v; |
289 | 0 | if (!js_options->Get(context, env->env_pairs_string()).ToLocal(&env_v)) { |
290 | 0 | return; |
291 | 0 | } |
292 | 0 | std::vector<char*> options_env; |
293 | 0 | std::vector<std::string> env_vals; |
294 | 0 | if (env_v->IsArray()) { |
295 | 0 | Local<Array> env_opt = env_v.As<Array>(); |
296 | 0 | int envc = env_opt->Length(); |
297 | 0 | CHECK_LT(envc, INT_MAX); // Check for overflow. |
298 | 0 | env_vals.reserve(envc); |
299 | 0 | for (int i = 0; i < envc; i++) { |
300 | 0 | Local<Value> val; |
301 | 0 | if (!env_opt->Get(context, i).ToLocal(&val)) { |
302 | 0 | return; |
303 | 0 | } |
304 | 0 | node::Utf8Value pair(env->isolate(), val); |
305 | 0 | env_vals.emplace_back(pair.ToString()); |
306 | 0 | } |
307 | 0 | options_env.resize(env_vals.size() + 1); |
308 | 0 | for (size_t i = 0; i < env_vals.size(); i++) { |
309 | 0 | options_env[i] = const_cast<char*>(env_vals[i].c_str()); |
310 | 0 | CHECK_NOT_NULL(options_env[i]); |
311 | 0 | } |
312 | 0 | options_env.back() = nullptr; |
313 | 0 | options.env = options_env.data(); |
314 | 0 | } |
315 | | |
316 | | // options.stdio |
317 | 0 | std::vector<uv_stdio_container_t> options_stdio; |
318 | 0 | if (ParseStdioOptions(env, js_options, &options_stdio).IsNothing()) { |
319 | 0 | return; |
320 | 0 | } |
321 | 0 | options.stdio = options_stdio.data(); |
322 | 0 | options.stdio_count = options_stdio.size(); |
323 | | |
324 | | // options.windowsHide |
325 | 0 | Local<Value> hide_v; |
326 | 0 | if (!js_options->Get(context, env->windows_hide_string()) |
327 | 0 | .ToLocal(&hide_v)) { |
328 | 0 | return; |
329 | 0 | } |
330 | | |
331 | 0 | if (hide_v->IsTrue()) { |
332 | 0 | options.flags |= UV_PROCESS_WINDOWS_HIDE; |
333 | 0 | } |
334 | |
|
335 | 0 | if (env->hide_console_windows()) { |
336 | 0 | options.flags |= UV_PROCESS_WINDOWS_HIDE_CONSOLE; |
337 | 0 | } |
338 | | |
339 | | // options.windows_verbatim_arguments |
340 | 0 | Local<Value> wva_v; |
341 | 0 | if (!js_options->Get(context, env->windows_verbatim_arguments_string()) |
342 | 0 | .ToLocal(&wva_v)) { |
343 | 0 | return; |
344 | 0 | } |
345 | | |
346 | 0 | if (wva_v->IsTrue()) { |
347 | 0 | options.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; |
348 | 0 | } |
349 | | |
350 | | // options.detached |
351 | 0 | Local<Value> detached_v; |
352 | 0 | if (!js_options->Get(context, env->detached_string()) |
353 | 0 | .ToLocal(&detached_v)) { |
354 | 0 | return; |
355 | 0 | } |
356 | | |
357 | 0 | if (detached_v->IsTrue()) { |
358 | 0 | options.flags |= UV_PROCESS_DETACHED; |
359 | 0 | } |
360 | |
|
361 | 0 | if (err == 0) { |
362 | 0 | err = uv_spawn(env->event_loop(), &wrap->process_, &options); |
363 | 0 | wrap->MarkAsInitialized(); |
364 | 0 | } |
365 | |
|
366 | 0 | if (err == 0) { |
367 | 0 | CHECK_EQ(wrap->process_.data, wrap); |
368 | 0 | if (wrap->object() |
369 | 0 | ->Set(context, |
370 | 0 | env->pid_string(), |
371 | 0 | Integer::New(env->isolate(), wrap->process_.pid)) |
372 | 0 | .IsNothing()) { |
373 | 0 | return; |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | 0 | args.GetReturnValue().Set(err); |
378 | 0 | } |
379 | | |
380 | 0 | static void Kill(const FunctionCallbackInfo<Value>& args) { |
381 | 0 | Environment* env = Environment::GetCurrent(args); |
382 | 0 | ProcessWrap* wrap; |
383 | 0 | ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); |
384 | 0 | int signal; |
385 | 0 | if (!args[0]->Int32Value(env->context()).To(&signal)) { |
386 | 0 | return; |
387 | 0 | } |
388 | | #ifdef _WIN32 |
389 | | if (signal != SIGKILL && signal != SIGTERM && signal != SIGINT && |
390 | | signal != SIGQUIT && signal != 0) { |
391 | | signal = SIGKILL; |
392 | | } |
393 | | #endif |
394 | 0 | int err = uv_process_kill(&wrap->process_, signal); |
395 | 0 | args.GetReturnValue().Set(err); |
396 | 0 | } |
397 | | |
398 | | static void OnExit(uv_process_t* handle, |
399 | | int64_t exit_status, |
400 | 0 | int term_signal) { |
401 | 0 | ProcessWrap* wrap = ContainerOf(&ProcessWrap::process_, handle); |
402 | 0 | CHECK_EQ(&wrap->process_, handle); |
403 | | |
404 | 0 | Environment* env = wrap->env(); |
405 | 0 | HandleScope handle_scope(env->isolate()); |
406 | 0 | Context::Scope context_scope(env->context()); |
407 | |
|
408 | 0 | Local<Value> argv[] = { |
409 | 0 | Number::New(env->isolate(), static_cast<double>(exit_status)), |
410 | 0 | OneByteString(env->isolate(), signo_string(term_signal)) |
411 | 0 | }; |
412 | |
|
413 | 0 | wrap->MakeCallback(env->onexit_string(), arraysize(argv), argv); |
414 | 0 | } |
415 | | |
416 | | uv_process_t process_; |
417 | | }; |
418 | | |
419 | | |
420 | | } // anonymous namespace |
421 | | } // namespace node |
422 | | |
423 | | NODE_BINDING_CONTEXT_AWARE_INTERNAL(process_wrap, node::ProcessWrap::Initialize) |
424 | | NODE_BINDING_EXTERNAL_REFERENCE(process_wrap, |
425 | | node::ProcessWrap::RegisterExternalReferences) |