src/vs/base/node/pfs.ts

866 LOC · 622 covered · 244 uncovered · 119 ranges · 2756 concepts · 40 introducers · 1371 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.

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 > /*--------------------------------------------------------------------------------------------- pfs.ts ×32
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 * as fs from 'fs';
7 > import { tmpdir } from 'os';
8 > import { promisify } from 'util';
9 > import { ResourceQueue, timeout } from '../common/async.js';
10 > import { isEqualOrParent, isRootOrDriveLetter, randomPath } from '../common/extpath.js';
11 > import { normalizeNFC } from '../common/normalization.js';
12 > import { basename, dirname, join, normalize, sep } from '../common/path.js';
13 > import { isLinux, isMacintosh, isWindows } from '../common/platform.js';
14 > import { extUriBiasedIgnorePathCase } from '../common/resources.js';
15 > import { URI } from '../common/uri.js';
16 > import { CancellationToken } from '../common/cancellation.js';
17 > import { rtrim } from '../common/strings.js';
18 >
19 > //#region rimraf
20 >
21 > export enum RimRafMode {
22 >
23 > /**
24 > * Slow version that unlinks each file and folder.
25 > */
26 > UNLINK,
27 >
28 > /**
29 > * Fast version that first moves the file/folder
30 > * into a temp directory and then deletes that
31 > * without waiting for it.
32 > */
33 > MOVE
34 > }
35 >
36 > /**
37 > * Allows to delete the provided path (either file or folder) recursively
38 > * with the options:
39 > * - `UNLINK`: direct removal from disk
40 > * - `MOVE`: faster variant that first moves the target to temp dir and then
41 > * deletes it in the background without waiting for that to finish.
42 > * the optional `moveToPath` allows to override where to rename the
43 > * path to before deleting it.
44 > */
45 > async function rimraf(path: string, mode: RimRafMode.UNLINK): Promise<void>;
46 > async function rimraf(path: string, mode: RimRafMode.MOVE, moveToPath?: string): Promise<void>;
47 > async function rimraf(path: string, mode?: RimRafMode, moveToPath?: string): Promise<void>;
48 > async function rimraf(path: string, mode = RimRafMode.UNLINK, moveToPath?: string): Promise<void> { pfs.ts ×3
49 > if (isRootOrDriveLetter(path)) {
50 throw new Error('rimraf - will refuse to recursively delete root');
51 }
52 > pfs.ts ×3
53 > // delete: via rm
54 > if (mode === RimRafMode.UNLINK) {
55 > return rimrafUnlink(path); pfs.ts ×1
56 > }
57 > pfs.ts ×3
58 > // delete: via move
59 > return rimrafMove(path, moveToPath);
60 > }
62 > async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Promise<void> { pfs.ts ×3
63 > try {
64 > try {
65 > await fs.promises.rename(path, moveToPath);
66 > } catch (error) {
67 > if (error.code === 'ENOENT') { pfs.ts ×1
68 > return; // ignore - path to delete did not exist
69 > }
70
71 return rimrafUnlink(path); // otherwise fallback to unlink
72 }
73 > pfs.ts ×1
74 > // Delete but do not return as promise
75 > rimrafUnlink(moveToPath).catch(() => {/* ignore */ });
76 > } catch (error) {
77 if (error.code !== 'ENOENT') {
78 throw error;
79 }
80 }
81 > } pfs.ts ×3
83 > async function rimrafUnlink(path: string): Promise<void> { pfs.ts ×3
84 > return fs.promises.rm(path, { recursive: true, force: true, maxRetries: 3 });
85 > }
87 > //#endregion
88 >
89 > //#region readdir with NFC support (macos)
90 >
91 > export interface IDirent {
92 > name: string;
93 >
94 > isFile(): boolean;
95 > isDirectory(): boolean;
96 > isSymbolicLink(): boolean;
97 > }
98 >
99 > /**
100 > * Drop-in replacement of `fs.readdir` with support
101 > * for converting from macOS NFD unicon form to NFC
102 > * (https://github.com/nodejs/node/issues/2165)
103 > */
104 > async function readdir(path: string): Promise<string[]>;
105 > async function readdir(path: string, options: { withFileTypes: true }): Promise<IDirent[]>;
106 > async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { pfs.ts ×5
107 > try {
108 > return await doReaddir(path, options);
109 > } catch (error) {
110 // Workaround for #252361 that should be removed once the upstream issue
111 // in node.js is resolved. Adds a trailing dot to a root drive letter path
112 // (G:\ => G:\.) as a workaround.
113 if (error.code === 'ENOENT' && isWindows && isRootOrDriveLetter(path)) {
114 try {
115 return await doReaddir(`${path}.`, options);
116 } catch {
117 // ignore
118 }
119 }
120 throw error;
121 }
122 > } pfs.ts ×5
123 > pfs.ts ×32
124 > async function doReaddir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { pfs.ts ×5
125 > return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : fs.promises.readdir(path)));
126 > }
127 > pfs.ts ×32
128 > async function safeReaddirWithFileTypes(path: string): Promise<IDirent[]> { pfs.ts ×1
129 > try {
130 > return await fs.promises.readdir(path, { withFileTypes: true });
131 > } catch (error) {
132 if (error.code !== 'ENOENT') {
133 console.warn('[node.js fs] readdir with filetypes failed with error: ', error);
134 }
135 }
136
137 // Fallback to manually reading and resolving each
138 // children of the folder in case we hit an error
139 // previously.
140 // This can only really happen on exotic file systems
141 // such as explained in #115645 where we get entries
142 // from `readdir` that we can later not `lstat`.
143 const result: IDirent[] = [];
144 const children = await readdir(path);
145 for (const child of children) {
146 let isFile = false;
147 let isDirectory = false;
148 let isSymbolicLink = false;
149
150 try {
151 const lstat = await fs.promises.lstat(join(path, child));
152
153 isFile = lstat.isFile();
154 isDirectory = lstat.isDirectory();
155 isSymbolicLink = lstat.isSymbolicLink();
156 } catch (error) {
157 if (error.code !== 'ENOENT') {
158 console.warn('[node.js fs] unexpected error from lstat after readdir: ', error);
159 }
160 }
161
162 result.push({
163 name: child,
164 isFile: () => isFile,
165 isDirectory: () => isDirectory,
166 isSymbolicLink: () => isSymbolicLink
167 });
168 }
169
170 return result;
171 }
172 > pfs.ts ×32
173 > function handleDirectoryChildren(children: string[]): string[];
174 > function handleDirectoryChildren(children: IDirent[]): IDirent[];
175 > function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[];
176 > function handleDirectoryChildren(children: (string | IDirent)[]): (string | IDirent)[] { pfs.ts ×5
177 > return children.map(child => {
178 > pfs.ts ×2
179 > // Mac: uses NFD unicode form on disk, but we want NFC
180 > // See also https://github.com/nodejs/node/issues/2165
181 >
182 > if (typeof child === 'string') {
183 > return isMacintosh ? normalizeNFC(child) : child; pfs.ts ×1
184 > }
185 > pfs.ts ×1
186 > child.name = isMacintosh ? normalizeNFC(child.name) : child.name; pfs.ts ×2
187 >
188 > return child;
189 > }); pfs.ts ×5
190 > }
191 > pfs.ts ×32
192 > /**
193 > * A convenience method to read all children of a path that
194 > * are directories.
195 > */
196 > async function readDirsInDir(dirPath: string): Promise<string[]> { pfs.ts ×3
197 > const children = await readdir(dirPath);
198 > const directories: string[] = [];
199 >
200 > for (const child of children) {
201 > if (await SymlinkSupport.existsDirectory(join(dirPath, child))) {
202 > directories.push(child);
203 > }
204 > }
205 >
206 > return directories;
207 > }
208 > pfs.ts ×32
209 > //#endregion
210 >
211 > //#region whenDeleted()
212 >
213 > /**
214 > * A `Promise` that resolves when the provided `path`
215 > * is deleted from disk.
216 > */
217 > export function whenDeleted(path: string, intervalMs = 1000): Promise<void> {
218 return new Promise<void>(resolve => {
219 let running = false;
220 const interval = setInterval(() => {
221 if (!running) {
222 running = true;
223 fs.access(path, err => {
224 running = false;
225
226 if (err) {
227 clearInterval(interval);
228 resolve(undefined);
229 }
230 });
231 }
232 }, intervalMs);
233 });
234 }
235 > pfs.ts ×32
236 > //#endregion
237 >
238 > //#region Methods with symbolic links support
239 >
240 > export namespace SymlinkSupport {
241 >
242 > export interface IStats {
243 >
244 > // The stats of the file. If the file is a symbolic
245 > // link, the stats will be of that target file and
246 > // not the link itself.
247 > // If the file is a symbolic link pointing to a non
248 > // existing file, the stat will be of the link and
249 > // the `dangling` flag will indicate this.
250 > stat: fs.Stats;
251 >
252 > // Will be provided if the resource is a symbolic link
253 > // on disk. Use the `dangling` flag to find out if it
254 > // points to a resource that does not exist on disk.
255 > symbolicLink?: { dangling: boolean };
256 > }
257 >
258 > /**
259 > * Resolves the `fs.Stats` of the provided path. If the path is a
260 > * symbolic link, the `fs.Stats` will be from the target it points
261 > * to. If the target does not exist, `dangling: true` will be returned
262 > * as `symbolicLink` value.
263 > */
264 > export async function stat(path: string): Promise<IStats> {
265 > pfs.ts ×4
266 > // First stat the link
267 > let lstats: fs.Stats | undefined;
268 > try {
269 > lstats = await fs.promises.lstat(path);
270 >
271 > // Return early if the stat is not a symbolic link at all
272 > if (!lstats.isSymbolicLink()) {
273 > return { stat: lstats }; pfs.ts ×1
274 > }
275 > } catch { pfs.ts ×4
276 > /* ignore - use stat() instead */ diskFileSystemProvider.ts ×20
277 > }
278 > pfs.ts ×1
279 > // If the stat is a symbolic link or failed to stat, use fs.stat()
280 > // which for symbolic links will stat the target they point to
281 > try {
282 > const stats = await fs.promises.stat(path);
283 > pfs.ts ×1
284 > return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined }; pfs.ts ×4
285 > } catch (error) {
286 > pfs.ts ×2
287 > // If the link points to a nonexistent file we still want
288 > // to return it as result while setting dangling: true flag
289 > if (error.code === 'ENOENT' && lstats) {
290 > return { stat: lstats, symbolicLink: { dangling: true } }; pfs.ts ×1
291 > }
293 > // Windows: workaround a node.js bug where reparse points
294 > // are not supported (https://github.com/nodejs/node/issues/36790)
295 > if (isWindows && error.code === 'EACCES') { pfs.ts ×2
296 try {
297 const stats = await fs.promises.stat(await fs.promises.readlink(path));
298
299 return { stat: stats, symbolicLink: { dangling: false } };
300 } catch (error) {
301
302 // If the link points to a nonexistent file we still want
303 // to return it as result while setting dangling: true flag
304 if (error.code === 'ENOENT' && lstats) {
305 return { stat: lstats, symbolicLink: { dangling: true } };
306 }
307
308 throw error;
309 }
310 }
312 > throw error;
313 > }
314 > } pfs.ts ×4
315 > pfs.ts ×32
316 > /**
317 > * Figures out if the `path` exists and is a file with support
318 > * for symlinks.
319 > *
320 > * Note: this will return `false` for a symlink that exists on
321 > * disk but is dangling (pointing to a nonexistent path).
322 > *
323 > * Use `exists` if you only care about the path existing on disk
324 > * or not without support for symbolic links.
325 > */
326 > export async function existsFile(path: string): Promise<boolean> {
327 try {
328 const { stat, symbolicLink } = await SymlinkSupport.stat(path);
329
330 return stat.isFile() && symbolicLink?.dangling !== true;
331 } catch {
332 // Ignore, path might not exist
333 }
334
335 return false;
336 }
337 > pfs.ts ×32
338 > /**
339 > * Figures out if the `path` exists and is a directory with support for
340 > * symlinks.
341 > *
342 > * Note: this will return `false` for a symlink that exists on
343 > * disk but is dangling (pointing to a nonexistent path).
344 > *
345 > * Use `exists` if you only care about the path existing on disk
346 > * or not without support for symbolic links.
347 > */
348 > export async function existsDirectory(path: string): Promise<boolean> {
349 > try { pfs.ts ×3
350 > const { stat, symbolicLink } = await SymlinkSupport.stat(path);
351 >
352 > return stat.isDirectory() && symbolicLink?.dangling !== true;
353 > } catch {
354 // Ignore, path might not exist
355 }
356
357 return false;
358 > } pfs.ts ×3
359 > } pfs.ts ×32
360 >
361 > //#endregion
362 >
363 > //#region Write File
364 >
365 > // According to node.js docs (https://nodejs.org/docs/v14.16.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
366 > // it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
367 > // Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
368 > const writeQueues = new ResourceQueue();
369 >
370 > /**
371 > * Same as `fs.writeFile` but with an additional call to
372 > * `fs.fdatasync` after writing to ensure changes are
373 > * flushed to disk.
374 > *
375 > * In addition, multiple writes to the same path are queued.
376 > */
377 > function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise<void>;
378 > function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise<void>;
379 > function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;
380 > function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void>;
381 > function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void> { pfs.ts ×2
382 > return writeQueues.queueFor(URI.file(path), () => {
383 > const ensuredOptions = ensureWriteOptions(options);
384 >
385 > return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()));
386 > }, extUriBiasedIgnorePathCase);
387 > }
388 > pfs.ts ×32
389 > interface IWriteFileOptions {
390 > mode?: number;
391 > flag?: string;
392 > }
393 >
394 > interface IEnsuredWriteFileOptions extends IWriteFileOptions {
395 > mode: number;
396 > flag: string;
397 > }
398 >
399 > let canFlush = true;
400 > export function configureFlushOnWrite(enabled: boolean): void {
401 > canFlush = enabled; pfs.ts ×1
402 > }
403 > pfs.ts ×32
404 > // Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk
405 > // We do this in cases where we want to make sure the data is really on disk and
406 > // not in some cache.
407 > //
408 > // See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
409 > function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void { pfs.ts ×2
410 > if (!canFlush) {
411 > return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback); pfs.ts ×1
412 > }
413 > pfs.ts ×4
414 > // Open the file with same flags and mode as fs.writeFile()
415 > fs.open(path, options.flag, options.mode, (openError, fd) => {
416 > if (openError) {
417 return callback(openError);
418 }
419 > pfs.ts ×4
420 > // It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!
421 > fs.writeFile(fd, data, writeError => {
422 > if (writeError) {
423 return fs.close(fd, () => callback(writeError)); // still need to close the handle on error!
424 }
425 > pfs.ts ×4
426 > // Flush contents (not metadata) of the file to disk
427 > // https://github.com/microsoft/vscode/issues/9589
428 > fs.fdatasync(fd, (syncError: Error | null) => {
429 >
430 > // In some exotic setups it is well possible that node fails to sync
431 > // In that case we disable flushing and warn to the console
432 > if (syncError) {
433 console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);
434 configureFlushOnWrite(false);
435 }
436 > pfs.ts ×4
437 > return fs.close(fd, closeError => callback(closeError));
438 > });
439 > });
440 > });
441 > }
442 > pfs.ts ×32
443 > /**
444 > * Same as `fs.writeFileSync` but with an additional call to
445 > * `fs.fdatasyncSync` after writing to ensure changes are
446 > * flushed to disk.
447 > *
448 > * @deprecated always prefer async variants over sync!
449 > */
450 > export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {
451 > const ensuredOptions = ensureWriteOptions(options); pfs.ts ×3
452 >
453 > if (!canFlush) {
454 > return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag }); pfs.ts ×1
455 > }
457 > // Open the file with same flags and mode as fs.writeFile()
458 > const fd = fs.openSync(path, ensuredOptions.flag, ensuredOptions.mode);
459 >
460 > try {
461 >
462 > // It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!
463 > fs.writeFileSync(fd, data);
464 >
465 > // Flush contents (not metadata) of the file to disk
466 > try {
467 > fs.fdatasyncSync(fd); // https://github.com/microsoft/vscode/issues/9589
468 > } catch (syncError) {
469 console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);
470 configureFlushOnWrite(false);
471 }
472 > } finally { pfs.ts ×3
473 > fs.closeSync(fd); stateService.ts ×16
474 > }
475 > } pfs.ts ×3
476 > pfs.ts ×32
477 > function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptions { pfs.ts ×2
478 > if (!options) {
479 > return { mode: 0o666 /* default node.js mode for files */, flag: 'w' };
480 > }
481
482 return {
483 > mode: typeof options.mode === 'number' ? options.mode : 0o666 /* default node.js mode for files */, pfs.ts ×2
484 > flag: typeof options.flag === 'string' ? options.flag : 'w'
485 > };
486 > }
487 > pfs.ts ×32
488 > //#endregion
489 >
490 > //#region Move / Copy
491 >
492 > /**
493 > * A drop-in replacement for `fs.rename` that:
494 > * - allows to move across multiple disks
495 > * - attempts to retry the operation for certain error codes on Windows
496 > */
497 > async function rename(source: string, target: string, windowsRetryTimeout: number | false = 60000): Promise<void> { pfs.ts ×4
498 > if (source === target) {
499 return; // simulate node.js behaviour here and do a no-op if paths match
500 }
501 > pfs.ts ×4
502 > try {
503 > if (isWindows && typeof windowsRetryTimeout === 'number') {
504 // On Windows, a rename can fail when either source or target
505 // is locked by AV software.
506 await renameWithRetry(source, target, Date.now(), windowsRetryTimeout);
507 > } else { pfs.ts ×4
508 > await fs.promises.rename(source, target);
509 > }
510 > } catch (error) {
511 // In two cases we fallback to classic copy and delete:
512 //
513 // 1.) The EXDEV error indicates that source and target are on different devices
514 // In this case, fallback to using a copy() operation as there is no way to
515 // rename() between different devices.
516 //
517 // 2.) The user tries to rename a file/folder that ends with a dot. This is not
518 // really possible to move then, at least on UNC devices.
519 if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) {
520 await copy(source, target, { preserveSymlinks: false /* copying to another device */ });
521 await rimraf(source, RimRafMode.MOVE);
522 } else {
523 throw error;
524 }
525 }
526 > } pfs.ts ×4
527 > pfs.ts ×32
528 async function renameWithRetry(source: string, target: string, startTime: number, retryTimeout: number, attempt = 0): Promise<void> {
529 try {
530 return await fs.promises.rename(source, target);
531 } catch (error) {
532 if (error.code !== 'EACCES' && error.code !== 'EPERM' && error.code !== 'EBUSY') {
533 throw error; // only for errors we think are temporary
534 }
535
536 if (Date.now() - startTime >= retryTimeout) {
537 console.error(`[node.js fs] rename failed after ${attempt} retries with error: ${error}`);
538
539 throw error; // give up after configurable timeout
540 }
541
542 if (attempt === 0) {
543 let abortRetry = false;
544 try {
545 const { stat } = await SymlinkSupport.stat(target);
546 if (!stat.isFile()) {
547 abortRetry = true; // if target is not a file, EPERM error may be raised and we should not attempt to retry
548 }
549 } catch {
550 // Ignore
551 }
552
553 if (abortRetry) {
554 throw error;
555 }
556 }
557
558 // Delay with incremental backoff up to 100ms
559 await timeout(Math.min(100, attempt * 10));
560
561 // Attempt again
562 return renameWithRetry(source, target, startTime, retryTimeout, attempt + 1);
563 }
564 }
565 > pfs.ts ×32
566 > interface ICopyPayload {
567 > readonly root: { source: string; target: string };
568 > readonly options: { preserveSymlinks: boolean };
569 > readonly handledSourcePaths: Set<string>;
570 > }
571 >
572 > /**
573 > * Recursively copies all of `source` to `target`.
574 > *
575 > * The options `preserveSymlinks` configures how symbolic
576 > * links should be handled when encountered. Set to
577 > * `false` to not preserve them and `true` otherwise.
578 > */
579 > async function copy(source: string, target: string, options: { preserveSymlinks: boolean }): Promise<void> { pfs.ts ×7
580 > return doCopy(source, target, { root: { source, target }, options, handledSourcePaths: new Set<string>() });
581 > }
582 > pfs.ts ×32
583 > // When copying a file or folder, we want to preserve the mode
584 > // it had and as such provide it when creating. However, modes
585 > // can go beyond what we expect (see link below), so we mask it.
586 > // (https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588)
587 > const COPY_MODE_MASK = 0o777;
588 >
589 > async function doCopy(source: string, target: string, payload: ICopyPayload): Promise<void> { pfs.ts ×7
590 >
591 > // Keep track of paths already copied to prevent
592 > // cycles from symbolic links to cause issues
593 > if (payload.handledSourcePaths.has(source)) {
594 return;
595 > } else { pfs.ts ×7
596 > payload.handledSourcePaths.add(source);
597 > }
598 >
599 > const { stat, symbolicLink } = await SymlinkSupport.stat(source);
600 >
601 > // Symlink
602 > if (symbolicLink) {
603 > pfs.ts ×5
604 > // Try to re-create the symlink unless `preserveSymlinks: false`
605 > if (payload.options.preserveSymlinks) {
606 > try {
607 > return await doCopySymlink(source, target, payload);
608 > } catch {
609 // in any case of an error fallback to normal copy via dereferencing
610 }
611 > } pfs.ts ×5
612 >
613 > if (symbolicLink.dangling) {
614 return; // skip dangling symbolic links from here on (https://github.com/microsoft/vscode/issues/111621)
615 }
616 > } pfs.ts ×5
617 > pfs.ts ×7
618 > // Folder
619 > if (stat.isDirectory()) {
620 > return doCopyDirectory(source, target, stat.mode & COPY_MODE_MASK, payload);
621 > }
622 > pfs.ts ×3
623 > // File or file-like
624 > else {
625 > return doCopyFile(source, target, stat.mode & COPY_MODE_MASK);
626 > }
627 > } pfs.ts ×7
628 > pfs.ts ×32
629 > async function doCopyDirectory(source: string, target: string, mode: number, payload: ICopyPayload): Promise<void> { pfs.ts ×7
630 >
631 > // Create folder
632 > await fs.promises.mkdir(target, { recursive: true, mode });
633 >
634 > // Copy each file recursively
635 > const files = await readdir(source);
636 > for (const file of files) {
637 > await doCopy(join(source, file), join(target, file), payload); pfs.ts ×3
638 > }
639 > } pfs.ts ×7
640 > pfs.ts ×32
641 > async function doCopyFile(source: string, target: string, mode: number): Promise<void> { pfs.ts ×3
642 >
643 > // Copy file
644 > await fs.promises.copyFile(source, target);
645 >
646 > // restore mode (https://github.com/nodejs/node/issues/1104)
647 > await fs.promises.chmod(target, mode);
648 > }
649 > pfs.ts ×32
650 > async function doCopySymlink(source: string, target: string, payload: ICopyPayload): Promise<void> { pfs.ts ×5
651 >
652 > // Figure out link target
653 > let linkTarget = await fs.promises.readlink(source);
654 >
655 > // Special case: the symlink points to a target that is
656 > // actually within the path that is being copied. In that
657 > // case we want the symlink to point to the target and
658 > // not the source
659 > if (isEqualOrParent(linkTarget, payload.root.source, !isLinux)) {
660 > linkTarget = join(payload.root.target, linkTarget.substr(payload.root.source.length + 1)); pfs.ts ×1
661 > }
662 > pfs.ts ×5
663 > // Create symlink
664 > await fs.promises.symlink(linkTarget, target);
665 > }
666 > pfs.ts ×32
667 > //#endregion
668 >
669 > //#region Path resolvers
670 >
671 > /**
672 > * Given an absolute, normalized, and existing file path 'realcase' returns the
673 > * exact path that the file has on disk.
674 > * On a case insensitive file system, the returned path might differ from the original
675 > * path by character casing.
676 > * On a case sensitive file system, the returned path will always be identical to the
677 > * original path.
678 > * In case of errors, null is returned. But you cannot use this function to verify that
679 > * a path exists.
680 > *
681 > * realcase does not handle '..' or '.' path segments and it does not take the locale into account.
682 > */
683 > export async function realcase(path: string, token?: CancellationToken): Promise<string | null> { pfs.ts ×3
684 > if (isLinux) {
685 > // This method is unsupported on OS that have case sensitive
686 > // file system where the same path can exist in different forms
687 > // (see also https://github.com/microsoft/vscode/issues/139709)
688 > return path;
689 > }
690
691 const dir = dirname(path);
692 if (path === dir) { // end recursion
693 return path;
694 }
695
696 > const name = (basename(path) /* can be '' for windows drive letters */ || path).toLowerCase(); pfs.ts ×3
697 > try {
698 > if (token?.isCancellationRequested) {
699 return null;
700 }
701
702 const entries = await Promises.readdir(dir);
703 const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search
704 if (found.length === 1) {
705 // on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition
706 const prefix = await realcase(dir, token); // recurse
707 if (prefix) {
708 return join(prefix, found[0]);
709 }
710 } else if (found.length > 1) {
711 // must be a case sensitive $filesystem
712 const ix = found.indexOf(name);
713 if (ix >= 0) { // case sensitive
714 const prefix = await realcase(dir, token); // recurse
715 if (prefix) {
716 return join(prefix, found[ix]);
717 }
718 }
719 }
720 > } catch { pfs.ts ×3
721 // silently ignore error
722 }
723
724 return null;
725 }
726 > pfs.ts ×32
727 > async function realpath(path: string): Promise<string> { pfs.ts ×2
728 > try {
729 > // DO NOT USE `fs.promises.realpath` here as it internally
730 > // calls `fs.native.realpath` which will result in subst
731 > // drives to be resolved to their target on Windows
732 > // https://github.com/microsoft/vscode/issues/118562
733 > return await promisify(fs.realpath)(path);
734 > } catch {
735
736 // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization
737 // we now do a similar normalization and then try again if we can access the path with read
738 // permissions at least. If that succeeds, we return that path.
739 // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is
740 // to not resolve links but to simply see if the path is read accessible or not.
741 const normalizedPath = normalizePath(path);
742
743 await fs.promises.access(normalizedPath, fs.constants.R_OK);
744
745 return normalizedPath;
746 }
747 > } pfs.ts ×2
748 > pfs.ts ×32
749 > /**
750 > * @deprecated always prefer async variants over sync!
751 > */
752 > export function realpathSync(path: string): string {
753 > try { pfs.ts ×2
754 > return fs.realpathSync(path);
755 > } catch {
756
757 // We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization
758 // we now do a similar normalization and then try again if we can access the path with read
759 // permissions at least. If that succeeds, we return that path.
760 // fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is
761 // to not resolve links but to simply see if the path is read accessible or not.
762 const normalizedPath = normalizePath(path);
763
764 fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error
765
766 return normalizedPath;
767 }
768 > } pfs.ts ×2
769 > pfs.ts ×32
770 function normalizePath(path: string): string {
771 return rtrim(normalize(path), sep);
772 }
773 > pfs.ts ×32
774 > //#endregion
775 >
776 > //#region Promise based fs methods
777 >
778 > /**
779 > * Some low level `fs` methods provided as `Promises` similar to
780 > * `fs.promises` but with notable differences, either implemented
781 > * by us or by restoring the original callback based behavior.
782 > *
783 > * At least `realpath` is implemented differently in the promise
784 > * based implementation compared to the callback based one. The
785 > * promise based implementation actually calls `fs.realpath.native`.
786 > * (https://github.com/microsoft/vscode/issues/118562)
787 > */
788 > export const Promises = new class {
789 >
790 > //#region Implemented by node.js
791 >
792 > get read() {
793 > io.ts ×12
794 > // Not using `promisify` here for a reason: the return
795 > // type is not an object as indicated by TypeScript but
796 > // just the bytes read, so we create our own wrapper.
797 >
798 > return (fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null) => {
799 > return new Promise<{ bytesRead: number; buffer: Uint8Array }>((resolve, reject) => {
800 > fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {
801 > if (err) {
802 return reject(err);
803 }
804 > io.ts ×12
805 > return resolve({ bytesRead, buffer });
806 > });
807 > });
808 > };
809 > }
810 > pfs.ts ×32
811 > get write() {
813 > // Not using `promisify` here for a reason: the return
814 > // type is not an object as indicated by TypeScript but
815 > // just the bytes written, so we create our own wrapper.
816 >
817 > return (fd: number, buffer: Uint8Array, offset: number | undefined | null, length: number | undefined | null, position: number | undefined | null) => {
818 > return new Promise<{ bytesWritten: number; buffer: Uint8Array }>((resolve, reject) => {
819 > fs.write(fd, buffer, offset, length, position, (err, bytesWritten, buffer) => {
820 > if (err) {
821 return reject(err);
822 }
824 > return resolve({ bytesWritten, buffer });
825 > });
826 > });
827 > };
828 > }
829 > pfs.ts ×32
830 > get fdatasync() { return promisify(fs.fdatasync); } // not exposed as API in 22.x yet
831 >
832 > get open() { return promisify(fs.open); } // changed to return `FileHandle` in promise API
833 > get close() { return promisify(fs.close); } // not exposed as API due to the `FileHandle` return type of `open`
834 >
835 > get ftruncate() { return promisify(fs.ftruncate); } // not exposed as API in 22.x yet
836 >
837 > //#endregion
838 >
839 > //#region Implemented by us
840 >
841 > async exists(path: string): Promise<boolean> {
842 > try { pfs.ts ×3
843 > await fs.promises.access(path);
844 > zip.ts ×9
845 > return true;
846 > } catch { pfs.ts ×3
847 > return false; pfs.ts ×1
848 > }
849 > } pfs.ts ×3
850 > pfs.ts ×32
851 > get readdir() { return readdir; }
852 > get readDirsInDir() { return readDirsInDir; }
853 >
854 > get writeFile() { return writeFile; }
855 >
856 > get rm() { return rimraf; }
857 >
858 > get rename() { return rename; }
859 > get copy() { return copy; }
860 >
861 > get realpath() { return realpath; } // `fs.promises.realpath` will use `fs.realpath.native` which we do not want
862 >
863 > //#endregion
864 > };
865 >
866 > //#endregion