Skip to content

Commit 27276b4

Browse files
committed
lib: fix fs.readdir recursive async
nodejs/node#56041
1 parent dbc86af commit 27276b4

File tree

1 file changed

+104
-22
lines changed

1 file changed

+104
-22
lines changed

lib/node/asar-fs-wrapper.ts

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const nextTick = (functionToCall: Function, args: any[] = []) => {
2424
process.nextTick(() => functionToCall(...args));
2525
};
2626

27+
const binding = internalBinding('fs');
28+
2729
// Cache asar archive objects.
2830
const cachedArchives = new Map<string, NodeJS.AsarArchive>();
2931

@@ -705,7 +707,87 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
705707
};
706708

707709
type ReaddirOptions = { encoding: BufferEncoding | null; withFileTypes?: false, recursive?: false } | undefined | null;
708-
type ReaddirCallback = (err: NodeJS.ErrnoException | null, files: string[]) => void;
710+
type ReaddirCallback = (err: NodeJS.ErrnoException | null, files?: string[]) => void;
711+
712+
const processReaddirResult = (args: any) => (args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args));
713+
714+
function handleDirents ({ result, currentPath, context }: { result: string[], currentPath: string, context: any }) {
715+
const { 0: names, 1: types } = result;
716+
const { length } = names;
717+
718+
for (let i = 0; i < length; i++) {
719+
// Avoid excluding symlinks, as they are not directories.
720+
// Refs: https://github.com/nodejs/node/issues/52663
721+
const fullPath = path.join(currentPath, names[i]);
722+
const dirent = getDirent(currentPath, names[i], types[i]);
723+
context.readdirResults.push(dirent);
724+
725+
if (dirent.isDirectory() || binding.internalModuleStat(binding, fullPath) === 1) {
726+
context.pathsQueue.push(fullPath);
727+
}
728+
}
729+
}
730+
731+
function handleFilePaths ({ result, currentPath, context }: { result: string[], currentPath: string, context: any }) {
732+
for (let i = 0; i < result.length; i++) {
733+
const resultPath = path.join(currentPath, result[i]);
734+
const relativeResultPath = path.relative(context.basePath, resultPath);
735+
const stat = binding.internalModuleStat(binding, resultPath);
736+
context.readdirResults.push(relativeResultPath);
737+
738+
if (stat === 1) {
739+
context.pathsQueue.push(resultPath);
740+
}
741+
}
742+
}
743+
744+
function readdirRecursive (basePath: string, options: ReaddirOptions, callback: ReaddirCallback) {
745+
const context = {
746+
withFileTypes: Boolean(options!.withFileTypes),
747+
encoding: options!.encoding,
748+
basePath,
749+
readdirResults: [],
750+
pathsQueue: [basePath]
751+
};
752+
753+
let i = 0;
754+
755+
function read (pathArg: string) {
756+
const req = new binding.FSReqCallback();
757+
req.oncomplete = (err: any, result: any) => {
758+
if (err) {
759+
callback(err);
760+
return;
761+
}
762+
763+
if (result === undefined) {
764+
callback(null, context.readdirResults);
765+
return;
766+
}
767+
768+
processReaddirResult({
769+
result,
770+
currentPath: pathArg,
771+
context
772+
});
773+
774+
if (i < context.pathsQueue.length) {
775+
read(context.pathsQueue[i++]);
776+
} else {
777+
callback(null, context.readdirResults);
778+
}
779+
};
780+
781+
binding.readdir(
782+
pathArg,
783+
context.encoding,
784+
context.withFileTypes,
785+
req
786+
);
787+
}
788+
789+
read(context.pathsQueue[i++]);
790+
}
709791

710792
const { readdir } = fs;
711793
fs.readdir = function (pathArgument: string, options: ReaddirOptions, callback: ReaddirCallback) {
@@ -720,7 +802,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
720802
}
721803

722804
if (options?.recursive) {
723-
nextTick(callback!, [null, readdirSyncRecursive(pathArgument, options)]);
805+
readdirRecursive(pathArgument, options, callback);
724806
return;
725807
}
726808

@@ -771,7 +853,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
771853
}
772854

773855
if (options?.recursive) {
774-
return readdirRecursive(pathArgument, options);
856+
return readdirRecursivePromises(pathArgument, options);
775857
}
776858

777859
const pathInfo = splitPath(pathArgument);
@@ -868,8 +950,6 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
868950
return readPackageJSON(realPath, isESM, base, specifier);
869951
};
870952

871-
const binding = internalBinding('fs');
872-
873953
const { internalModuleStat } = binding;
874954
internalBinding('fs').internalModuleStat = (receiver: unknown, pathArgument: string) => {
875955
const pathInfo = splitPath(pathArgument);
@@ -888,7 +968,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
888968
};
889969

890970
const { kUsePromises } = binding;
891-
async function readdirRecursive (originalPath: string, options: ReaddirOptions) {
971+
async function readdirRecursivePromises (originalPath: string, options: ReaddirOptions) {
892972
const result: any[] = [];
893973

894974
const pathInfo = splitPath(originalPath);
@@ -992,11 +1072,13 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
9921072
}
9931073

9941074
function readdirSyncRecursive (basePath: string, options: ReaddirOptions) {
995-
const withFileTypes = Boolean(options!.withFileTypes);
996-
const encoding = options!.encoding;
997-
998-
const readdirResults: string[] = [];
999-
const pathsQueue = [basePath];
1075+
const context = {
1076+
withFileTypes: Boolean(options!.withFileTypes),
1077+
encoding: options!.encoding,
1078+
basePath,
1079+
readdirResults: [] as any,
1080+
pathsQueue: [basePath]
1081+
};
10001082

10011083
function read (pathArg: string) {
10021084
let readdirResult;
@@ -1011,7 +1093,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
10111093
if (!readdirResult) return;
10121094
// If we're in an asar dir, we need to ensure the result is in the same format as the
10131095
// native call to readdir withFileTypes i.e. an array of arrays.
1014-
if (withFileTypes) {
1096+
if (context.withFileTypes) {
10151097
readdirResult = [
10161098
[...readdirResult], readdirResult.map((p: string) => {
10171099
return internalBinding('fs').internalModuleStat(binding, path.join(pathArg, p));
@@ -1021,14 +1103,14 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
10211103
} else {
10221104
readdirResult = binding.readdir(
10231105
path.toNamespacedPath(pathArg),
1024-
encoding,
1025-
withFileTypes
1106+
context.encoding,
1107+
context.withFileTypes
10261108
);
10271109
}
10281110

10291111
if (readdirResult === undefined) return;
10301112

1031-
if (withFileTypes) {
1113+
if (context.withFileTypes) {
10321114
const length = readdirResult[0].length;
10331115
for (let i = 0; i < length; i++) {
10341116
const resultPath = path.join(pathArg, readdirResult[0][i]);
@@ -1045,9 +1127,9 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
10451127

10461128
const dirent = getDirent(pathArg, readdirResult[0][i], type);
10471129

1048-
readdirResults.push(dirent);
1130+
context.readdirResults.push(dirent);
10491131
if (dirent.isDirectory()) {
1050-
pathsQueue.push(path.join(dirent.path, dirent.name));
1132+
context.pathsQueue.push(path.join(dirent.path, dirent.name));
10511133
}
10521134
}
10531135
} else {
@@ -1056,17 +1138,17 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
10561138
const relativeResultPath = path.relative(basePath, resultPath);
10571139
const stat = internalBinding('fs').internalModuleStat(binding, resultPath);
10581140

1059-
readdirResults.push(relativeResultPath);
1060-
if (stat === 1) pathsQueue.push(resultPath);
1141+
context.readdirResults.push(relativeResultPath);
1142+
if (stat === 1) context.pathsQueue.push(resultPath);
10611143
}
10621144
}
10631145
}
10641146

1065-
for (let i = 0; i < pathsQueue.length; i++) {
1066-
read(pathsQueue[i]);
1147+
for (let i = 0; i < context.pathsQueue.length; i++) {
1148+
read(context.pathsQueue[i]);
10671149
}
10681150

1069-
return readdirResults;
1151+
return context.readdirResults;
10701152
}
10711153

10721154
// Calling mkdir for directory inside asar archive should throw ENOTDIR

0 commit comments

Comments
 (0)