| 1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 | // for details. All rights reserved. Use of this source code is governed by a |
| 3 | // BSD-style license that can be found in the LICENSE file. |
| 4 | |
| 5 | #include "platform/globals.h" |
| 6 | #if defined(HOST_OS_FUCHSIA) |
| 7 | |
| 8 | #include "bin/process.h" |
| 9 | |
| 10 | #include <errno.h> |
| 11 | #include <fcntl.h> |
| 12 | #include <lib/fdio/io.h> |
| 13 | #include <lib/fdio/namespace.h> |
| 14 | #include <lib/fdio/spawn.h> |
| 15 | #include <poll.h> |
| 16 | #include <pthread.h> |
| 17 | #include <stdbool.h> |
| 18 | #include <stdio.h> |
| 19 | #include <stdlib.h> |
| 20 | #include <string.h> |
| 21 | #include <unistd.h> |
| 22 | #include <zircon/process.h> |
| 23 | #include <zircon/processargs.h> |
| 24 | #include <zircon/status.h> |
| 25 | #include <zircon/syscalls.h> |
| 26 | #include <zircon/syscalls/object.h> |
| 27 | #include <zircon/types.h> |
| 28 | |
| 29 | #include "bin/dartutils.h" |
| 30 | #include "bin/eventhandler.h" |
| 31 | #include "bin/fdutils.h" |
| 32 | #include "bin/file.h" |
| 33 | #include "bin/lockers.h" |
| 34 | #include "bin/namespace.h" |
| 35 | #include "bin/namespace_fuchsia.h" |
| 36 | #include "platform/signal_blocker.h" |
| 37 | #include "platform/syslog.h" |
| 38 | #include "platform/utils.h" |
| 39 | |
| 40 | // #define PROCESS_LOGGING 1 |
| 41 | #if defined(PROCESS_LOGGING) |
| 42 | #define LOG_ERR(msg, ...) Syslog::PrintErr("Dart Process: " msg, ##__VA_ARGS__) |
| 43 | #define LOG_INFO(msg, ...) Syslog::Print("Dart Process: " msg, ##__VA_ARGS__) |
| 44 | #else |
| 45 | #define LOG_ERR(msg, ...) |
| 46 | #define LOG_INFO(msg, ...) |
| 47 | #endif // defined(PROCESS_LOGGING) |
| 48 | |
| 49 | namespace dart { |
| 50 | namespace bin { |
| 51 | |
| 52 | int Process::global_exit_code_ = 0; |
| 53 | Mutex* Process::global_exit_code_mutex_ = nullptr; |
| 54 | Process::ExitHook Process::exit_hook_ = NULL; |
| 55 | |
| 56 | // ProcessInfo is used to map a process id to the file descriptor for |
| 57 | // the pipe used to communicate the exit code of the process to Dart. |
| 58 | // ProcessInfo objects are kept in the static singly-linked |
| 59 | // ProcessInfoList. |
| 60 | class ProcessInfo { |
| 61 | public: |
| 62 | ProcessInfo(zx_handle_t process, intptr_t fd) |
| 63 | : process_(process), exit_pipe_fd_(fd) {} |
| 64 | ~ProcessInfo() { |
| 65 | int closed = NO_RETRY_EXPECTED(close(exit_pipe_fd_)); |
| 66 | if (closed != 0) { |
| 67 | LOG_ERR("Failed to close process exit code pipe" ); |
| 68 | } |
| 69 | zx_handle_close(process_); |
| 70 | } |
| 71 | zx_handle_t process() const { return process_; } |
| 72 | intptr_t exit_pipe_fd() const { return exit_pipe_fd_; } |
| 73 | ProcessInfo* next() const { return next_; } |
| 74 | void set_next(ProcessInfo* info) { next_ = info; } |
| 75 | |
| 76 | private: |
| 77 | zx_handle_t process_; |
| 78 | intptr_t exit_pipe_fd_; |
| 79 | ProcessInfo* next_; |
| 80 | |
| 81 | DISALLOW_COPY_AND_ASSIGN(ProcessInfo); |
| 82 | }; |
| 83 | |
| 84 | // Singly-linked list of ProcessInfo objects for all active processes |
| 85 | // started from Dart. |
| 86 | class ProcessInfoList { |
| 87 | public: |
| 88 | static void Init(); |
| 89 | static void Cleanup(); |
| 90 | |
| 91 | static void AddProcess(zx_handle_t process, intptr_t fd) { |
| 92 | MutexLocker locker(mutex_); |
| 93 | ProcessInfo* info = new ProcessInfo(process, fd); |
| 94 | info->set_next(active_processes_); |
| 95 | active_processes_ = info; |
| 96 | } |
| 97 | |
| 98 | static intptr_t LookupProcessExitFd(zx_handle_t process) { |
| 99 | MutexLocker locker(mutex_); |
| 100 | ProcessInfo* current = active_processes_; |
| 101 | while (current != NULL) { |
| 102 | if (current->process() == process) { |
| 103 | return current->exit_pipe_fd(); |
| 104 | } |
| 105 | current = current->next(); |
| 106 | } |
| 107 | return 0; |
| 108 | } |
| 109 | |
| 110 | static bool Exists(zx_handle_t process) { |
| 111 | return LookupProcessExitFd(process) != 0; |
| 112 | } |
| 113 | |
| 114 | static void RemoveProcess(zx_handle_t process) { |
| 115 | MutexLocker locker(mutex_); |
| 116 | ProcessInfo* prev = NULL; |
| 117 | ProcessInfo* current = active_processes_; |
| 118 | while (current != NULL) { |
| 119 | if (current->process() == process) { |
| 120 | if (prev == NULL) { |
| 121 | active_processes_ = current->next(); |
| 122 | } else { |
| 123 | prev->set_next(current->next()); |
| 124 | } |
| 125 | delete current; |
| 126 | return; |
| 127 | } |
| 128 | prev = current; |
| 129 | current = current->next(); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | private: |
| 134 | // Linked list of ProcessInfo objects for all active processes |
| 135 | // started from Dart code. |
| 136 | static ProcessInfo* active_processes_; |
| 137 | // Mutex protecting all accesses to the linked list of active |
| 138 | // processes. |
| 139 | static Mutex* mutex_; |
| 140 | |
| 141 | DISALLOW_ALLOCATION(); |
| 142 | DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessInfoList); |
| 143 | }; |
| 144 | |
| 145 | ProcessInfo* ProcessInfoList::active_processes_ = NULL; |
| 146 | Mutex* ProcessInfoList::mutex_ = nullptr; |
| 147 | |
| 148 | // The exit code handler sets up a separate thread which waits for child |
| 149 | // processes to terminate. That separate thread can then get the exit code from |
| 150 | // processes that have exited and communicate it to Dart through the |
| 151 | // event loop. |
| 152 | class ExitCodeHandler { |
| 153 | public: |
| 154 | static void Init(); |
| 155 | static void Cleanup(); |
| 156 | |
| 157 | // Notify the ExitCodeHandler that another process exists. |
| 158 | static void Start() { |
| 159 | // Multiple isolates could be starting processes at the same |
| 160 | // time. Make sure that only one ExitCodeHandler thread exists. |
| 161 | MonitorLocker locker(monitor_); |
| 162 | if (running_) { |
| 163 | return; |
| 164 | } |
| 165 | LOG_INFO("ExitCodeHandler Starting\n" ); |
| 166 | |
| 167 | zx_status_t status = zx_port_create(0, &port_); |
| 168 | if (status != ZX_OK) { |
| 169 | FATAL1("ExitCodeHandler: zx_port_create failed: %s\n" , |
| 170 | zx_status_get_string(status)); |
| 171 | return; |
| 172 | } |
| 173 | |
| 174 | // Start thread that handles process exits when wait returns. |
| 175 | intptr_t result = |
| 176 | Thread::Start("dart:io Process.start" , ExitCodeHandlerEntry, 0); |
| 177 | if (result != 0) { |
| 178 | FATAL1("Failed to start exit code handler worker thread %ld" , result); |
| 179 | } |
| 180 | |
| 181 | running_ = true; |
| 182 | } |
| 183 | |
| 184 | static zx_status_t Add(zx_handle_t process) { |
| 185 | MonitorLocker locker(monitor_); |
| 186 | LOG_INFO("ExitCodeHandler Adding Process: %u\n" , process); |
| 187 | return zx_object_wait_async(process, port_, static_cast<uint64_t>(process), |
| 188 | ZX_TASK_TERMINATED, ZX_WAIT_ASYNC_ONCE); |
| 189 | } |
| 190 | |
| 191 | static void Terminate() { |
| 192 | MonitorLocker locker(monitor_); |
| 193 | if (!running_) { |
| 194 | return; |
| 195 | } |
| 196 | running_ = false; |
| 197 | |
| 198 | LOG_INFO("ExitCodeHandler Terminating\n" ); |
| 199 | SendShutdownMessage(); |
| 200 | |
| 201 | while (!terminate_done_) { |
| 202 | monitor_->Wait(Monitor::kNoTimeout); |
| 203 | } |
| 204 | zx_handle_close(port_); |
| 205 | LOG_INFO("ExitCodeHandler Terminated\n" ); |
| 206 | } |
| 207 | |
| 208 | private: |
| 209 | static const uint64_t kShutdownPacketKey = 1; |
| 210 | |
| 211 | static void SendShutdownMessage() { |
| 212 | zx_port_packet_t pkt; |
| 213 | pkt.key = kShutdownPacketKey; |
| 214 | zx_status_t status = zx_port_queue(port_, &pkt); |
| 215 | if (status != ZX_OK) { |
| 216 | Syslog::PrintErr("ExitCodeHandler: zx_port_queue failed: %s\n" , |
| 217 | zx_status_get_string(status)); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | // Entry point for the separate exit code handler thread started by |
| 222 | // the ExitCodeHandler. |
| 223 | static void ExitCodeHandlerEntry(uword param) { |
| 224 | LOG_INFO("ExitCodeHandler Entering ExitCodeHandler thread\n" ); |
| 225 | |
| 226 | zx_port_packet_t pkt; |
| 227 | while (true) { |
| 228 | zx_status_t status = zx_port_wait(port_, ZX_TIME_INFINITE, &pkt); |
| 229 | if (status != ZX_OK) { |
| 230 | FATAL1("ExitCodeHandler: zx_port_wait failed: %s\n" , |
| 231 | zx_status_get_string(status)); |
| 232 | } |
| 233 | if (pkt.type == ZX_PKT_TYPE_USER) { |
| 234 | ASSERT(pkt.key == kShutdownPacketKey); |
| 235 | break; |
| 236 | } |
| 237 | zx_handle_t process = static_cast<zx_handle_t>(pkt.key); |
| 238 | zx_signals_t observed = pkt.signal.observed; |
| 239 | if ((observed & ZX_TASK_TERMINATED) == ZX_SIGNAL_NONE) { |
| 240 | LOG_ERR("ExitCodeHandler: Unexpected signals, process %u: %ux\n" , |
| 241 | process, observed); |
| 242 | } |
| 243 | SendProcessStatus(process); |
| 244 | } |
| 245 | |
| 246 | LOG_INFO("ExitCodeHandler thread shutting down\n" ); |
| 247 | terminate_done_ = true; |
| 248 | monitor_->Notify(); |
| 249 | } |
| 250 | |
| 251 | static void SendProcessStatus(zx_handle_t process) { |
| 252 | LOG_INFO("ExitCodeHandler thread getting process status: %u\n" , process); |
| 253 | int return_code = -1; |
| 254 | zx_info_process_t proc_info; |
| 255 | zx_status_t status = zx_object_get_info( |
| 256 | process, ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), NULL, NULL); |
| 257 | if (status != ZX_OK) { |
| 258 | Syslog::PrintErr("ExitCodeHandler: zx_object_get_info failed: %s\n" , |
| 259 | zx_status_get_string(status)); |
| 260 | } else { |
| 261 | return_code = proc_info.return_code; |
| 262 | } |
| 263 | zx_handle_close(process); |
| 264 | LOG_INFO("ExitCodeHandler thread process %u exited with %d\n" , process, |
| 265 | return_code); |
| 266 | |
| 267 | const intptr_t exit_code_fd = ProcessInfoList::LookupProcessExitFd(process); |
| 268 | LOG_INFO("ExitCodeHandler thread sending %u code %d on fd %ld\n" , process, |
| 269 | return_code, exit_code_fd); |
| 270 | if (exit_code_fd != 0) { |
| 271 | int exit_message[2]; |
| 272 | exit_message[0] = abs(return_code); |
| 273 | exit_message[1] = return_code >= 0 ? 0 : 1; |
| 274 | intptr_t result = FDUtils::WriteToBlocking(exit_code_fd, &exit_message, |
| 275 | sizeof(exit_message)); |
| 276 | ASSERT((result == -1) || (result == sizeof(exit_code_fd))); |
| 277 | if ((result == -1) && (errno != EPIPE)) { |
| 278 | int err = errno; |
| 279 | Syslog::PrintErr("Failed to write exit code for process %d: errno=%d\n" , |
| 280 | process, err); |
| 281 | } |
| 282 | LOG_INFO("ExitCodeHandler thread wrote %ld bytes to fd %ld\n" , result, |
| 283 | exit_code_fd); |
| 284 | LOG_INFO("ExitCodeHandler thread removing process %u from list\n" , |
| 285 | process); |
| 286 | ProcessInfoList::RemoveProcess(process); |
| 287 | } else { |
| 288 | LOG_ERR("ExitCodeHandler: Process %u not found\n" , process); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | static zx_handle_t port_; |
| 293 | |
| 294 | // Protected by monitor_. |
| 295 | static bool terminate_done_; |
| 296 | static bool running_; |
| 297 | static Monitor* monitor_; |
| 298 | |
| 299 | DISALLOW_ALLOCATION(); |
| 300 | DISALLOW_IMPLICIT_CONSTRUCTORS(ExitCodeHandler); |
| 301 | }; |
| 302 | |
| 303 | zx_handle_t ExitCodeHandler::port_ = ZX_HANDLE_INVALID; |
| 304 | bool ExitCodeHandler::running_ = false; |
| 305 | bool ExitCodeHandler::terminate_done_ = false; |
| 306 | Monitor* ExitCodeHandler::monitor_ = nullptr; |
| 307 | |
| 308 | void Process::TerminateExitCodeHandler() { |
| 309 | ExitCodeHandler::Terminate(); |
| 310 | } |
| 311 | |
| 312 | intptr_t Process::CurrentProcessId() { |
| 313 | return static_cast<intptr_t>(getpid()); |
| 314 | } |
| 315 | |
| 316 | int64_t Process::CurrentRSS() { |
| 317 | zx_info_task_stats_t task_stats; |
| 318 | zx_handle_t process = zx_process_self(); |
| 319 | zx_status_t status = zx_object_get_info( |
| 320 | process, ZX_INFO_TASK_STATS, &task_stats, sizeof(task_stats), NULL, NULL); |
| 321 | if (status != ZX_OK) { |
| 322 | // TODO(zra): Translate this to a Unix errno. |
| 323 | errno = status; |
| 324 | return -1; |
| 325 | } |
| 326 | return task_stats.mem_private_bytes + task_stats.mem_shared_bytes; |
| 327 | } |
| 328 | |
| 329 | int64_t Process::MaxRSS() { |
| 330 | // There is currently no way to get the high watermark value on Fuchsia, so |
| 331 | // just return the current RSS value. |
| 332 | return CurrentRSS(); |
| 333 | } |
| 334 | |
| 335 | class IOHandleScope { |
| 336 | public: |
| 337 | explicit IOHandleScope(IOHandle* io_handle) : io_handle_(io_handle) {} |
| 338 | ~IOHandleScope() { |
| 339 | io_handle_->Close(); |
| 340 | io_handle_->Release(); |
| 341 | } |
| 342 | |
| 343 | private: |
| 344 | IOHandle* io_handle_; |
| 345 | |
| 346 | DISALLOW_ALLOCATION(); |
| 347 | DISALLOW_COPY_AND_ASSIGN(IOHandleScope); |
| 348 | }; |
| 349 | |
| 350 | bool Process::Wait(intptr_t pid, |
| 351 | intptr_t in, |
| 352 | intptr_t out, |
| 353 | intptr_t err, |
| 354 | intptr_t exit_event, |
| 355 | ProcessResult* result) { |
| 356 | IOHandle* out_iohandle = reinterpret_cast<IOHandle*>(out); |
| 357 | IOHandle* err_iohandle = reinterpret_cast<IOHandle*>(err); |
| 358 | IOHandle* exit_iohandle = reinterpret_cast<IOHandle*>(exit_event); |
| 359 | |
| 360 | // There is no return from this function using Dart_PropagateError |
| 361 | // as memory used by the buffer lists is freed through their |
| 362 | // destructors. |
| 363 | BufferList out_data; |
| 364 | BufferList err_data; |
| 365 | union { |
| 366 | uint8_t bytes[8]; |
| 367 | int32_t ints[2]; |
| 368 | } exit_code_data; |
| 369 | |
| 370 | // Create a port, which is like an epoll() fd on Linux. |
| 371 | zx_handle_t port; |
| 372 | zx_status_t status = zx_port_create(0, &port); |
| 373 | if (status != ZX_OK) { |
| 374 | Syslog::PrintErr("Process::Wait: zx_port_create failed: %s\n" , |
| 375 | zx_status_get_string(status)); |
| 376 | return false; |
| 377 | } |
| 378 | |
| 379 | IOHandle* out_tmp = out_iohandle; |
| 380 | IOHandle* err_tmp = err_iohandle; |
| 381 | IOHandle* exit_tmp = exit_iohandle; |
| 382 | const uint64_t out_key = reinterpret_cast<uint64_t>(out_tmp); |
| 383 | const uint64_t err_key = reinterpret_cast<uint64_t>(err_tmp); |
| 384 | const uint64_t exit_key = reinterpret_cast<uint64_t>(exit_tmp); |
| 385 | const uint32_t events = POLLRDHUP | POLLIN; |
| 386 | if (!out_tmp->AsyncWait(port, events, out_key)) { |
| 387 | return false; |
| 388 | } |
| 389 | if (!err_tmp->AsyncWait(port, events, err_key)) { |
| 390 | return false; |
| 391 | } |
| 392 | if (!exit_tmp->AsyncWait(port, events, exit_key)) { |
| 393 | return false; |
| 394 | } |
| 395 | while ((out_tmp != NULL) || (err_tmp != NULL) || (exit_tmp != NULL)) { |
| 396 | zx_port_packet_t pkt; |
| 397 | status = zx_port_wait(port, ZX_TIME_INFINITE, &pkt); |
| 398 | if (status != ZX_OK) { |
| 399 | Syslog::PrintErr("Process::Wait: zx_port_wait failed: %s\n" , |
| 400 | zx_status_get_string(status)); |
| 401 | return false; |
| 402 | } |
| 403 | IOHandle* event_handle = reinterpret_cast<IOHandle*>(pkt.key); |
| 404 | const intptr_t event_mask = event_handle->WaitEnd(pkt.signal.observed); |
| 405 | if (event_handle == out_tmp) { |
| 406 | if ((event_mask & POLLIN) != 0) { |
| 407 | const intptr_t avail = FDUtils::AvailableBytes(out_tmp->fd()); |
| 408 | if (!out_data.Read(out_tmp->fd(), avail)) { |
| 409 | return false; |
| 410 | } |
| 411 | } |
| 412 | if ((event_mask & POLLRDHUP) != 0) { |
| 413 | out_tmp->CancelWait(port, out_key); |
| 414 | out_tmp = NULL; |
| 415 | } |
| 416 | } else if (event_handle == err_tmp) { |
| 417 | if ((event_mask & POLLIN) != 0) { |
| 418 | const intptr_t avail = FDUtils::AvailableBytes(err_tmp->fd()); |
| 419 | if (!err_data.Read(err_tmp->fd(), avail)) { |
| 420 | return false; |
| 421 | } |
| 422 | } |
| 423 | if ((event_mask & POLLRDHUP) != 0) { |
| 424 | err_tmp->CancelWait(port, err_key); |
| 425 | err_tmp = NULL; |
| 426 | } |
| 427 | } else if (event_handle == exit_tmp) { |
| 428 | if ((event_mask & POLLIN) != 0) { |
| 429 | const intptr_t avail = FDUtils::AvailableBytes(exit_tmp->fd()); |
| 430 | if (avail == 8) { |
| 431 | intptr_t b = |
| 432 | NO_RETRY_EXPECTED(read(exit_tmp->fd(), exit_code_data.bytes, 8)); |
| 433 | if (b != 8) { |
| 434 | return false; |
| 435 | } |
| 436 | } |
| 437 | } |
| 438 | if ((event_mask & POLLRDHUP) != 0) { |
| 439 | exit_tmp->CancelWait(port, exit_key); |
| 440 | exit_tmp = NULL; |
| 441 | } |
| 442 | } else { |
| 443 | Syslog::PrintErr("Process::Wait: Unexpected wait key: %p\n" , |
| 444 | event_handle); |
| 445 | } |
| 446 | if (out_tmp != NULL) { |
| 447 | if (!out_tmp->AsyncWait(port, events, out_key)) { |
| 448 | return false; |
| 449 | } |
| 450 | } |
| 451 | if (err_tmp != NULL) { |
| 452 | if (!err_tmp->AsyncWait(port, events, err_key)) { |
| 453 | return false; |
| 454 | } |
| 455 | } |
| 456 | if (exit_tmp != NULL) { |
| 457 | if (!exit_tmp->AsyncWait(port, events, exit_key)) { |
| 458 | return false; |
| 459 | } |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | // All handles closed and all data read. |
| 464 | result->set_stdout_data(out_data.GetData()); |
| 465 | result->set_stderr_data(err_data.GetData()); |
| 466 | DEBUG_ASSERT(out_data.IsEmpty()); |
| 467 | DEBUG_ASSERT(err_data.IsEmpty()); |
| 468 | |
| 469 | // Calculate the exit code. |
| 470 | intptr_t exit_code = exit_code_data.ints[0]; |
| 471 | intptr_t negative = exit_code_data.ints[1]; |
| 472 | if (negative != 0) { |
| 473 | exit_code = -exit_code; |
| 474 | } |
| 475 | result->set_exit_code(exit_code); |
| 476 | |
| 477 | // Close the process handle. |
| 478 | zx_handle_t process = static_cast<zx_handle_t>(pid); |
| 479 | zx_handle_close(process); |
| 480 | return true; |
| 481 | } |
| 482 | |
| 483 | bool Process::Kill(intptr_t id, int signal) { |
| 484 | LOG_INFO("Sending signal %d to process with id %ld\n" , signal, id); |
| 485 | // zx_task_kill is definitely going to kill the process. |
| 486 | if ((signal != SIGTERM) && (signal != SIGKILL)) { |
| 487 | LOG_ERR("Signal %d not supported\n" , signal); |
| 488 | errno = ENOSYS; |
| 489 | return false; |
| 490 | } |
| 491 | // We can only use zx_task_kill if we know id is a process handle, and we only |
| 492 | // know that for sure if it's in our list. |
| 493 | zx_handle_t process = static_cast<zx_handle_t>(id); |
| 494 | if (!ProcessInfoList::Exists(process)) { |
| 495 | LOG_ERR("Process %ld wasn't in the ProcessInfoList\n" , id); |
| 496 | errno = ESRCH; // No such process. |
| 497 | return false; |
| 498 | } |
| 499 | zx_status_t status = zx_task_kill(process); |
| 500 | if (status != ZX_OK) { |
| 501 | LOG_ERR("zx_task_kill failed: %s\n" , zx_status_get_string(status)); |
| 502 | errno = EPERM; // TODO(zra): Figure out what it really should be. |
| 503 | return false; |
| 504 | } |
| 505 | LOG_INFO("Signal %d sent successfully to process %ld\n" , signal, id); |
| 506 | return true; |
| 507 | } |
| 508 | |
| 509 | class ProcessStarter { |
| 510 | public: |
| 511 | ProcessStarter(Namespace* namespc, |
| 512 | const char* path, |
| 513 | char* arguments[], |
| 514 | intptr_t arguments_length, |
| 515 | const char* working_directory, |
| 516 | char* environment[], |
| 517 | intptr_t environment_length, |
| 518 | ProcessStartMode mode, |
| 519 | intptr_t* in, |
| 520 | intptr_t* out, |
| 521 | intptr_t* err, |
| 522 | intptr_t* id, |
| 523 | intptr_t* exit_event, |
| 524 | char** os_error_message) |
| 525 | : namespc_(namespc), |
| 526 | path_(path), |
| 527 | working_directory_(working_directory), |
| 528 | mode_(mode), |
| 529 | in_(in), |
| 530 | out_(out), |
| 531 | err_(err), |
| 532 | id_(id), |
| 533 | exit_event_(exit_event), |
| 534 | os_error_message_(os_error_message) { |
| 535 | LOG_INFO("ProcessStarter: ctor %s with %ld args, mode = %d\n" , path, |
| 536 | arguments_length, mode); |
| 537 | |
| 538 | read_in_ = -1; |
| 539 | read_err_ = -1; |
| 540 | write_out_ = -1; |
| 541 | |
| 542 | program_arguments_ = reinterpret_cast<char**>(Dart_ScopeAllocate( |
| 543 | (arguments_length + 2) * sizeof(*program_arguments_))); |
| 544 | program_arguments_[0] = const_cast<char*>(path_); |
| 545 | for (int i = 0; i < arguments_length; i++) { |
| 546 | program_arguments_[i + 1] = arguments[i]; |
| 547 | } |
| 548 | program_arguments_[arguments_length + 1] = NULL; |
| 549 | |
| 550 | program_environment_ = NULL; |
| 551 | if (environment != NULL) { |
| 552 | program_environment_ = reinterpret_cast<char**>(Dart_ScopeAllocate( |
| 553 | (environment_length + 1) * sizeof(*program_environment_))); |
| 554 | for (int i = 0; i < environment_length; i++) { |
| 555 | program_environment_[i] = environment[i]; |
| 556 | } |
| 557 | program_environment_[environment_length] = NULL; |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | ~ProcessStarter() { |
| 562 | if (read_in_ != -1) { |
| 563 | close(read_in_); |
| 564 | } |
| 565 | if (read_err_ != -1) { |
| 566 | close(read_err_); |
| 567 | } |
| 568 | if (write_out_ != -1) { |
| 569 | close(write_out_); |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | int Start() { |
| 574 | LOG_INFO("ProcessStarter: Start()\n" ); |
| 575 | int exit_pipe_fds[2]; |
| 576 | intptr_t result = NO_RETRY_EXPECTED(pipe(exit_pipe_fds)); |
| 577 | if (result != 0) { |
| 578 | *os_error_message_ = DartUtils::ScopedCopyCString( |
| 579 | "Failed to create exit code pipe for process start." ); |
| 580 | return result; |
| 581 | } |
| 582 | LOG_INFO("ProcessStarter: Start() set up exit_pipe_fds (%d, %d)\n" , |
| 583 | exit_pipe_fds[0], exit_pipe_fds[1]); |
| 584 | |
| 585 | NamespaceScope ns(namespc_, path_); |
| 586 | const int pathfd = |
| 587 | TEMP_FAILURE_RETRY(openat(ns.fd(), ns.path(), O_RDONLY)); |
| 588 | zx_handle_t vmo = ZX_HANDLE_INVALID; |
| 589 | zx_status_t status = fdio_get_vmo_clone(pathfd, &vmo); |
| 590 | close(pathfd); |
| 591 | if (status != ZX_OK) { |
| 592 | close(exit_pipe_fds[0]); |
| 593 | close(exit_pipe_fds[1]); |
| 594 | *os_error_message_ = DartUtils::ScopedCopyCString( |
| 595 | "Failed to load executable for process start." ); |
| 596 | return status; |
| 597 | } |
| 598 | |
| 599 | // After reading the binary into a VMO, we need to mark it as executable, |
| 600 | // since the VMO returned by fdio_get_vmo_clone should be read-only. |
| 601 | status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo); |
| 602 | if (status != ZX_OK) { |
| 603 | close(exit_pipe_fds[0]); |
| 604 | close(exit_pipe_fds[1]); |
| 605 | *os_error_message_ = DartUtils::ScopedCopyCString( |
| 606 | "Failed to mark binary as executable for process start." ); |
| 607 | return status; |
| 608 | } |
| 609 | |
| 610 | fdio_spawn_action_t* actions; |
| 611 | const intptr_t actions_count = BuildSpawnActions( |
| 612 | namespc_->namespc()->fdio_ns(), &actions); |
| 613 | if (actions_count < 0) { |
| 614 | zx_handle_close(vmo); |
| 615 | close(exit_pipe_fds[0]); |
| 616 | close(exit_pipe_fds[1]); |
| 617 | *os_error_message_ = DartUtils::ScopedCopyCString( |
| 618 | "Failed to build spawn actions array." ); |
| 619 | return ZX_ERR_IO; |
| 620 | } |
| 621 | |
| 622 | // TODO(zra): Use the supplied working directory when fdio_spawn_vmo adds an |
| 623 | // API to set it. |
| 624 | |
| 625 | LOG_INFO("ProcessStarter: Start() Calling fdio_spawn_vmo\n" ); |
| 626 | zx_handle_t process = ZX_HANDLE_INVALID; |
| 627 | char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| 628 | uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_DEFAULT_LDSVC; |
| 629 | status = fdio_spawn_vmo(ZX_HANDLE_INVALID, flags, vmo, program_arguments_, |
| 630 | program_environment_, actions_count, actions, |
| 631 | &process, err_msg); |
| 632 | // Handles are consumed by fdio_spawn_vmo even if it fails. |
| 633 | delete[] actions; |
| 634 | if (status != ZX_OK) { |
| 635 | LOG_ERR("ProcessStarter: Start() fdio_spawn_vmo failed\n" ); |
| 636 | close(exit_pipe_fds[0]); |
| 637 | close(exit_pipe_fds[1]); |
| 638 | ReportStartError(err_msg); |
| 639 | return status; |
| 640 | } |
| 641 | |
| 642 | LOG_INFO("ProcessStarter: Start() adding %u to list with exit_pipe %d\n" , |
| 643 | process, exit_pipe_fds[1]); |
| 644 | ProcessInfoList::AddProcess(process, exit_pipe_fds[1]); |
| 645 | ExitCodeHandler::Start(); |
| 646 | status = ExitCodeHandler::Add(process); |
| 647 | if (status != ZX_OK) { |
| 648 | LOG_ERR("ProcessStarter: ExitCodeHandler: Add failed: %s\n" , |
| 649 | zx_status_get_string(status)); |
| 650 | close(exit_pipe_fds[0]); |
| 651 | close(exit_pipe_fds[1]); |
| 652 | zx_task_kill(process); |
| 653 | ProcessInfoList::RemoveProcess(process); |
| 654 | ReportStartError(zx_status_get_string(status)); |
| 655 | return status; |
| 656 | } |
| 657 | |
| 658 | // The IOHandles allocated below are returned to Dart code. The Dart code |
| 659 | // calls into the runtime again to allocate a C++ Socket object, which |
| 660 | // becomes the native field of a Dart _NativeSocket object. The C++ Socket |
| 661 | // object and the EventHandler manage the lifetime of these IOHandles. |
| 662 | *id_ = process; |
| 663 | FDUtils::SetNonBlocking(read_in_); |
| 664 | *in_ = reinterpret_cast<intptr_t>(new IOHandle(read_in_)); |
| 665 | read_in_ = -1; |
| 666 | FDUtils::SetNonBlocking(read_err_); |
| 667 | *err_ = reinterpret_cast<intptr_t>(new IOHandle(read_err_)); |
| 668 | read_err_ = -1; |
| 669 | FDUtils::SetNonBlocking(write_out_); |
| 670 | *out_ = reinterpret_cast<intptr_t>(new IOHandle(write_out_)); |
| 671 | write_out_ = -1; |
| 672 | FDUtils::SetNonBlocking(exit_pipe_fds[0]); |
| 673 | *exit_event_ = reinterpret_cast<intptr_t>(new IOHandle(exit_pipe_fds[0])); |
| 674 | return 0; |
| 675 | } |
| 676 | |
| 677 | private: |
| 678 | void ReportStartError(const char* errormsg) { |
| 679 | const intptr_t kMaxMessageSize = 256; |
| 680 | char* message = DartUtils::ScopedCString(kMaxMessageSize); |
| 681 | snprintf(message, kMaxMessageSize, "Process start failed: %s\n" , errormsg); |
| 682 | *os_error_message_ = message; |
| 683 | } |
| 684 | |
| 685 | zx_status_t AddPipe(int target_fd, int* local_fd, |
| 686 | fdio_spawn_action_t* action) { |
| 687 | zx_status_t status = fdio_pipe_half(local_fd, &action->h.handle); |
| 688 | if (status != ZX_OK) return status; |
| 689 | action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; |
| 690 | action->h.id = PA_HND(PA_HND_TYPE(PA_FD), target_fd); |
| 691 | return ZX_OK; |
| 692 | } |
| 693 | |
| 694 | // Fills in 'actions_out' and returns action count. |
| 695 | intptr_t BuildSpawnActions(fdio_ns_t* ns, fdio_spawn_action_t** actions_out) { |
| 696 | const intptr_t fixed_actions_cnt = 4; |
| 697 | intptr_t ns_cnt = 0; |
| 698 | zx_status_t status; |
| 699 | |
| 700 | // First, figure out how many namespace actions are needed. |
| 701 | fdio_flat_namespace_t* flat_ns = nullptr; |
| 702 | if (ns != nullptr) { |
| 703 | status = fdio_ns_export(ns, &flat_ns); |
| 704 | if (status != ZX_OK) { |
| 705 | LOG_ERR("ProcessStarter: BuildSpawnActions: fdio_ns_export: %s\n" , |
| 706 | zx_status_get_string(status)); |
| 707 | return -1; |
| 708 | } |
| 709 | ns_cnt = flat_ns->count; |
| 710 | } |
| 711 | |
| 712 | // Allocate the actions array. |
| 713 | const intptr_t actions_cnt = ns_cnt + fixed_actions_cnt; |
| 714 | fdio_spawn_action_t* actions = new fdio_spawn_action_t[actions_cnt]; |
| 715 | |
| 716 | // Fill in the entries for passing stdin/out/err handles, and the program |
| 717 | // name. |
| 718 | status = AddPipe(0, &write_out_, &actions[0]); |
| 719 | if (status != ZX_OK) { |
| 720 | LOG_ERR("ProcessStarter: BuildSpawnActions: stdout AddPipe failed: %s\n" , |
| 721 | zx_status_get_string(status)); |
| 722 | if (flat_ns != nullptr) { |
| 723 | fdio_ns_free_flat_ns(flat_ns); |
| 724 | } |
| 725 | return -1; |
| 726 | } |
| 727 | status = AddPipe(1, &read_in_, &actions[1]); |
| 728 | if (status != ZX_OK) { |
| 729 | LOG_ERR("ProcessStarter: BuildSpawnActions: stdin AddPipe failed: %s\n" , |
| 730 | zx_status_get_string(status)); |
| 731 | if (flat_ns != nullptr) { |
| 732 | fdio_ns_free_flat_ns(flat_ns); |
| 733 | } |
| 734 | return -1; |
| 735 | } |
| 736 | status = AddPipe(2, &read_err_, &actions[2]); |
| 737 | if (status != ZX_OK) { |
| 738 | LOG_ERR("ProcessStarter: BuildSpawnActions: stderr AddPipe failed: %s\n" , |
| 739 | zx_status_get_string(status)); |
| 740 | if (flat_ns != nullptr) { |
| 741 | fdio_ns_free_flat_ns(flat_ns); |
| 742 | } |
| 743 | return -1; |
| 744 | } |
| 745 | actions[3] = { |
| 746 | .action = FDIO_SPAWN_ACTION_SET_NAME, |
| 747 | .name = { |
| 748 | .data = program_arguments_[0], |
| 749 | }, |
| 750 | }; |
| 751 | |
| 752 | // Then fill in the namespace actions. |
| 753 | if (ns != nullptr) { |
| 754 | for (size_t i = 0; i < flat_ns->count; i++) { |
| 755 | actions[fixed_actions_cnt + i] = { |
| 756 | .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY, |
| 757 | .ns = { |
| 758 | .prefix = flat_ns->path[i], |
| 759 | .handle = flat_ns->handle[i], |
| 760 | }, |
| 761 | }; |
| 762 | } |
| 763 | free(flat_ns); |
| 764 | flat_ns = nullptr; |
| 765 | } |
| 766 | |
| 767 | *actions_out = actions; |
| 768 | return actions_cnt; |
| 769 | } |
| 770 | |
| 771 | int read_in_; // Pipe for stdout to child process. |
| 772 | int read_err_; // Pipe for stderr to child process. |
| 773 | int write_out_; // Pipe for stdin to child process. |
| 774 | |
| 775 | char** program_arguments_; |
| 776 | char** program_environment_; |
| 777 | |
| 778 | Namespace* namespc_; |
| 779 | const char* path_; |
| 780 | const char* working_directory_; |
| 781 | ProcessStartMode mode_; |
| 782 | intptr_t* in_; |
| 783 | intptr_t* out_; |
| 784 | intptr_t* err_; |
| 785 | intptr_t* id_; |
| 786 | intptr_t* exit_event_; |
| 787 | char** os_error_message_; |
| 788 | |
| 789 | DISALLOW_ALLOCATION(); |
| 790 | DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessStarter); |
| 791 | }; |
| 792 | |
| 793 | int Process::Start(Namespace* namespc, |
| 794 | const char* path, |
| 795 | char* arguments[], |
| 796 | intptr_t arguments_length, |
| 797 | const char* working_directory, |
| 798 | char* environment[], |
| 799 | intptr_t environment_length, |
| 800 | ProcessStartMode mode, |
| 801 | intptr_t* in, |
| 802 | intptr_t* out, |
| 803 | intptr_t* err, |
| 804 | intptr_t* id, |
| 805 | intptr_t* exit_event, |
| 806 | char** os_error_message) { |
| 807 | if (mode != kNormal) { |
| 808 | *os_error_message = DartUtils::ScopedCopyCString( |
| 809 | "Only ProcessStartMode.NORMAL is supported on this platform" ); |
| 810 | return -1; |
| 811 | } |
| 812 | ProcessStarter starter(namespc, path, arguments, arguments_length, |
| 813 | working_directory, environment, environment_length, |
| 814 | mode, in, out, err, id, exit_event, os_error_message); |
| 815 | return starter.Start(); |
| 816 | } |
| 817 | |
| 818 | intptr_t Process::SetSignalHandler(intptr_t signal) { |
| 819 | errno = ENOSYS; |
| 820 | return -1; |
| 821 | } |
| 822 | |
| 823 | void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) {} |
| 824 | |
| 825 | void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) {} |
| 826 | |
| 827 | void ProcessInfoList::Init() { |
| 828 | ASSERT(ProcessInfoList::mutex_ == nullptr); |
| 829 | ProcessInfoList::mutex_ = new Mutex(); |
| 830 | } |
| 831 | |
| 832 | void ProcessInfoList::Cleanup() { |
| 833 | ASSERT(ProcessInfoList::mutex_ != nullptr); |
| 834 | delete ProcessInfoList::mutex_; |
| 835 | ProcessInfoList::mutex_ = nullptr; |
| 836 | } |
| 837 | |
| 838 | void ExitCodeHandler::Init() { |
| 839 | ASSERT(ExitCodeHandler::monitor_ == nullptr); |
| 840 | ExitCodeHandler::monitor_ = new Monitor(); |
| 841 | } |
| 842 | |
| 843 | void ExitCodeHandler::Cleanup() { |
| 844 | ASSERT(ExitCodeHandler::monitor_ != nullptr); |
| 845 | delete ExitCodeHandler::monitor_; |
| 846 | ExitCodeHandler::monitor_ = nullptr; |
| 847 | } |
| 848 | |
| 849 | void Process::Init() { |
| 850 | ExitCodeHandler::Init(); |
| 851 | ProcessInfoList::Init(); |
| 852 | |
| 853 | ASSERT(Process::global_exit_code_mutex_ == nullptr); |
| 854 | Process::global_exit_code_mutex_ = new Mutex(); |
| 855 | } |
| 856 | |
| 857 | void Process::Cleanup() { |
| 858 | ASSERT(Process::global_exit_code_mutex_ != nullptr); |
| 859 | delete Process::global_exit_code_mutex_; |
| 860 | Process::global_exit_code_mutex_ = nullptr; |
| 861 | |
| 862 | ProcessInfoList::Cleanup(); |
| 863 | ExitCodeHandler::Cleanup(); |
| 864 | } |
| 865 | |
| 866 | } // namespace bin |
| 867 | } // namespace dart |
| 868 | |
| 869 | #endif // defined(HOST_OS_FUCHSIA) |
| 870 | |