From f7ba232da4f19f43f7e7699a207c87b2c37c2cb2 Mon Sep 17 00:00:00 2001 From: Philipp Czarnetzki Date: Fri, 15 Dec 2017 20:51:14 +0100 Subject: [PATCH 1/4] Updated dup2 to the NAPI Interface --- .gitignore | 2 + dup2.cc | 126 +++++++++++++++++++++++++++++++++++++++------- dup2.js | 2 +- package-lock.json | 13 +++++ package.json | 17 +++++-- testdup2.js | 25 +++++++-- 6 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index a72b52e..b6bc349 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ results npm-debug.log node_modules +build/ +.vscode \ No newline at end of file diff --git a/dup2.cc b/dup2.cc index 8c57af9..847a2e9 100644 --- a/dup2.cc +++ b/dup2.cc @@ -1,32 +1,120 @@ -#include -#include +#include #include +#include + #ifdef WIN32 #include #else #include #endif -using namespace v8; - -Handle InvokeMethod(const Arguments& args) { - HandleScope scope; - int oldfd = 0, newfd = 0; - int ret = 0; - if(!(args[0]->IsNumber() && args[1]->IsNumber())){ - ThrowException(Exception::TypeError(String::New("FileDescriptor must be number"))); - }else{ - oldfd = args[0]->Uint32Value(); - newfd = args[1]->Uint32Value(); +/** + * Changes a old file descriptor to a new one (rather duplicates it) + */ +napi_value change (napi_env env, napi_callback_info info) { + napi_status status = napi_generic_failure; + napi_deferred deferred; // this will be either resolved or rejected and freed after + napi_value promise; // promise which will be returned + napi_value argv[2]; // arg value + napi_value ret; // refers to the number that was returnd by fcntl + napi_value This; // refers to the js this + + napi_valuetype argType1; + napi_valuetype argType2; + + uint32_t oldfd = 0; + uint32_t newfd = 0; + size_t argc = 2; + + // create promise + status = napi_create_promise(env, &deferred, &promise); + assert(status == napi_ok); + + // get args + status = napi_get_cb_info(env, info, &argc, argv, &This, nullptr); + assert(status == napi_ok); + + // argc is an "in-out" argument of "napi_get_cb_info" you pass the + // expected arg count in and get the actual count so you can compare it + // against something + if (argc < 2) { + napi_throw_range_error(env, nullptr, "The function needs two arguments"); + return nullptr; + } + + // type checking + status = napi_typeof(env, argv[0], &argType1); + if (status != napi_ok) { + napi_throw_type_error(env, nullptr, "Old FileDescriptor must be a Number"); + return nullptr; + } + + status = napi_typeof(env, argv[1], &argType2); + if (status != napi_ok) { + napi_throw_type_error(env, nullptr, "New FileDescriptor must be a Number"); + return nullptr; } + + // get the actual values and convert them to C types + status = napi_get_value_uint32(env, argv[0], &oldfd); + assert(status == napi_ok); + + status = napi_get_value_uint32(env, argv[1], &newfd); + assert(status == napi_ok); + + // all the duplicating magic close(newfd); - ret = fcntl(oldfd, F_DUPFD, newfd); - return scope.Close(Number::New(ret)); + + int retfd = fcntl(oldfd, F_DUPFD, newfd); + + // either resolve or reject with an error + if (newfd == (uint32_t)retfd) { + status = napi_create_uint32(env, retfd, &ret); + assert(status == napi_ok); + + status = napi_resolve_deferred(env, deferred, ret); + assert(status == napi_ok); + } else { + napi_value error; + napi_value nReason; + const char* reason = "Changed FileDescriptor not matches the chosen"; + + status = napi_create_string_utf8(env, reason, sizeof(reason), &nReason); + assert(status == napi_ok); + + status = napi_create_error(env, nullptr, nReason, &error); + assert(status == napi_ok); + + status = napi_reject_deferred(env, deferred, error); + assert(status == napi_ok); + } + + // the deferred object was freed by "napi_resolve_deferred" or + // "napi_reject_deferred" so we can set it to NULL + deferred = NULL; + + // return the promise to the js land so we can do async stuff with it + return promise; } -void init(Handle exports) { - exports->Set(String::NewSymbol("invoke"), - FunctionTemplate::New(InvokeMethod)->GetFunction()); +// helper to create a function on the exports value object +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +/** + * Initializes the module and sets up the change property on the exports object + */ +napi_value init (napi_env env, napi_value exports) { + napi_status status; + + napi_property_descriptor changeDescriptor = DECLARE_NAPI_METHOD("change", change); + + status = napi_define_properties(env, exports, 1, &changeDescriptor); + assert(status == napi_ok); + + return exports; + } -NODE_MODULE(dup2, init) +// NODE_GYP_MODULE_NAME refers to the name of the addon in the binding.gyp file +NAPI_MODULE(NODE_GYP_MODULE_NAME, init) \ No newline at end of file diff --git a/dup2.js b/dup2.js index f2fb6d1..030718d 100644 --- a/dup2.js +++ b/dup2.js @@ -1 +1 @@ -module.exports = require("./build/Release/dup2"); +module.exports = require('bindings')("dup2"); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c0e4c44 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "node-dup2", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + } + } +} diff --git a/package.json b/package.json index d0f2e92..8fc074c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-dup2", - "version": "0.0.1", + "version": "1.0.0", "description": "I achieve dup2 of C", "main": "dup2.js", "scripts": { @@ -8,12 +8,23 @@ "install": "node-gyp rebuild" }, "keywords": [ - "dup2", "stdout" + "dup2", + "stdout", + "napi" ], "repository": { "url": "https://github.com/solidco2/node-dup2" }, "author": "Solid", + "contributors": [ + { + "name": "Philipp 'luii' Czarnetzki", + "url": "https://github.com/luii" + } + ], "license": "ISC", - "gypfile": true + "gypfile": true, + "dependencies": { + "bindings": "^1.3.0" + } } diff --git a/testdup2.js b/testdup2.js index 53b4c25..72a5878 100644 --- a/testdup2.js +++ b/testdup2.js @@ -1,6 +1,21 @@ #!/usr/bin/env node -var dup2 = require("./build/Release/dup2"); -var fs = require("fs"); -var fd = fs.openSync("test.log", "a"); -dup2.invoke(fd, 1); -console.log("hello, file"); + +let fs = require('fs'); +let dup2 = require('bindings')('dup2'); + +async function test () { + + let fd = fs.openSync('test.log', 'a'); + let newfd; + + try { + newfd = await dup2.change(fd, 2); + } catch (error) { + throw(error); + } + + console.log('hello, file'); + console.log('New FileDescriptor:', newfd); +} + +test() \ No newline at end of file From 674e5f9ca3f94b10e1fbd701e02ee2a845a44570 Mon Sep 17 00:00:00 2001 From: Philipp Czarnetzki Date: Fri, 15 Dec 2017 21:09:44 +0100 Subject: [PATCH 2/4] Updated README.md --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++------ testdup2.js | 2 +- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e71a03a..c728320 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,46 @@ node-dup2 ========= -There isn't any way for us to redirect #1(fd) to any other file, before I born. +There wasn't any way for us to redirect #1(fd) to any other file, before I was born. -好吧,我是说,node里面没有dup2函数(当然你可以先关掉一个文件描述符,然后立即打开一个文件),我的出现就是用来提供dup2支持的。 -Demo -require("node-dup2").invoke(oldfd, newfd); -此句会先关掉newfd文件描述符,而克隆oldfd的文件打开状态到newfd上。 -失败时返回-1 +Example +------- + +```js +const dup = require('dup') +const fs = require('fs') + +let test = async () => { + try { + let oldfd = fs.openSync('myFile.dat', 'a') + let newfd = await dup.change(oldfd, 1) + } catch (error) { + throw error + } + + console.log('New FileDescriptor:', newfd); +} + +test() // --> New FileDescriptor: 1 +``` + +API +------------- + +```js +const dup = require('dup') +``` + +### .change(oldfd, newfd) + +- oldfd: The old file descriptor +- newfd: The new file descriptor +- Returns a resolved promise with the new file descriptor +- Returns a rejected promise with an Error when the File descriptor cant be changed and it dont match with the new File descriptor + + +Testing +------- + +```bash +$ npm test +``` \ No newline at end of file diff --git a/testdup2.js b/testdup2.js index 72a5878..de0b8a1 100644 --- a/testdup2.js +++ b/testdup2.js @@ -11,7 +11,7 @@ async function test () { try { newfd = await dup2.change(fd, 2); } catch (error) { - throw(error); + throw error; } console.log('hello, file'); From c0b06d183aa9a8866143b2b1e3f2c23c1c0bc243 Mon Sep 17 00:00:00 2001 From: Philipp Czarnetzki Date: Fri, 15 Dec 2017 22:05:39 +0100 Subject: [PATCH 3/4] Update from fcntl to dup2 Update from fcntl to dup2 with proper errno return codes; Return rejected promise if argc not matches; Return rejected promise if the args aren't numbers --- dup2.cc | 67 +++++++++++++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/dup2.cc b/dup2.cc index 847a2e9..361166c 100644 --- a/dup2.cc +++ b/dup2.cc @@ -1,6 +1,8 @@ #include #include #include +#include +#include #ifdef WIN32 #include @@ -37,22 +39,49 @@ napi_value change (napi_env env, napi_callback_info info) { // argc is an "in-out" argument of "napi_get_cb_info" you pass the // expected arg count in and get the actual count so you can compare it // against something - if (argc < 2) { - napi_throw_range_error(env, nullptr, "The function needs two arguments"); - return nullptr; + if (argc != 2) { + napi_value error; + napi_value nReason; + const char* reason = "The function needs two arguments"; + + status = napi_create_string_utf8(env, reason, 32, &nReason); + assert(status == napi_ok); + + status = napi_create_error(env, nullptr, nReason, &error); + assert(status == napi_ok); + + status = napi_reject_deferred(env, deferred, error); + assert(status == napi_ok); + + deferred = NULL; + + return promise; } // type checking status = napi_typeof(env, argv[0], &argType1); - if (status != napi_ok) { - napi_throw_type_error(env, nullptr, "Old FileDescriptor must be a Number"); - return nullptr; - } + assert(status == napi_ok); status = napi_typeof(env, argv[1], &argType2); - if (status != napi_ok) { - napi_throw_type_error(env, nullptr, "New FileDescriptor must be a Number"); - return nullptr; + assert(status == napi_ok); + + if (argType1 != napi_number || argType2 != napi_number) { + napi_value error; + napi_value nReason; + const char* reason = "The FileDescriptor must be a Number"; + + status = napi_create_string_utf8(env, reason, 35, &nReason); + assert(status == napi_ok); + + status = napi_create_error(env, nullptr, nReason, &error); + assert(status == napi_ok); + + status = napi_reject_deferred(env, deferred, error); + assert(status == napi_ok); + + deferred = NULL; + + return promise; } // get the actual values and convert them to C types @@ -63,10 +92,10 @@ napi_value change (napi_env env, napi_callback_info info) { assert(status == napi_ok); // all the duplicating magic - close(newfd); + close(newfd); // close the new file descriptor first if its occupied + + int retfd = dup2(oldfd, newfd); - int retfd = fcntl(oldfd, F_DUPFD, newfd); - // either resolve or reject with an error if (newfd == (uint32_t)retfd) { status = napi_create_uint32(env, retfd, &ret); @@ -74,15 +103,19 @@ napi_value change (napi_env env, napi_callback_info info) { status = napi_resolve_deferred(env, deferred, ret); assert(status == napi_ok); - } else { + } else if (retfd == -1 && errno) { napi_value error; + napi_value nErrno; napi_value nReason; - const char* reason = "Changed FileDescriptor not matches the chosen"; + char* reason = strerror(errno); - status = napi_create_string_utf8(env, reason, sizeof(reason), &nReason); + status = napi_create_int32(env, (int32_t)errno, &nErrno); assert(status == napi_ok); - status = napi_create_error(env, nullptr, nReason, &error); + status = napi_create_string_utf8(env, (const char*)reason, sizeof(reason), &nReason); + assert(status == napi_ok); + + status = napi_create_error(env, nErrno, nReason, &error); assert(status == napi_ok); status = napi_reject_deferred(env, deferred, error); diff --git a/package.json b/package.json index 8fc074c..383875d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "I achieve dup2 of C", "main": "dup2.js", "scripts": { - "test": "testdup2.js", + "test": "node testdup2.js", "install": "node-gyp rebuild" }, "keywords": [ From f08c4f457b97a45faef6c45245b8c67c54047e45 Mon Sep 17 00:00:00 2001 From: Philipp Czarnetzki Date: Fri, 15 Dec 2017 22:07:00 +0100 Subject: [PATCH 4/4] 1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 383875d..db5918c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-dup2", - "version": "1.0.0", + "version": "1.0.1", "description": "I achieve dup2 of C", "main": "dup2.js", "scripts": {