Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d2007ae

Browse filesBrowse files
RafaelGSSruyadorno
authored andcommitted
lib: fix fs.readdir recursive async
Fixes: #56006 PR-URL: #56041 Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
1 parent 28a11ad commit d2007ae
Copy full SHA for d2007ae

File tree

Expand file treeCollapse file tree

2 files changed

+119
-39
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+119
-39
lines changed
Open diff view settings
Collapse file

‎lib/fs.js‎

Copy file name to clipboardExpand all lines: lib/fs.js
+114-39Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,102 @@ function mkdirSync(path, options) {
13721372
}
13731373
}
13741374

1375+
/*
1376+
* An recursive algorithm for reading the entire contents of the `basePath` directory.
1377+
* This function does not validate `basePath` as a directory. It is passed directly to
1378+
* `binding.readdir`.
1379+
* @param {string} basePath
1380+
* @param {{ encoding: string, withFileTypes: boolean }} options
1381+
* @param {(
1382+
* err?: Error,
1383+
* files?: string[] | Buffer[] | Dirent[]
1384+
* ) => any} callback
1385+
* @returns {void}
1386+
*/
1387+
function readdirRecursive(basePath, options, callback) {
1388+
const context = {
1389+
withFileTypes: Boolean(options.withFileTypes),
1390+
encoding: options.encoding,
1391+
basePath,
1392+
readdirResults: [],
1393+
pathsQueue: [basePath],
1394+
};
1395+
1396+
let i = 0;
1397+
1398+
function read(path) {
1399+
const req = new FSReqCallback();
1400+
req.oncomplete = (err, result) => {
1401+
if (err) {
1402+
callback(err);
1403+
return;
1404+
}
1405+
1406+
if (result === undefined) {
1407+
callback(null, context.readdirResults);
1408+
return;
1409+
}
1410+
1411+
processReaddirResult({
1412+
result,
1413+
currentPath: path,
1414+
context,
1415+
});
1416+
1417+
if (i < context.pathsQueue.length) {
1418+
read(context.pathsQueue[i++]);
1419+
} else {
1420+
callback(null, context.readdirResults);
1421+
}
1422+
};
1423+
1424+
binding.readdir(
1425+
path,
1426+
context.encoding,
1427+
context.withFileTypes,
1428+
req,
1429+
);
1430+
}
1431+
1432+
read(context.pathsQueue[i++]);
1433+
}
1434+
1435+
// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
1436+
// The first array is the names, and the second array is the types.
1437+
// They are guaranteed to be the same length; hence, setting `length` to the length
1438+
// of the first array within the result.
1439+
const processReaddirResult = (args) => (args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args));
1440+
1441+
function handleDirents({ result, currentPath, context }) {
1442+
const { 0: names, 1: types } = result;
1443+
const { length } = names;
1444+
1445+
for (let i = 0; i < length; i++) {
1446+
// Avoid excluding symlinks, as they are not directories.
1447+
// Refs: https://github.com/nodejs/node/issues/52663
1448+
const fullPath = pathModule.join(currentPath, names[i]);
1449+
const dirent = getDirent(currentPath, names[i], types[i]);
1450+
ArrayPrototypePush(context.readdirResults, dirent);
1451+
1452+
if (dirent.isDirectory() || binding.internalModuleStat(binding, fullPath) === 1) {
1453+
ArrayPrototypePush(context.pathsQueue, fullPath);
1454+
}
1455+
}
1456+
}
1457+
1458+
function handleFilePaths({ result, currentPath, context }) {
1459+
for (let i = 0; i < result.length; i++) {
1460+
const resultPath = pathModule.join(currentPath, result[i]);
1461+
const relativeResultPath = pathModule.relative(context.basePath, resultPath);
1462+
const stat = binding.internalModuleStat(binding, resultPath);
1463+
ArrayPrototypePush(context.readdirResults, relativeResultPath);
1464+
1465+
if (stat === 1) {
1466+
ArrayPrototypePush(context.pathsQueue, resultPath);
1467+
}
1468+
}
1469+
}
1470+
13751471
/**
13761472
* An iterative algorithm for reading the entire contents of the `basePath` directory.
13771473
* This function does not validate `basePath` as a directory. It is passed directly to
@@ -1381,58 +1477,37 @@ function mkdirSync(path, options) {
13811477
* @returns {string[] | Dirent[]}
13821478
*/
13831479
function readdirSyncRecursive(basePath, options) {
1384-
const withFileTypes = Boolean(options.withFileTypes);
1385-
const encoding = options.encoding;
1386-
1387-
const readdirResults = [];
1388-
const pathsQueue = [basePath];
1480+
const context = {
1481+
withFileTypes: Boolean(options.withFileTypes),
1482+
encoding: options.encoding,
1483+
basePath,
1484+
readdirResults: [],
1485+
pathsQueue: [basePath],
1486+
};
13891487

13901488
function read(path) {
13911489
const readdirResult = binding.readdir(
13921490
path,
1393-
encoding,
1394-
withFileTypes,
1491+
context.encoding,
1492+
context.withFileTypes,
13951493
);
13961494

13971495
if (readdirResult === undefined) {
13981496
return;
13991497
}
14001498

1401-
if (withFileTypes) {
1402-
// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
1403-
// The first array is the names, and the second array is the types.
1404-
// They are guaranteed to be the same length; hence, setting `length` to the length
1405-
// of the first array within the result.
1406-
const length = readdirResult[0].length;
1407-
for (let i = 0; i < length; i++) {
1408-
// Avoid excluding symlinks, as they are not directories.
1409-
// Refs: https://github.com/nodejs/node/issues/52663
1410-
const stat = binding.internalModuleStat(binding, pathModule.join(path, readdirResult[0][i]));
1411-
const dirent = getDirent(path, readdirResult[0][i], readdirResult[1][i]);
1412-
ArrayPrototypePush(readdirResults, dirent);
1413-
if (dirent.isDirectory() || stat === 1) {
1414-
ArrayPrototypePush(pathsQueue, pathModule.join(dirent.parentPath, dirent.name));
1415-
}
1416-
}
1417-
} else {
1418-
for (let i = 0; i < readdirResult.length; i++) {
1419-
const resultPath = pathModule.join(path, readdirResult[i]);
1420-
const relativeResultPath = pathModule.relative(basePath, resultPath);
1421-
const stat = binding.internalModuleStat(binding, resultPath);
1422-
ArrayPrototypePush(readdirResults, relativeResultPath);
1423-
// 1 indicates directory
1424-
if (stat === 1) {
1425-
ArrayPrototypePush(pathsQueue, resultPath);
1426-
}
1427-
}
1428-
}
1499+
processReaddirResult({
1500+
result: readdirResult,
1501+
currentPath: path,
1502+
context,
1503+
});
14291504
}
14301505

1431-
for (let i = 0; i < pathsQueue.length; i++) {
1432-
read(pathsQueue[i]);
1506+
for (let i = 0; i < context.pathsQueue.length; i++) {
1507+
read(context.pathsQueue[i]);
14331508
}
14341509

1435-
return readdirResults;
1510+
return context.readdirResults;
14361511
}
14371512

14381513
/**
@@ -1458,7 +1533,7 @@ function readdir(path, options, callback) {
14581533
}
14591534

14601535
if (options.recursive) {
1461-
callback(null, readdirSyncRecursive(path, options));
1536+
readdirRecursive(path, options, callback);
14621537
return;
14631538
}
14641539

Collapse file

‎test/fixtures/permission/fs-read.js‎

Copy file name to clipboardExpand all lines: test/fixtures/permission/fs-read.js
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ const regularFile = __filename;
289289
permission: 'FileSystemRead',
290290
resource: path.toNamespacedPath(blockedFolder),
291291
}));
292+
fs.readdir(blockedFolder, { recursive: true }, common.expectsError({
293+
code: 'ERR_ACCESS_DENIED',
294+
permission: 'FileSystemRead',
295+
resource: path.toNamespacedPath(blockedFolder),
296+
}));
292297
assert.throws(() => {
293298
fs.readdirSync(blockedFolder);
294299
}, common.expectsError({

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.