// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "platform/globals.h" #if defined(HOST_OS_LINUX) #include "bin/process.h" #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include "bin/dartutils.h" #include "bin/directory.h" #include "bin/fdutils.h" #include "bin/file.h" #include "bin/lockers.h" #include "bin/reference_counting.h" #include "bin/thread.h" #include "platform/syslog.h" #include "platform/signal_blocker.h" #include "platform/utils.h" extern char** environ; namespace dart { namespace bin { int Process::global_exit_code_ = 0; Mutex* Process::global_exit_code_mutex_ = nullptr; Process::ExitHook Process::exit_hook_ = NULL; // ProcessInfo is used to map a process id to the file descriptor for // the pipe used to communicate the exit code of the process to Dart. // ProcessInfo objects are kept in the static singly-linked // ProcessInfoList. class ProcessInfo { public: ProcessInfo(pid_t pid, intptr_t fd) : pid_(pid), fd_(fd) {} ~ProcessInfo() { int closed = close(fd_); if (closed != 0) { FATAL("Failed to close process exit code pipe"); } } pid_t pid() { return pid_; } intptr_t fd() { return fd_; } ProcessInfo* next() { return next_; } void set_next(ProcessInfo* info) { next_ = info; } private: pid_t pid_; intptr_t fd_; ProcessInfo* next_; DISALLOW_COPY_AND_ASSIGN(ProcessInfo); }; // Singly-linked list of ProcessInfo objects for all active processes // started from Dart. class ProcessInfoList { public: static void Init(); static void Cleanup(); static void AddProcess(pid_t pid, intptr_t fd) { MutexLocker locker(mutex_); ProcessInfo* info = new ProcessInfo(pid, fd); info->set_next(active_processes_); active_processes_ = info; } static intptr_t LookupProcessExitFd(pid_t pid) { MutexLocker locker(mutex_); ProcessInfo* current = active_processes_; while (current != NULL) { if (current->pid() == pid) { return current->fd(); } current = current->next(); } return 0; } static void RemoveProcess(pid_t pid) { MutexLocker locker(mutex_); ProcessInfo* prev = NULL; ProcessInfo* current = active_processes_; while (current != NULL) { if (current->pid() == pid) { if (prev == NULL) { active_processes_ = current->next(); } else { prev->set_next(current->next()); } delete current; return; } prev = current; current = current->next(); } } private: // Linked list of ProcessInfo objects for all active processes // started from Dart code. static ProcessInfo* active_processes_; // Mutex protecting all accesses to the linked list of active // processes. static Mutex* mutex_; DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessInfoList); }; ProcessInfo* ProcessInfoList::active_processes_ = NULL; Mutex* ProcessInfoList::mutex_ = nullptr; // The exit code handler sets up a separate thread which waits for child // processes to terminate. That separate thread can then get the exit code from // processes that have exited and communicate it to Dart through the // event loop. class ExitCodeHandler { public: static void Init(); static void Cleanup(); // Notify the ExitCodeHandler that another process exists. static void ProcessStarted() { // Multiple isolates could be starting processes at the same // time. Make sure that only one ExitCodeHandler thread exists. MonitorLocker locker(monitor_); process_count_++; monitor_->Notify(); if (running_) { return; } // Start thread that handles process exits when wait returns. int result = Thread::Start("dart:io Process.start", ExitCodeHandlerEntry, 0); if (result != 0) { FATAL1("Failed to start exit code handler worker thread %d", result); } running_ = true; } static void TerminateExitCodeThread() { MonitorLocker locker(monitor_); if (!running_) { return; } // Set terminate_done_ to false, so we can use it as a guard for our // monitor. running_ = false; // Wake up the [ExitCodeHandler] thread which is blocked on `wait()` (see // [ExitCodeHandlerEntry]). if (TEMP_FAILURE_RETRY(fork()) == 0) { // We avoid running through registered atexit() handlers because that is // unnecessary work. _exit(0); } monitor_->Notify(); while (!terminate_done_) { monitor_->Wait(Monitor::kNoTimeout); } } private: // Entry point for the separate exit code handler thread started by // the ExitCodeHandler. static void ExitCodeHandlerEntry(uword param) { pid_t pid = 0; int status = 0; while (true) { { MonitorLocker locker(monitor_); while (running_ && process_count_ == 0) { monitor_->Wait(Monitor::kNoTimeout); } if (!running_) { terminate_done_ = true; monitor_->Notify(); return; } } if ((pid = TEMP_FAILURE_RETRY(wait(&status))) > 0) { int exit_code = 0; int negative = 0; if (WIFEXITED(status)) { exit_code = WEXITSTATUS(status); } if (WIFSIGNALED(status)) { exit_code = WTERMSIG(status); negative = 1; } intptr_t exit_code_fd = ProcessInfoList::LookupProcessExitFd(pid); if (exit_code_fd != 0) { int message[2] = {exit_code, negative}; ssize_t result = FDUtils::WriteToBlocking(exit_code_fd, &message, sizeof(message)); // If the process has been closed, the read end of the exit // pipe has been closed. It is therefore not a problem that // write fails with a broken pipe error. Other errors should // not happen. if ((result != -1) && (result != sizeof(message))) { FATAL("Failed to write entire process exit message"); } else if ((result == -1) && (errno != EPIPE)) { FATAL1("Failed to write exit code: %d", errno); } ProcessInfoList::RemoveProcess(pid); { MonitorLocker locker(monitor_); process_count_--; } } } else if (pid < 0) { FATAL1("Wait for process exit failed: %d", errno); } } } static bool terminate_done_; static int process_count_; static bool running_; static Monitor* monitor_; DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(ExitCodeHandler); }; bool ExitCodeHandler::running_ = false; int ExitCodeHandler::process_count_ = 0; bool ExitCodeHandler::terminate_done_ = false; Monitor* ExitCodeHandler::monitor_ = nullptr; class ProcessStarter { public: ProcessStarter(Namespace* namespc, const char* path, char* arguments[], intptr_t arguments_length, const char* working_directory, char* environment[], intptr_t environment_length, ProcessStartMode mode, intptr_t* in, intptr_t* out, intptr_t* err, intptr_t* id, intptr_t* exit_event, char** os_error_message) : namespc_(namespc), path_(path), working_directory_(working_directory), mode_(mode), in_(in), out_(out), err_(err), id_(id), exit_event_(exit_event), os_error_message_(os_error_message) { read_in_[0] = -1; read_in_[1] = -1; read_err_[0] = -1; read_err_[1] = -1; write_out_[0] = -1; write_out_[1] = -1; exec_control_[0] = -1; exec_control_[1] = -1; program_arguments_ = reinterpret_cast(Dart_ScopeAllocate( (arguments_length + 2) * sizeof(*program_arguments_))); program_arguments_[0] = const_cast(path_); for (int i = 0; i < arguments_length; i++) { program_arguments_[i + 1] = arguments[i]; } program_arguments_[arguments_length + 1] = NULL; program_environment_ = NULL; if (environment != NULL) { program_environment_ = reinterpret_cast(Dart_ScopeAllocate( (environment_length + 1) * sizeof(*program_environment_))); for (int i = 0; i < environment_length; i++) { program_environment_[i] = environment[i]; } program_environment_[environment_length] = NULL; } } int Start() { // Create pipes required. int err = CreatePipes(); if (err != 0) { return err; } // Fork to create the new process. pid_t pid = TEMP_FAILURE_RETRY(fork()); if (pid < 0) { // Failed to fork. return CleanupAndReturnError(); } else if (pid == 0) { // This runs in the new process. NewProcess(); } // This runs in the original process. // If the child process is not started in detached mode, be sure to // listen for exit-codes, now that we have a non detached child process // and also Register this child process. if (Process::ModeIsAttached(mode_)) { ExitCodeHandler::ProcessStarted(); err = RegisterProcess(pid); if (err != 0) { return err; } } // Notify child process to start. This is done to delay the call to exec // until the process is registered above, and we are ready to receive the // exit code. char msg = '1'; int bytes_written = FDUtils::WriteToBlocking(read_in_[1], &msg, sizeof(msg)); if (bytes_written != sizeof(msg)) { return CleanupAndReturnError(); } // Read the result of executing the child process. close(exec_control_[1]); exec_control_[1] = -1; if (Process::ModeIsAttached(mode_)) { err = ReadExecResult(); } else { err = ReadDetachedExecResult(&pid); } close(exec_control_[0]); exec_control_[0] = -1; // Return error code if any failures. if (err != 0) { if (Process::ModeIsAttached(mode_)) { // Since exec() failed, we're not interested in the exit code. // We close the reading side of the exit code pipe here. // GetProcessExitCodes will get a broken pipe error when it // tries to write to the writing side of the pipe and it will // ignore the error. close(*exit_event_); *exit_event_ = -1; } CloseAllPipes(); return err; } if (Process::ModeHasStdio(mode_)) { // Connect stdio, stdout and stderr. FDUtils::SetNonBlocking(read_in_[0]); *in_ = read_in_[0]; close(read_in_[1]); FDUtils::SetNonBlocking(write_out_[1]); *out_ = write_out_[1]; close(write_out_[0]); FDUtils::SetNonBlocking(read_err_[0]); *err_ = read_err_[0]; close(read_err_[1]); } else { // Close all fds. close(read_in_[0]); close(read_in_[1]); ASSERT(write_out_[0] == -1); ASSERT(write_out_[1] == -1); ASSERT(read_err_[0] == -1); ASSERT(read_err_[1] == -1); } ASSERT(exec_control_[0] == -1); ASSERT(exec_control_[1] == -1); *id_ = pid; return 0; } private: static constexpr int kErrorBufferSize = 1024; int CreatePipes() { int result; result = TEMP_FAILURE_RETRY(pipe2(exec_control_, O_CLOEXEC)); if (result < 0) { return CleanupAndReturnError(); } // For a detached process the pipe to connect stdout is still used for // signaling when to do the first fork. result = TEMP_FAILURE_RETRY(pipe2(read_in_, O_CLOEXEC)); if (result < 0) { return CleanupAndReturnError(); } // For detached processes the pipe to connect stderr and stdin are not used. if (Process::ModeHasStdio(mode_)) { result = TEMP_FAILURE_RETRY(pipe2(read_err_, O_CLOEXEC)); if (result < 0) { return CleanupAndReturnError(); } result = TEMP_FAILURE_RETRY(pipe2(write_out_, O_CLOEXEC)); if (result < 0) { return CleanupAndReturnError(); } } return 0; } void NewProcess() { // Wait for parent process before setting up the child process. char msg; int bytes_read = FDUtils::ReadFromBlocking(read_in_[0], &msg, sizeof(msg)); if (bytes_read != sizeof(msg)) { perror("Failed receiving notification message"); exit(1); } if (Process::ModeIsAttached(mode_)) { ExecProcess(); } else { ExecDetachedProcess(); } } // Tries to find path_ relative to the current namespace unless it should be // searched in the PATH. // The path that should be passed to exec is returned in realpath. // Returns true on success, and false if there was an error that should // be reported to the parent. bool FindPathInNamespace(char* realpath, intptr_t realpath_size) { // Perform a PATH search if there's no slash in the path. if (strchr(path_, '/') == NULL) { // TODO(zra): If there is a non-default namespace, the entries in PATH // should be treated as relative to the namespace. strncpy(realpath, path_, realpath_size); realpath[realpath_size - 1] = '\0'; return true; } NamespaceScope ns(namespc_, path_); const int fd = TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC)); if (fd == -1) { return false; } char procpath[PATH_MAX]; snprintf(procpath, PATH_MAX, "/proc/self/fd/%d", fd); const intptr_t length = TEMP_FAILURE_RETRY(readlink(procpath, realpath, realpath_size)); if (length < 0) { FDUtils::SaveErrorAndClose(fd); return false; } realpath[length] = '\0'; FDUtils::SaveErrorAndClose(fd); return true; } void ExecProcess() { if (mode_ == kNormal) { if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) { ReportChildError(); } if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) { ReportChildError(); } if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) { ReportChildError(); } } else { ASSERT(mode_ == kInheritStdio); } if (working_directory_ != NULL && !Directory::SetCurrent(namespc_, working_directory_)) { ReportChildError(); } if (program_environment_ != NULL) { environ = program_environment_; } char realpath[PATH_MAX]; if (!FindPathInNamespace(realpath, PATH_MAX)) { ReportChildError(); } // TODO(dart:io) Test for the existence of execveat, and use it instead. execvp(realpath, const_cast(program_arguments_)); ReportChildError(); } void ExecDetachedProcess() { if (mode_ == kDetached) { ASSERT(write_out_[0] == -1); ASSERT(write_out_[1] == -1); ASSERT(read_err_[0] == -1); ASSERT(read_err_[1] == -1); // For a detached process the pipe to connect stdout is only used for // signaling when to do the first fork. close(read_in_[0]); read_in_[0] = -1; close(read_in_[1]); read_in_[1] = -1; } else { // Don't close any fds if keeping stdio open to the detached process. ASSERT(mode_ == kDetachedWithStdio); } // Fork once more to start a new session. pid_t pid = TEMP_FAILURE_RETRY(fork()); if (pid < 0) { ReportChildError(); } else if (pid == 0) { // Start a new session. if (TEMP_FAILURE_RETRY(setsid()) == -1) { ReportChildError(); } else { // Do a final fork to not be the session leader. pid = TEMP_FAILURE_RETRY(fork()); if (pid < 0) { ReportChildError(); } else if (pid == 0) { if (mode_ == kDetached) { SetupDetached(); } else { SetupDetachedWithStdio(); } if ((working_directory_ != NULL) && !Directory::SetCurrent(namespc_, working_directory_)) { ReportChildError(); } if (program_environment_ != NULL) { environ = program_environment_; } // Report the final PID and do the exec. ReportPid(getpid()); // getpid cannot fail. char realpath[PATH_MAX]; if (!FindPathInNamespace(realpath, PATH_MAX)) { ReportChildError(); } // TODO(dart:io) Test for the existence of execveat, and use it // instead. execvp(realpath, const_cast(program_arguments_)); ReportChildError(); } else { // Exit the intermediate process. exit(0); } } } else { // Exit the intermediate process. exit(0); } } int RegisterProcess(pid_t pid) { int result; int event_fds[2]; result = TEMP_FAILURE_RETRY(pipe2(event_fds, O_CLOEXEC)); if (result < 0) { return CleanupAndReturnError(); } ProcessInfoList::AddProcess(pid, event_fds[1]); *exit_event_ = event_fds[0]; FDUtils::SetNonBlocking(event_fds[0]); return 0; } int ReadExecResult() { int child_errno; int bytes_read = -1; // Read exec result from child. If no data is returned the exec was // successful and the exec call closed the pipe. Otherwise the errno // is written to the pipe. bytes_read = FDUtils::ReadFromBlocking(exec_control_[0], &child_errno, sizeof(child_errno)); if (bytes_read == sizeof(child_errno)) { ReadChildError(); return child_errno; } else if (bytes_read == -1) { return errno; } return 0; } int ReadDetachedExecResult(pid_t* pid) { int child_errno; int bytes_read = -1; // Read exec result from child. If only pid data is returned the exec was // successful and the exec call closed the pipe. Otherwise the errno // is written to the pipe as well. int result[2]; bytes_read = FDUtils::ReadFromBlocking(exec_control_[0], result, sizeof(result)); if (bytes_read == sizeof(int)) { *pid = result[0]; } else if (bytes_read == 2 * sizeof(int)) { *pid = result[0]; child_errno = result[1]; ReadChildError(); return child_errno; } else if (bytes_read == -1) { return errno; } return 0; } void SetupDetached() { ASSERT(mode_ == kDetached); // Close all open file descriptors except for exec_control_[1]. int max_fds = sysconf(_SC_OPEN_MAX); if (max_fds == -1) { max_fds = _POSIX_OPEN_MAX; } for (int fd = 0; fd < max_fds; fd++) { if (fd != exec_control_[1]) { close(fd); } } // Re-open stdin, stdout and stderr and connect them to /dev/null. // The loop above should already have closed all of them, so // creating new file descriptors should start at STDIN_FILENO. int fd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)); if (fd != STDIN_FILENO) { ReportChildError(); } if (TEMP_FAILURE_RETRY(dup2(STDIN_FILENO, STDOUT_FILENO)) != STDOUT_FILENO) { ReportChildError(); } if (TEMP_FAILURE_RETRY(dup2(STDIN_FILENO, STDERR_FILENO)) != STDERR_FILENO) { ReportChildError(); } } void SetupDetachedWithStdio() { // Close all open file descriptors except for // exec_control_[1], write_out_[0], read_in_[1] and // read_err_[1]. int max_fds = sysconf(_SC_OPEN_MAX); if (max_fds == -1) { max_fds = _POSIX_OPEN_MAX; } for (int fd = 0; fd < max_fds; fd++) { if ((fd != exec_control_[1]) && (fd != write_out_[0]) && (fd != read_in_[1]) && (fd != read_err_[1])) { close(fd); } } if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) { ReportChildError(); } close(write_out_[0]); if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) { ReportChildError(); } close(read_in_[1]); if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) { ReportChildError(); } close(read_err_[1]); } int CleanupAndReturnError() { int actual_errno = errno; // If CleanupAndReturnError is called without an actual errno make // sure to return an error anyway. if (actual_errno == 0) { actual_errno = EPERM; } SetChildOsErrorMessage(); CloseAllPipes(); return actual_errno; } void SetChildOsErrorMessage() { char* error_message = DartUtils::ScopedCString(kErrorBufferSize); Utils::StrError(errno, error_message, kErrorBufferSize); *os_error_message_ = error_message; } void ReportChildError() { // In the case of failure in the child process write the errno and // the OS error message to the exec control pipe and exit. int child_errno = errno; char error_buf[kErrorBufferSize]; char* os_error_message = Utils::StrError(errno, error_buf, kErrorBufferSize); int bytes_written = FDUtils::WriteToBlocking(exec_control_[1], &child_errno, sizeof(child_errno)); if (bytes_written == sizeof(child_errno)) { FDUtils::WriteToBlocking(exec_control_[1], os_error_message, strlen(os_error_message) + 1); } close(exec_control_[1]); // We avoid running through registered atexit() handlers because that is // unnecessary work. _exit(1); } void ReportPid(int pid) { // In the case of starting a detached process the actual pid of that process // is communicated using the exec control pipe. int bytes_written = FDUtils::WriteToBlocking(exec_control_[1], &pid, sizeof(pid)); ASSERT(bytes_written == sizeof(int)); USE(bytes_written); } void ReadChildError() { char* message = DartUtils::ScopedCString(kErrorBufferSize); if (message != NULL) { FDUtils::ReadFromBlocking(exec_control_[0], message, kErrorBufferSize); message[kErrorBufferSize - 1] = '\0'; *os_error_message_ = message; } else { // Could not get error message. It will be NULL. ASSERT(*os_error_message_ == NULL); } } void ClosePipe(int* fds) { for (int i = 0; i < 2; i++) { if (fds[i] != -1) { close(fds[i]); fds[i] = -1; } } } void CloseAllPipes() { ClosePipe(exec_control_); ClosePipe(read_in_); ClosePipe(read_err_); ClosePipe(write_out_); } int read_in_[2]; // Pipe for stdout to child process. int read_err_[2]; // Pipe for stderr to child process. int write_out_[2]; // Pipe for stdin to child process. int exec_control_[2]; // Pipe to get the result from exec. char** program_arguments_; char** program_environment_; Namespace* namespc_; const char* path_; const char* working_directory_; ProcessStartMode mode_; intptr_t* in_; intptr_t* out_; intptr_t* err_; intptr_t* id_; intptr_t* exit_event_; char** os_error_message_; DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessStarter); }; int Process::Start(Namespace* namespc, const char* path, char* arguments[], intptr_t arguments_length, const char* working_directory, char* environment[], intptr_t environment_length, ProcessStartMode mode, intptr_t* in, intptr_t* out, intptr_t* err, intptr_t* id, intptr_t* exit_event, char** os_error_message) { ProcessStarter starter(namespc, path, arguments, arguments_length, working_directory, environment, environment_length, mode, in, out, err, id, exit_event, os_error_message); return starter.Start(); } static bool CloseProcessBuffers(struct pollfd* fds, int alive) { int e = errno; for (int i = 0; i < alive; i++) { close(fds[i].fd); } errno = e; return false; } bool Process::Wait(intptr_t pid, intptr_t in, intptr_t out, intptr_t err, intptr_t exit_event, ProcessResult* result) { // Close input to the process right away. close(in); // There is no return from this function using Dart_PropagateError // as memory used by the buffer lists is freed through their // destructors. BufferList out_data; BufferList err_data; union { uint8_t bytes[8]; int32_t ints[2]; } exit_code_data; struct pollfd fds[3]; fds[0].fd = out; fds[1].fd = err; fds[2].fd = exit_event; for (int i = 0; i < 3; i++) { fds[i].events = POLLIN; } int alive = 3; while (alive > 0) { // Blocking call waiting for events from the child process. if (TEMP_FAILURE_RETRY(poll(fds, alive, -1)) <= 0) { return CloseProcessBuffers(fds, alive); } // Process incoming data. for (int i = 0; i < alive; i++) { if ((fds[i].revents & (POLLNVAL | POLLERR)) != 0) { return CloseProcessBuffers(fds, alive); } if ((fds[i].revents & POLLIN) != 0) { intptr_t avail = FDUtils::AvailableBytes(fds[i].fd); if (fds[i].fd == out) { if (!out_data.Read(out, avail)) { return CloseProcessBuffers(fds, alive); } } else if (fds[i].fd == err) { if (!err_data.Read(err, avail)) { return CloseProcessBuffers(fds, alive); } } else if (fds[i].fd == exit_event) { if (avail == 8) { intptr_t b = TEMP_FAILURE_RETRY(read(exit_event, exit_code_data.bytes, 8)); if (b != 8) { return CloseProcessBuffers(fds, alive); } } } else { UNREACHABLE(); } } if ((fds[i].revents & POLLHUP) != 0) { // Remove the pollfd from the list of pollfds. close(fds[i].fd); alive--; if (i < alive) { fds[i] = fds[alive]; } // Process the same index again. i--; continue; } } } // All handles closed and all data read. result->set_stdout_data(out_data.GetData()); result->set_stderr_data(err_data.GetData()); DEBUG_ASSERT(out_data.IsEmpty()); DEBUG_ASSERT(err_data.IsEmpty()); // Calculate the exit code. intptr_t exit_code = exit_code_data.ints[0]; intptr_t negative = exit_code_data.ints[1]; if (negative != 0) { exit_code = -exit_code; } result->set_exit_code(exit_code); return true; } bool Process::Kill(intptr_t id, int signal) { return (TEMP_FAILURE_RETRY(kill(id, signal)) != -1); } void Process::TerminateExitCodeHandler() { ExitCodeHandler::TerminateExitCodeThread(); } intptr_t Process::CurrentProcessId() { return static_cast(getpid()); } static void SaveErrorAndClose(FILE* file) { int actual_errno = errno; fclose(file); errno = actual_errno; } int64_t Process::CurrentRSS() { // The second value in /proc/self/statm is the current RSS in pages. // It is not possible to use getrusage() because the interested fields are not // implemented by the linux kernel. FILE* statm = fopen("/proc/self/statm", "r"); if (statm == NULL) { return -1; } int64_t current_rss_pages = 0; int matches = fscanf(statm, "%*s%" Pd64 "", ¤t_rss_pages); if (matches != 1) { SaveErrorAndClose(statm); return -1; } fclose(statm); return current_rss_pages * getpagesize(); } int64_t Process::MaxRSS() { struct rusage usage; usage.ru_maxrss = 0; int r = getrusage(RUSAGE_SELF, &usage); if (r < 0) { return -1; } return usage.ru_maxrss * KB; } static Mutex* signal_mutex = nullptr; static SignalInfo* signal_handlers = NULL; static const int kSignalsCount = 7; static const int kSignals[kSignalsCount] = { SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGWINCH, SIGQUIT // Allow VMService to listen on SIGQUIT. }; SignalInfo::~SignalInfo() { close(fd_); } static void SignalHandler(int signal) { MutexLocker lock(signal_mutex); const SignalInfo* handler = signal_handlers; while (handler != NULL) { if (handler->signal() == signal) { int value = 0; VOID_TEMP_FAILURE_RETRY(write(handler->fd(), &value, 1)); } handler = handler->next(); } } intptr_t Process::SetSignalHandler(intptr_t signal) { bool found = false; for (int i = 0; i < kSignalsCount; i++) { if (kSignals[i] == signal) { found = true; break; } } if (!found) { return -1; } int fds[2]; if (NO_RETRY_EXPECTED(pipe2(fds, O_CLOEXEC)) != 0) { return -1; } ThreadSignalBlocker blocker(kSignalsCount, kSignals); MutexLocker lock(signal_mutex); SignalInfo* handler = signal_handlers; bool listen = true; sa_handler_t oldact_handler = nullptr; while (handler != NULL) { if (handler->signal() == signal) { oldact_handler = handler->oldact(); listen = false; break; } handler = handler->next(); } if (listen) { struct sigaction act = {}; act.sa_handler = SignalHandler; sigemptyset(&act.sa_mask); for (int i = 0; i < kSignalsCount; i++) { sigaddset(&act.sa_mask, kSignals[i]); } struct sigaction oldact = {}; int status = NO_RETRY_EXPECTED(sigaction(signal, &act, &oldact)); if (status < 0) { int err = errno; close(fds[0]); close(fds[1]); errno = err; return -1; } oldact_handler = oldact.sa_handler; } signal_handlers = new SignalInfo(fds[1], signal, oldact_handler, signal_handlers); return fds[0]; } void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) { ThreadSignalBlocker blocker(kSignalsCount, kSignals); MutexLocker lock(signal_mutex); SignalInfo* handler = signal_handlers; sa_handler_t oldact_handler = SIG_DFL; bool any_removed = false; bool any_remaining = false; while (handler != NULL) { bool remove = false; if (handler->signal() == signal) { if ((port == ILLEGAL_PORT) || (handler->port() == port)) { if (signal_handlers == handler) { signal_handlers = handler->next(); } handler->Unlink(); remove = true; oldact_handler = handler->oldact(); any_removed = true; } else { any_remaining = true; } } SignalInfo* next = handler->next(); if (remove) { delete handler; } handler = next; } if (any_removed && !any_remaining) { struct sigaction act = {}; act.sa_handler = oldact_handler; VOID_NO_RETRY_EXPECTED(sigaction(signal, &act, NULL)); } } void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) { ThreadSignalBlocker blocker(kSignalsCount, kSignals); MutexLocker lock(signal_mutex); SignalInfo* handler = signal_handlers; sa_handler_t oldact_handler = SIG_DFL; bool any_remaining = false; intptr_t signal = -1; while (handler != NULL) { bool remove = false; if (handler->fd() == fd) { if ((port == ILLEGAL_PORT) || (handler->port() == port)) { if (signal_handlers == handler) { signal_handlers = handler->next(); } handler->Unlink(); remove = true; signal = handler->signal(); } else { any_remaining = true; } } SignalInfo* next = handler->next(); if (remove) { delete handler; } handler = next; } if ((signal != -1) && !any_remaining) { struct sigaction act = {}; act.sa_handler = oldact_handler; VOID_NO_RETRY_EXPECTED(sigaction(signal, &act, NULL)); } } void ProcessInfoList::Init() { ASSERT(ProcessInfoList::mutex_ == nullptr); ProcessInfoList::mutex_ = new Mutex(); } void ProcessInfoList::Cleanup() { ASSERT(ProcessInfoList::mutex_ != nullptr); delete ProcessInfoList::mutex_; ProcessInfoList::mutex_ = nullptr; } void ExitCodeHandler::Init() { ASSERT(ExitCodeHandler::monitor_ == nullptr); ExitCodeHandler::monitor_ = new Monitor(); } void ExitCodeHandler::Cleanup() { ASSERT(ExitCodeHandler::monitor_ != nullptr); delete ExitCodeHandler::monitor_; ExitCodeHandler::monitor_ = nullptr; } void Process::Init() { ExitCodeHandler::Init(); ProcessInfoList::Init(); ASSERT(signal_mutex == nullptr); signal_mutex = new Mutex(); ASSERT(Process::global_exit_code_mutex_ == nullptr); Process::global_exit_code_mutex_ = new Mutex(); } void Process::Cleanup() { ClearAllSignalHandlers(); ASSERT(signal_mutex != nullptr); delete signal_mutex; signal_mutex = nullptr; ASSERT(Process::global_exit_code_mutex_ != nullptr); delete Process::global_exit_code_mutex_; Process::global_exit_code_mutex_ = nullptr; ProcessInfoList::Cleanup(); ExitCodeHandler::Cleanup(); } } // namespace bin } // namespace dart #endif // defined(HOST_OS_LINUX)