Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 656cfae

Browse files
PickBasaduh95
authored andcommitted
fs: add windowsHandle option to file streams
Fixes: #57288 Signed-off-by: PickBas <sayed.kirill@gmail.com> PR-URL: #63851 Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
1 parent ee5f72c commit 656cfae

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

β€Ždoc/api/fs.mdβ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,9 @@ behavior is similar to `cp dir1/ dir2/`.
29332933
<!-- YAML
29342934
added: v0.1.31
29352935
changes:
2936+
- version: REPLACEME
2937+
pr-url: https://github.com/nodejs/node/pull/63851
2938+
description: Add the `windowsHandle` option.
29362939
- version: v16.10.0
29372940
pr-url: https://github.com/nodejs/node/pull/40013
29382941
description: The `fs` option does not need `open` method if an `fd` was provided.
@@ -2989,6 +2992,8 @@ changes:
29892992
* `highWaterMark` {integer} **Default:** `64 * 1024`
29902993
* `fs` {Object|null} **Default:** `null`
29912994
* `signal` {AbortSignal|null} **Default:** `null`
2995+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to read from, in place
2996+
of `fd`. Windows only. **Default:** `null`
29922997
* Returns: {fs.ReadStream}
29932998
29942999
`options` can include `start` and `end` values to read a range of bytes from
@@ -3009,6 +3014,12 @@ If `fd` points to a character device that only supports blocking reads
30093014
available. This can prevent the process from exiting and the stream from
30103015
closing naturally.
30113016
3017+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3018+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3019+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3020+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3021+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3022+
30123023
By default, the stream will emit a `'close'` event after it has been
30133024
destroyed. Set the `emitClose` option to `false` to change this behavior.
30143025
@@ -3059,6 +3070,9 @@ If `options` is a string, then it specifies the encoding.
30593070
<!-- YAML
30603071
added: v0.1.31
30613072
changes:
3073+
- version: REPLACEME
3074+
pr-url: https://github.com/nodejs/node/pull/63851
3075+
description: Add the `windowsHandle` option.
30623076
- version: v22.0.0
30633077
pr-url: https://github.com/nodejs/node/pull/52037
30643078
description: bump default highWaterMark.
@@ -3123,6 +3137,8 @@ changes:
31233137
[`stream.getDefaultHighWaterMark()`][].
31243138
* `flush` {boolean} If `true`, the underlying file descriptor is flushed
31253139
prior to closing it. **Default:** `false`.
3140+
* `windowsHandle` {bigint} A raw Win32 `HANDLE` value to write to, in place
3141+
of `fd`. Windows only. **Default:** `null`
31263142
* Returns: {fs.WriteStream}
31273143
31283144
`options` may also include a `start` option to allow writing data at some
@@ -3137,6 +3153,12 @@ then the file descriptor won't be closed, even if there's an error.
31373153
It is the application's responsibility to close it and make sure there's no
31383154
file descriptor leak.
31393155
3156+
On Windows, a value passed in `fd` is interpreted as a CRT file descriptor. To
3157+
use a raw Win32 `HANDLE` instead, such as an inherited anonymous pipe handle
3158+
obtained from another process, pass it as `windowsHandle`. The handle is wrapped
3159+
in a file descriptor that the stream owns and closes. The `windowsHandle` option
3160+
throws on non-Windows platforms and cannot be combined with the `fs` option.
3161+
31403162
By default, the stream will emit a `'close'` event after it has been
31413163
destroyed. Set the `emitClose` option to `false` to change this behavior.
31423164

β€Žlib/internal/fs/streams.jsβ€Ž

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ const {
1313
}=primordials;
1414

1515
const{
16+
ERR_FEATURE_UNAVAILABLE_ON_PLATFORM,
17+
ERR_INCOMPATIBLE_OPTION_PAIR,
1618
ERR_INVALID_ARG_TYPE,
1719
ERR_METHOD_NOT_IMPLEMENTED,
20+
ERR_MISSING_OPTION,
1821
ERR_OUT_OF_RANGE,
1922
ERR_STREAM_DESTROYED,
2023
ERR_SYSTEM_ERROR,
2124
}=require('internal/errors').codes;
2225
const{
26+
isWindows,
2327
kEmptyObject,
2428
}=require('internal/util');
2529
const{
@@ -40,6 +44,8 @@ const {
4044
}=require('internal/fs/utils');
4145
const{ Readable, Writable, finished }=require('stream');
4246
const{ toPathIfFileURL }=require('internal/url');
47+
constbinding=internalBinding('fs');
48+
const{O_RDONLY,O_WRONLY}=internalBinding('constants').fs;
4349
constkIoDone=Symbol('kIoDone');
4450
constkIsPerformingIO=Symbol('kIsPerformingIO');
4551

@@ -160,6 +166,26 @@ function importFd(stream, options) {
160166
['number','FileHandle'],options.fd);
161167
}
162168

169+
functionimportWindowsHandle(stream,options,flags){
170+
if(options.windowsHandle==null){
171+
thrownewERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if(!isWindows){
174+
thrownewERR_FEATURE_UNAVAILABLE_ON_PLATFORM('windowsHandle');
175+
}
176+
if(options.fs){
177+
// The HANDLE is wrapped using the real filesystem, so a custom fs
178+
// implementation cannot be combined with it.
179+
thrownewERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if(typeofoptions.windowsHandle!=='bigint'){
182+
thrownewERR_INVALID_ARG_TYPE('options.windowsHandle','bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs]=fs;
186+
returnbinding.handleToFd(options.windowsHandle,flags);
187+
}
188+
163189
functionReadStream(path,options){
164190
if(!(thisinstanceofReadStream))
165191
returnnewReadStream(path,options);
@@ -173,7 +199,11 @@ function ReadStream(path, options) {
173199
options.autoDestroy=false;
174200
}
175201

176-
if(options.fd==null){
202+
if(options.fd!=null&&options.windowsHandle!=null){
203+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
204+
}elseif(options.windowsHandle!=null){
205+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_RDONLY));
206+
}elseif(options.fd==null){
177207
this.fd=null;
178208
this[kFs]=options.fs||fs;
179209
validateFunction(this[kFs].open,'options.fs.open');
@@ -325,7 +355,11 @@ function WriteStream(path, options) {
325355
// Only buffers are supported.
326356
options.decodeStrings=true;
327357

328-
if(options.fd==null){
358+
if(options.fd!=null&&options.windowsHandle!=null){
359+
thrownewERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle','fd');
360+
}elseif(options.windowsHandle!=null){
361+
this.fd=getValidatedFd(importWindowsHandle(this,options,O_WRONLY));
362+
}elseif(options.fd==null){
329363
this.fd=null;
330364
this[kFs]=options.fs||fs;
331365
validateFunction(this[kFs].open,'options.fs.open');

β€Žsrc/node_file.ccβ€Ž

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4150,6 +4150,33 @@ InternalFieldInfoBase* BindingData::Serialize(int index) {
41504150
return info;
41514151
}
41524152

4153+
#ifdef _WIN32
4154+
staticvoidHandleToFd(const FunctionCallbackInfo<Value>& args) {
4155+
Environment* env = Environment::GetCurrent(args);
4156+
CHECK_GE(args.Length(), 1);
4157+
CHECK(args[0]->IsBigInt());
4158+
4159+
int flags = 0;
4160+
if (args[1]->IsNumber()) {
4161+
flags = args[1].As<Int32>()->Value();
4162+
}
4163+
4164+
bool lossless;
4165+
int64_t handle = args[0].As<BigInt>()->Int64Value(&lossless);
4166+
if (!lossless) {
4167+
returnTHROW_ERR_OUT_OF_RANGE(env,
4168+
"windowsHandle does not fit into 64 bits");
4169+
}
4170+
intptr_t value = static_cast<intptr_t>(handle);
4171+
4172+
int fd = _open_osfhandle(value, flags);
4173+
if (fd == -1) {
4174+
return env->ThrowErrnoException(errno, "_open_osfhandle");
4175+
}
4176+
args.GetReturnValue().Set(fd);
4177+
}
4178+
#endif// _WIN32
4179+
41534180
voidBindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
41544181
Local<ObjectTemplate> target) {
41554182
Isolate* isolate = isolate_data->isolate();
@@ -4216,6 +4243,10 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
42164243

42174244
SetMethod(isolate, target, "mkdtemp", Mkdtemp);
42184245

4246+
#ifdef _WIN32
4247+
SetMethod(isolate, target, "handleToFd", HandleToFd);
4248+
#endif
4249+
42194250
SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths);
42204251
SetMethod(isolate, target, "cpSyncOverrideFile", CpSyncOverrideFile);
42214252
SetMethod(isolate, target, "cpSyncCopyDir", CpSyncCopyDir);
@@ -4343,6 +4374,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
43434374
registry->Register(LUTimes);
43444375

43454376
registry->Register(Mkdtemp);
4377+
#ifdef _WIN32
4378+
registry->Register(HandleToFd);
4379+
#endif
43464380
registry->Register(NewFSReqCallback);
43474381

43484382
registry->Register(FileHandle::New);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include<node.h>
2+
#include<v8.h>
3+
4+
#ifdef _WIN32
5+
#include<windows.h>
6+
#endif
7+
8+
namespace {
9+
10+
using v8::BigInt;
11+
using v8::Context;
12+
using v8::FunctionCallbackInfo;
13+
using v8::Isolate;
14+
using v8::Local;
15+
using v8::Object;
16+
using v8::String;
17+
using v8::Value;
18+
19+
// Creates an anonymous pipe and returns its raw Win32 read/write HANDLE values
20+
// as JS bigints. These are NOT CRT file descriptors, so passing them as the
21+
// `windowsHandle` stream option exercises the HANDLE -> fd conversion path on
22+
// Windows. Returns undefined on other platforms.
23+
voidCreatePipeHandles(const FunctionCallbackInfo<Value>& args) {
24+
Isolate* isolate = args.GetIsolate();
25+
#ifdef _WIN32
26+
Local<Context> context = isolate->GetCurrentContext();
27+
28+
HANDLE read_handle = nullptr;
29+
HANDLE write_handle = nullptr;
30+
if (!CreatePipe(&read_handle, &write_handle, nullptr, 0)) {
31+
isolate->ThrowException(v8::Exception::Error(
32+
String::NewFromUtf8(isolate, "CreatePipe failed").ToLocalChecked()));
33+
return;
34+
}
35+
36+
Local<Object> result = Object::New(isolate);
37+
result
38+
->Set(context,
39+
String::NewFromUtf8(isolate, "readHandle").ToLocalChecked(),
40+
BigInt::New(
41+
isolate,
42+
static_cast<int64_t>(reinterpret_cast<intptr_t>(read_handle))))
43+
.Check();
44+
result
45+
->Set(context,
46+
String::NewFromUtf8(isolate, "writeHandle").ToLocalChecked(),
47+
BigInt::New(
48+
isolate,
49+
static_cast<int64_t>(reinterpret_cast<intptr_t>(write_handle))))
50+
.Check();
51+
args.GetReturnValue().Set(result);
52+
#else
53+
args.GetReturnValue().SetUndefined();
54+
#endif
55+
}
56+
57+
} // anonymous namespace
58+
59+
extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(
60+
Local<Object> exports, Local<Value> module, Local<Context> context) {
61+
NODE_SET_METHOD(exports, "createPipeHandles", CreatePipeHandles);
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
// Verifies that fs.createReadStream()/createWriteStream() accept a raw Win32
3+
// HANDLE through the `windowsHandle` option, as happens when a parent process
4+
// passes an inherited anonymous pipe handle. The addon produces such handles
5+
// via CreatePipe(); Node must wrap them in CRT file descriptors instead of
6+
// failing with EBADF.
7+
8+
constcommon=require('../../common');
9+
10+
if(!common.isWindows){
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
constassert=require('assert');
15+
constfs=require('fs');
16+
17+
constbinding=require(`./build/${common.buildType}/binding`);
18+
19+
const{ readHandle, writeHandle }=binding.createPipeHandles();
20+
assert.strictEqual(typeofreadHandle,'bigint');
21+
assert.strictEqual(typeofwriteHandle,'bigint');
22+
23+
constpayload='payload';
24+
25+
constchunks=[];
26+
constrs=fs.createReadStream(null,{windowsHandle: readHandle});
27+
rs.on('error',(err)=>assert.fail(err));
28+
rs.on('data',(chunk)=>chunks.push(chunk));
29+
rs.on('end',common.mustCall(()=>{
30+
assert.strictEqual(Buffer.concat(chunks).toString(),payload);
31+
}));
32+
33+
constws=fs.createWriteStream(null,{windowsHandle: writeHandle});
34+
ws.on('error',(err)=>assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
// Tests option validation for the `windowsHandle` option of
4+
// fs.createReadStream()/createWriteStream(). The functional round-trip on
5+
// Windows (where a real Win32 HANDLE is wrapped in a file descriptor) is
6+
// covered by test/addons/fs-windows-handle.
7+
8+
constcommon=require('../common');
9+
constassert=require('assert');
10+
constfs=require('fs');
11+
12+
consthandle=1n;
13+
14+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
15+
assert.throws(()=>create(null,{windowsHandle: handle,fd: 2}),{
16+
code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
17+
});
18+
}
19+
20+
if(!common.isWindows){
21+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
22+
assert.throws(()=>create(null,{windowsHandle: handle}),{
23+
code: 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM',
24+
});
25+
}
26+
return;
27+
}
28+
29+
for(constcreateof[fs.createReadStream,fs.createWriteStream]){
30+
// Cannot be combined with a custom `fs` implementation.
31+
assert.throws(()=>create(null,{windowsHandle: handle,fs: {}}),{
32+
code: 'ERR_METHOD_NOT_IMPLEMENTED',
33+
});
34+
35+
// Must be a bigint.
36+
assert.throws(()=>create(null,{windowsHandle: 'nope'}),{
37+
code: 'ERR_INVALID_ARG_TYPE',
38+
});
39+
assert.throws(()=>create(null,{windowsHandle: 1}),{
40+
code: 'ERR_INVALID_ARG_TYPE',
41+
});
42+
43+
// Must fit into 64 bits.
44+
assert.throws(()=>create(null,{windowsHandle: 2n**64n}),{
45+
code: 'ERR_OUT_OF_RANGE',
46+
});
47+
}

0 commit comments

Comments
Β (0)