fs: add windowsHandle option to file streams · nodejs/node@656cfae · GitHub
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

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+
const binding = internalBinding('fs');
48+
const { O_RDONLY, O_WRONLY } = internalBinding('constants').fs;
4349
const kIoDone = Symbol('kIoDone');
4450
const kIsPerformingIO = Symbol('kIsPerformingIO');
4551

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

169+
function importWindowsHandle(stream, options, flags) {
170+
if (options.windowsHandle == null) {
171+
throw new ERR_MISSING_OPTION('options.windowsHandle');
172+
}
173+
if (!isWindows) {
174+
throw new ERR_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+
throw new ERR_METHOD_NOT_IMPLEMENTED('windowsHandle with fs');
180+
}
181+
if (typeof options.windowsHandle !== 'bigint') {
182+
throw new ERR_INVALID_ARG_TYPE('options.windowsHandle', 'bigint',
183+
options.windowsHandle);
184+
}
185+
stream[kFs] = fs;
186+
return binding.handleToFd(options.windowsHandle, flags);
187+
}
188+
163189
function ReadStream(path, options) {
164190
if (!(this instanceof ReadStream))
165191
return new ReadStream(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+
throw new ERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle', 'fd');
204+
} else if (options.windowsHandle != null) {
205+
this.fd = getValidatedFd(importWindowsHandle(this, options, O_RDONLY));
206+
} else if (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+
throw new ERR_INCOMPATIBLE_OPTION_PAIR('windowsHandle', 'fd');
360+
} else if (options.windowsHandle != null) {
361+
this.fd = getValidatedFd(importWindowsHandle(this, options, O_WRONLY));
362+
} else if (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+
static void HandleToFd(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+
return THROW_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
void BindingData::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+
void CreatePipeHandles(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_EXPORT void NODE_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+
const common = require('../../common');
9+
10+
if (!common.isWindows) {
11+
common.skip('windowsHandle is Windows-only');
12+
}
13+
14+
const assert = require('assert');
15+
const fs = require('fs');
16+
17+
const binding = require(`./build/${common.buildType}/binding`);
18+
19+
const { readHandle, writeHandle } = binding.createPipeHandles();
20+
assert.strictEqual(typeof readHandle, 'bigint');
21+
assert.strictEqual(typeof writeHandle, 'bigint');
22+
23+
const payload = 'payload';
24+
25+
const chunks = [];
26+
const rs = 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+
const ws = fs.createWriteStream(null, { windowsHandle: writeHandle });
34+
ws.on('error', (err) => assert.fail(err));
35+
ws.end(payload);
Lines changed: 47 additions & 0 deletions

0 commit comments

Comments
 (0)