489
return Object.create(null); // file might not exist
490
}
492
>
// File cannot be directory
493
>
if ((stat.type & FileType.Directory) !== 0) {
494
throw new FileOperationError(localize('fileIsDirectoryWriteError', "Unable to write file '{0}' that is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
495
}
497
>
// File cannot be readonly
498
>
this.throwIfFileIsReadonly(resource, stat);
499
>
500
>
// Dirty write prevention: if the file on disk has been changed and does not match our expected
501
>
// mtime and etag, we bail out to prevent dirty writing.
502
>
//
503
>
// First, we check for a mtime that is in the future before we do more checks. The assumption is
504
>
// that only the mtime is an indicator for a file that has changed on disk.
505
>
//
506
>
// Second, if the mtime has advanced, we compare the size of the file on disk with our previous
507
>
// one using the etag() function. Relying only on the mtime check has prooven to produce false
508
>
// positives due to file system weirdness (especially around remote file systems). As such, the
509
>
// check for size is a weaker check because it can return a false negative if the file has changed
510
>
// but to the same length. This is a compromise we take to avoid having to produce checksums of
511
>
// the file content for comparison which would be much slower to compute.
512
>
//
513
>
// Third, if the etag() turns out to be different, we do one attempt to compare the buffer we
514
>
// are about to write with the contents on disk to figure out if the contents are identical.
515
>
// In that case we allow the writing as it would result in the same contents in the file.
516
>
let buffer: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream | undefined;
517
>
if (
518
typeof options?.mtime === 'number' && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED &&
519
typeof stat.mtime === 'number' && typeof stat.size === 'number' &&