Skip to content

Commit d2007ae

Browse 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

File tree

2 files changed

+119
-39
lines changed

2 files changed

+119
-39
lines changed

lib/fs.js

Lines 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

test/fixtures/permission/fs-read.js

Lines 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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy