diff --git a/lib/fs.js b/lib/fs.js index 81c4d2b9c884..f3b1fea90b81 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -25,6 +25,7 @@ 'use strict'; const { + Array, ArrayFromAsync, ArrayPrototypePush, BigIntPrototypeToString, @@ -94,7 +95,13 @@ const { defineLazyProperties, isWindows, isMacOS, + normalizeEncoding, } = require('internal/util'); + +function isUtf8Encoding(encoding) { + return typeof encoding === 'string' && normalizeEncoding(encoding) === 'utf8'; +} + const { constants: { kIoMaxLength, @@ -530,12 +537,18 @@ function readFileSync(path, options) { validateReadFileBufferOptions(options); const hasUserBuffer = options.buffer !== undefined; - if ((options.encoding === 'utf8' || options.encoding === 'utf-8') && - !hasUserBuffer) { + if (!hasUserBuffer) { if (!isInt32(path)) { path = getValidatedPath(path); } - return binding.readFileUtf8(path, stringToFlags(options.flag)); + const flags = stringToFlags(options.flag); + // C++ fast paths: single native call instead of open/stat/read/close. + if (isUtf8Encoding(options.encoding)) { + return binding.readFileUtf8(path, flags); + } + if (options.encoding === undefined || options.encoding === null) { + return binding.readFileBuffer(path, flags); + } } const isUserFd = isFd(path); // File descriptor ownership @@ -1755,45 +1768,35 @@ function handleFilePaths({ result, currentPath, context }) { } /** - * An iterative algorithm for reading the entire contents of the `basePath` directory. - * This function does not validate `basePath` as a directory. It is passed directly to - * `binding.readdir`. - * @param {string} basePath + * Reads the entire contents of the `basePath` directory recursively. + * This function does not validate `basePath` as a directory. It is passed + * directly to `binding.readdirRecursiveSync`. + * @param {string | Buffer} basePath * @param {{ encoding: string, withFileTypes: boolean }} options - * @returns {string[] | Dirent[]} + * @returns {string[] | Buffer[] | Dirent[]} */ function readdirSyncRecursive(basePath, options) { - const context = { - withFileTypes: Boolean(options.withFileTypes), - encoding: options.encoding, + const withFileTypes = Boolean(options.withFileTypes); + // C++ walk uses scandir dirent types and avoids per-entry stat() for the + // common case (known directory/file types). + const result = binding.readdirRecursiveSync( basePath, - readdirResults: [], - pathsQueue: [basePath], - }; - - function read(path) { - const readdirResult = binding.readdir( - path, - context.encoding, - context.withFileTypes, - ); - - if (readdirResult === undefined) { - return; - } - - processReaddirResult({ - result: readdirResult, - currentPath: path, - context, - }); + options.encoding, + withFileTypes, + ); + if (result === undefined) { + return undefined; } - - for (let i = 0; i < context.pathsQueue.length; i++) { - read(context.pathsQueue[i]); + if (!withFileTypes) { + return result; } - - return context.readdirResults; + // result = [names, types, parentPaths] + const { 0: names, 1: types, 2: parentPaths } = result; + const out = new Array(names.length); + for (let i = 0; i < names.length; i++) { + out[i] = new Dirent(names[i], types[i], parentPaths[i]); + } + return out; } /** @@ -2905,7 +2908,7 @@ function writeFileSync(path, data, options) { const flag = options.flag || 'w'; // C++ fast path for string data and UTF8 encoding - if (typeof data === 'string' && (options.encoding === 'utf8' || options.encoding === 'utf-8')) { + if (typeof data === 'string' && isUtf8Encoding(options.encoding)) { if (!isInt32(path)) { path = getValidatedPath(path); } @@ -2918,6 +2921,19 @@ function writeFileSync(path, data, options) { ); } + // C++ fast path for Buffer / TypedArray payloads (no flush option). + if (isArrayBufferView(data) && !flush) { + if (!isInt32(path)) { + path = getValidatedPath(path); + } + return binding.writeFileBuffer( + path, + data, + stringToFlags(flag), + parseFileMode(options.mode, 'mode', 0o666), + ); + } + if (!isArrayBufferView(data)) { validateStringAfterArrayBufferView(data, 'data'); data = Buffer.from(data, options.encoding || 'utf8'); diff --git a/src/node_errors.h b/src/node_errors.h index 62cfba88f00d..3c67516ea131 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -86,6 +86,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); V(ERR_FS_CP_NON_DIR_TO_DIR, Error) \ V(ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY, Error) \ V(ERR_FS_EISDIR, Error) \ + V(ERR_FS_FILE_TOO_LARGE, RangeError) \ V(ERR_FS_CP_EEXIST, Error) \ V(ERR_FS_CP_SOCKET, Error) \ V(ERR_FS_CP_FIFO_PIPE, Error) \ diff --git a/src/node_file.cc b/src/node_file.cc index ae0d9f34f8e1..234e2567d077 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -47,6 +47,8 @@ #include #include #include +#include +#include #if defined(__MINGW32__) || defined(_MSC_VER) #include @@ -91,6 +93,10 @@ using v8::Value; #define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) #endif +#ifndef S_ISREG +#define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) +#endif + #ifdef __POSIX__ constexpr char kPathSeparator = '/'; #else @@ -2952,6 +2958,379 @@ static void ReadFileUtf8(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(val); } +// Fast C++ path for fs.readFileSync(path) / fs.readFileSync(path, null) +// returning a Buffer — avoids open/fstat/read/close JS round-trips. +static void ReadFileBuffer(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + + CHECK_GE(args.Length(), 2); + CHECK(args[1]->IsInt32()); + const int flags = args[1].As()->Value(); + + uv_file file; + uv_fs_t req; + + bool is_fd = args[0]->IsInt32(); + + if (is_fd) { + file = args[0].As()->Value(); + } else { + BufferValue path(isolate, args[0]); + CHECK_NOT_NULL(*path); + ToNamespacedPath(env, &path); + if (CheckOpenPermissions(env, path, flags).IsNothing()) return; + + FS_SYNC_TRACE_BEGIN(open); + file = uv_fs_open(nullptr, &req, *path, flags, 0666, nullptr); + FS_SYNC_TRACE_END(open); + if (req.result < 0) { + uv_fs_req_cleanup(&req); + return env->ThrowUVException( + static_cast(req.result), "open", nullptr, path.out()); + } + uv_fs_req_cleanup(&req); + } + + auto defer_close = OnScopeLeave([file, is_fd, &req]() { + if (!is_fd) { + FS_SYNC_TRACE_BEGIN(close); + CHECK_EQ(0, uv_fs_close(nullptr, &req, file, nullptr)); + FS_SYNC_TRACE_END(close); + } + uv_fs_req_cleanup(&req); + }); + + // Prefer a single allocation when fstat reports a regular file size. + // Match JS tryCreateBuffer: refuse files larger than 2 GiB - 1. + constexpr int64_t kIoMaxLength = (static_cast(1) << 31) - 1; + int64_t size = -1; + { + FS_SYNC_TRACE_BEGIN(fstat); + int r = uv_fs_fstat(nullptr, &req, file, nullptr); + FS_SYNC_TRACE_END(fstat); + if (r == 0) { + const uv_stat_t* s = static_cast(req.ptr); + if (S_ISREG(s->st_mode)) { + if (s->st_size > kIoMaxLength) { + uv_fs_req_cleanup(&req); + std::string msg = "File size (" + + std::to_string(static_cast(s->st_size)) + + ") is greater than 2 GiB"; + return THROW_ERR_FS_FILE_TOO_LARGE(env, msg.c_str()); + } + size = static_cast(s->st_size); + } + } + uv_fs_req_cleanup(&req); + } + + if (size > 0) { + Local buf_obj; + if (!Buffer::New(isolate, static_cast(size)).ToLocal(&buf_obj)) { + return; + } + char* data = Buffer::Data(buf_obj); + size_t remaining = static_cast(size); + size_t offset = 0; + + FS_SYNC_TRACE_BEGIN(read); + while (remaining > 0) { + uv_buf_t buf = uv_buf_init(data + offset, remaining); + auto r = uv_fs_read(nullptr, &req, file, &buf, 1, -1, nullptr); + if (req.result < 0) { + FS_SYNC_TRACE_END(read); + return env->ThrowUVException( + static_cast(req.result), "read", nullptr); + } + if (r == 0) break; + offset += static_cast(r); + remaining -= static_cast(r); + uv_fs_req_cleanup(&req); + } + FS_SYNC_TRACE_END(read); + + if (offset < static_cast(size)) { + // Truncate logical length without reallocating when the file shrank. + Local truncated; + if (!Buffer::Copy(isolate, data, offset).ToLocal(&truncated)) { + return; + } + args.GetReturnValue().Set(truncated); + return; + } + args.GetReturnValue().Set(buf_obj); + return; + } + + // Unknown / zero size: grow like ReadFileUtf8. + std::string result; + char stack_buf[8192]; + uv_buf_t buf = uv_buf_init(stack_buf, sizeof(stack_buf)); + + FS_SYNC_TRACE_BEGIN(read); + while (true) { + auto r = uv_fs_read(nullptr, &req, file, &buf, 1, -1, nullptr); + if (req.result < 0) { + FS_SYNC_TRACE_END(read); + return env->ThrowUVException( + static_cast(req.result), "read", nullptr); + } + if (r <= 0) break; + result.append(buf.base, static_cast(r)); + uv_fs_req_cleanup(&req); + } + FS_SYNC_TRACE_END(read); + + Local out; + if (!Buffer::Copy(isolate, result.data(), result.size()).ToLocal(&out)) { + return; + } + args.GetReturnValue().Set(out); +} + +// Fast C++ path for fs.writeFileSync(path, buffer) with ArrayBufferView data. +// Mirrors WriteFileUtf8 but takes a Buffer/TypedArray payload. +static void WriteFileBuffer(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK_EQ(args.Length(), 4); + CHECK(Buffer::HasInstance(args[1])); + CHECK(args[2]->IsInt32()); + CHECK(args[3]->IsInt32()); + + Local data_obj = args[1].As(); + char* data = Buffer::Data(data_obj); + const size_t length = Buffer::Length(data_obj); + const int flags = args[2].As()->Value(); + const int mode = args[3].As()->Value(); + + uv_file file; + bool is_fd = args[0]->IsInt32(); + + if (is_fd) { + file = args[0].As()->Value(); + } else { + BufferValue path(env->isolate(), args[0]); + CHECK_NOT_NULL(*path); + ToNamespacedPath(env, &path); + if (CheckOpenPermissions(env, path, flags).IsNothing()) return; + + FSReqWrapSync req_open("open", *path); + FS_SYNC_TRACE_BEGIN(open); + file = + SyncCallAndThrowOnError(env, &req_open, uv_fs_open, *path, flags, mode); + FS_SYNC_TRACE_END(open); + if (is_uv_error(file)) return; + } + + int bytes_written = 0; + size_t offset = 0; + uv_buf_t uvbuf = uv_buf_init(data, length); + + FS_SYNC_TRACE_BEGIN(write); + while (offset < length) { + FSReqWrapSync req_write("write"); + bytes_written = SyncCallAndThrowOnError( + env, &req_write, uv_fs_write, file, &uvbuf, 1, -1); + if (bytes_written < 0) break; + offset += static_cast(bytes_written); + DCHECK_LE(offset, length); + uvbuf.base += bytes_written; + uvbuf.len -= static_cast(bytes_written); + } + FS_SYNC_TRACE_END(write); + + if (!is_fd) { + FSReqWrapSync req_close("close"); + FS_SYNC_TRACE_BEGIN(close); + int result = SyncCallAndThrowOnError(env, &req_close, uv_fs_close, file); + FS_SYNC_TRACE_END(close); + if (is_uv_error(result)) return; + } +} + +// Map st_mode to a uv_dirent type (used when scandir reports +// UV_DIRENT_UNKNOWN). +static int ModeToDirentType(uint64_t mode) { + switch (mode & S_IFMT) { + case S_IFDIR: + return UV_DIRENT_DIR; + case S_IFREG: + return UV_DIRENT_FILE; +#ifdef S_IFLNK + case S_IFLNK: + return UV_DIRENT_LINK; +#endif +#ifdef S_IFIFO + case S_IFIFO: + return UV_DIRENT_FIFO; +#endif +#ifdef S_IFSOCK + case S_IFSOCK: + return UV_DIRENT_SOCKET; +#endif +#ifdef S_IFCHR + case S_IFCHR: + return UV_DIRENT_CHAR; +#endif +#ifdef S_IFBLK + case S_IFBLK: + return UV_DIRENT_BLOCK; +#endif + default: + return UV_DIRENT_UNKNOWN; + } +} + +// Fast recursive directory walk for readdirSync({ recursive: true }). +// Uses dirent types from scandir when available so most entries avoid an +// extra stat() (the JS path calls internalModuleStat per entry). +static void ReadDirRecursiveSync(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + + CHECK_GE(args.Length(), 2); + BufferValue base_path(isolate, args[0]); + CHECK_NOT_NULL(*base_path); + ToNamespacedPath(env, &base_path); + + const enum encoding encoding = ParseEncoding(isolate, args[1], UTF8); + const bool with_types = args.Length() > 2 && args[2]->IsTrue(); + + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, + permission::PermissionScope::kFileSystemRead, + base_path.ToStringView()); + + std::string base(*base_path, base_path.length()); + // BFS queue matches the historical JS walk order. + std::vector queue; + queue.push_back(base); + size_t qhead = 0; + + LocalVector out_names(isolate); + LocalVector out_types(isolate); + LocalVector out_paths(isolate); // parentPath for Dirent + + const char sep = +#ifdef _WIN32 + '\\'; +#else + '/'; +#endif + + while (qhead < queue.size()) { + std::string current = std::move(queue[qhead++]); + + uv_fs_t req; + FS_SYNC_TRACE_BEGIN(readdir); + int err = uv_fs_scandir(nullptr, &req, current.c_str(), 0, nullptr); + FS_SYNC_TRACE_END(readdir); + if (err < 0) { + uv_fs_req_cleanup(&req); + return env->ThrowUVException(err, "scandir", nullptr, current.c_str()); + } + + // Encode parent path once per directory (withFileTypes). + Local parent_v; + if (with_types) { + if (!StringBytes::Encode(isolate, current.c_str(), encoding) + .ToLocal(&parent_v)) { + uv_fs_req_cleanup(&req); + return; + } + } + + for (;;) { + uv_dirent_t ent; + int r = uv_fs_scandir_next(&req, &ent); + if (r == UV_EOF) break; + if (r < 0) { + uv_fs_req_cleanup(&req); + return env->ThrowUVException(r, "scandir", nullptr, current.c_str()); + } + + std::string full = current; + if (full.empty() || full.back() != sep) full.push_back(sep); + full.append(ent.name); + + int ent_type = ent.type; + + // Resolve UNKNOWN types (common on some filesystems / Windows) so + // Dirent.isDirectory() / isFile() match getDirent()'s lstat behavior. + if (with_types && ent_type == UV_DIRENT_UNKNOWN) { + uv_fs_t lreq; + if (uv_fs_lstat(nullptr, &lreq, full.c_str(), nullptr) == 0) { + const uv_stat_t* s = static_cast(lreq.ptr); + ent_type = ModeToDirentType(s->st_mode); + } + uv_fs_req_cleanup(&lreq); + } + + if (with_types) { + Local base_name_v; + if (!StringBytes::Encode(isolate, ent.name, encoding) + .ToLocal(&base_name_v)) { + uv_fs_req_cleanup(&req); + return; + } + out_names.push_back(base_name_v); + out_types.push_back(Integer::New(isolate, ent_type)); + out_paths.push_back(parent_v); + } else { + // Relative path from base (JS returns relative paths). + const char* rel = ent.name; + if (full.size() > base.size() && + full.compare(0, base.size(), base) == 0) { + size_t start = base.size(); + if (start < full.size() && + (full[start] == '/' || full[start] == '\\')) { + start++; + } + rel = full.c_str() + start; + } + Local name_v; + if (!StringBytes::Encode(isolate, rel, encoding).ToLocal(&name_v)) { + uv_fs_req_cleanup(&req); + return; + } + out_names.push_back(name_v); + } + + // Descend into directories. For symlinks / unknown types, follow with + // stat() like internalModuleStat (JS: isDirectory() || stat===1). + bool is_dir = ent_type == UV_DIRENT_DIR; + if (!is_dir && + (ent_type == UV_DIRENT_UNKNOWN || ent_type == UV_DIRENT_LINK)) { + uv_fs_t sreq; + if (uv_fs_stat(nullptr, &sreq, full.c_str(), nullptr) == 0) { + const uv_stat_t* s = static_cast(sreq.ptr); + is_dir = S_ISDIR(s->st_mode); + } + uv_fs_req_cleanup(&sreq); + } + + if (is_dir) { + queue.push_back(std::move(full)); + } + } + uv_fs_req_cleanup(&req); + } + + Local names = Array::New(isolate, out_names.data(), out_names.size()); + if (with_types) { + Local parts[] = { + names, + Array::New(isolate, out_types.data(), out_types.size()), + Array::New(isolate, out_paths.data(), out_paths.size()), + }; + args.GetReturnValue().Set(Array::New(isolate, parts, arraysize(parts))); + } else { + args.GetReturnValue().Set(names); + } +} + // Wrapper for readv(2). // // bytesRead = fs.readv(fd, buffers[, position], callback) @@ -4205,6 +4584,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "openFileHandle", OpenFileHandle); SetMethod(isolate, target, "read", Read); SetMethod(isolate, target, "readFileUtf8", ReadFileUtf8); + SetMethod(isolate, target, "readFileBuffer", ReadFileBuffer); SetMethod(isolate, target, "readBuffers", ReadBuffers); SetMethod(isolate, target, "fdatasync", Fdatasync); SetMethod(isolate, target, "fsync", Fsync); @@ -4227,6 +4607,8 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "writeBuffers", WriteBuffers); SetMethod(isolate, target, "writeString", WriteString); SetMethod(isolate, target, "writeFileUtf8", WriteFileUtf8); + SetMethod(isolate, target, "writeFileBuffer", WriteFileBuffer); + SetMethod(isolate, target, "readdirRecursiveSync", ReadDirRecursiveSync); SetMethod(isolate, target, "realpath", RealPath); SetMethod(isolate, target, "copyFile", CopyFile); @@ -4333,6 +4715,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(OpenFileHandle); registry->Register(Read); registry->Register(ReadFileUtf8); + registry->Register(ReadFileBuffer); registry->Register(ReadBuffers); registry->Register(Fdatasync); registry->Register(Fsync); @@ -4355,6 +4738,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(WriteBuffers); registry->Register(WriteString); registry->Register(WriteFileUtf8); + registry->Register(WriteFileBuffer); + registry->Register(ReadDirRecursiveSync); registry->Register(RealPath); registry->Register(CopyFile); diff --git a/test/known_issues/test-fs-readdir-sync-recursive-with-buffer.js b/test/known_issues/test-fs-readdir-sync-recursive-with-buffer.js deleted file mode 100644 index 0f60c290f15d..000000000000 --- a/test/known_issues/test-fs-readdir-sync-recursive-with-buffer.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -// This test will fail because the implementation does not properly -// handle the case when the path is a Buffer and the function is called -// in recursive mode. - -// Refs: https://github.com/nodejs/node/issues/58892 - -require('../common'); - -const { readdirSync } = require('node:fs'); -const { join } = require('node:path'); - -const testDirPath = join(__dirname, '..', '..'); -readdirSync(Buffer.from(testDirPath), { recursive: true }); diff --git a/test/parallel/test-fs-readdir-sync-recursive-with-buffer.js b/test/parallel/test-fs-readdir-sync-recursive-with-buffer.js new file mode 100644 index 000000000000..74323a5c813b --- /dev/null +++ b/test/parallel/test-fs-readdir-sync-recursive-with-buffer.js @@ -0,0 +1,57 @@ +'use strict'; + +// Refs: https://github.com/nodejs/node/issues/58892 + +require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const dir = tmpdir.path; +fs.mkdirSync(path.join(dir, 'sub')); +fs.writeFileSync(path.join(dir, 'a.txt'), 'a'); +fs.writeFileSync(path.join(dir, 'sub', 'b.txt'), 'b'); + +const expected = fs.readdirSync(dir, { recursive: true }).sort(); + +// Buffer path must not throw and must match the string-path result. +const actual = fs.readdirSync(Buffer.from(dir), { recursive: true }).sort(); +assert.deepStrictEqual(actual, expected); + +const bufResult = fs.readdirSync(Buffer.from(dir), { + recursive: true, + encoding: 'buffer', +}); +assert.ok(bufResult.every((entry) => Buffer.isBuffer(entry))); +assert.deepStrictEqual( + bufResult.map((entry) => entry.toString()).sort(), + expected, +); + +const expectedDirents = fs.readdirSync(dir, { + recursive: true, + withFileTypes: true, +}); +const dirents = fs.readdirSync(Buffer.from(dir), { + recursive: true, + withFileTypes: true, +}); +assert.strictEqual(dirents.length, expectedDirents.length); +assert.ok(dirents.every((dirent) => dirent instanceof fs.Dirent)); +assert.deepStrictEqual( + dirents.map((dirent) => ({ + name: dirent.name, + parentPath: dirent.parentPath, + isFile: dirent.isFile(), + isDirectory: dirent.isDirectory(), + })).sort((a, b) => (a.name < b.name ? -1 : 1)), + expectedDirents.map((dirent) => ({ + name: dirent.name, + parentPath: dirent.parentPath, + isFile: dirent.isFile(), + isDirectory: dirent.isDirectory(), + })).sort((a, b) => (a.name < b.name ? -1 : 1)), +); diff --git a/test/parallel/test-fs-readfile-utf8-fast-path.js b/test/parallel/test-fs-readfile-utf8-fast-path.js index 18d0d884dfa4..d67a70ee8cfa 100644 --- a/test/parallel/test-fs-readfile-utf8-fast-path.js +++ b/test/parallel/test-fs-readfile-utf8-fast-path.js @@ -100,4 +100,12 @@ describe('fs.readFileSync utf8 simdutf dispatch', () => { const buf = Buffer.from([0x41, 0xE4, 0xB8]); expectMatches(writeFile('truncated-multibyte.txt', buf), buf); }); + + it('accepts case-insensitive utf8 encoding aliases', () => { + const p = writeFile('encoding-alias.txt', Buffer.from('hello')); + for (const encoding of ['utf8', 'utf-8', 'UTF8', 'UTF-8', 'Utf8', 'uTf-8']) { + assert.strictEqual(fs.readFileSync(p, encoding), 'hello'); + assert.strictEqual(fs.readFileSync(p, { encoding }), 'hello'); + } + }); }); diff --git a/test/parallel/test-fs-write-file-sync.js b/test/parallel/test-fs-write-file-sync.js index e5fbe32eab6d..d54a03764ccb 100644 --- a/test/parallel/test-fs-write-file-sync.js +++ b/test/parallel/test-fs-write-file-sync.js @@ -50,6 +50,15 @@ tmpdir.refresh(); assert.strictEqual(fs.statSync(file).mode & 0o777, mode); } +// Test writeFileSync utf8 encoding aliases (including uppercase) +{ + for (const encoding of ['utf8', 'utf-8', 'UTF8', 'UTF-8', 'Utf8', 'uTf-8']) { + const file = tmpdir.resolve(`testWriteFileSync-${encoding}.txt`); + fs.writeFileSync(file, 'hello', { encoding, mode }); + assert.strictEqual(fs.readFileSync(file, 'utf8'), 'hello'); + } +} + // Test appendFileSync { const file = tmpdir.resolve('testAppendFileSync.txt'); diff --git a/typings/internalBinding/fs.d.ts b/typings/internalBinding/fs.d.ts index 6e1702996dfc..e8fa3ca9a083 100644 --- a/typings/internalBinding/fs.d.ts +++ b/typings/internalBinding/fs.d.ts @@ -180,6 +180,11 @@ declare namespace InternalFSBinding { function readdir(path: StringOrBuffer, encoding: unknown, withFileTypes: false, usePromises: typeof kUsePromises): Promise; function readFileUtf8(path: StringOrBuffer, flags: number): string; + function readFileBuffer(path: StringOrBuffer, flags: number): Buffer; + + function readdirRecursiveSync(path: StringOrBuffer, encoding: unknown, withFileTypes: true): [string[], number[], string[]]; + function readdirRecursiveSync(path: StringOrBuffer, encoding: unknown, withFileTypes: false): string[]; + function readdirRecursiveSync(path: StringOrBuffer, encoding: unknown, withFileTypes: boolean): string[] | [string[], number[], string[]]; function readlink(path: StringOrBuffer, encoding: unknown, req: FSReqCallback): void; function readlink(path: StringOrBuffer, encoding: unknown, req: undefined, ctx: FSSyncContext): string | Buffer; @@ -241,6 +246,9 @@ declare namespace InternalFSBinding { function writeFileUtf8(path: string, data: string, flag: number, mode: number): void; function writeFileUtf8(fd: number, data: string, flag: number, mode: number): void; + + function writeFileBuffer(path: string, data: ArrayBufferView, flag: number, mode: number): void; + function writeFileBuffer(fd: number, data: ArrayBufferView, flag: number, mode: number): void; } export interface FsBinding { @@ -283,6 +291,8 @@ export interface FsBinding { read: typeof InternalFSBinding.read; readBuffers: typeof InternalFSBinding.readBuffers; readdir: typeof InternalFSBinding.readdir; + readdirRecursiveSync: typeof InternalFSBinding.readdirRecursiveSync; + readFileBuffer: typeof InternalFSBinding.readFileBuffer; readFileUtf8: typeof InternalFSBinding.readFileUtf8; readlink: typeof InternalFSBinding.readlink; realpath: typeof InternalFSBinding.realpath; @@ -295,6 +305,7 @@ export interface FsBinding { utimes: typeof InternalFSBinding.utimes; writeBuffer: typeof InternalFSBinding.writeBuffer; writeBuffers: typeof InternalFSBinding.writeBuffers; + writeFileBuffer: typeof InternalFSBinding.writeFileBuffer; writeFileUtf8: typeof InternalFSBinding.writeFileUtf8; writeString: typeof InternalFSBinding.writeString;