fix(windows): rewrite the preference store in place when a reader vetoes the rename
dart:io opens files without FILE_SHARE_DELETE, so MoveFileExW with MOVEFILE_REPLACE_EXISTING - and any other replacement strategy - fails while such a reader holds the document open. Fall back to the upstream in-place rewrite so a hostile reader costs at most crash-atomicity for that one write instead of silently dropping it, and sweep the staging copy the rename did not consume.
This commit is contained in:
@@ -427,7 +427,29 @@ Future<bool> _writePreferences(
|
|||||||
staged.parent.createSync(recursive: true);
|
staged.parent.createSync(recursive: true);
|
||||||
}
|
}
|
||||||
staged.writeAsStringSync(stringMap, flush: true);
|
staged.writeAsStringSync(stringMap, flush: true);
|
||||||
staged.renameSync(localDataFile.path);
|
try {
|
||||||
|
staged.renameSync(localDataFile.path);
|
||||||
|
} on FileSystemException {
|
||||||
|
// PLEZY DELTA: a reader that denies delete sharing vetoes every rename
|
||||||
|
// over the document. `MoveFileExW` with MOVEFILE_REPLACE_EXISTING — and
|
||||||
|
// even a POSIX-semantics rename — fails while an open handle on the
|
||||||
|
// destination lacks FILE_SHARE_DELETE, and dart:io's own `File.open`
|
||||||
|
// produces exactly such a handle (`_wopen` shares read/write only), as
|
||||||
|
// do some antivirus and indexing services. The atomic path is not slow
|
||||||
|
// there; it is unavailable.
|
||||||
|
//
|
||||||
|
// Fall back to the upstream in-place rewrite: the sharing mode that
|
||||||
|
// blocks the rename still admits opening the document for write. This
|
||||||
|
// reintroduces the truncate window only while such a reader holds the
|
||||||
|
// store — a brief loss of crash-atomicity beats turning every
|
||||||
|
// preference write into a silent no-op on exactly the machines whose
|
||||||
|
// scanners are most likely to have damaged the store in the first
|
||||||
|
// place.
|
||||||
|
localDataFile.writeAsStringSync(stringMap, flush: true);
|
||||||
|
// The staging copy holds the whole document, credentials included, and
|
||||||
|
// was not consumed by a rename, so it must not outlive the write.
|
||||||
|
_removeStagingFile(localDataFile);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error saving preferences to disk: $e');
|
debugPrint('Error saving preferences to disk: $e');
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user