Skip to content
Permalink
Browse files

node-syscall: fix support for Node 12

Some APIs were removed in Node 12, and new "safer" APIs must be used
instead. Perhaps this implementation is not the most elegant or produces
less then ideal error messages, but at least it compiles.

While at it, added an arena type to manage and gracefully free temporary
buffers we allocate for the duration of the call.

I couldn't find any tests for the syscall module, so I wrote a small one.

Closes #935

GitHub-Pull-Request: #949
  • Loading branch information...
nevkontakte authored and dmitshur committed Nov 6, 2019
1 parent d3ddacd commit ce3c9ade29deed38a85f259f40e823cc17213830
Showing with 159 additions and 60 deletions.
  1. +15 −4 node-syscall/binding.gyp
  2. +108 −56 node-syscall/syscall.cc
  3. +36 −0 tests/syscall_test.go
@@ -2,7 +2,18 @@
'targets': [
{
'target_name': 'syscall',
'sources': [ 'syscall.cc' ]
}
]
}
'sources': [ 'syscall.cc' ],

'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'xcode_settings': {
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
},
'msvs_settings': {
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
},
},
],
}
@@ -1,3 +1,6 @@
#include <memory>
#include <stdexcept>
#include <vector>
#include <cstdlib>
#include <node.h>
#include <v8.h>
@@ -13,7 +16,37 @@ using namespace v8;
#define ARRAY_BUFFER_DATA_OFFSET 31
#endif

intptr_t toNative(Local<Value> value) {
// 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::unique_ptr<intptr_t[]>> allocs_;
public:
arena() = default;
virtual ~arena() = default;
arena(const arena& a) = delete;

intptr_t* allocate(size_t n) {
allocs_.emplace_back(new intptr_t[n]);
return allocs_.end()->get();
}
};

// 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");
}

intptr_t toNative(Local<Context> ctx, arena& a, Local<Value> value) {
if (value.IsEmpty()) {
return 0;
}
@@ -23,76 +56,95 @@ intptr_t toNative(Local<Value> value) {
}
if (value->IsArray()) {
Local<Array> array = Local<Array>::Cast(value);
intptr_t* native = reinterpret_cast<intptr_t*>(malloc(array->Length() * sizeof(intptr_t))); // TODO memory leak
intptr_t* native = a.allocate(array->Length());
for (uint32_t i = 0; i < array->Length(); i++) {
native[i] = toNative(array->Get(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>(value->ToInteger()->Value()));

return static_cast<intptr_t>(static_cast<int32_t>(integerOrDie(ctx, value)->Value()));
}

void Syscall(const FunctionCallbackInfo<Value>& info) {
int trap = info[0]->ToInteger()->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];
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;
}
break;
default:
r1 = syscall(
trap,
toNative(info[1]),
toNative(info[2]),
toNative(info[3])
);
break;
}
int err = 0;
if (r1 < 0) {
err = errno;
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;
}
Isolate* isolate = info.GetIsolate();
Local<Array> res = Array::New(isolate, 3);
res->Set(0, Integer::New(isolate, r1));
res->Set(1, Integer::New(isolate, r2));
res->Set(2, Integer::New(isolate, err));
info.GetReturnValue().Set(res);
}

void Syscall6(const FunctionCallbackInfo<Value>& info) {
int r = syscall(
info[0]->ToInteger()->Value(),
toNative(info[1]),
toNative(info[2]),
toNative(info[3]),
toNative(info[4]),
toNative(info[5]),
toNative(info[6])
);
int err = 0;
if (r < 0) {
err = errno;
}
arena a;
Isolate* isolate = info.GetIsolate();
Local<Array> res = Array::New(isolate, 3);
res->Set(0, Integer::New(isolate, r));
res->Set(1, Integer::New(isolate, 0));
res->Set(2, Integer::New(isolate, err));
info.GetReturnValue().Set(res);
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;
}
}

void init(Handle<Object> target) {
NODE_SET_METHOD(target, "Syscall", Syscall);
NODE_SET_METHOD(target, "Syscall6", Syscall6);
void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "Syscall", Syscall);
NODE_SET_METHOD(exports, "Syscall6", Syscall6);
}

NODE_MODULE(syscall, init);
@@ -0,0 +1,36 @@
// +build js

package tests

import (
"io/ioutil"
"os"
"syscall"
"testing"
)

func TestGetpid(t *testing.T) {
pid := syscall.Getpid()
if pid <= 0 {
t.Errorf("Got invalid pid %d. Want: > 0", pid)
} else {
t.Logf("Got pid %d", pid)
}
}

func TestOpen(t *testing.T) {
f, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("Failed to create a temp file: %s", err)
}
f.Close()
defer os.Remove(f.Name())
fd, err := syscall.Open(f.Name(), syscall.O_RDONLY, 0600)
if err != nil {
t.Fatalf("syscall.Open() returned error: %s", err)
}
err = syscall.Close(fd)
if err != nil {
t.Fatalf("syscall.Close() returned error: %s", err)
}
}

0 comments on commit ce3c9ad

Please sign in to comment.
You can’t perform that action at this time.