blob: be4fb786de8ec859df23e440d6d1868192956134 [file]
#include <memory>
#include <stdexcept>
#include <vector>
#include <cstdlib>
#include <node.h>
#include <v8.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <iostream>
using namespace v8;
// arena stores buffers we allocate for data passed to syscalls.
//
// This object lives for the duration of Syscall() or Syscall6() and correctly
// frees all allocated buffers at the end. This is necessary to avoid memory
// leaks on each call.
class arena {
std::vector<std::vector<uint8_t>> allocs_;
public:
arena() = default;
virtual ~arena() = default;
arena(const arena& a) = delete;
void* allocate(size_t n) {
allocs_.emplace_back(n); // Allocate a new vector of n byte size.
return allocs_[allocs_.size() - 1].data(); // Return the pointer to its data buffer;
}
};
// Cast value to integer or throw an exception.
//
// The exception must be explicitly caught up the call stack, since the Node API
// this extension is using doesn't seem to handle exceptions.
Local<Integer> integerOrDie(Local<Context> ctx, Local<Value> value) {
Local<Integer> integer;
if (value->ToInteger(ctx).ToLocal(&integer)) {
return integer;
}
throw std::runtime_error("expected integer, got something else");
}
// Transforms a JS value into a native value that can be passed to the syscall() call.
intptr_t toNative(Local<Context> ctx, arena& a, Local<Value> value) {
if (value.IsEmpty()) {
return 0;
}
if (value->IsArrayBufferView()) {
Local<ArrayBufferView> view = Local<ArrayBufferView>::Cast(value);
void* native = a.allocate(view->ByteLength());
view->CopyContents(native, view->ByteLength());
return reinterpret_cast<intptr_t>(native);
}
if (value->IsArray()) {
Local<Array> array = Local<Array>::Cast(value);
intptr_t* native = reinterpret_cast<intptr_t*>(a.allocate(array->Length() * sizeof(intptr_t)));
for (uint32_t i = 0; i < array->Length(); i++) {
native[i] = toNative(ctx, a, array->Get(ctx, i).ToLocalChecked());
}
return reinterpret_cast<intptr_t>(native);
}
return static_cast<intptr_t>(static_cast<int32_t>(integerOrDie(ctx, value)->Value()));
}
void Syscall(const FunctionCallbackInfo<Value>& info) {
arena a;
Isolate* isolate = info.GetIsolate();
Local<Context> ctx = isolate->GetCurrentContext();
try {
int trap = integerOrDie(ctx, info[0])->Value();
int r1 = 0, r2 = 0;
switch (trap) {
case SYS_fork:
r1 = fork();
break;
case SYS_pipe:
int fd[2];
r1 = pipe(fd);
if (r1 == 0) {
r1 = fd[0];
r2 = fd[1];
}
break;
default:
r1 = syscall(
trap,
toNative(ctx, a, info[1]),
toNative(ctx, a, info[2]),
toNative(ctx, a, info[3])
);
break;
}
int err = 0;
if (r1 < 0) {
err = errno;
}
Local<Array> res = Array::New(isolate, 3);
res->Set(ctx, 0, Integer::New(isolate, r1)).ToChecked();
res->Set(ctx, 1, Integer::New(isolate, r2)).ToChecked();
res->Set(ctx, 2, Integer::New(isolate, err)).ToChecked();
info.GetReturnValue().Set(res);
} catch (std::exception& e) {
auto message = String::NewFromUtf8(isolate, e.what(), NewStringType::kNormal).ToLocalChecked();
isolate->ThrowException(Exception::TypeError(message));
return;
}
}
void Syscall6(const FunctionCallbackInfo<Value>& info) {
arena a;
Isolate* isolate = info.GetIsolate();
Local<Context> ctx = Context::New(isolate);
try {
int r = syscall(
integerOrDie(ctx, info[0])->Value(),
toNative(ctx, a, info[1]),
toNative(ctx, a, info[2]),
toNative(ctx, a, info[3]),
toNative(ctx, a, info[4]),
toNative(ctx, a, info[5]),
toNative(ctx, a, info[6])
);
int err = 0;
if (r < 0) {
err = errno;
}
Local<Array> res = Array::New(isolate, 3);
res->Set(ctx, 0, Integer::New(isolate, r)).ToChecked();
res->Set(ctx, 1, Integer::New(isolate, 0)).ToChecked();
res->Set(ctx, 2, Integer::New(isolate, err)).ToChecked();
info.GetReturnValue().Set(res);
} catch (std::exception& e) {
auto message = String::NewFromUtf8(isolate, e.what(), NewStringType::kNormal).ToLocalChecked();
isolate->ThrowException(Exception::TypeError(message));
return;
}
}
extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
NODE_SET_METHOD(exports, "Syscall", Syscall);
NODE_SET_METHOD(exports, "Syscall6", Syscall6);
}