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/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/dup2.cc b/dup2.cc index 8c57af9..361166c 100644 --- a/dup2.cc +++ b/dup2.cc @@ -1,32 +1,153 @@ -#include -#include +#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_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); + assert(status == napi_ok); + + status = napi_typeof(env, argv[1], &argType2); + 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; } - close(newfd); - ret = fcntl(oldfd, F_DUPFD, newfd); - return scope.Close(Number::New(ret)); + + // 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); // close the new file descriptor first if its occupied + + int retfd = dup2(oldfd, 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 if (retfd == -1 && errno) { + napi_value error; + napi_value nErrno; + napi_value nReason; + char* reason = strerror(errno); + + status = napi_create_int32(env, (int32_t)errno, &nErrno); + assert(status == napi_ok); + + 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); + 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..db5918c 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,30 @@ { "name": "node-dup2", - "version": "0.0.1", + "version": "1.0.1", "description": "I achieve dup2 of C", "main": "dup2.js", "scripts": { - "test": "testdup2.js", + "test": "node testdup2.js", "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..de0b8a1 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