src/vs/platform/files/node/diskFileSystemProvider.ts

898 LOC · 595 covered · 303 uncovered · 143 ranges · 58 concepts · 13 introducers · 32 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filesrc/vs/base/node/pfs.ts · 866 LOCnode/pfs.tssrc/vs/base/parts/ipc/node/ipc.cp.ts · 284 LOCnode/ipc.cp.tssrc/vs/platform/agentHost/node/agentSdkDownloader.ts · 626 LOCnode/agentSdkDownloader.…src/vs/platform/checksum/node/checksumService.ts · 30 LOCnode/checksumService.tssrc/vs/platform/files/common/diskFileSystemProvider.ts · 256 LOCcommon/diskFileSystemPro…src/vs/platform/files/common/fileService.ts · 1491 LOCcommon/fileService.tssrc/vs/platform/files/common/files.ts · 1658 LOCcommon/files.tssrc/vs/platform/files/common/io.ts · 133 LOCcommon/io.tssrc/vs/platform/files/node/watcher/baseWatcher.ts · 275 LOCwatcher/baseWatcher.tssrc/vs/platform/files/node/watcher/nodejs/nodejsClient.ts · 26 LOCnodejs/nodejsClient.tssrc/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts · 178 LOCnodejs/nodejsWatcher.tssrc/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts · 637 LOCnodejs/nodejsWatcherLib.…src/vs/platform/files/node/watcher/watcherClient.ts · 47 LOCwatcher/watcherClient.tssrc/vs/platform/request/node/proxy.ts · 54 LOCnode/proxy.tssrc/vs/platform/request/node/requestService.ts · 296 LOCnode/requestService.tssrc/vs/platform/shell/node/shellEnv.ts · 222 LOCnode/shellEnv.tssrc/vs/platform/state/node/stateService.ts · 204 LOCnode/stateService.tscolorThemeData.ts ×16 · 64 introduced LOCcolorThemeData.ts ×16tokenClassificationRegistry.ts ×1 · 3 introduced LOCtokenClassificationRegis…tokenClassificationRegistry.ts ×2 · 7 introduced LOCtokenClassificationRegis…tokenStyleResolving.test|title=Themes - TokenStyleResolving language|occurrence=1 · 0 introduced LOCtokenStyleResolving.test…colorThemeData.ts ×1 · 2 introduced LOCcolorThemeData.ts ×1colorThemeData.ts ×12 · 57 introduced LOCcolorThemeData.ts ×12tokenClassificationRegistry.ts ×2 · 6 introduced LOCtokenClassificationRegis…colorThemeData.ts ×5 · 21 introduced LOCcolorThemeData.ts ×5colorThemeData.ts ×1 · 6 introduced LOCcolorThemeData.ts ×1colorThemeData.ts ×33 · 242 introduced LOCcolorThemeData.ts ×33colorThemeData.ts ×2 · 5 introduced LOCcolorThemeData.ts ×2tokenStyleResolving.test|title=Themes - TokenStyleResolving resolveScopes - match most specific|occurrence=1 · 0 introduced LOCtokenStyleResolving.test…colorThemeData.ts ×5 · 33 introduced LOCcolorThemeData.ts ×5colorThemeData.ts ×1 · 4 introduced LOCcolorThemeData.ts ×1tokenClassificationRegistry.ts ×1 · 16 introduced LOCtokenClassificationRegis…colorThemeData.ts ×1 · 1 introduced LOCcolorThemeData.ts ×1colorThemeData.ts ×13 · 114 introduced LOCcolorThemeData.ts ×13colorThemeData.ts ×1 · 1 introduced LOCcolorThemeData.ts ×1colorThemeData.ts ×4 · 15 introduced LOCcolorThemeData.ts ×4colorThemeData.ts ×57 · 483 introduced LOCcolorThemeData.ts ×57agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache hit returns immediately without re-downloading|occurrence=1 · 0 introduced LOCagentSdkDownloader.test|…diskFileSystemProvider.ts ×11 · 79 introduced LOCdiskFileSystemProvider.t…agentSdkDownloader.ts ×3 · 28 introduced LOCagentSdkDownloader.ts ×3diskFileSystemProvider.ts ×20 · 260 introduced LOCdiskFileSystemProvider.t…agentSdkDownloader.ts ×1 · 2 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×1 · 2 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×2 · 20 introduced LOCagentSdkDownloader.ts ×2stateService.ts ×2 · 7 introduced LOCstateService.ts ×2stateService.ts ×1 · 2 introduced LOCstateService.ts ×1stateService.ts ×2 · 14 introduced LOCstateService.ts ×2stateService.ts ×1 · 2 introduced LOCstateService.ts ×1diskFileSystemProvider.ts ×9 · 73 introduced LOCdiskFileSystemProvider.t…agentSdkDownloader.ts ×1 · 6 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×2 · 8 introduced LOCagentSdkDownloader.ts ×2agentSdkDownloader.ts ×1 · 5 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×2 · 8 introduced LOCagentSdkDownloader.ts ×2agentSdkDownloader.ts ×5 · 10 introduced LOCagentSdkDownloader.ts ×5agentSdkDownloader.ts ×1 · 3 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×3 · 5 introduced LOCagentSdkDownloader.ts ×3agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: true when product config populated and host has a target|occurrence=1 · 0 introduced LOCagentSdkDownloader.test|…agentSdkDownloader.ts ×1 · 2 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×2 · 3 introduced LOCagentSdkDownloader.ts ×2agentSdkDownloader.ts ×1 · 8 introduced LOCagentSdkDownloader.ts ×1diskFileSystemProvider.ts ×28 · 135 introduced LOCdiskFileSystemProvider.t…diskFileSystemProvider.ts ×3 · 12 introduced LOCdiskFileSystemProvider.t…io.ts ×12 · 175 introduced LOCio.ts ×12agentSdkDownloader.ts ×1 · 2 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×1 · 2 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×1 · 2 introduced LOCagentSdkDownloader.ts ×1agentSdkDownloader.ts ×2 · 5 introduced LOCagentSdkDownloader.ts ×2diskFileSystemProvider.ts ×17 · 105 introduced LOCdiskFileSystemProvider.t…diskFileSystemProvider.ts ×1 · 21 introduced LOCdiskFileSystemProvider.t…stateService.ts ×1 · 2 introduced LOCstateService.ts ×1stateService.ts ×16 · 141 introduced LOCstateService.ts ×16diskFileSystemProvider.ts ×2 · 3 introduced LOCdiskFileSystemProvider.t…diskFileSystemProvider.ts ×10 · 34 introduced LOCdiskFileSystemProvider.t…diskFileSystemProvider.ts ×15 · 104 introduced LOCdiskFileSystemProvider.t…diskFileSystemProvider.ts ×21 · 577 introduced LOCdiskFileSystemProvider.t…agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: false when no env override and no product config|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: false when no env override and no product config|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: true when env override set|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: true when env override set|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: true when product config populated and host has a target|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader isAvailable: true when product config populated and host has a target|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache dir includes sdkTarget so Universal launches stay separate|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache dir includes sdkTarget so Universal launches stay separate|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache hit returns immediately without re-downloading|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache hit returns immediately without re-downloading|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache miss → downloads, extracts, writes sentinel|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cache miss → downloads, extracts, writes sentinel|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cancel before download completes cleans up scratch dir|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: cancel before download completes cleans up scratch dir|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: concurrent calls in same process share one download|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: concurrent calls in same process share one download|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: dev override returns the path unchanged|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: dev override returns the path unchanged|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: missing product config and no env override throws actionable error|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: missing product config and no env override throws actionable error|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: rename-loser path returns existing cache when winner already published|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: rename-loser path returns existing cache when winner already published|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: reports monotonic download progress ending at totalBytes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: reports monotonic download progress ending at totalBytes|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: substitutes {sdkTarget} into urlTemplate|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: substitutes {sdkTarget} into urlTemplate|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: urlTemplate with unknown placeholder throws config error|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=AgentSdkDownloader loadSdkRoot: urlTemplate with unknown placeholder throws config error|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=resolveSdkTarget appends -musl on musl Linux iff the package has separate musl SKUs|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=resolveSdkTarget appends -musl on musl Linux iff the package has separate musl SKUs|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=resolveSdkTarget returns <platform>-<arch> for supported (platform, arch)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=resolveSdkTarget returns <platform>-<arch> for supported (platform, arch)|occurrence=1agentSdkDownloader.test|…agentSdkDownloader.test|title=resolveSdkTarget returns undefined for unsupported (platform, arch)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/agentHost/test/node/agentSdkDownloader.test|title=resolveSdkTarget returns undefined for unsupported (platform, arch)|occurrence=1agentSdkDownloader.test|…checksumService.test|title=Checksum Service checksum|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/checksum/test/node/checksumService.test|title=Checksum Service checksum|occurrence=1checksumService.test|tit…state.test|title=StateService Basics (delayed strategy)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Basics (delayed strategy)|occurrence=1state.test|title=StateSe…state.test|title=StateService Basics (immediate strategy)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Basics (immediate strategy)|occurrence=1state.test|title=StateSe…state.test|title=StateService Closed before init|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Closed before init|occurrence=1state.test|title=StateSe…state.test|title=StateService Multiple ops (Immediate Strategy)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Multiple ops (Immediate Strategy)|occurrence=1state.test|title=StateSe…state.test|title=StateService Multiple ops are buffered and applied|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Multiple ops are buffered and applied|occurrence=1state.test|title=StateSe…state.test|title=StateService Used after close|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Used after close|occurrence=1state.test|title=StateSe…state.test|title=StateService Used before init|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/state/test/node/state.test|title=StateService Used before init|occurrence=1state.test|title=StateSe…tokenStyleResolving.test|title=Themes - TokenStyleResolving color defaults|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving color defaults|occurrence=1tokenStyleResolving.test…tokenStyleResolving.test|title=Themes - TokenStyleResolving language - scope resolving|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving language - scope resolving|occurrence=1tokenStyleResolving.test…tokenStyleResolving.test|title=Themes - TokenStyleResolving language|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving language|occurrence=1tokenStyleResolving.test…tokenStyleResolving.test|title=Themes - TokenStyleResolving resolveScopes - match most specific|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving resolveScopes - match most specific|occurrence=1tokenStyleResolving.test…tokenStyleResolving.test|title=Themes - TokenStyleResolving resolveScopes|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving resolveScopes|occurrence=1tokenStyleResolving.test…tokenStyleResolving.test|title=Themes - TokenStyleResolving rule matching|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving rule matching|occurrence=1tokenStyleResolving.test…tokenStyleResolving.test|title=Themes - TokenStyleResolving super type|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/services/themes/test/node/tokenStyleResolving.test|title=Themes - TokenStyleResolving super type|occurrence=1tokenStyleResolving.test…Focused file · src/vs/platform/files/node/diskFileSystemProvider.ts · 898 LOCnode/diskFileSystemProvi…

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- diskFileSystemProvider.ts ×21
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Stats, constants, promises } from 'fs';
7 > import { Barrier, retry } from '../../../base/common/async.js';
8 > import { ResourceMap } from '../../../base/common/map.js';
9 > import { VSBuffer } from '../../../base/common/buffer.js';
10 > import { CancellationToken } from '../../../base/common/cancellation.js';
11 > import { Event } from '../../../base/common/event.js';
12 > import { isEqual } from '../../../base/common/extpath.js';
13 > import { DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
14 > import { basename, dirname, join } from '../../../base/common/path.js';
15 > import { isLinux, isWindows } from '../../../base/common/platform.js';
16 > import { extUriBiasedIgnorePathCase, joinPath, basename as resourcesBasename, dirname as resourcesDirname } from '../../../base/common/resources.js';
17 > import { newWriteableStream, ReadableStreamEvents } from '../../../base/common/stream.js';
18 > import { URI } from '../../../base/common/uri.js';
19 > import { IDirent, Promises, RimRafMode, SymlinkSupport } from '../../../base/node/pfs.js';
20 > import { localize } from '../../../nls.js';
21 > import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileChange, IFileSystemProviderWithFileRealpathCapability } from '../common/files.js';
22 > import { readFileIntoStream } from '../common/io.js';
23 > import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage } from '../common/watcher.js';
24 > import { AbstractDiskFileSystemProvider } from '../common/diskFileSystemProvider.js';
25 > import { UniversalWatcherClient } from './watcher/watcherClient.js';
26 > import { NodeJSWatcherClient } from './watcher/nodejs/nodejsClient.js';
27 >
28 > export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider implements
29 > IFileSystemProviderWithFileReadWriteCapability,
30 > IFileSystemProviderWithOpenReadWriteCloseCapability,
31 > IFileSystemProviderWithFileReadStreamCapability,
32 > IFileSystemProviderWithFileFolderCopyCapability,
33 > IFileSystemProviderWithFileAtomicReadCapability,
34 > IFileSystemProviderWithFileAtomicWriteCapability,
35 > IFileSystemProviderWithFileAtomicDeleteCapability,
36 > IFileSystemProviderWithFileCloneCapability,
37 > IFileSystemProviderWithFileRealpathCapability {
39 > private static TRACE_LOG_RESOURCE_LOCKS = false; // not enabled by default because very spammy
40 >
41 > //#region File Capabilities
42 >
43 > readonly onDidChangeCapabilities = Event.None;
44 >
45 > private _capabilities: FileSystemProviderCapabilities | undefined;
46 > get capabilities(): FileSystemProviderCapabilities {
47 > if (!this._capabilities) { diskFileSystemProvider.ts ×1
48 > this._capabilities =
49 > FileSystemProviderCapabilities.FileReadWrite |
50 > FileSystemProviderCapabilities.FileOpenReadWriteClose |
51 > FileSystemProviderCapabilities.FileReadStream |
52 > FileSystemProviderCapabilities.FileFolderCopy |
53 > FileSystemProviderCapabilities.FileWriteUnlock |
54 > FileSystemProviderCapabilities.FileAppend |
55 > FileSystemProviderCapabilities.FileAtomicRead |
56 > FileSystemProviderCapabilities.FileAtomicWrite |
57 > FileSystemProviderCapabilities.FileAtomicDelete |
58 > FileSystemProviderCapabilities.FileClone |
59 > FileSystemProviderCapabilities.FileRealpath;
60 >
61 > if (isLinux) {
62 > this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
63 > }
64 > }
65 >
66 > return this._capabilities;
67 > }
69 > //#endregion
70 >
71 > //#region File Metadata Resolving
72 >
73 > async stat(resource: URI): Promise<IStat> {
75 > const { stat, symbolicLink } = await SymlinkSupport.stat(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly
76 >
77 > let permissions: FilePermission | undefined = undefined;
78 > if ((stat.mode & 0o200) === 0) {
79 permissions = FilePermission.Locked;
80 }
82 > stat.mode & constants.S_IXUSR ||
83 > stat.mode & constants.S_IXGRP || diskFileSystemProvider.ts ×2
84 > stat.mode & constants.S_IXOTH
86 > permissions = (permissions ?? 0) | FilePermission.Executable; diskFileSystemProvider.ts ×20
87 > }
89 > return {
90 > type: this.toType(stat, symbolicLink),
91 > ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time
92 > mtime: stat.mtime.getTime(),
93 > size: stat.size,
94 > permissions
95 > };
96 > } catch (error) {
97 > throw this.toFileSystemProviderError(error); diskFileSystemProvider.ts ×20
98 > }
101 > private async statIgnoreError(resource: URI): Promise<IStat | undefined> {
103 > return await this.stat(resource);
104 > } catch (error) {
105 > return undefined; diskFileSystemProvider.ts ×11
106 > }
109 > async realpath(resource: URI): Promise<string> {
110 const filePath = this.toFilePath(resource);
111
112 return Promises.realpath(filePath);
113 }
115 > async readdir(resource: URI): Promise<[string, FileType][]> {
117 > const children = await Promises.readdir(this.toFilePath(resource), { withFileTypes: true });
118 >
119 > const result: [string, FileType][] = [];
120 > await Promise.all(children.map(async child => {
122 > let type: FileType;
123 > if (child.isSymbolicLink()) {
124 type = (await this.stat(joinPath(resource, child.name))).type; // always resolve target the link points to if any
126 > type = this.toType(child);
127 > }
128 >
129 > result.push([child.name, type]);
130 > } catch (error) {
131 this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied
132 }
134 >
135 > return result;
136 > } catch (error) {
137 throw this.toFileSystemProviderError(error);
138 }
141 > private toType(entry: Stats | IDirent, symbolicLink?: { dangling: boolean }): FileType {
143 > // Signal file type by checking for file / directory, except:
144 > // - symbolic links pointing to nonexistent files are FileType.Unknown
145 > // - files that are neither file nor directory are FileType.Unknown
146 > let type: FileType;
147 > if (symbolicLink?.dangling) {
148 type = FileType.Unknown;
149 > } else if (entry.isFile()) { diskFileSystemProvider.ts ×10
150 > type = FileType.File; diskFileSystemProvider.ts ×2
151 > } else if (entry.isDirectory()) { diskFileSystemProvider.ts ×10
152 > type = FileType.Directory; diskFileSystemProvider.ts ×20
153 > } else {
154 type = FileType.Unknown;
155 }
157 > // Always signal symbolic link as file type additionally
158 > if (symbolicLink) {
159 type |= FileType.SymbolicLink;
160 }
162 > return type;
163 > }
165 > //#endregion
166 >
167 > //#region File Reading/Writing
168 >
169 > private readonly resourceLocks = new ResourceMap<Barrier>(resource => extUriBiasedIgnorePathCase.getComparisonKey(resource));
170 >
171 > private async createResourceLock(resource: URI): Promise<IDisposable> {
172 > const filePath = this.toFilePath(resource); diskFileSystemProvider.ts ×28
173 > this.traceLock(`[Disk FileSystemProvider]: createResourceLock() - request to acquire resource lock (${filePath})`);
174 >
175 > // Await pending locks for resource. It is possible for a new lock being
176 > // added right after opening, so we have to loop over locks until no lock
177 > // remains.
178 > let existingLock: Barrier | undefined = undefined;
179 > while (existingLock = this.resourceLocks.get(resource)) {
180 this.traceLock(`[Disk FileSystemProvider]: createResourceLock() - waiting for resource lock to be released (${filePath})`);
181 await existingLock.wait();
182 }
184 > // Store new
185 > const newLock = new Barrier();
186 > this.resourceLocks.set(resource, newLock);
187 >
188 > this.traceLock(`[Disk FileSystemProvider]: createResourceLock() - new resource lock created (${filePath})`);
189 >
190 > return toDisposable(() => {
191 > this.traceLock(`[Disk FileSystemProvider]: createResourceLock() - resource lock dispose() (${filePath})`);
192 >
193 > // Delete lock if it is still ours
194 > if (this.resourceLocks.get(resource) === newLock) {
195 > this.traceLock(`[Disk FileSystemProvider]: createResourceLock() - resource lock removed from resource-lock map (${filePath})`);
196 > this.resourceLocks.delete(resource);
197 > }
198 >
199 > // Open lock
200 > this.traceLock(`[Disk FileSystemProvider]: createResourceLock() - resource lock barrier open() (${filePath})`);
201 > newLock.open();
202 > });
203 > }
205 > async readFile(resource: URI, options?: IFileAtomicReadOptions): Promise<Uint8Array> {
206 > let lock: IDisposable | undefined = undefined; diskFileSystemProvider.ts ×3
207 > try {
208 > if (options?.atomic) {
209 this.traceLock(`[Disk FileSystemProvider]: atomic read operation started (${this.toFilePath(resource)})`);
210
211 // When the read should be atomic, make sure
212 // to await any pending locks for the resource
213 // and lock for the duration of the read.
214 lock = await this.createResourceLock(resource);
215 }
217 > const filePath = this.toFilePath(resource);
218 >
219 > return await promises.readFile(filePath);
220 > } catch (error) {
221 throw this.toFileSystemProviderError(error);
222 > } finally { diskFileSystemProvider.ts ×3
223 > lock?.dispose();
224 > }
225 > }
227 > private traceLock(msg: string): void {
228 > if (DiskFileSystemProvider.TRACE_LOG_RESOURCE_LOCKS) { diskFileSystemProvider.ts ×28
229 this.logService.trace(msg);
230 }
233 > readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
234 > const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); io.ts ×12
235 >
236 > readFileIntoStream(this, resource, stream, data => data.buffer, {
237 > ...opts,
238 > bufferSize: 256 * 1024 // read into chunks of 256kb each to reduce IPC overhead
239 > }, token);
240 >
241 > return stream;
242 > }
244 > async writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
245 > if (opts?.atomic !== false && opts?.atomic?.postfix && await this.canWriteFileAtomic(resource)) { diskFileSystemProvider.ts ×28
246 > return this.doWriteFileAtomic(resource, joinPath(resourcesDirname(resource), `${resourcesBasename(resource)}${opts.atomic.postfix}`), content, opts); diskFileSystemProvider.ts ×9
248 > return this.doWriteFile(resource, content, opts); diskFileSystemProvider.ts ×11
249 > }
252 > private async canWriteFileAtomic(resource: URI): Promise<boolean> {
254 > const filePath = this.toFilePath(resource);
255 > const { symbolicLink } = await SymlinkSupport.stat(filePath);
256 > if (symbolicLink) {
257 // atomic writes are unsupported for symbolic links because
258 // we need to ensure that the `rename` operation is atomic
259 // and that only works if the link is on the same disk.
260 // Since we do not know where the symbolic link points to
261 // we refuse to write atomically.
262 return false;
263 }
264 > } catch (error) { diskFileSystemProvider.ts ×9
265 // ignore stat errors here and just proceed trying to write
266 }
268 > return true; // atomic writing supported
269 > }
271 > private async doWriteFileAtomic(resource: URI, tempResource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise<void> {
273 > // Ensure to create locks for all resources involved
274 > // since atomic write involves mutiple disk operations
275 > // and resources.
276 >
277 > const locks = new DisposableStore();
278 >
279 > try {
280 > locks.add(await this.createResourceLock(resource));
281 > locks.add(await this.createResourceLock(tempResource));
282 >
283 > // Write to temp resource first
284 > await this.doWriteFile(tempResource, content, { ...opts, create: true, overwrite: true }, true /* disable write lock */);
285 >
286 > try {
287 >
288 > // Rename over existing to ensure atomic replace
289 > await this.rename(tempResource, resource, { overwrite: true });
290 >
291 > } catch (error) {
292
293 // Cleanup in case of rename error
294 try {
295 await this.delete(tempResource, { recursive: false, useTrash: false, atomic: false });
296 } catch (error) {
297 // ignore - we want the outer error to bubble up
298 }
299
300 throw error;
301 }
302 > } finally { diskFileSystemProvider.ts ×9
303 > locks.dispose();
304 > }
305 > }
307 > private async doWriteFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions, disableWriteLock?: boolean): Promise<void> {
308 > let handle: number | undefined = undefined; diskFileSystemProvider.ts ×28
309 > try {
310 > const filePath = this.toFilePath(resource);
311 >
312 > // Validate target unless { create: true, overwrite: true }
313 > if (!opts.create || !opts.overwrite) {
314 const fileExists = await Promises.exists(filePath);
315 if (fileExists) {
316 if (!opts.overwrite) {
317 throw createFileSystemProviderError(localize('fileExists', "File already exists"), FileSystemProviderErrorCode.FileExists);
318 }
319 } else {
320 if (!opts.create) {
321 throw createFileSystemProviderError(localize('fileNotExists', "File does not exist"), FileSystemProviderErrorCode.FileNotFound);
322 }
323 }
324 }
326 > // Open
327 > handle = await this.open(resource, { create: true, append: opts.append, unlock: opts.unlock }, disableWriteLock);
328 >
329 > // Write content at once
330 > await this.write(handle, 0, content, 0, content.byteLength);
331 > } catch (error) {
332 throw await this.toFileSystemProviderWriteError(resource, error);
333 > } finally { diskFileSystemProvider.ts ×28
334 > if (typeof handle === 'number') {
335 > await this.close(handle);
336 > }
337 > }
338 > }
340 > private readonly mapHandleToPos = new Map<number, number>();
341 > private readonly mapHandleToLock = new Map<number, IDisposable>();
342 >
343 > private readonly writeHandles = new Map<number, URI>();
345 > private static canFlush = true;
346 >
347 > static configureFlushOnWrite(enabled: boolean): void {
348 DiskFileSystemProvider.canFlush = enabled;
349 }
351 > async open(resource: URI, opts: IFileOpenOptions, disableWriteLock?: boolean): Promise<number> {
352 > const filePath = this.toFilePath(resource); diskFileSystemProvider.ts ×17
353 >
354 > // Writes: guard multiple writes to the same resource
355 > // behind a single lock to prevent races when writing
356 > // from multiple places at the same time to the same file
357 > let lock: IDisposable | undefined = undefined;
358 > if (isFileOpenForWriteOptions(opts) && !disableWriteLock) {
359 > lock = await this.createResourceLock(resource); diskFileSystemProvider.ts ×11
360 > }
362 > let fd: number | undefined = undefined;
363 > try {
364 >
365 > // Determine whether to unlock the file (write only)
366 > if (isFileOpenForWriteOptions(opts) && opts.unlock) {
367 try {
368 const { stat } = await SymlinkSupport.stat(filePath);
369 if (!(stat.mode & 0o200 /* File mode indicating writable by owner */)) {
370 await promises.chmod(filePath, stat.mode | 0o200);
371 }
372 } catch (error) {
373 if (error.code !== 'ENOENT') {
374 this.logService.trace(error); // log errors but do not give up writing
375 }
376 }
377 }
379 > // Windows gets special treatment (write only, but not for append)
380 > if (isWindows && isFileOpenForWriteOptions(opts) && !opts.append) {
381 try {
382
383 // We try to use 'r+' for opening (which will fail if the file does not exist)
384 // to prevent issues when saving hidden files or preserving alternate data
385 // streams.
386 // Related issues:
387 // - https://github.com/microsoft/vscode/issues/931
388 // - https://github.com/microsoft/vscode/issues/6363
389 fd = await Promises.open(filePath, 'r+');
390
391 // The flag 'r+' will not truncate the file, so we have to do this manually
392 await Promises.ftruncate(fd, 0);
393 } catch (error) {
394 if (error.code !== 'ENOENT') {
395 this.logService.trace(error); // log errors but do not give up writing
396 }
397
398 // Make sure to close the file handle if we have one
399 if (typeof fd === 'number') {
400 try {
401 await Promises.close(fd);
402 } catch (error) {
403 this.logService.trace(error); // log errors but do not give up writing
404 }
405
406 // Reset `fd` to be able to try again with 'w'
407 fd = undefined;
408 }
409 }
410 }
412 > if (typeof fd !== 'number') {
413 > fd = await Promises.open(filePath, isFileOpenForWriteOptions(opts) ?
414 > // We take `opts.create` as a hint that the file is opened for writing diskFileSystemProvider.ts ×28
415 > // as such we use 'w' to truncate an existing or create the
416 > // file otherwise. we do not allow reading.
417 > // If `opts.append` is true, use 'a' to append to the file.
418 > (opts.append ? 'a' : 'w') :
419 > // Otherwise we assume the file is opened for reading io.ts ×12
420 > // as such we use 'r' to neither truncate, nor create
421 > // the file.
422 > 'r'
424 > }
425 >
426 > } catch (error) {
427
428 // Release lock because we have no valid handle
429 // if we did open a lock during this operation
430 lock?.dispose();
431
432 // Rethrow as file system provider error
433 if (isFileOpenForWriteOptions(opts)) {
434 throw await this.toFileSystemProviderWriteError(resource, error);
435 } else {
436 throw this.toFileSystemProviderError(error);
437 }
438 }
440 > // Remember this handle to track file position of the handle
441 > // we init the position to 0 since the file descriptor was
442 > // just created and the position was not moved so far (see
443 > // also http://man7.org/linux/man-pages/man2/open.2.html -
444 > // "The file offset is set to the beginning of the file.")
445 > this.mapHandleToPos.set(fd, 0);
446 >
447 > // remember that this handle was used for writing
448 > if (isFileOpenForWriteOptions(opts)) {
449 > this.writeHandles.set(fd, resource); diskFileSystemProvider.ts ×28
450 > }
452 > if (lock) {
453 > const previousLock = this.mapHandleToLock.get(fd); diskFileSystemProvider.ts ×11
454 >
455 > // Remember that this handle has an associated lock
456 > this.traceLock(`[Disk FileSystemProvider]: open() - storing lock for handle ${fd} (${filePath})`);
457 > this.mapHandleToLock.set(fd, lock);
458 >
459 > // There is a slight chance that a resource lock for a
460 > // handle was not yet disposed when we acquire a new
461 > // lock, so we must ensure to dispose the previous lock
462 > // before storing a new one for the same handle, other
463 > // wise we end up in a deadlock situation
464 > // https://github.com/microsoft/vscode/issues/142462
465 > if (previousLock) {
466 this.traceLock(`[Disk FileSystemProvider]: open() - disposing a previous lock that was still stored on same handle ${fd} (${filePath})`);
467 previousLock.dispose();
468 }
471 > return fd;
472 > }
474 > async close(fd: number): Promise<void> {
476 > // It is very important that we keep any associated lock
477 > // for the file handle before attempting to call `fs.close(fd)`
478 > // because of a possible race condition: as soon as a file
479 > // handle is released, the OS may assign the same handle to
480 > // the next `fs.open` call and as such it is possible that our
481 > // lock is getting overwritten
482 > const lockForHandle = this.mapHandleToLock.get(fd);
483 >
484 > try {
485 >
486 > // Remove this handle from map of positions
487 > this.mapHandleToPos.delete(fd);
488 >
489 > // If a handle is closed that was used for writing, ensure
490 > // to flush the contents to disk if possible.
491 > if (this.writeHandles.delete(fd) && DiskFileSystemProvider.canFlush) {
493 > await Promises.fdatasync(fd); // https://github.com/microsoft/vscode/issues/9589
494 > } catch (error) {
495 // In some exotic setups it is well possible that node fails to sync
496 // In that case we disable flushing and log the error to our logger
497 DiskFileSystemProvider.configureFlushOnWrite(false);
498 this.logService.error(error);
499 }
502 > return await Promises.close(fd);
503 > } catch (error) {
504 throw this.toFileSystemProviderError(error);
505 > } finally { diskFileSystemProvider.ts ×17
506 > if (lockForHandle) {
507 > if (this.mapHandleToLock.get(fd) === lockForHandle) { diskFileSystemProvider.ts ×11
508 > this.traceLock(`[Disk FileSystemProvider]: close() - resource lock removed from handle-lock map ${fd}`);
509 > this.mapHandleToLock.delete(fd); // only delete from map if this is still our lock!
510 > }
511 >
512 > this.traceLock(`[Disk FileSystemProvider]: close() - disposing lock for handle ${fd}`);
513 > lockForHandle.dispose();
514 > }
516 > }
518 > async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
519 > const normalizedPos = this.normalizePos(fd, pos); io.ts ×12
520 >
521 > let bytesRead: number | null = null;
522 > try {
523 > bytesRead = (await Promises.read(fd, data, offset, length, normalizedPos)).bytesRead;
524 > } catch (error) {
525 throw this.toFileSystemProviderError(error);
526 > } finally { io.ts ×12
527 > this.updatePos(fd, normalizedPos, bytesRead);
528 > }
529 >
530 > return bytesRead;
531 > }
533 > private normalizePos(fd: number, pos: number): number | null {
535 > // When calling fs.read/write we try to avoid passing in the "pos" argument and
536 > // rather prefer to pass in "null" because this avoids an extra seek(pos)
537 > // call that in some cases can even fail (e.g. when opening a file over FTP -
538 > // see https://github.com/microsoft/vscode/issues/73884).
539 > //
540 > // as such, we compare the passed in position argument with our last known
541 > // position for the file descriptor and use "null" if they match.
542 > if (pos === this.mapHandleToPos.get(fd)) {
543 > return null;
544 > }
545
546 return pos;
549 > private updatePos(fd: number, pos: number | null, bytesLength: number | null): void {
550 > const lastKnownPos = this.mapHandleToPos.get(fd); diskFileSystemProvider.ts ×17
551 > if (typeof lastKnownPos === 'number') {
552 >
553 > // pos !== null signals that previously a position was used that is
554 > // not null. node.js documentation explains, that in this case
555 > // the internal file pointer is not moving and as such we do not move
556 > // our position pointer.
557 > //
558 > // Docs: "If position is null, data will be read from the current file position,
559 > // and the file position will be updated. If position is an integer, the file position
560 > // will remain unchanged."
561 > if (typeof pos === 'number') {
562 // do not modify the position
563 }
565 > // bytesLength = number is a signal that the read/write operation was
566 > // successful and as such we need to advance the position in the Map
567 > //
568 > // Docs (http://man7.org/linux/man-pages/man2/read.2.html):
569 > // "On files that support seeking, the read operation commences at the
570 > // file offset, and the file offset is incremented by the number of
571 > // bytes read."
572 > //
573 > // Docs (http://man7.org/linux/man-pages/man2/write.2.html):
574 > // "For a seekable file (i.e., one to which lseek(2) may be applied, for
575 > // example, a regular file) writing takes place at the file offset, and
576 > // the file offset is incremented by the number of bytes actually
577 > // written."
578 > else if (typeof bytesLength === 'number') {
579 > this.mapHandleToPos.set(fd, lastKnownPos + bytesLength);
580 > }
581
582 // bytesLength = null signals an error in the read/write operation
583 // and as such we drop the handle from the Map because the position
584 // is unspecificed at this point.
585 else {
586 this.mapHandleToPos.delete(fd);
587 }
589 > }
591 > async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
593 > // We know at this point that the file to write to is truncated and thus empty
594 > // if the write now fails, the file remains empty. as such we really try hard
595 > // to ensure the write succeeds by retrying up to three times.
596 > return retry(() => this.doWrite(fd, pos, data, offset, length), 100 /* ms delay */, 3 /* retries */);
597 > }
599 > private async doWrite(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
600 > const normalizedPos = this.normalizePos(fd, pos); diskFileSystemProvider.ts ×28
601 >
602 > let bytesWritten: number | null = null;
603 > try {
604 > bytesWritten = (await Promises.write(fd, data, offset, length, normalizedPos)).bytesWritten;
605 > } catch (error) {
606 throw await this.toFileSystemProviderWriteError(this.writeHandles.get(fd), error);
607 > } finally { diskFileSystemProvider.ts ×28
608 > this.updatePos(fd, normalizedPos, bytesWritten);
609 > }
610 >
611 > return bytesWritten;
612 > }
614 > //#endregion
615 >
616 > //#region Move/Copy/Delete/Create Folder
617 >
618 > async mkdir(resource: URI): Promise<void> {
620 > await promises.mkdir(this.toFilePath(resource));
621 > } catch (error) {
622 throw this.toFileSystemProviderError(error);
623 }
626 > async delete(resource: URI, opts: IFileDeleteOptions): Promise<void> {
628 > const filePath = this.toFilePath(resource);
629 > if (opts.recursive) {
630 > let rmMoveToPath: string | undefined = undefined; agentSdkDownloader.ts ×3
631 > if (opts?.atomic !== false && opts.atomic.postfix) {
632 rmMoveToPath = join(dirname(filePath), `${basename(filePath)}${opts.atomic.postfix}`);
633 }
635 > await Promises.rm(filePath, RimRafMode.MOVE, rmMoveToPath);
638 > await promises.unlink(filePath);
639 > } catch (unlinkError) {
640
641 // `fs.unlink` will throw when used on directories
642 // we try to detect this error and then see if the
643 // provided resource is actually a directory. in that
644 // case we use `fs.rmdir` to delete the directory.
645
646 if (unlinkError.code === 'EPERM' || unlinkError.code === 'EISDIR') {
647 let isDirectory = false;
648 try {
649 const { stat, symbolicLink } = await SymlinkSupport.stat(filePath);
650 isDirectory = stat.isDirectory() && !symbolicLink;
651 } catch (statError) {
652 // ignore
653 }
654
655 if (isDirectory) {
656 await promises.rmdir(filePath);
657 } else {
658 throw unlinkError;
659 }
660 } else {
661 throw unlinkError;
662 }
663 }
665 > } catch (error) { diskFileSystemProvider.ts ×20
666 throw this.toFileSystemProviderError(error);
667 }
670 > async rename(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
671 > const fromFilePath = this.toFilePath(from); diskFileSystemProvider.ts ×28
672 > const toFilePath = this.toFilePath(to);
673 >
674 > if (fromFilePath === toFilePath) {
675 return; // simulate node.js behaviour here and do a no-op if paths match
676 }
678 > try {
679 >
680 > // Validate the move operation can perform
681 > await this.validateMoveCopy(from, to, 'move', opts.overwrite);
682 >
683 > // Rename
684 > await Promises.rename(fromFilePath, toFilePath);
685 > } catch (error) {
686
687 // Rewrite some typical errors that can happen especially around symlinks
688 // to something the user can better understand
689 if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') {
690 error = new Error(localize('moveError', "Unable to move '{0}' into '{1}' ({2}).", basename(fromFilePath), basename(dirname(toFilePath)), error.toString()));
691 }
692
693 throw this.toFileSystemProviderError(error);
694 }
697 > async copy(from: URI, to: URI, opts: IFileOverwriteOptions): Promise<void> {
698 const fromFilePath = this.toFilePath(from);
699 const toFilePath = this.toFilePath(to);
700
701 if (fromFilePath === toFilePath) {
702 return; // simulate node.js behaviour here and do a no-op if paths match
703 }
704
705 try {
706
707 // Validate the copy operation can perform
708 await this.validateMoveCopy(from, to, 'copy', opts.overwrite);
709
710 // Copy
711 await Promises.copy(fromFilePath, toFilePath, { preserveSymlinks: true });
712 } catch (error) {
713
714 // Rewrite some typical errors that can happen especially around symlinks
715 // to something the user can better understand
716 if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') {
717 error = new Error(localize('copyError', "Unable to copy '{0}' into '{1}' ({2}).", basename(fromFilePath), basename(dirname(toFilePath)), error.toString()));
718 }
719
720 throw this.toFileSystemProviderError(error);
721 }
722 }
724 > private async validateMoveCopy(from: URI, to: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<void> {
725 > const fromFilePath = this.toFilePath(from); diskFileSystemProvider.ts ×28
726 > const toFilePath = this.toFilePath(to);
727 >
728 > let isSameResourceWithDifferentPathCase = false;
729 > const isPathCaseSensitive = !!(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
730 > if (!isPathCaseSensitive) {
731 isSameResourceWithDifferentPathCase = isEqual(fromFilePath, toFilePath, true /* ignore case */);
732 }
734 > if (isSameResourceWithDifferentPathCase) {
735
736 // You cannot copy the same file to the same location with different
737 // path case unless you are on a case sensitive file system
738 if (mode === 'copy') {
739 throw createFileSystemProviderError(localize('fileCopyErrorPathCase', "File cannot be copied to same path with different path case"), FileSystemProviderErrorCode.FileExists);
740 }
741
742 // You can move the same file to the same location with different
743 // path case on case insensitive file systems
744 else if (mode === 'move') {
745 return;
746 }
747 }
749 > // Here we have to see if the target to move/copy to exists or not.
750 > // We need to respect the `overwrite` option to throw in case the
751 > // target exists.
752 >
753 > const fromStat = await this.statIgnoreError(from);
754 > if (!fromStat) {
755 throw createFileSystemProviderError(localize('fileMoveCopyErrorNotFound', "File to move/copy does not exist"), FileSystemProviderErrorCode.FileNotFound);
756 }
758 > const toStat = await this.statIgnoreError(to);
759 > if (!toStat) {
760 > return; // target does not exist so we are good diskFileSystemProvider.ts ×11
761 > }
763 > if (!overwrite) {
764 throw createFileSystemProviderError(localize('fileMoveCopyErrorExists', "File at target already exists and thus will not be moved/copied to unless overwrite is specified"), FileSystemProviderErrorCode.FileExists);
765 }
767 > // Handle existing target for move/copy
768 > if ((fromStat.type & FileType.File) !== 0 && (toStat.type & FileType.File) !== 0) { diskFileSystemProvider.ts ×28
769 > return; // node.js can move/copy a file over an existing file without having to delete it first diskFileSystemProvider.ts ×9
770 > } else {
771 await this.delete(to, { recursive: true, useTrash: false, atomic: false });
772 }
775 > //#endregion
776 >
777 > //#region Clone File
778 >
779 > async cloneFile(from: URI, to: URI): Promise<void> {
780 return this.doCloneFile(from, to, false /* optimistically assume parent folders exist */);
781 }
783 > private async doCloneFile(from: URI, to: URI, mkdir: boolean): Promise<void> {
784 const fromFilePath = this.toFilePath(from);
785 const toFilePath = this.toFilePath(to);
786
787 const isPathCaseSensitive = !!(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive);
788 if (isEqual(fromFilePath, toFilePath, !isPathCaseSensitive)) {
789 return; // cloning is only supported `from` and `to` are different files
790 }
791
792 // Implement clone by using `fs.copyFile`, however setup locks
793 // for both `from` and `to` because node.js does not ensure
794 // this to be an atomic operation
795
796 const locks = new DisposableStore();
797
798 try {
799 locks.add(await this.createResourceLock(from));
800 locks.add(await this.createResourceLock(to));
801
802 if (mkdir) {
803 await promises.mkdir(dirname(toFilePath), { recursive: true });
804 }
805
806 await promises.copyFile(fromFilePath, toFilePath);
807 } catch (error) {
808 if (error.code === 'ENOENT' && !mkdir) {
809 return this.doCloneFile(from, to, true);
810 }
811
812 throw this.toFileSystemProviderError(error);
813 } finally {
814 locks.dispose();
815 }
816 }
818 > //#endregion
819 >
820 > //#region File Watching
821 >
822 > protected createUniversalWatcher(
823 onChange: (changes: IFileChange[]) => void,
824 onLogMessage: (msg: ILogMessage) => void,
825 verboseLogging: boolean
826 ): AbstractUniversalWatcherClient {
827 return new UniversalWatcherClient(changes => onChange(changes), msg => onLogMessage(msg), verboseLogging);
828 }
830 > protected createNonRecursiveWatcher(
831 onChange: (changes: IFileChange[]) => void,
832 onLogMessage: (msg: ILogMessage) => void,
833 verboseLogging: boolean
834 ): AbstractNonRecursiveWatcherClient {
835 return new NodeJSWatcherClient(changes => onChange(changes), msg => onLogMessage(msg), verboseLogging);
836 }
838 > //#endregion
839 >
840 > //#region Helpers
841 >
842 > private toFileSystemProviderError(error: NodeJS.ErrnoException): FileSystemProviderError {
843 > if (error instanceof FileSystemProviderError) { diskFileSystemProvider.ts ×20
844 return error; // avoid double conversion
845 }
847 > let resultError: Error | string = error;
848 > let code: FileSystemProviderErrorCode;
849 > switch (error.code) {
850 > case 'ENOENT':
851 > code = FileSystemProviderErrorCode.FileNotFound;
852 > break;
853 > case 'EISDIR':
854 code = FileSystemProviderErrorCode.FileIsADirectory;
855 break;
856 > case 'ENOTDIR': diskFileSystemProvider.ts ×20
857 code = FileSystemProviderErrorCode.FileNotADirectory;
858 break;
859 > case 'EEXIST': diskFileSystemProvider.ts ×20
860 code = FileSystemProviderErrorCode.FileExists;
861 break;
862 > case 'EPERM': diskFileSystemProvider.ts ×20
863 > case 'EACCES':
864 code = FileSystemProviderErrorCode.NoPermissions;
865 break;
866 > case 'ERR_UNC_HOST_NOT_ALLOWED': diskFileSystemProvider.ts ×20
867 resultError = `${error.message}. Please update the 'security.allowedUNCHosts' setting if you want to allow this host.`;
868 code = FileSystemProviderErrorCode.Unknown;
869 break;
871 code = FileSystemProviderErrorCode.Unknown;
873 >
874 > return createFileSystemProviderError(resultError, code);
875 > }
877 > private async toFileSystemProviderWriteError(resource: URI | undefined, error: NodeJS.ErrnoException): Promise<FileSystemProviderError> {
878 let fileSystemProviderWriteError = this.toFileSystemProviderError(error);
879
880 // If the write error signals permission issues, we try
881 // to read the file's mode to see if the file is write
882 // locked.
883 if (resource && fileSystemProviderWriteError.code === FileSystemProviderErrorCode.NoPermissions) {
884 try {
885 const { stat } = await SymlinkSupport.stat(this.toFilePath(resource));
886 if (!(stat.mode & 0o200 /* File mode indicating writable by owner */)) {
887 fileSystemProviderWriteError = createFileSystemProviderError(error, FileSystemProviderErrorCode.FileWriteLocked);
888 }
889 } catch (error) {
890 this.logService.trace(error); // ignore - return original error
891 }
892 }
893
894 return fileSystemProviderWriteError;
895 }
897 > //#endregion
898 > }