diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index d854c68b523..2733fb9432b 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -100,15 +100,55 @@ export function rimrafSync(path: string): void { //#region readdir with NFC support (macos) +export interface IDirent { + name: string; + + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; +} + /** * Drop-in replacement of `fs.readdir` with support * for converting from macOS NFD unicon form to NFC * (https://github.com/nodejs/node/issues/2165) */ export async function readdir(path: string): Promise; -export async function readdir(path: string, options: { withFileTypes: true }): Promise; -export async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | fs.Dirent)[]> { - return handleDirectoryChildren(await (options ? fs.promises.readdir(path, options) : fs.promises.readdir(path))); +export async function readdir(path: string, options: { withFileTypes: true }): Promise; +export async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { + return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : fs.promises.readdir(path))); +} + +async function safeReaddirWithFileTypes(path: string): Promise { + try { + return await fs.promises.readdir(path, { withFileTypes: true }); + } catch (error) { + console.warn('[node.js fs] readdir with filetypes failed with error: ', error); + } + + // Fallback to manually reading and resolving each + // children of the folder in case we hit an error + // previously. + // This can only really happen on exotic file systems + // such as explained in #115645 where we get entries + // from `readdir` that we can later not `lstat`. + const result: IDirent[] = []; + const children = await readdir(path); + for (const child of children) { + try { + const lstat = await fs.promises.lstat(join(path, child)); + result.push({ + name: child, + isFile: () => lstat.isFile(), + isDirectory: () => lstat.isDirectory(), + isSymbolicLink: () => lstat.isSymbolicLink() + }); + } catch (error) { + console.warn('[node.js fs] unexpected error from lstat after readdir: ', error); + } + } + + return result; } /** @@ -121,9 +161,9 @@ export function readdirSync(path: string): string[] { } function handleDirectoryChildren(children: string[]): string[]; -function handleDirectoryChildren(children: fs.Dirent[]): fs.Dirent[]; -function handleDirectoryChildren(children: (string | fs.Dirent)[]): (string | fs.Dirent)[]; -function handleDirectoryChildren(children: (string | fs.Dirent)[]): (string | fs.Dirent)[] { +function handleDirectoryChildren(children: IDirent[]): IDirent[]; +function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[]; +function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[] { return children.map(child => { // Mac: uses NFD unicode form on disk, but we want NFC diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index c975790fa99..cc61cc0c478 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -5,7 +5,7 @@ import * as osLib from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; -import { Dirent, exists, readFile } from 'fs'; +import { exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError, getNodeType } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; @@ -19,7 +19,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Iterable } from 'vs/base/common/iterator'; import { Schemas } from 'vs/base/common/network'; import { ByteSize } from 'vs/platform/files/common/files'; -import { readdir } from 'vs/base/node/pfs'; +import { IDirent, readdir } from 'vs/base/node/pfs'; export const ID = 'diagnosticsService'; export const IDiagnosticsService = createDecorator(ID); @@ -81,7 +81,7 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P const relativePath = dir.substring(root.length + 1); return new Promise(async resolve => { - let files: Dirent[]; + let files: IDirent[]; try { files = await readdir(dir, { withFileTypes: true }); } catch (error) { diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 4017e98c06c..a7216230eb6 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { open, close, read, write, fdatasync, Dirent, Stats, promises } from 'fs'; +import { open, close, read, write, fdatasync, Stats, promises } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { SymlinkSupport, move, copy, rimraf, RimRafMode, exists, readdir } from 'vs/base/node/pfs'; +import { SymlinkSupport, move, copy, rimraf, RimRafMode, exists, readdir, IDirent } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; @@ -119,7 +119,7 @@ export class DiskFileSystemProvider extends Disposable implements } } - private toType(entry: Stats | Dirent, symbolicLink?: { dangling: boolean }): FileType { + private toType(entry: Stats | IDirent, symbolicLink?: { dangling: boolean }): FileType { // Signal file type by checking for file / directory, except: // - symbolic links pointing to non-existing files are FileType.Unknown