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 <[email protected]> Reviewed-By: Juan José Arboleda <[email protected]>
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+
functionreaddirRecursive(basePath,options,callback){
1388+
constcontext={
1389+
withFileTypes: Boolean(options.withFileTypes),
1390+
encoding: options.encoding,
1391+
basePath,
1392+
readdirResults: [],
1393+
pathsQueue: [basePath],
1394+
};
1395+
1396+
leti=0;
1397+
1398+
functionread(path){
1399+
constreq=newFSReqCallback();
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+
constprocessReaddirResult=(args)=>(args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args));
1440+
1441+
functionhandleDirents({ result, currentPath, context }){
1442+
const{0: names,1: types}=result;
1443+
const{ length }=names;
1444+
1445+
for(leti=0;i<length;i++){
1446+
// Avoid excluding symlinks, as they are not directories.
1447+
// Refs: https://github.com/nodejs/node/issues/52663
1448+
constfullPath=pathModule.join(currentPath,names[i]);
1449+
constdirent=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+
functionhandleFilePaths({ result, currentPath, context }){
1459+
for(leti=0;i<result.length;i++){
1460+
constresultPath=pathModule.join(currentPath,result[i]);
1461+
constrelativeResultPath=pathModule.relative(context.basePath,resultPath);
1462+
conststat=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
functionreaddirSyncRecursive(basePath,options){
1384-
constwithFileTypes=Boolean(options.withFileTypes);
1385-
constencoding=options.encoding;
1386-
1387-
constreaddirResults=[];
1388-
constpathsQueue=[basePath];
1480+
constcontext={
1481+
withFileTypes: Boolean(options.withFileTypes),
1482+
encoding: options.encoding,
1483+
basePath,
1484+
readdirResults: [],
1485+
pathsQueue: [basePath],
1486+
};
13891487

13901488
functionread(path){
13911489
constreaddirResult=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-
constlength=readdirResult[0].length;
1407-
for(leti=0;i<length;i++){
1408-
// Avoid excluding symlinks, as they are not directories.
1409-
// Refs: https://github.com/nodejs/node/issues/52663
1410-
conststat=binding.internalModuleStat(binding,pathModule.join(path,readdirResult[0][i]));
1411-
constdirent=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(leti=0;i<readdirResult.length;i++){
1419-
constresultPath=pathModule.join(path,readdirResult[i]);
1420-
constrelativeResultPath=pathModule.relative(basePath,resultPath);
1421-
conststat=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(leti=0;i<pathsQueue.length;i++){
1432-
read(pathsQueue[i]);
1506+
for(leti=0;i<context.pathsQueue.length;i++){
1507+
read(context.pathsQueue[i]);
14331508
}
14341509

1435-
returnreaddirResults;
1510+
returncontext.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)