7b25ce88d3
Use VmInteropHandler for invoking sub commands instead of running them in an isolate. Running sub commands in an isolate causes an increased footprint. Changing this to use VmInteropHandler avoids the additional memory footprint. Commands that need to use an AOT runtime for execution now exec the AOT runtime and run the command. TEST=ci Change-Id: Ic96845b19951170effea3dd3619f798e2c72968a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402781 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Siva Annamalai <asiva@google.com>
413 lines
11 KiB
C++
413 lines
11 KiB
C++
// 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.
|
|
|
|
#ifndef RUNTIME_BIN_PROCESS_H_
|
|
#define RUNTIME_BIN_PROCESS_H_
|
|
|
|
#include <errno.h>
|
|
|
|
#include "bin/builtin.h"
|
|
#include "bin/io_buffer.h"
|
|
#include "bin/lockers.h"
|
|
#include "bin/namespace.h"
|
|
#include "bin/thread.h"
|
|
#include "platform/globals.h"
|
|
#if !defined(DART_HOST_OS_WINDOWS)
|
|
#include "platform/signal_blocker.h"
|
|
#endif
|
|
#include "platform/utils.h"
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
class ProcessResult {
|
|
public:
|
|
ProcessResult() : exit_code_(0) {}
|
|
|
|
void set_stdout_data(Dart_Handle stdout_data) { stdout_data_ = stdout_data; }
|
|
void set_stderr_data(Dart_Handle stderr_data) { stderr_data_ = stderr_data; }
|
|
|
|
void set_exit_code(intptr_t exit_code) { exit_code_ = exit_code; }
|
|
|
|
Dart_Handle stdout_data() { return stdout_data_; }
|
|
Dart_Handle stderr_data() { return stderr_data_; }
|
|
intptr_t exit_code() { return exit_code_; }
|
|
|
|
private:
|
|
Dart_Handle stdout_data_;
|
|
Dart_Handle stderr_data_;
|
|
intptr_t exit_code_;
|
|
|
|
DISALLOW_ALLOCATION();
|
|
};
|
|
|
|
// To be kept in sync with ProcessSignal consts in sdk/lib/io/process.dart
|
|
// Note that this map is as on Linux.
|
|
enum ProcessSignals {
|
|
kSighup = 1,
|
|
kSigint = 2,
|
|
kSigquit = 3,
|
|
kSigill = 4,
|
|
kSigtrap = 5,
|
|
kSigabrt = 6,
|
|
kSigbus = 7,
|
|
kSigfpe = 8,
|
|
kSigkill = 9,
|
|
kSigusr1 = 10,
|
|
kSigsegv = 11,
|
|
kSigusr2 = 12,
|
|
kSigpipe = 13,
|
|
kSigalrm = 14,
|
|
kSigterm = 15,
|
|
kSigchld = 17,
|
|
kSigcont = 18,
|
|
kSigstop = 19,
|
|
kSigtstp = 20,
|
|
kSigttin = 21,
|
|
kSigttou = 22,
|
|
kSigurg = 23,
|
|
kSigxcpu = 24,
|
|
kSigxfsz = 25,
|
|
kSigvtalrm = 26,
|
|
kSigprof = 27,
|
|
kSigwinch = 28,
|
|
kSigpoll = 29,
|
|
kSigsys = 31,
|
|
kLastSignal = kSigsys,
|
|
};
|
|
|
|
// To be kept in sync with ProcessStartMode consts in sdk/lib/io/process.dart.
|
|
enum ProcessStartMode {
|
|
kNormal = 0,
|
|
kInheritStdio = 1,
|
|
kDetached = 2,
|
|
kDetachedWithStdio = 3,
|
|
};
|
|
|
|
class Process {
|
|
public:
|
|
static void Init();
|
|
static void Cleanup();
|
|
|
|
// Start a new process providing access to stdin, stdout, stderr and
|
|
// process exit streams.
|
|
static int Start(Namespace* namespc,
|
|
const char* path,
|
|
const 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_handler,
|
|
char** os_error_message);
|
|
|
|
static bool Wait(intptr_t id,
|
|
intptr_t in,
|
|
intptr_t out,
|
|
intptr_t err,
|
|
intptr_t exit_handler,
|
|
ProcessResult* result);
|
|
|
|
// Exec process.
|
|
// On systems that support 'exec' it will use it to replace
|
|
// the current process image with the image corresponding to 'path'
|
|
// On systems that do not support it (Windows) it will start in a
|
|
// child process in the same group as the parent so that when the parent
|
|
// is killed the child also dies.
|
|
// Returns 0 if the process could be execed successfully
|
|
// Returns -1 if the exec could not be done successfully and 'errmsg'
|
|
// points to the error message
|
|
static int Exec(Namespace* namespc,
|
|
const char* path,
|
|
const char* arguments[],
|
|
intptr_t arguments_length,
|
|
const char* working_directory,
|
|
char* errmsg,
|
|
intptr_t errmsg_len);
|
|
|
|
// Kill a process with a given pid.
|
|
static bool Kill(intptr_t id, int signal);
|
|
|
|
// Terminate the exit code handler thread. Does not return before
|
|
// the thread has terminated.
|
|
static void TerminateExitCodeHandler();
|
|
|
|
static int GlobalExitCode() {
|
|
MutexLocker ml(global_exit_code_mutex_);
|
|
return global_exit_code_;
|
|
}
|
|
|
|
static void SetGlobalExitCode(int exit_code) {
|
|
MutexLocker ml(global_exit_code_mutex_);
|
|
global_exit_code_ = exit_code;
|
|
}
|
|
|
|
typedef void (*ExitHook)(int64_t exit_code);
|
|
static void SetExitHook(ExitHook hook) { exit_hook_ = hook; }
|
|
static void RunExitHook(int64_t exit_code) {
|
|
if (exit_hook_ != nullptr) {
|
|
exit_hook_(exit_code);
|
|
}
|
|
}
|
|
|
|
static intptr_t CurrentProcessId();
|
|
|
|
static intptr_t SetSignalHandler(intptr_t signal);
|
|
// When there is a current Isolate and the 'port' argument is
|
|
// Dart_GetMainPortId(), this clears the signal handler for the current
|
|
// isolate. When 'port' is ILLEGAL_PORT, this clears all signal handlers for
|
|
// 'signal' for all Isolates.
|
|
static void ClearSignalHandler(intptr_t signal, Dart_Port port);
|
|
static void ClearSignalHandlerByFd(intptr_t fd, Dart_Port port);
|
|
static void ClearAllSignalHandlers();
|
|
|
|
static Dart_Handle GetProcessIdNativeField(Dart_Handle process,
|
|
intptr_t* pid);
|
|
static Dart_Handle SetProcessIdNativeField(Dart_Handle process, intptr_t pid);
|
|
|
|
static int64_t CurrentRSS();
|
|
static int64_t MaxRSS();
|
|
static void GetRSSInformation(int64_t* max_rss, int64_t* current_rss);
|
|
|
|
static bool ModeIsAttached(ProcessStartMode mode);
|
|
static bool ModeHasStdio(ProcessStartMode mode);
|
|
|
|
private:
|
|
static int global_exit_code_;
|
|
static Mutex* global_exit_code_mutex_;
|
|
static ExitHook exit_hook_;
|
|
|
|
DISALLOW_ALLOCATION();
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(Process);
|
|
};
|
|
|
|
typedef void (*sa_handler_t)(int);
|
|
|
|
class SignalInfo {
|
|
public:
|
|
SignalInfo(intptr_t fd,
|
|
intptr_t signal,
|
|
sa_handler_t oldact,
|
|
SignalInfo* next)
|
|
: fd_(fd),
|
|
signal_(signal),
|
|
oldact_(oldact),
|
|
// SignalInfo is expected to be created when in a isolate.
|
|
port_(Dart_GetMainPortId()),
|
|
next_(next),
|
|
prev_(nullptr) {
|
|
if (next_ != nullptr) {
|
|
next_->prev_ = this;
|
|
}
|
|
}
|
|
|
|
~SignalInfo();
|
|
|
|
void Unlink() {
|
|
if (prev_ != nullptr) {
|
|
prev_->next_ = next_;
|
|
}
|
|
if (next_ != nullptr) {
|
|
next_->prev_ = prev_;
|
|
}
|
|
}
|
|
|
|
intptr_t fd() const { return fd_; }
|
|
intptr_t signal() const { return signal_; }
|
|
sa_handler_t oldact() const { return oldact_; }
|
|
Dart_Port port() const { return port_; }
|
|
SignalInfo* next() const { return next_; }
|
|
|
|
private:
|
|
intptr_t fd_;
|
|
intptr_t signal_;
|
|
sa_handler_t oldact_;
|
|
// The port_ is used to identify what isolate the signal-info belongs to.
|
|
Dart_Port port_;
|
|
SignalInfo* next_;
|
|
SignalInfo* prev_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SignalInfo);
|
|
};
|
|
|
|
// Utility class for collecting the output when running a process
|
|
// synchronously by using Process::Wait. This class is sub-classed in
|
|
// the platform specific files to implement reading into the buffers
|
|
// allocated.
|
|
class BufferListBase {
|
|
protected:
|
|
static constexpr intptr_t kBufferSize = 16 * 1024;
|
|
|
|
class BufferListNode {
|
|
public:
|
|
explicit BufferListNode(intptr_t size) {
|
|
data_ = new uint8_t[size];
|
|
// We check for a failed allocation below in Allocate()
|
|
next_ = nullptr;
|
|
}
|
|
|
|
~BufferListNode() { delete[] data_; }
|
|
|
|
bool Valid() const { return data_ != nullptr; }
|
|
|
|
uint8_t* data() const { return data_; }
|
|
BufferListNode* next() const { return next_; }
|
|
void set_next(BufferListNode* n) { next_ = n; }
|
|
|
|
private:
|
|
uint8_t* data_;
|
|
BufferListNode* next_;
|
|
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(BufferListNode);
|
|
};
|
|
|
|
public:
|
|
BufferListBase()
|
|
: head_(nullptr), tail_(nullptr), data_size_(0), free_size_(0) {}
|
|
~BufferListBase() {
|
|
Free();
|
|
DEBUG_ASSERT(IsEmpty());
|
|
}
|
|
|
|
// Returns the collected data as a Uint8List. If an error occurs an
|
|
// error handle is returned.
|
|
Dart_Handle GetData() {
|
|
uint8_t* buffer;
|
|
intptr_t buffer_position = 0;
|
|
Dart_Handle result = IOBuffer::Allocate(data_size_, &buffer);
|
|
if (Dart_IsNull(result)) {
|
|
return DartUtils::NewDartOSError();
|
|
}
|
|
if (Dart_IsError(result)) {
|
|
Free();
|
|
return result;
|
|
}
|
|
for (BufferListNode* current = head_; current != nullptr;
|
|
current = current->next()) {
|
|
intptr_t to_copy = dart::Utils::Minimum(data_size_, kBufferSize);
|
|
memmove(buffer + buffer_position, current->data(), to_copy);
|
|
buffer_position += to_copy;
|
|
data_size_ -= to_copy;
|
|
}
|
|
ASSERT(data_size_ == 0);
|
|
Free();
|
|
return result;
|
|
}
|
|
|
|
#if defined(DEBUG)
|
|
bool IsEmpty() const { return (head_ == nullptr) && (tail_ == nullptr); }
|
|
#endif
|
|
|
|
protected:
|
|
bool Allocate() {
|
|
ASSERT(free_size_ == 0);
|
|
BufferListNode* node = new BufferListNode(kBufferSize);
|
|
if ((node == nullptr) || !node->Valid()) {
|
|
// Failed to allocate a buffer for the node.
|
|
delete node;
|
|
return false;
|
|
}
|
|
if (head_ == nullptr) {
|
|
head_ = node;
|
|
tail_ = node;
|
|
} else {
|
|
ASSERT(tail_->next() == nullptr);
|
|
tail_->set_next(node);
|
|
tail_ = node;
|
|
}
|
|
free_size_ = kBufferSize;
|
|
return true;
|
|
}
|
|
|
|
void Free() {
|
|
BufferListNode* current = head_;
|
|
while (current != nullptr) {
|
|
BufferListNode* tmp = current;
|
|
current = current->next();
|
|
delete tmp;
|
|
}
|
|
head_ = nullptr;
|
|
tail_ = nullptr;
|
|
data_size_ = 0;
|
|
free_size_ = 0;
|
|
}
|
|
|
|
// Returns the address of the first byte in the free space.
|
|
uint8_t* FreeSpaceAddress() {
|
|
return tail_->data() + (kBufferSize - free_size_);
|
|
}
|
|
|
|
intptr_t data_size() const { return data_size_; }
|
|
void set_data_size(intptr_t size) { data_size_ = size; }
|
|
|
|
intptr_t free_size() const { return free_size_; }
|
|
void set_free_size(intptr_t size) { free_size_ = size; }
|
|
|
|
BufferListNode* head() const { return head_; }
|
|
BufferListNode* tail() const { return tail_; }
|
|
|
|
private:
|
|
// Linked list for data collected.
|
|
BufferListNode* head_;
|
|
BufferListNode* tail_;
|
|
|
|
// Number of bytes of data collected in the linked list.
|
|
intptr_t data_size_;
|
|
|
|
// Number of free bytes in the last node in the list.
|
|
intptr_t free_size_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(BufferListBase);
|
|
};
|
|
|
|
#if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA) || \
|
|
defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS)
|
|
class BufferList : public BufferListBase {
|
|
public:
|
|
BufferList() {}
|
|
|
|
bool Read(int fd, intptr_t available) {
|
|
// Read all available bytes.
|
|
while (available > 0) {
|
|
if (free_size() == 0) {
|
|
if (!Allocate()) {
|
|
errno = ENOMEM;
|
|
return false;
|
|
}
|
|
}
|
|
ASSERT(free_size() > 0);
|
|
ASSERT(free_size() <= kBufferSize);
|
|
intptr_t block_size = dart::Utils::Minimum(free_size(), available);
|
|
#if defined(DART_HOST_OS_FUCHSIA)
|
|
intptr_t bytes = NO_RETRY_EXPECTED(
|
|
read(fd, reinterpret_cast<void*>(FreeSpaceAddress()), block_size));
|
|
#else
|
|
intptr_t bytes = TEMP_FAILURE_RETRY(
|
|
read(fd, reinterpret_cast<void*>(FreeSpaceAddress()), block_size));
|
|
#endif // defined(DART_HOST_OS_FUCHSIA)
|
|
if (bytes < 0) {
|
|
return false;
|
|
}
|
|
set_data_size(data_size() + bytes);
|
|
set_free_size(free_size() - bytes);
|
|
available -= bytes;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(BufferList);
|
|
};
|
|
#endif // defined(DART_HOST_OS_ANDROID) ...
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_BIN_PROCESS_H_
|